1 — AFL Data & Performance Question

1.1 AFL Performance Analytics

High-speed running (HSR) is one example of a performance variable that can be tracked across repeated AFL matches.

In this tutorial, we use a simulated AFL dataset to demonstrate how longitudinal player data can be analysed using a multilevel model in R.

The example focuses on two playing-role groups:

  • Midfield
  • Key Position

The purpose is not to analyse real AFL players. Instead, the simulated dataset provides a simple and reproducible example for learning how repeated performance measurements can be analysed.

Learning goal
By the end of this tutorial, you will understand how a multilevel longitudinal model can account for repeated observations from the same player.

1.2 Research Question

How does simulated high-speed running performance change across AFL matches, and does the performance trajectory differ between playing roles?

1.3 Simulated AFL Dataset

The dataset contains repeated observations for 60 simulated AFL players across 10 matches.

Each row represents one player in one match.

Variable Description
Player_ID Unique player identifier
Match Match number
Match_c Match number centred at the first match
Position_Group Midfield or Key Position
HSR_m_min High-speed running distance per minute

The dataset contains 600 observations:

60 players × 10 matches = 600 observations

set.seed(2026)

players <- tibble(
  Player_ID = paste0("P", sprintf("%02d", 1:60)),
  Position_Group = rep(
    c("Midfield", "Key Position"),
    times = c(40, 20)
  )
)

afl <- expand_grid(
  Player_ID = players$Player_ID,
  Match = 1:10
) %>%
  left_join(players, by = "Player_ID") %>%
  mutate(
    Match_c = Match - 1
  )

player_effects <- tibble(
  Player_ID = players$Player_ID,
  random_intercept = rnorm(
    60,
    mean = 0,
    sd = 0.14
  ),
  random_slope = rnorm(
    60,
    mean = 0,
    sd = 0.015
  )
)

afl <- afl %>%
  left_join(player_effects, by = "Player_ID")

afl <- afl %>%
  mutate(
    position_effect = if_else(
      Position_Group == "Midfield",
      0.15,
      -0.15
    ),
    
    time_effect = -0.018 * Match_c,
    
    position_time_effect = if_else(
      Position_Group == "Midfield",
      -0.010 * Match_c,
      0.010 * Match_c
    ),
    
    HSR_m_min =
      1.45 +
      position_effect +
      time_effect +
      position_time_effect +
      random_intercept +
      random_slope * Match_c +
      rnorm(
        n(),
        mean = 0,
        sd = 0.10
      )
  )

afl <- afl %>%
  select(
    Player_ID,
    Match,
    Match_c,
    Position_Group,
    HSR_m_min
  )

1.4 Visualising the Data

Before fitting a statistical model, it is useful to examine how HSR performance varies across matches and playing roles.

afl_summary <- afl %>%
  group_by(Match, Position_Group) %>%
  summarise(
    mean_HSR = mean(HSR_m_min),
    se = sd(HSR_m_min) / sqrt(n()),
    .groups = "drop"
  )

ggplot(
  afl_summary,
  aes(
    x = Match,
    y = mean_HSR,
    colour = Position_Group,
    group = Position_Group
  )
) +
  geom_ribbon(
    aes(
      ymin = mean_HSR - 1.96 * se,
      ymax = mean_HSR + 1.96 * se,
      fill = Position_Group
    ),
    alpha = 0.15,
    colour = NA
  ) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2.5) +
  scale_colour_manual(
    values = c(
      "Midfield" = "#2F75B5",
      "Key Position" = "#E67E22"
    )
  ) +
  scale_fill_manual(
    values = c(
      "Midfield" = "#2F75B5",
      "Key Position" = "#E67E22"
    )
  ) +
  labs(
    title = "Simulated HSR Performance Across Matches",
    x = "Match",
    y = "HSR (m/min)",
    colour = "Playing role",
    fill = "Playing role"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(
      face = "bold",
      size = 15
    ),
    legend.position = "top"
  )

Figure 1. Mean simulated HSR performance across matches by playing role. Shaded areas represent approximate 95% confidence intervals for the group means.

What can we see?
The plot provides an initial view of differences between playing roles and changes in HSR performance across matches. These patterns will be examined more formally using a multilevel longitudinal model in the following sections.

2 — Exploring Longitudinal Player Performance

The data contain repeated measurements from the same players across multiple matches. This means we can examine both differences between players and changes within each player over time.

The individual trajectories below help us understand this longitudinal structure before fitting a multilevel model.

2.1 Individual Player Trajectories

Each line represents one simulated player across 10 matches. Players can start at different performance levels and can also change at different rates across matches.

ggplot(
  afl,
  aes(
    x = Match,
    y = HSR_m_min,
    group = Player_ID,
    colour = Position_Group
  )
) +
  geom_line(
    alpha = 0.35,
    linewidth = 0.7
  ) +
  geom_smooth(
    aes(group = Position_Group),
    method = "lm",
    se = TRUE,
    linewidth = 1.3
  ) +
  scale_colour_manual(
    values = c(
      "Midfield" = "#2F75B5",
      "Key Position" = "#E67E22"
    )
  ) +
  labs(
    title = "Individual Player HSR Trajectories",
    subtitle = "Each line represents one simulated player",
    x = "Match",
    y = "HSR (m/min)",
    colour = "Playing role"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(
      face = "bold",
      size = 15
    ),
    plot.subtitle = element_text(
      colour = "#666666"
    ),
    legend.position = "top"
  )

Figure 2. Individual simulated player trajectories across 10 matches. Thin lines show individual players, while the coloured lines show the overall trajectory for each playing-role group.

2.2 Why Does the Data Need a Multilevel Model?

The observations are not independent because multiple measurements come from the same player.

For example, Player P01 has 10 observations:

Player P01 → Match 1, Match 2, Match 3, … Match 10

The observations from the same player are therefore clustered within that player.

Longitudinal data structure



Player

→

Match 1

Match 2

Match 3

…

Match 10


Repeated match observations are nested within each player.

Key idea
Players can differ in their baseline HSR performance and in how their performance changes across matches. A multilevel model can account for this player-level variation while estimating the overall effects of match and playing role.

This provides the basis for building the multilevel models in Page 3.

3 — Building and Interpreting the Multilevel Model

The exploratory analysis showed that repeated observations are clustered within players. We can now build a multilevel model to account for this structure and examine changes in HSR performance across matches.

3.1 Model 1 — Unconditional Means Model

The first model estimates the overall average HSR performance while allowing each player to have their own baseline level.

This model separates variation between players from variation within players.

model_1 <- lmer(
  HSR_m_min ~ 1 + (1 | Player_ID),
  data = afl
)

summary(model_1)
## Linear mixed model fit by REML ['lmerMod']
## Formula: HSR_m_min ~ 1 + (1 | Player_ID)
##    Data: afl
## 
## REML criterion at convergence: -505.5
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -3.06399 -0.63696 -0.04616  0.60569  2.86579 
## 
## Random effects:
##  Groups    Name        Variance Std.Dev.
##  Player_ID (Intercept) 0.03762  0.1939  
##  Residual              0.01842  0.1357  
## Number of obs: 600, groups:  Player_ID, 60
## 
## Fixed effects:
##             Estimate Std. Error t value
## (Intercept)  1.38750    0.02564   54.11

3.2 Model 2 — Unconditional Growth Model

Next, match number is added to examine whether HSR performance changes across matches.

A random slope allows each player to have their own rate of change.

model_2 <- lmer(
  HSR_m_min ~ Match_c + (Match_c | Player_ID),
  data = afl
)

summary(model_2)
## Linear mixed model fit by REML ['lmerMod']
## Formula: HSR_m_min ~ Match_c + (Match_c | Player_ID)
##    Data: afl
## 
## REML criterion at convergence: -765.8
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.74010 -0.66047 -0.03749  0.59168  2.75618 
## 
## Random effects:
##  Groups    Name        Variance  Std.Dev. Corr  
##  Player_ID (Intercept) 0.0454335 0.21315        
##            Match_c     0.0003976 0.01994  -0.39 
##  Residual              0.0094793 0.09736        
## Number of obs: 600, groups:  Player_ID, 60
## 
## Fixed effects:
##              Estimate Std. Error t value
## (Intercept)  1.496474   0.028492  52.522
## Match_c     -0.024215   0.002923  -8.286
## 
## Correlation of Fixed Effects:
##         (Intr)
## Match_c -0.437

3.3 Model 3 — Adding Playing Role

Playing role is then added as a fixed effect. This allows the model to estimate the average difference in HSR performance between Midfield and Key Position players.

model_3 <- lmer(
  HSR_m_min ~ Match_c + Position_Group + (Match_c | Player_ID),
  data = afl
)

summary(model_3)
## Linear mixed model fit by REML ['lmerMod']
## Formula: HSR_m_min ~ Match_c + Position_Group + (Match_c | Player_ID)
##    Data: afl
## 
## REML criterion at convergence: -796.5
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.74768 -0.65429 -0.01373  0.60967  2.68806 
## 
## Random effects:
##  Groups    Name        Variance  Std.Dev. Corr  
##  Player_ID (Intercept) 0.0200758 0.14169        
##            Match_c     0.0003976 0.01994  -0.11 
##  Residual              0.0094792 0.09736        
## Number of obs: 600, groups:  Player_ID, 60
## 
## Fixed effects:
##                         Estimate Std. Error t value
## (Intercept)             1.291424   0.033524  38.522
## Match_c                -0.024215   0.002923  -8.285
## Position_GroupMidfield  0.307576   0.040658   7.565
## 
## Correlation of Fixed Effects:
##             (Intr) Mtch_c
## Match_c     -0.139       
## Pstn_GrpMdf -0.809  0.000

3.4 Model 4 — Match × Playing Role

The final model includes an interaction between match and playing role.

This tests whether the change in HSR performance across matches differs between the two playing-role groups.

model_4 <- lmer(
  HSR_m_min ~ Match_c * Position_Group + (Match_c | Player_ID),
  data = afl
)

summary(model_4)
## Linear mixed model fit by REML ['lmerMod']
## Formula: HSR_m_min ~ Match_c * Position_Group + (Match_c | Player_ID)
##    Data: afl
## 
## REML criterion at convergence: -798.9
## 
## Scaled residuals: 
##      Min       1Q   Median       3Q      Max 
## -2.73850 -0.65766 -0.02019  0.60995  2.63769 
## 
## Random effects:
##  Groups    Name        Variance  Std.Dev. Corr  
##  Player_ID (Intercept) 0.0198731 0.14097        
##            Match_c     0.0003182 0.01784  -0.07 
##  Residual              0.0094792 0.09736        
## Number of obs: 600, groups:  Player_ID, 60
## 
## Fixed effects:
##                                 Estimate Std. Error t value
## (Intercept)                     1.270546   0.034020  37.347
## Match_c                        -0.011154   0.004653  -2.397
## Position_GroupMidfield          0.338893   0.041666   8.134
## Match_c:Position_GroupMidfield -0.019592   0.005699  -3.438
## 
## Correlation of Fixed Effects:
##             (Intr) Mtch_c Pst_GM
## Match_c     -0.219              
## Pstn_GrpMdf -0.816  0.179       
## Mtch_c:P_GM  0.179 -0.816 -0.219

3.5 Comparing the Models

The models can be compared using AIC. Lower AIC values indicate a better balance between model fit and model complexity.

AIC(
  model_1,
  model_2,
  model_3,
  model_4
)
##         df       AIC
## model_1  3 -499.4887
## model_2  6 -753.7522
## model_3  7 -782.4968
## model_4  8 -782.9325

3.6 Interpreting the Final Model

The final model estimates the effects of match, playing role, and their interaction while allowing players to differ in both their baseline performance and their rate of change.

The key term is the Match × Position interaction. This term represents whether the performance trajectory across matches differs between Midfield and Key Position players.

fixef(model_4)
##                    (Intercept)                        Match_c 
##                     1.27054557                    -0.01115386 
##         Position_GroupMidfield Match_c:Position_GroupMidfield 
##                     0.33889284                    -0.01959238

3.7 Visualising the Final Model

The final model can be used to visualise the estimated HSR trajectories for the two playing-role groups.

prediction_data <- expand_grid(
  Match = 1:10,
  Position_Group = c(
    "Midfield",
    "Key Position"
  )
)

prediction_data <- prediction_data %>%
  mutate(
    Match_c = Match - 1
  )

prediction_data$Predicted_HSR <- predict(
  model_4,
  newdata = prediction_data,
  re.form = NA
)

ggplot(
  prediction_data,
  aes(
    x = Match,
    y = Predicted_HSR,
    colour = Position_Group,
    group = Position_Group
  )
) +
  geom_line(
    linewidth = 1.4
  ) +
  geom_point(
    size = 2.5
  ) +
  scale_colour_manual(
    values = c(
      "Midfield" = "#2F75B5",
      "Key Position" = "#E67E22"
    )
  ) +
  labs(
    title = "Predicted HSR Performance Across Matches",
    subtitle = "Predictions from the final multilevel model",
    x = "Match",
    y = "Predicted HSR (m/min)",
    colour = "Playing role"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(
      face = "bold",
      size = 15
    ),
    plot.subtitle = element_text(
      colour = "#666666"
    ),
    legend.position = "top"
  )

Figure 3. Predicted HSR trajectories across matches from the final multilevel model. The model accounts for repeated observations within players and estimates different trajectories for the two playing-role groups.

3.8 Key Takeaways

The analysis followed a simple progression:

  1. The unconditional means model accounted for differences between players.
  2. The growth model introduced change across matches and player-specific slopes.
  3. Playing role was added to estimate differences between groups.
  4. The interaction model examined whether the trajectory differed between playing roles.

The final model estimates a higher HSR trajectory for Midfield players than Key Position players. The two groups also show different rates of change across matches, which is represented by the interaction between Match and Position_Group.