In this document, we will use the fitzroy package to get
the ladder positions of each team, after each round, in the 2025 AFL
season. We will then create an animated plot showing how each team’s
position changed across the season.
# Install packages if required
#install.packages("fitzRoy")
#install.packages("dplyr")
#install.packages("ggplot2")
#install.packages("gganimate")
#install.packages("gifski")
# Open packages ready for data collection and visualisation
library(fitzRoy)
library(dplyr)
library(ggplot2)
library(gganimate)
library(gifski)
Ladder_2025 <- fetch_ladder_afltables(season = 2025)
## No round number specified, trying to return most recent ladder for specified
## season
Ladder_2025
## # A tibble: 18 × 8
## Season Team Round.Number Season.Points Score.For Score.Against Percentage
## <dbl> <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 2025 Adelaide 25 72 2278 1635 1.39
## 2 2025 Geelong 25 68 2425 1714 1.41
## 3 2025 Brisban… 25 66 2061 1804 1.14
## 4 2025 Colling… 25 64 1991 1627 1.22
## 5 2025 GWS 25 64 2114 1834 1.15
## 6 2025 Fremant… 25 64 1978 1815 1.09
## 7 2025 Gold Co… 25 60 2173 1740 1.25
## 8 2025 Hawthorn 25 60 2045 1691 1.21
## 9 2025 Footscr… 25 56 2493 1820 1.37
## 10 2025 Sydney 25 48 1845 1902 0.970
## 11 2025 Carlton 25 36 1799 1861 0.967
## 12 2025 St Kilda 25 36 1839 2077 0.885
## 13 2025 Port Ad… 25 36 1705 2136 0.798
## 14 2025 Melbour… 25 28 1902 2038 0.933
## 15 2025 Essendon 25 24 1535 2209 0.695
## 16 2025 North M… 25 22 1805 2365 0.763
## 17 2025 Richmond 25 20 1449 2197 0.660
## 18 2025 West Co… 25 4 1466 2438 0.601
## # ℹ 1 more variable: Ladder.Position <int>
Note this is the final ladder, from the end of the season.
We want the ladder position after every round. To do this we will
need to create a for loop.
# Set max round number for the 2025 season
maxround <- 25
# Create an empty list to store ladder results from each round
ladder_by_round <- list()
# Loop through each round of the season and collect the ladder data
for (i in 1:maxround){
# Temporarily store ladder for round i
ladder <- fetch_ladder_afltables(season = 2025, round = i)
# Add ladder for each round into empty list
ladder_by_round[[i]] <- ladder
}
# Combine all rounds into one dataframe
ladder_all_rounds <- bind_rows(ladder_by_round)
# Rename Western Bulldogs
ladder_all_rounds <- ladder_all_rounds %>%
mutate(Team = recode(Team, "Footscray" = "Western Bulldogs"))
# Check dataframe head and tail to ensure all rounds have worked
head(ladder_all_rounds)
## # A tibble: 6 × 8
## Season Team Round.Number Season.Points Score.For Score.Against Percentage
## <dbl> <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 2025 Gold Coa… 1 4 153 58 2.64
## 2 2025 GWS 1 4 104 52 2
## 3 2025 Hawthorn 1 4 96 76 1.26
## 4 2025 Brisbane… 1 4 70 61 1.15
## 5 2025 Geelong 1 0 61 70 0.871
## 6 2025 Sydney 1 0 76 96 0.792
## # ℹ 1 more variable: Ladder.Position <int>
tail(ladder_all_rounds)
## # A tibble: 6 × 8
## Season Team Round.Number Season.Points Score.For Score.Against Percentage
## <dbl> <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 2025 Port Ade… 25 36 1705 2136 0.798
## 2 2025 Melbourne 25 28 1902 2038 0.933
## 3 2025 Essendon 25 24 1535 2209 0.695
## 4 2025 North Me… 25 22 1805 2365 0.763
## 5 2025 Richmond 25 20 1449 2197 0.660
## 6 2025 West Coa… 25 4 1466 2438 0.601
## # ℹ 1 more variable: Ladder.Position <int>
Only require Round number, Team name and ladder position so the dataframe can be simplified
ladder_all_rounds <- ladder_all_rounds %>%
select(Round.Number, Team, Ladder.Position)
head(ladder_all_rounds)
## # A tibble: 6 × 3
## Round.Number Team Ladder.Position
## <int> <chr> <int>
## 1 1 Gold Coast 1
## 2 1 GWS 2
## 3 1 Hawthorn 3
## 4 1 Brisbane Lions 4
## 5 1 Geelong 5
## 6 1 Sydney 6
library(ggplot2)
library(dplyr)
# Filter to Round 2, (Round 1 only had 4 games)
ladder_round2 <- ladder_all_rounds %>%
filter(Round.Number == 2) %>%
mutate(Ladder.Position = as.numeric(Ladder.Position))
# Vertical ladder plot
ggplot(ladder_round2, aes(x = 2, y = Ladder.Position, color = Team)) +
geom_point(size = 4) + # dot for each team
scale_y_reverse(breaks = 1:18) + # 1 at top
labs(
title = "AFL 2025 Ladder - Round 2",
x = "Round 2",
y = "Ladder Position",
color = "Team"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_blank(), # For single round dont need full x axis
axis.ticks.x = element_blank(),
legend.key.size = unit(0.5, "cm"), # shrink legend boxes
legend.text = element_text(size = 8) # smaller legend text
)
Hard to identify team easily just from a range of colours, so we will add an abbreviation to each team’s circle.
library(ggplot2)
library(dplyr)
# Add team abbreviation to round 2 to add the the plot
ladder_round2 <- ladder_round2 %>%
mutate(TeamAbbr = case_when(
Team == "St Kilda" ~ "STK",
Team == "West Coast" ~ "WCE",
Team == "Western Bulldogs" ~ "WBD",
TRUE ~ substr(Team, 1, 3) # Every other team just select first 3 letters
))
# Vertical ladder plot with labels
ggplot(ladder_round2, aes(x = 2, y = Ladder.Position, color = Team)) +
geom_point(size = 5) + # larger dots to fit text
geom_text(aes(label = TeamAbbr), color = "black", size = 2) + # label inside dot
scale_y_reverse(breaks = 1:18) + # 1 at top
labs(
title = "AFL 2025 Ladder - Round 2",
x = "Round 2",
y = "Ladder Position",
color = "Team"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_blank(), # For single round dont need full x axis
axis.ticks.x = element_blank(),
legend.key.size = unit(0.5, "cm"), # shrink legend boxes
legend.text = element_text(size = 8) # smaller legend text
)
library(ggplot2)
library(dplyr)
# Make sure Ladder.Position is numeric
ladder_all_rounds <- ladder_all_rounds %>%
mutate(Ladder.Position = as.numeric(Ladder.Position))
# Add team abbreviation
ladder_all_rounds <- ladder_all_rounds %>%
group_by(Team) %>%
mutate(TeamAbbr = case_when(
Team == "St Kilda" ~ "STK",
Team == "West Coast" ~ "WCE",
Team == "Western Bulldogs" ~ "WBD",
TRUE ~ substr(Team, 1, 3) # Every other team just select first 3 letters
))
# Ladder position vs round plot
ladder_plot <- ggplot(ladder_all_rounds, aes(x = Round.Number, y = Ladder.Position, group = Team, color = Team)) +
geom_line(size = 1, alpha = 0.7) + # lines showing ladder progression
geom_point(size = 4) + # dots at each round
geom_text(aes(label = TeamAbbr), color = "black", size = 2) + # label inside dot
scale_y_reverse(limits = c(18, 1), # 1 is top, 18 is bottom
breaks = 1:18,
expand = expansion(add = c(1, 1))) + # x-axis spacing, needed for animation to fit
scale_x_continuous(breaks = 1:max(ladder_all_rounds$Round.Number)) +
labs(
title = "AFL 2025 Ladder Progression",
x = "Round",
y = "Ladder Position",
) +
theme_minimal(base_size = 10) +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none",
plot.margin = unit(c(1, 1, 2, 1), "cm")) # extra space around plot for animation
ladder_plot
As we have the round by round ladder for each team, we can turn this plot into an animation
# Only run this in RStudio
# Animate round-by-round
animated_ladder <- ladder_plot +
transition_reveal(Round.Number) +
ease_aes("linear")
# Save the animation as a .gif file
anim_save(
filename = "ladder_anim.gif",
animation = animated_ladder,
fps = 8,
width = 800,
height = 600,
res = 150,
renderer = gifski_renderer(loop = TRUE),
end_pause = 20
)
# Display the saved file
knitr::include_graphics("ladder_anim.gif")