Clearances are an important part of Australian Football League (AFL) performance, allowing teams to gain possession following centre bounces and stoppages. But does winning more clearances than the opposition relate to a greater winning margin?
This tutorial explores the relationship between clearance differential and match margin using data from the 2026 AFL men’s season. Simple linear regression in R is used to examine this relationship through data preparation, visualisation, statistical modelling and interpretation.
The analysis focuses on two key variables:
Positive values indicate an advantage to the home team, while negative values indicate an advantage to the away team.
The fitzRoy package is used to retrieve official AFL
statistics and match results, while tidyverse is used for
data preparation and visualisation, and knitr for
presenting tables.
If the packages are not already installed, they can be installed by running the following code once in the RStudio Console:
To compare teams, we first add individual player clearances together to calculate each team’s total clearances per match.
# Groups player statistics by match and team
team_clearances <- afl_stats %>%
group_by(providerId, utcStartTime, teamStatus,
`home.team.name`, `away.team.name`) %>%
# Adds individual player clearances to calculate team totals
summarise(
clearances = sum(clearances.totalClearances, na.rm = TRUE),
.groups = "drop"
) %>%
# Converts team status to lowercase for consistency
mutate(teamStatus = str_to_lower(teamStatus)) %>%
select(providerId, utcStartTime, `home.team.name`,
`away.team.name`, teamStatus, clearances) %>%
# Creates separate columns for home and away team clearances
pivot_wider(names_from = teamStatus, values_from = clearances) %>%
# Renames variables and calculates the clearance differential
transmute(
providerId,
match_time = as.POSIXct(substr(as.character(utcStartTime), 1, 19),
format = "%Y-%m-%dT%H:%M:%S", tz = "UTC"),
home_team = `home.team.name`,
away_team = `away.team.name`,
home_clearances = home,
away_clearances = away,
clearance_diff = home - away
)Next, we calculate match margins from the official AFL scores and match them to the clearance totals. We use match identifiers where possible, with match times as a fallback.
# Prepare match results and calculate the match margin
results_clean <- match_results %>%
transmute(
result_id = as.character(`match.matchId`),
match_time = as.POSIXct(`match.date`, tz = "UTC"),
margin = as.numeric(`homeTeamScore.matchScore.totalScore`) -
as.numeric(`awayTeamScore.matchScore.totalScore`)
)
# Convert match IDs to the same data type
team_clearances <- team_clearances %>%
mutate(
providerId = as.character(providerId)
)
# Match clearance statistics with match results using match IDs
id_matches <- team_clearances %>%
inner_join(results_clean, by = c("providerId" = "result_id"),
suffix = c("", ".result"))
# Match clearance statistics with match results using match times
time_matches <- team_clearances %>%
inner_join(results_clean, by = "match_time")
# Use match IDs if all matches are found, otherwise use match times
if (nrow(id_matches) == nrow(match_results)) {
afl_matches <- id_matches
} else {
afl_matches <- time_matches
}
# Remove missing values and convert match dates to Melbourne time
afl_matches <- afl_matches %>%
filter(!is.na(clearance_diff), !is.na(margin)) %>%
mutate(match_date = as.Date(match_time, tz = "Australia/Melbourne"))
# Check that all matches are included and there are no duplicates
if (nrow(afl_matches) != nrow(match_results) ||
n_distinct(afl_matches$providerId) != nrow(afl_matches)) {
stop("Some AFL matches could not be matched uniquely. Check match IDs and start times.")
}A positive clearance differential means the home team recorded more clearances; a positive match margin means the home team won.
# Select the match details and clearance statistics to display
afl_matches %>%
select(match_date, home_team, away_team,
home_clearances, away_clearances, clearance_diff, margin) %>%
# Display the first six matches
head(6) %>%
# Present the results in a formatted table
kable(caption = "Table 1. Example matches from the 2026 AFL season")| match_date | home_team | away_team | home_clearances | away_clearances | clearance_diff | margin |
|---|---|---|---|---|---|---|
| 2026-03-05 | Sydney Swans | Carlton | 40 | 39 | 1 | 63 |
| 2026-03-06 | Gold Coast SUNS | Geelong Cats | 34 | 41 | -7 | 56 |
| 2026-03-07 | GWS GIANTS | Hawthorn | 44 | 37 | 7 | 27 |
| 2026-03-07 | Brisbane Lions | Western Bulldogs | 45 | 39 | 6 | -5 |
| 2026-03-08 | St Kilda | Collingwood | 31 | 29 | 2 | -12 |
| 2026-03-12 | Carlton | Richmond | 37 | 29 | 8 | 4 |
Before creating graphs, we can summarise the typical values and variation in clearance differential and match margin.
# Calculate summary statistics for clearance differential and match margin
summary_table <- afl_matches %>%
summarise(
`Mean clearance differential` = round(mean(clearance_diff), 1),
`Median clearance differential` = round(median(clearance_diff), 1),
`SD clearance differential` = round(sd(clearance_diff), 1),
`Minimum clearance differential` = min(clearance_diff),
`Maximum clearance differential` = max(clearance_diff),
`Mean match margin` = round(mean(margin), 1),
`SD match margin` = round(sd(margin), 1)
) %>%
# Organise the statistics into two columns
pivot_longer(everything(), names_to = "Statistic", values_to = "Value")
# Display the summary statistics in a formatted table
kable(summary_table, caption = "Table 2. Summary statistics for 2026 AFL matches")| Statistic | Value |
|---|---|
| Mean clearance differential | 0.9 |
| Median clearance differential | 0.0 |
| SD clearance differential | 9.3 |
| Minimum clearance differential | -22.0 |
| Maximum clearance differential | 24.0 |
| Mean match margin | 7.2 |
| SD match margin | 40.3 |
The mean and median describe typical values, while the standard deviation and range describe variation between matches.
A histogram shows how often different clearance differentials occur. Values near zero indicate similar clearance totals for the two teams.
# Create a histogram showing the distribution of clearance differentials
ggplot(afl_matches, aes(x = clearance_diff)) +
# Group clearance differentials into intervals of five
geom_histogram(binwidth = 5, fill = "navyblue", colour = "white") +
# Add a dashed line at zero to indicate equal clearances
geom_vline(xintercept = 0, linetype = "dashed", colour = "red") +
# Add the graph title and axis labels
labs(title = "Distribution of Clearance Differentials",
subtitle = "2026 AFL men's season",
x = "Clearance differential (home - away)",
y = "Number of matches") +
# Apply a simple theme to the graph
theme_minimal(base_size = 13)A boxplot compares clearance differentials for home-team wins and losses. Draws are excluded from this comparison.
# Categorise matches as home wins, losses or draws based on match margin
afl_matches <- afl_matches %>%
mutate(result = case_when(
margin > 0 ~ "Home Win",
margin < 0 ~ "Home Loss",
TRUE ~ "Draw"
))
# Exclude drawn matches and compare clearance differentials by match result
ggplot(afl_matches %>% filter(result != "Draw"),
aes(x = result, y = clearance_diff, fill = result)) +
# Create a boxplot showing the distribution for wins and losses
geom_boxplot(alpha = 0.8, width = 0.5) +
# Set the colours for home wins and losses
scale_fill_manual(values = c("Home Win" = "lightgreen",
"Home Loss" = "orange")) +
# Add the graph title and axis labels
labs(title = "Clearance Differentials in Wins and Losses",
subtitle = "2026 AFL men's season",
x = "Match result", y = "Clearance differential (home - away)") +
# Apply a simple theme and remove the legend
theme_minimal(base_size = 13) +
theme(legend.position = "none")The line inside each box is the median. Home-team wins generally had a higher median clearance differential than home-team losses, suggesting a positive relationship between clearance performance and winning matches.
Each dot on this scatterplot represents one match. The fitted line shows the estimated linear relationship between clearance differential and match margin.
# Create a scatterplot comparing clearance differential and match margin
ggplot(afl_matches, aes(x = clearance_diff, y = margin)) +
# Add a point for each match
geom_point(colour = "navyblue", alpha = 0.6, size = 2.2) +
# Add a linear regression line with a confidence interval
geom_smooth(method = "lm", se = TRUE, colour = "red") +
# Add dashed lines at zero on both axes
geom_hline(yintercept = 0, linetype = "dashed", colour = "grey65") +
geom_vline(xintercept = 0, linetype = "dashed", colour = "grey65") +
# Add the graph title and axis labels
labs(title = "Clearance Differential and AFL Match Margin",
subtitle = "2026 AFL men's season",
x = "Clearance differential (home - away)",
y = "Match margin (points; home - away)") +
# Apply a simple theme to the graph
theme_minimal(base_size = 13)The upward-sloping line indicates a positive relationship, with greater clearance differentials generally associated with higher match margins. However, the wide spread of points suggests that clearances alone do not fully explain match outcomes.
The lm() function is used to estimate how match margin
changes with clearance differential.
# Fit a simple linear regression model to predict match margin from clearance differential
model <- lm(margin ~ clearance_diff, data = afl_matches)
# Display the regression results, including coefficients, R-squared and p-values
summary(model)##
## Call:
## lm(formula = margin ~ clearance_diff, data = afl_matches)
##
## Residuals:
## Min 1Q Median 3Q Max
## -120.448 -21.482 -0.893 25.050 105.324
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.9583 2.6071 2.285 0.0233 *
## clearance_diff 1.3586 0.2796 4.860 2.26e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 38.3 on 216 degrees of freedom
## Multiple R-squared: 0.09856, Adjusted R-squared: 0.09438
## F-statistic: 23.62 on 1 and 216 DF, p-value: 2.26e-06
The slope represents the estimated change in match margin for a one-clearance increase in differential. R-squared describes the proportion of variation in match margins explained by the linear regression model, while the p-value indicates whether there is statistical evidence of a relationship.
# Extract the slope, R-squared and p-value from the regression model
slope <- coef(model)[["clearance_diff"]]
r2 <- summary(model)$r.squared
p_value <- coef(summary(model))["clearance_diff", "Pr(>|t|)"]
# Create a formatted table with the regression results
tibble(
Measure = c("Slope (points per clearance)", "R-squared", "p-value"),
Value = c(
sprintf("%.2f", slope),
sprintf("%.3f", r2),
ifelse(p_value < 0.001, "< 0.001", sprintf("%.3f", p_value))
)
) %>%
knitr::kable(caption = "Linear regression results")| Measure | Value |
|---|---|
| Slope (points per clearance) | 1.36 |
| R-squared | 0.099 |
| p-value | < 0.001 |
For these matches, the slope was 1.36 points per clearance, and the model explained approximately 9.9% of the variation in match margins (R-squared = 0.099). The slope’s p-value was 2.26^{-6}.
A residual is the difference between the actual match margin and the margin estimated by the model. A residual plot is used to identify patterns that may not be captured by the linear regression model.
# Calculate predicted match margins and residuals from the regression model
afl_matches <- afl_matches %>%
mutate(predicted_margin = predict(model),
residual = margin - predicted_margin)
# Create a scatterplot comparing residuals with predicted match margins
ggplot(afl_matches, aes(x = predicted_margin, y = residual)) +
# Add a point for each match
geom_point(colour = "navyblue", alpha = 0.6, size = 2.2) +
# Add a dashed line at zero to identify positive and negative residuals
geom_hline(yintercept = 0, linetype = "dashed", colour = "red") +
# Add the graph title and axis labels
labs(title = "Residuals vs Predicted Match Margins",
x = "Predicted match margin (points)",
y = "Residual (points)") +
# Apply a simple theme to the graph
theme_minimal(base_size = 13)Ideally, residuals are scattered around zero without a clear curved pattern or major change in spread. Large residuals indicate matches where clearance differential alone did not explain the margin well.
This tutorial demonstrated how simple linear regression can be applied to AFL match statistics to investigate the relationship between clearance differential and match margin.
Using 218 matches from the 2026 AFL season, the estimated relationship was positive. Clearance differential accounted for approximately 9.9% of the observed variation in match margins, suggesting that clearance performance has a relatively weak association with match outcomes.
Clearances are only one aspect of AFL performance. Scoring efficiency, inside-50 entries, defensive pressure and opposition strength may also influence results.