Diagnosing the Stubborn Mediocrity of the Western Bulldogs
Last Updated on July 17, 2023 by Editorial Team
Author(s): Ranganath Venkataraman
Originally published on Towards AI.
Using data to get insight into why my supported Australian Football League team perennially languishes and the corresponding lessons learned
Note β this article reflects my own work and approach/beliefs/thoughts, not those of current or prior employers.
I use public datasets to continuously hone my skills in analyzing data and synthesizing my findings into succinct and effective communications. While searching around for a new topic, I got curious about my beloved and beleaguered Western Bulldogs team within the Australian Football League (AFL).
Knowledge about Aussie Rules football β the premier competition of which is the AFL β and about the Western Bulldogs can be gained using Wikipedia; I have provided some resources here for convenience:
- Australian football tactics and skills β Wikipedia,
- Laws of Australian rules football β Wikipedia,
- Western Bulldogs β Wikipedia
The key question: why are the Bulldogs mediocre, and can this be fixed?
Like any other key question, this requires that another set of questions be first answered.
1. Just how mediocre is the Western Bulldogs?
Is it possible that my perspective is informed through a selective or biased view not backed by data?
Answering this requires data, and since the AFLβs own website doesnβt make historical data easy to parse and download, I used Kaggle with all credit and responsibility for the following data going to the owner of these datasets:
- games.csv: data for every AFL game from 2012 to 2021
- stats.csv: data for every player in every AFL game
- players.csv: data for each individual player
1a. Letβs first explore the datasets
games = pd.read_csv('games.csv')
players = pd.read_csv('players.csv')
stats = pd.read_csv('stats.csv')
games.isna().sum() #31 nulls in rainfall
games.tail()p
stats.isna().sum() #43,560 nulls in Subs
stats.columns
players.isna().sum() #4 nulls in origin
players.tail() #1,494 unique players.
There are 2,024 occurrences in the games dataframe, while weβd expect a total of 3,600 games. However, weβd expect 2,061 games:
- Each club plays 22 games per season, for 10seasons: 22 games / team / season* 9 teams * 10 seasons = 1,980 games
- 4 finals rounds add 9 games per year or 81 total to 1,980, gettings us to 2,061 games.
I donβt see those 37 missing games β 1.7% β as impacting the results of the analysis significantly, but itβs good to observe.
The games and stats contain information on wins and losses, as well as the elements of play. I donβt plan to use the players dataset since it contains nothing about contributions to a game that I couldnβt get from the other datasets.
1b. How do we define mediocrity?
If we define success and failure as winning and losing a given AFL game, then mediocrity means that the Western Bulldogs would win at a frequency near the middle compared to to other teams in the league.
Letβs first use the games dataframe to define the number of total wins β this will require first defining games won at home and games won away, then aggregate by team, and sort:
# Defining home and away victories
games['homeTeam less awayTeam'] = games['homeTeamScore'] - games['awayTeamScore']
games['homeTeamwon'] = games['homeTeam less awayTeam'].apply(lambda x: 1 if x > 0 else 0)
games['awayTeamwon'] = games['homeTeam less awayTeam'].apply(lambda x: 1 if x < 0 else 0)
home_teams_record = games.groupby(['homeTeam','year']).agg({'homeTeamwon':sum}).reset_index()
home_teams_record.columns = ['Team','Year','Home games won']
away_teams_record = games.groupby(['homeTeam','year']).agg({'awayTeamwon':sum}).reset_index()
away_teams_record.columns = ['Team','Year','Away games won']
# Merging home and away games and defining total wins as home victories + away victories
Games_record = home_teams_record.merge(away_teams_record,on=['Team','Year'])
Games_record['total wins'] = Games_record['Home games won'] + Games_record['Away games won']
# Calculating total home and away
Wins_across_years = Games_record.groupby('Team').agg({'total wins':'sum'}).reset_index()
Wins_across_years.sort_values(by='total wins',ascending=False)
The Western Bulldogs have won 109 games, and by analyzing percentiles of the total wins above β in Figure 3 below β they are at the 40th percentile.
Wins_across_years['total wins'].describe(percentiles=[.1, .2, .3, .4, .5, .6 , .7, .8, .9, 1])
Therefore, the answer to question 1 is the Western Bulldogs are near exact mediocrity per this definition of success as being games won. Theyβre at the 40th percentile and not the only ones.
2. What are the drivers that lead to success for a team, as measured by a number of wins?
Now time for the stats dataframe β to connect victories and losses with performance statistics, Iβll merge the stats and games dataframes by Team and Year:
# Grouping stats by team and year, and renaming columns
stats_by_team = stats.groupby(['team','year'])[['Disposals', 'Kicks', 'Marks', 'Handballs', 'Goals',
'Behinds', 'Hit Outs', 'Tackles', 'Rebounds', 'Inside 50s',
'Clearances', 'Clangers', 'Frees', 'Frees Against', 'Brownlow Votes',
'Contested Possessions', 'Uncontested Possessions', 'Contested Marks',
'Marks Inside 50', 'One Percenters', 'Bounces', 'Goal Assists']].sum().reset_index()
stats_by_team.columns = ['Team', 'Year','Disposals', 'Kicks', 'Marks', 'Handballs', 'Goals', 'Behinds',
'Hit Outs', 'Tackles', 'Rebounds', 'Inside 50s', 'Clearances',
'Clangers', 'Frees', 'Frees Against', 'Brownlow Votes',
'Contested Possessions', 'Uncontested Possessions', 'Contested Marks',
'Marks Inside 50', 'One Percenters', 'Bounces', 'Goal Assists']
# Merging games and stats datasets on Team and Year columns
Games_record_and_stats = stats_by_team.merge(Games_record,on=['Team','Year'])
Games_record_and_stats.head()
Correlation analysis across the years β see Figure 5 below β finds that the following aspects of play are in the top 20% in terms of their influence on winning games: contested possessions, inside the 50s, kicks and disposals, clearances, and goals
The following commonly focused aspects of the game are in the BOTTOM 50% of their importance to the game: frees, handballs, hit outs, Brownlow votes, and rebounds.
Getting the ball out of a throw-up, getting it near your teamβs goal, and actually scoring are key. Being able to tackle people is less critical than just winning the ball during contested possessions.
2a. How do our conclusions change if we zoom into correlations within any specific year?
I filtered the combined games and stats dataframe by year and then built a dataframe of correlation scores to total wins for each of these βyear-wiseβ data frames; the example for 2012 follows:
Games_record_and_stats_2012 = Games_record_and_stats[Games_record_and_stats['Year']==2012]
Corr_matrix_2012 = pd.DataFrame(Games_record_and_stats_2012.corr())
total_wins_2012 = pd.DataFrame(Corr_matrix_2012['total wins']).reset_index()
total_wins_2012.columns = ['index','total wins 2012']
I was also curious about scoring accuracy, a much-discussed topic in game post-mortems β i.e., how many goals vs. how many behinds did a game score? Therefore, I added a measure of scoring accuracy to the dataframe before filtering by year:
Games_record_and_stats['Scoring accuracy'] = Games_record_and_stats['Goals'] / (Games_record_and_stats['Goals'] + Games_record_and_stats['Behinds'])
Games_record_and_stats['GoalsPlusBehinds'] = Games_record_and_stats['Goals'] + Games_record_and_stats['Behinds']
Since the βtotal wins 2xxxβ column now has Pearson correlation scores connecting aspects of gameplay to total wins, I plotted those correlation scores over 2012 through 2021:
The original plotly bar plot is available in the codebase for those interested β here are key takeaways:
- Contested marks and possessions have become increasingly important over the last 4 years
- Uncontested possessions declined in importance over the last 4 years, perhaps as they became rarer in gameplay
- Inside the 50s have stayed consistently important, unsurprisingly
- Clearances, goals, and behinds have maintained their importance over the years
- Kicks are more influential to a win than handballs; kicking the ball over longer distances also increases the potential of a contest in getting the ball, leading to point 1
- Scoring accuracy is seemingly not as critical, which surprises me
- Certain features e.g., disposals and marks, had brief years in which they hit troughs
This year-wise analysis largely reinforces the same aspects of gameplay as informing success, i.e., wins, as the analysis that looked at 2012β2021 in totality.
3. How have the Western Bulldogs performed along these gameplay aspects relative to the successful teams? And therefore, what should the Bulldogs focus on?
Again, letβs break down this question.
3a. First, who are the βsuccessfulβ teams? Since 2012, that would be those teams that have won premierships i.e., the annual championship:
2012 β Sydney Swans
2013, 2014, and 2015 β Hawthorn Hawks
2016 β Western Bulldogs (!!), only their second, with the first in 1954
2017 β Richmond Tigers
2018 β West Coast Eagles
2019 and 2020 β Richmond Tigers
2021 β Melbourne Demons (who defeated the Bulldogs in the premiership game)
2022 β Geelong Cats
3b. How have the Bulldogs performed relative to the teams above?
I first created columns to measure the percentile scores along different metrics β see the example below contested possessions and kicks in 2016:
Games_record_and_stats_2016['Kicks %ile'] = Games_record_and_stats_2016.Kicks.rank(pct = True)
Games_record_and_stats_2016['Contested Possessions %ile'] = Games_record_and_stats_2016['Contested Possessions'].rank(pct = True)
I then isolated those teams of interest β the Western Bulldogs and the winners noted above β into a single βComparisonβ dataframe:
WB_2016 = Games_record_and_stats_2016[Games_record_and_stats_2016['Team']=='Western Bulldogs']
WestCoast_2016 = Games_record_and_stats_2016[Games_record_and_stats_2016['Team']=='West Coast']
Melb_2016 = Games_record_and_stats_2016[Games_record_and_stats_2016['Team']=='Melbourne']
Richm_2016 = Games_record_and_stats_2016[Games_record_and_stats_2016['Team']=='Richmond']
Sydney_2016 = Games_record_and_stats_2016[Games_record_and_stats_2016['Team']=='Sydney']
Hawth_2016 = Games_record_and_stats_2016[Games_record_and_stats_2016['Team']=='Hawthorn']
## Similar steps above for other years, following by creation of a consolidated Comparison dataframe
Comparison = WB_2012.append(WB_2013).append(WB_2014).append(WB_2015).append(WB_2016).append(WB_2017).append(WB_2018).append(WB_2019).append(WB_2020).append(WB_2021).append(WestCoast_2018).append(Melb_2021).append(Hawth_2013).append(Hawth_2014).append(Hawth_2015).append(Richm_2017).append(Richm_2019).append(Richm_2020).append(Sydney_2012)
Comparison = Comparison.append(WestCoast_2016).append(Melb_2016).append(Richm_2016).append(Sydney_2016).append(Hawth_2016)
We now have a single dataframe with teamsβ achieved percentiles for those aspects of gameplay β plotting that is the next step:
px.bar(Comparison,y=['Kicks %ile', 'Contested Possessions %ile', 'Contested Marks %ile',
'Inside 50 %ile', 'Marks Inside 50 %ile', 'Clearances %ile',
'Scoring Accuracy %ile', 'GoalsPlusBehinds %ile'],x='Year',hover_name='Team')
How have the Western Bulldogs performed relative to those teams that tasted premiership glory?
- They were consistently below the 50th percentile mark when it came to kicks and contested possessions, while the ultimate premiers were at or near the top percentile. The notable aberration is Richmond in 2017, who was at the 72nd percentile in kicks and 77th in contested possessions.
- Getting inside the 50s is interesting; the Western Bulldogs have largely done well here, except in 2014 and 2018. Therefore, it is their inability to keep possession of the ball within the 50 and convert it into scores that are hampering them.
- This then leads us to score accuracy and further scrutiny of the surprising observation above; the Western Bulldogs in 2016, Melbourne Demons in 2021, and Richmond in 2017 and 2020 all managed with <70% accuracy, sometimes near 50%. What may be more important is accuracy relative to the other team in a given match for a given number of scoring opportunities.
- Scoring opportunities β i.e., the total number of goals + behinds β are a key indicator of ultimate wins. In 2016, the Western Bulldogs β champions β and Sydney Swans (runner-ups) were in the 77th and 94th percentile, respectively. In 2021, the Western Bulldogs and Melbourne were near each other in the top spot. In all other years, the Western Bulldogsβ percentile was anywhere from 25β50 % of the championship teamβs percentile. Therefore, practice getting and keeping possession of the ball long enough to take shots at goal.
- Clearances are simply not that critical to winning premierships; it doesnβt matter if you can get the ball out of a throw-up. What matters is retaining possession.
3c. Based on the analysis above, where should the Western Bulldogs focus during practice?
Focus on kicking and contested possessions in practice
Prioritize getting contested possessions over contested marking; the ultimate premiers are rarely near the top percentile on contested marking. Once the ball is kicked, it doesnβt matter if you mark in a contest; just ensure that the other team doesnβt catch the ball, and win it in the subsequent hustle.
Practice getting and keeping possession of the ball long enough to take shots at goal.
This focus β both in offense and defense β maximizes the Western Bulldogsβ chances at winning games and ultimately winning premierships while minimizing their opponentβs chances. Removing the opposition teamβs ability to win the ball and take scoring shots by overwhelming them during contests, and minimizing opposing teamsβ ability to enter their 50, are well-honed strategies for success.
Just one last thing: before leaving this, I got curious about whether a predictive model built on this data would give high weight to the same features.
Therefore, I created an x array of features (dropping team name, year, and games won) and y array of total winsβ to apply the AutoML method of TPOT:
x = Games_record_and_stats.drop(columns=['total wins','Team', 'Year','Home games won', 'Away games won'])
y = Games_record_and_stats['total wins']
tpr = TPOTRegressor(generations=5, population_size=50, cv=cv, scoring='explained_variance', verbosity=2, random_state=1, n_jobs=-1)
tpr.fit(x,y)
As seen in Figure 8 below, TPOT suggested an ExtraTrees regressor with 69% observed variance explained:
Applying SHAP to explain the ExtraTrees regressor model performance yielded insights, some of which did not align with earlier observations:
# Defining the model
etr = ExtraTreesRegressor(bootstrap=True, max_features=0.8, min_samples_leaf=1, min_samples_split=10, n_estimators=100)
etr.fit(xtrain,ytrain)
#Importing and installing SHAP, and applying to etr model
%pip install shap
import shap
explainer = shap.Explainer(etr)
shap_values = explainer(xtest)
# Viewing SHAP outputs
shap.initjs()
shap.plots.beeswarm(shap_values)
Inside 50s and contested possessions are correlated to increased game wins, but interestingly so are Brownlow votes. Kicks and disposals become less important.
I then focused on games in the last 5 years to see if zooming in would change the view; as seen in Figure 20 below, this led to a Ridge Regression model that analyzed data which was filtered by a SelectPercentile preprocessor and also scaled:
Games_record_and_stats_2016_and_after = Games_record_and_stats[Games_record_and_stats['Year']>2015]
x_2016_and_after = Games_record_and_stats_2016_and_after.drop(columns=['total wins','Team', 'Year','Home games won', 'Away games won'])
y_2016_and_after = Games_record_and_stats_2016_and_after['total wins']
tpr.fit(x_2016_and_after,y_2016_and_after)
tpr.export('best_afl_using_AutoML_2016_and_after.py')
Applying SHAP explainers to this linear Ridge regressor required first applying the scaling and percentile selector. reinforced the importance of contested possessions, but deprioritized kicks:
x_2016_and_after_new = SelectPercentile(score_func=f_regression, percentile=86).fit_transform(x_2016_and_after,y_2016_and_after)
std = StandardScaler()
x_2016_and_after_new_scaled = std.fit(x_2016_and_after_new).transform(x_2016_and_after_new)
from sklearn.linear_model import Ridge
rdg_model = Ridge(alpha=np.array([ 0.1, 1. , 10. ]))
rdg_model = rdg.fit(x_2016_and_after_new_scaled,y_2016_and_after)
explainer_new = shap.explainers.Linear(rdg_model,x_2016_and_after_new_scaled)
values = explainer_new.shap_values(x_2016_and_after_new_scaled)
shap.summary_plot(values, x_2016_and_after_new_scaled)
The analysis above confirms that contested possessions have the largest impact, with more contested possessions (top red dots) driving more games won i.e. impact on model output.
All other inputs have β at most β less than half the impact on the model output as contested possessions; of those, though, uncontested possessions, goals, tackles, and handballs
Kicking and total scoring opportunities get lower in priority.
Parting thoughts on an approach to data and achieving success
Iβll make three observations to end this article:
- The importance of domain knowledge when analyzing data and drawing inferences reinforces the priority of the ongoing democratization of data science and ML through low-code and no-code products
- Like any other space, focusing practice on key skills can enhance the chances of success and prevents dilution of effort/exhaustion from spreading players too thin.
- Keeping track of success factors as the rules of the game and playing styles evolve can ensure that your focus keeps you attuned to the latest trends and prevents you from falling behind the times.
Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming aΒ sponsor.
Published via Towards AI