1. Introduction

Statistical Process Control (SPC) is a method used to measure variation in a process over time and identify when observations differ from the expected pattern. Although SPC originated in quality control, its principles can be applied within a sporting context, where repeated observations across matches can be considered a process occurring over time. SPC charts display observations sequentially and include a mean centreline to show how the processes change relative to a benchmark. Control limits provide boundaries for the expected range of variation and are used to assess whether processes operate within statistical control. Observations within these limits are considered common-cause variation while observations outside the expected limits indicate special-cause variation. Ultimately, within a sporting context, a poor performance in a single match does not immediately indicate a decline in performance. Instead, it may simply reflect normal match-to-match variation, where different contextual elements may have contributed to the statistical deviation. Therefore, the aim of this report is to:

  • Detect changes in Will Ashcroft’s technical performance to determine whether systematic poor performance or random variation occurs. Common metrics in the AFL will be assessed:
    • Disposals (D)
    • Kicks (K)
    • Marks (M)
    • Clearances (CL)
    • Inside 50s (I50)

The following research question will be examined:

  • How much match-to-match variation exists in Ashcroft’s technical performance, and can SPC identify performances that fall outside the player’s typical variation?


2. Load Libraries

First of all, we load all required packages. Use the install.packages() function if you do not have them installed.

library(tidyverse)
library(qicharts2)
library(fitzRoy)
library(kableExtra)
library(plotly)


3. Import Dataset

The dataset contains player-level statistics from the 2026 AFL season, sourced from footywire using the fitzRoy package. Each row represents an individual players performance across each round and includes technical-based parameters such as kicks, marks, handballs, tackles, disposals. The data is recorded by round, inclusive of date, venue, player, team, and opposition.

afl_df <- fitzRoy::fetch_player_stats(season = 2026, source = "footywire") |> as_tibble()
rmarkdown::paged_table(afl_df)


4. Data Cleaning

The data will focus on the technical performance of Will Ashcroft during the 2026 season. Thus, we can filter by player and select the relevant performance indicators. The arrange function will be used to ensure observations are ordered sequentially.

w_ash <- afl_df |> 
  dplyr::filter(Player == "Will Ashcroft") |> 
  dplyr::select(Date, Season, Round, Player, D, K, M, CL, I50) |> 
  dplyr::arrange(Date)

Now, let’s check if the data has any empty columns.

colSums(is.na(w_ash))
##   Date Season  Round Player      D      K      M     CL    I50 
##      0      0      0      0      0      0      0      0      0

Before examining performance using SPC, descriptive statistics are calculated to provide an overview of Ashcroft’s performance and match variability across each technical parameter.

eda <- w_ash |> 
  dplyr::summarise(
    D_mean = mean(D, na.rm = T),
    D_SD = sd(D, na.rm = T),
    K_mean = mean(K, na.rm = T),
    K_SD = sd(K, na.rm = T),
    M_mean = mean(M, na.rm = T),
    M_SD = sd(M, na.rm = T),
    CL_mean = mean(CL, na.rm = T),
    CL_SD = sd(CL, na.rm = T),
    I50_mean = mean(I50, na.rm = T),
    I50_SD = sd(I50, na.rm = T))

eda_table <- data.frame(
  Metric = c("Disposals", "Kicks", "Marks", "Clearances", "Inside 50s"),
  Mean = c(eda$D_mean, eda$K_mean, eda$M_mean, eda$CL_mean, eda$I50_mean),
  SD = c(eda$D_SD, eda$K_SD, eda$M_SD, eda$CL_SD, eda$I50_SD)
)

eda_table |> 
  kableExtra::kbl(digits = 2) |> 
  kableExtra::kable_material_dark(full_width = F)
Metric Mean SD
Disposals 28.56 5.58
Kicks 16.93 4.26
Marks 4.85 2.77
Clearances 5.59 2.32
Inside 50s 5.07 2.43


5. Exploratory Data Analysis

Let’s plot the data to assess Ashcroft’s performance over time.

a <- w_ash |> 
  dplyr::select(Date, D, K, M, CL, I50) |> 
  pivot_longer(cols = c(D, K, M, CL, I50), names_to = "metric", values_to = "value") |> 
  ggplot(aes(x = Date, y = value, color = metric)) +
  geom_line(alpha = 0.8) +
  geom_point(alpha = 0.5) +
  labs(
    title = "Ashcroft, W - 2026 Performance",
    x = "Time",
    y = "Value") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold"))

ggplotly(a)


6. SPC Plot

An Individual Moving Range (I-MR) chart was used to examine variation in the previously selected metrics.

The Individual(I) chart plots each match observation sequentially and compares performance against the centreline and control limits. The Moving Range(MR) chart measures short-term variability by assessing the absolute difference between consecutive match observations. This provides an overview of how Ashcroft’s performance changes between each match in order. Collectively, the I and MR chart allow the variability of performance to be measured over time to help identify both common-cause variation and potential special-cause variation.

To efficiently visualise the I-MR process, a function was created using the qicharts2 package. The function allows the chart type, performance metric, and title to be specified, enabling the I and MR charts to be visualised in an efficient and consistent manner.

IMR_plots <- function(chart, metric, title){
  
  imr_plot <- qicharts2::qic(metric,
                 chart = chart,
                 title = title)
  
  return(imr_plot)
}

Disposals (I)

IMR_plots(chart = "i", metric = w_ash$D, title = "Disposals (i)")

Disposals (MR)

IMR_plots(chart = "mr", metric = w_ash$D, title = "Disposals (mr)")

Kicks (I)

IMR_plots(chart = "i", metric = w_ash$K, title = "Kicks (i)")

Kicks (MR)

IMR_plots(chart = "mr", metric = w_ash$K, title = "Kicks (mr)")

Marks (I)

IMR_plots(chart = "i", metric = w_ash$M, title = "Marks (i)")

Marks (MR)

IMR_plots(chart = "mr", metric = w_ash$M, title = "Marks (mr)")

Clearances (I)

IMR_plots(chart = "i", metric = w_ash$CL, title = "Clearances (i)")

Clearances (MR)

IMR_plots(chart = "mr", metric = w_ash$CL, title = "Clearances (mr)")

Inside 50s (I)

IMR_plots(chart = "i", metric = w_ash$I50, title = "Inside 50 (i)")

Inside 50s (MR)

IMR_plots(chart = "mr", metric = w_ash$I50, title = "Inside 50 (mr)")


7. Interpretation

Based on the I-MR process, kicks in the MR plot reveal special-cause variation in the earlier rounds, indicating short-term variability between matches. Use the select function to assess Ashcroft’s kicks by round and calculate the MR for kicks by round.

kicks <- w_ash |> 
  dplyr::select(Date, Round, K) |> 
  dplyr::mutate(MR_K = abs(K - lag(K)))

rmarkdown::paged_table(kicks)

The table indicates special-cause variation in Round 5, exceeding the upper control limit by 0.5 (UCL = 15).


The MR chart for kicks identified a special-cause signal between Rounds 4 and 5, indicating a reduction in kicking output from 26 to 11 kicks. This produced a MR of 15, exceeding the upper control limit (UCL = 14.5), suggesting the magnitude of the match-to-match change was greater than the expected pattern. Thus, further research is required to gain a contextual understanding of why Ashcroft’s kick performance was flagged at this time. After researching within this time frame, there were three relevant factors that could potentially contribute to the special-cause signal. 1) Opposition tagging, 2) Match conditions, and 3) Team possession.


1) Opposition Tagging

The AFL match report for Round 5 (Brisbane v North Melbourne) reported that Jy Simpkin was assigned a run-with role on Ashcroft, describing it as Ashcroft’s first tag. Simpkin adopted a physical approach throughout the match, providing Ashcroft with additional opposition attention. This tactical approach was specifically intended to restrict Ashcroft’s influence, mitigating his ball movement and overall possession. Thus, providing plausible contextual explanation for the reduction in kick output from Round 4 to Round 5.


2) Match Conditions

The AFL match report described the Round 5 fixture as being played in wet and windy conditions. These conditions represent a potential contextual factor that may have contributed to the observed reduction in kicks. However, the current analysis cannot determine whether these conditions directly caused this change.


3) Possession

Brisbane’s uncontested possessions decreased from 277 in Round 4 to 166 in Round 5, indicating a large reduction in the team’s possession across the two matches. Concomitantly, Ashcroft’s individual output declined, with disposals decreasing from 36 to 19, marks from 6 to 1, metres gained from 475m to 340m, and score involvements from 11 to 3. The reduction in Brisbane’s uncontested possessions may therefore represent an important contextual factor associated with Ashcroft’s reduced kick output, potentially reflecting fewer opportunities to receive and dispose the ball.


Takeaway

By using SPC, we were able to understand how much match-to-match variation exists, specifically through the use of MR charts where the difference in kick performance in Round 4 & Round 5 was flagged. The variation captured during this period was likely attributed to opposition tagging, match conditions, or uncontested possessions; however, further research is required to capture all elements contributing to the special-cause signal.