1 Introduction

The aim of this project was to find out at what age AFL players generally perform best. Once again the Wheel-o player ratings were used to determine performance. The data was used from Wheel-o ratings and was 2012-2026. Peak age will be separated by and compared between positions.

2 Data and Software Requirements

2.1 Required Data

This analysis uses AFL player statistics from 2012 to 2026.

The files should be saved in the same folder as this R Markdown file.

The files are:

  • afl-player-stats-2012.csv
  • afl-player-stats-2013.csv
  • afl-player-stats-2014.csv
  • afl-player-stats-2015.csv
  • afl-player-stats-2016.csv
  • afl-player-stats-2017.csv
  • afl-player-stats-2018.csv
  • afl-player-stats-2019.csv
  • afl-player-stats-2020.csv
  • afl-player-stats-2021.csv
  • afl-player-stats-2022.csv
  • afl-player-stats-2023.csv
  • afl-player-stats-2024.csv
  • afl-player-stats-2025.csv
  • afl-player-stats-2026.csv

Data source: https://www.wheeloratings.com/afl_stats.html?comp=afl&season=2026

2.2 Required Package

The tidyverse package is used.

# load library
library(tidyverse)

3 Importing the Data

Each season is stored in a separate CSV file.

# import data
afl12 <- read.csv("afl-player-stats-2012.csv")
afl13 <- read.csv("afl-player-stats-2013.csv")
afl14 <- read.csv("afl-player-stats-2014.csv")
afl15 <- read.csv("afl-player-stats-2015.csv")
afl16 <- read.csv("afl-player-stats-2016.csv")
afl17 <- read.csv("afl-player-stats-2017.csv")
afl18 <- read.csv("afl-player-stats-2018.csv")
afl19 <- read.csv("afl-player-stats-2019.csv")
afl20 <- read.csv("afl-player-stats-2020.csv")
afl21 <- read.csv("afl-player-stats-2021.csv")
afl22 <- read.csv("afl-player-stats-2022.csv")
afl23 <- read.csv("afl-player-stats-2023.csv")
afl24 <- read.csv("afl-player-stats-2024.csv")
afl25 <- read.csv("afl-player-stats-2025.csv")
afl26 <- read.csv("afl-player-stats-2026.csv")

A season variable is added before combining the data.

# add season
afl12$Season <- 2012
afl13$Season <- 2013
afl14$Season <- 2014
afl15$Season <- 2015
afl16$Season <- 2016
afl17$Season <- 2017
afl18$Season <- 2018
afl19$Season <- 2019
afl20$Season <- 2020
afl21$Season <- 2021
afl22$Season <- 2022
afl23$Season <- 2023
afl24$Season <- 2024
afl25$Season <- 2025
afl26$Season <- 2026

The seasons are then combined into one dataset.

# combine data
afl <- bind_rows(
  afl12,
  afl13,
  afl14,
  afl15,
  afl16,
  afl17,
  afl18,
  afl19,
  afl20,
  afl21,
  afl22,
  afl23,
  afl24,
  afl25,
  afl26
)

4 Checking the Data

Some basic checks are done before the analysis.

# inspect data
head(afl)
##            Player            Team Age Age_Decimal Matches RatingPoints_Avg
## 1     Aaron Black North Melbourne  22       22.08       3            7.233
## 2 Aaron Cornelius        Brisbane  22       22.59       8            7.312
## 3     Aaron Davey       Melbourne  29       29.55       8           11.300
## 4   Aaron Edwards North Melbourne  28       28.83       9            6.711
## 5      Aaron Hall      Gold Coast  22       22.14       8            8.787
## 6    Aaron Joseph         Carlton  23       23.49      13            5.338
##   Supercoach_Avg DreamTeamPoints_Avg CoachesVotes_Total CoachesVotes_Avg
## 1         49.667              39.000                  0            0.000
## 2         47.875              53.000                  5            0.625
## 3         62.625              53.875                  0            0.000
## 4         68.667              62.333                  3            0.333
## 5         60.250              62.250                  0            0.000
## 6         50.077              48.692                  1            0.077
##   CoachesVotes_MatchesPolled BnFVotes_Total TimeOnGround Kicks Handballs
## 1                          0              0       72.667 3.333     4.333
## 2                          1              0       79.750 6.250     3.500
## 3                          0              0       79.250 6.500     4.125
## 4                          1              0       85.444 6.889     3.889
## 5                          0              1       72.500 8.125     4.500
## 6                          1              0       76.846 7.538     4.769
##   Disposals DisposalEfficiency KickingEfficiency HandballEfficiency
## 1     7.667             69.565            80.000             61.538
## 2     9.750             61.538            64.000             57.143
## 3    10.625             75.294            75.000             75.758
## 4    10.778             60.825            59.677             62.857
## 5    12.625             69.307            61.538             83.333
## 6    12.308             73.125            67.347             82.258
##   KickPercentage Inside50s Rebound50s Clangers DisposalsPerClanger Turnovers
## 1         43.478     0.667      0.000    0.667              11.500     1.000
## 2         64.103     1.750      0.000    2.250               4.333     2.750
## 3         61.176     1.875      0.750    1.750               6.071     1.875
## 4         63.918     1.222      0.000    1.889               5.706     2.667
## 5         64.356     2.500      0.500    1.250              10.100     2.625
## 6         61.250     1.231      1.462    1.846               6.667     2.000
##   DisposalsPerTurnover MetresGained MetresGainedPerDisposal
## 1                7.667      103.000                  13.435
## 2                3.545      149.125                  15.295
## 3                5.667      155.250                  14.612
## 4                4.042      165.222                  15.330
## 5                4.810      252.125                  19.970
## 6                6.154      150.385                  12.219
##   ContestedPossessions UncontestedPossessions TotalPossessions
## 1                4.667                  3.000            7.667
## 2                4.125                  5.125            9.250
## 3                6.375                  4.625           11.000
## 4                5.000                  5.667           10.667
## 5                3.000                  9.625           12.625
## 6                4.615                  7.308           11.923
##   ContestedPossessionRate Intercepts GroundBallGets GroundBallGetsForward50
## 1                  60.870      0.333          4.000                   2.333
## 2                  44.595      0.500          2.375                   1.250
## 3                  57.955      1.875          5.125                   1.750
## 4                  46.875      0.556          3.111                   1.667
## 5                  23.762      1.500          2.250                   0.625
## 6                  38.710      4.231          4.000                   0.077
##   CentreBounceAttendances CentreClearances StoppageClearances TotalClearances
## 1                   0.000            0.000              0.000           0.000
## 2                   0.000            0.000              0.000           0.000
## 3                   0.250            0.250              0.625           0.875
## 4                   0.000            0.111              0.333           0.444
## 5                   0.125            0.000              0.750           0.750
## 6                   1.385            0.077              0.231           0.308
##   Marks ContestedMarks MarksInside50 InterceptMarks MarksOnLead FreesFor
## 1 2.667          0.667         1.000          0.000       1.333    0.000
## 2 4.125          1.250         2.125          0.125       1.000    0.500
## 3 1.625          0.000         0.250          0.000       0.250    0.875
## 4 5.000          1.000         2.667          0.000       2.333    0.889
## 5 3.000          0.125         0.750          0.250       0.625    0.250
## 6 3.308          0.154         0.077          0.538       0.000    0.462
##   FreesAgainst FreesDiff RuckContests RuckContestPercentage Hitouts
## 1        0.667    -0.667         0.00                 0.000    0.00
## 2        0.750    -0.250         0.75                 0.755    0.25
## 3        1.250    -0.375         0.00                 0.000    0.00
## 4        0.667     0.222         0.00                 0.000    0.00
## 5        0.500    -0.250         0.00                 0.000    0.00
## 6        1.077    -0.615         0.00                 0.000    0.00
##   HitoutsWinPercentage HitoutsToAdvantage HitoutsToAdvantagePercentage Tackles
## 1                   NA                  0                           NA   2.000
## 2               33.333                  0                            0   1.500
## 3                   NA                  0                           NA   4.625
## 4                   NA                  0                           NA   2.000
## 5                   NA                  0                           NA   3.625
## 6                   NA                  0                           NA   2.231
##   TacklesInside50 ContestDefensiveOneOnOnes ContestDefensiveLossPercentage
## 1           1.333                     0.000                             NA
## 2           0.875                     0.125                              0
## 3           1.500                     0.000                             NA
## 4           0.667                     0.111                            100
## 5           0.750                     0.000                             NA
## 6           0.077                     0.769                             20
##   ContestOffensiveOneOnOnes ContestOffensiveWinPercentage Goals_Total Goals_Avg
## 1                     1.000                        33.333           3     1.000
## 2                     1.875                        26.667          13     1.625
## 3                     0.500                        25.000           7     0.875
## 4                     1.778                        31.250          17     1.889
## 5                     0.125                         0.000           8     1.000
## 6                     0.000                            NA           1     0.077
##   Behinds ShotsAtGoal GoalAssists ScoreboardImpact GoalAccuracy
## 1   0.333       1.667       0.667           10.333       60.000
## 2   0.625       2.375       0.375           12.625       68.421
## 3   0.375       1.375       0.375            7.875       63.636
## 4   0.667       3.111       0.778           16.667       60.714
## 5   0.625       2.000       0.375            8.875       50.000
## 6   0.000       0.077       0.077            0.923      100.000
##   ScoreInvolvements ScoreInvolvementPercentage ScoreLaunches KickIns
## 1             3.333                     13.699         0.333   0.000
## 2             3.875                     19.018         0.250   0.000
## 3             4.125                     19.760         0.875   0.125
## 4             5.444                     19.291         0.333   0.000
## 5             3.750                     19.481         0.625   0.000
## 6             2.308                      9.585         0.615   0.077
##   KickInsPlayOnPercentage Bounces OnePercenters PressureActs
## 1                      NA   0.000         0.333        9.667
## 2                      NA   0.000         0.750       10.250
## 3                       0   0.125         0.500       17.625
## 4                      NA   0.222         1.000       10.667
## 5                      NA   0.125         0.625       15.875
## 6                       0   0.154         2.154        7.769
##   PressureActsDefensiveHalf Spoils Season Position FirstPossessions
## 1                     1.667  0.333   2012     <NA>               NA
## 2                     2.625  0.375   2012     <NA>               NA
## 3                     4.500  0.125   2012     <NA>               NA
## 4                     2.111  0.556   2012     <NA>               NA
## 5                     4.875  0.500   2012     <NA>               NA
## 6                     5.308  1.615   2012     <NA>               NA
##   FirstPossessionToClearance HitoutToFirstPossession HitoutToClearance
## 1                         NA                      NA                NA
## 2                         NA                      NA                NA
## 3                         NA                      NA                NA
## 4                         NA                      NA                NA
## 5                         NA                      NA                NA
## 6                         NA                      NA                NA
##   DisposalRetentionPercentage KicksIntoF50 KickIntoF50RetentionPercentage
## 1                          NA           NA                             NA
## 2                          NA           NA                             NA
## 3                          NA           NA                             NA
## 4                          NA           NA                             NA
## 5                          NA           NA                             NA
## 6                          NA           NA                             NA
##   AssistedMetresGained NetMetresGained KickMetresGained RetainedMetresGained
## 1                   NA              NA               NA                   NA
## 2                   NA              NA               NA                   NA
## 3                   NA              NA               NA                   NA
## 4                   NA              NA               NA                   NA
## 5                   NA              NA               NA                   NA
## 6                   NA              NA               NA                   NA
##   KickMetresGainedPerKick HandballMetresGained HandballMetresGainedPerHandball
## 1                      NA                   NA                              NA
## 2                      NA                   NA                              NA
## 3                      NA                   NA                              NA
## 4                      NA                   NA                              NA
## 5                      NA                   NA                              NA
## 6                      NA                   NA                              NA
##   Equity_PreClearance Equity_PostClearance Equity_Possession Equity_BallUse
## 1                  NA                   NA                NA             NA
## 2                  NA                   NA                NA             NA
## 3                  NA                   NA                NA             NA
## 4                  NA                   NA                NA             NA
## 5                  NA                   NA                NA             NA
## 6                  NA                   NA                NA             NA
##   xThreatPerKick ThreatRating xRetainPerKick RetentionRating
## 1             NA           NA             NA              NA
## 2             NA           NA             NA              NA
## 3             NA           NA             NA              NA
## 4             NA           NA             NA              NA
## 5             NA           NA             NA              NA
## 6             NA           NA             NA              NA
##   GroundBallGetsDefensive50 HardBallGets LooseBallGets
## 1                        NA           NA            NA
## 2                        NA           NA            NA
## 3                        NA           NA            NA
## 4                        NA           NA            NA
## 5                        NA           NA            NA
## 6                        NA           NA            NA
##   PostClearanceContestedPossessions PostClearanceGroundBallGets
## 1                                NA                          NA
## 2                                NA                          NA
## 3                                NA                          NA
## 4                                NA                          NA
## 5                                NA                          NA
## 6                                NA                          NA
##   GathersFromHitout CrumbingPossessions HandballReceives AverageFieldPosition
## 1                NA                  NA               NA                   NA
## 2                NA                  NA               NA                   NA
## 3                NA                  NA               NA                   NA
## 4                NA                  NA               NA                   NA
## 5                NA                  NA               NA                   NA
## 6                NA                  NA               NA                   NA
##   Defensive50Possessions DefensiveMidfieldPossessions
## 1                     NA                           NA
## 2                     NA                           NA
## 3                     NA                           NA
## 4                     NA                           NA
## 5                     NA                           NA
## 6                     NA                           NA
##   AttackingMidfieldPossessions Forward50Possessions
## 1                           NA                   NA
## 2                           NA                   NA
## 3                           NA                   NA
## 4                           NA                   NA
## 5                           NA                   NA
## 6                           NA                   NA
##   CentreBounceAttendancePercentage CentreClearanceRate RuckHardBallGets
## 1                               NA                  NA               NA
## 2                               NA                  NA               NA
## 3                               NA                  NA               NA
## 4                               NA                  NA               NA
## 5                               NA                  NA               NA
## 6                               NA                  NA               NA
##   ScoreAssists xScoreShots xScorePerShot xScoreRatingPerShot xScoreShots_Set
## 1           NA          NA            NA                  NA              NA
## 2           NA          NA            NA                  NA              NA
## 3           NA          NA            NA                  NA              NA
## 4           NA          NA            NA                  NA              NA
## 5           NA          NA            NA                  NA              NA
## 6           NA          NA            NA                  NA              NA
##   xScorePerShot_Set xScoreRatingPerShot_Set xScoreShots_General
## 1                NA                      NA                  NA
## 2                NA                      NA                  NA
## 3                NA                      NA                  NA
## 4                NA                      NA                  NA
## 5                NA                      NA                  NA
## 6                NA                      NA                  NA
##   xScorePerShot_General xScoreRatingPerShot_General KickInPercentage
## 1                    NA                          NA               NA
## 2                    NA                          NA               NA
## 3                    NA                          NA               NA
## 4                    NA                          NA               NA
## 5                    NA                          NA               NA
## 6                    NA                          NA               NA
dim(afl)
## [1] 9976  127
names(afl)
##   [1] "Player"                            "Team"                             
##   [3] "Age"                               "Age_Decimal"                      
##   [5] "Matches"                           "RatingPoints_Avg"                 
##   [7] "Supercoach_Avg"                    "DreamTeamPoints_Avg"              
##   [9] "CoachesVotes_Total"                "CoachesVotes_Avg"                 
##  [11] "CoachesVotes_MatchesPolled"        "BnFVotes_Total"                   
##  [13] "TimeOnGround"                      "Kicks"                            
##  [15] "Handballs"                         "Disposals"                        
##  [17] "DisposalEfficiency"                "KickingEfficiency"                
##  [19] "HandballEfficiency"                "KickPercentage"                   
##  [21] "Inside50s"                         "Rebound50s"                       
##  [23] "Clangers"                          "DisposalsPerClanger"              
##  [25] "Turnovers"                         "DisposalsPerTurnover"             
##  [27] "MetresGained"                      "MetresGainedPerDisposal"          
##  [29] "ContestedPossessions"              "UncontestedPossessions"           
##  [31] "TotalPossessions"                  "ContestedPossessionRate"          
##  [33] "Intercepts"                        "GroundBallGets"                   
##  [35] "GroundBallGetsForward50"           "CentreBounceAttendances"          
##  [37] "CentreClearances"                  "StoppageClearances"               
##  [39] "TotalClearances"                   "Marks"                            
##  [41] "ContestedMarks"                    "MarksInside50"                    
##  [43] "InterceptMarks"                    "MarksOnLead"                      
##  [45] "FreesFor"                          "FreesAgainst"                     
##  [47] "FreesDiff"                         "RuckContests"                     
##  [49] "RuckContestPercentage"             "Hitouts"                          
##  [51] "HitoutsWinPercentage"              "HitoutsToAdvantage"               
##  [53] "HitoutsToAdvantagePercentage"      "Tackles"                          
##  [55] "TacklesInside50"                   "ContestDefensiveOneOnOnes"        
##  [57] "ContestDefensiveLossPercentage"    "ContestOffensiveOneOnOnes"        
##  [59] "ContestOffensiveWinPercentage"     "Goals_Total"                      
##  [61] "Goals_Avg"                         "Behinds"                          
##  [63] "ShotsAtGoal"                       "GoalAssists"                      
##  [65] "ScoreboardImpact"                  "GoalAccuracy"                     
##  [67] "ScoreInvolvements"                 "ScoreInvolvementPercentage"       
##  [69] "ScoreLaunches"                     "KickIns"                          
##  [71] "KickInsPlayOnPercentage"           "Bounces"                          
##  [73] "OnePercenters"                     "PressureActs"                     
##  [75] "PressureActsDefensiveHalf"         "Spoils"                           
##  [77] "Season"                            "Position"                         
##  [79] "FirstPossessions"                  "FirstPossessionToClearance"       
##  [81] "HitoutToFirstPossession"           "HitoutToClearance"                
##  [83] "DisposalRetentionPercentage"       "KicksIntoF50"                     
##  [85] "KickIntoF50RetentionPercentage"    "AssistedMetresGained"             
##  [87] "NetMetresGained"                   "KickMetresGained"                 
##  [89] "RetainedMetresGained"              "KickMetresGainedPerKick"          
##  [91] "HandballMetresGained"              "HandballMetresGainedPerHandball"  
##  [93] "Equity_PreClearance"               "Equity_PostClearance"             
##  [95] "Equity_Possession"                 "Equity_BallUse"                   
##  [97] "xThreatPerKick"                    "ThreatRating"                     
##  [99] "xRetainPerKick"                    "RetentionRating"                  
## [101] "GroundBallGetsDefensive50"         "HardBallGets"                     
## [103] "LooseBallGets"                     "PostClearanceContestedPossessions"
## [105] "PostClearanceGroundBallGets"       "GathersFromHitout"                
## [107] "CrumbingPossessions"               "HandballReceives"                 
## [109] "AverageFieldPosition"              "Defensive50Possessions"           
## [111] "DefensiveMidfieldPossessions"      "AttackingMidfieldPossessions"     
## [113] "Forward50Possessions"              "CentreBounceAttendancePercentage" 
## [115] "CentreClearanceRate"               "RuckHardBallGets"                 
## [117] "ScoreAssists"                      "xScoreShots"                      
## [119] "xScorePerShot"                     "xScoreRatingPerShot"              
## [121] "xScoreShots_Set"                   "xScorePerShot_Set"                
## [123] "xScoreRatingPerShot_Set"           "xScoreShots_General"              
## [125] "xScorePerShot_General"             "xScoreRatingPerShot_General"      
## [127] "KickInPercentage"

5 Preparing the Data

Only players who played at least 10 matches are used.

Players with a missing age or Player Rating are also removed.

# create analysis dataset
peak_data <- afl[
  afl$Matches >= 10 &
    !is.na(afl$Age) &
    !is.na(afl$RatingPoints_Avg),
]

# inspect analysis data
dim(peak_data)
## [1] 6702  127
summary(peak_data$Age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   19.00   22.00   25.00   25.43   28.00   39.00
summary(peak_data$RatingPoints_Avg)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.350   7.139   8.915   9.208  10.938  22.213

Using a minimum of 10 matches helps stop a player with only a few games from having too much effect on the results.

6 Average Player Rating by Age

The number of observations at each age is first calculated.

# count observations by age
age_counts <- aggregate(
  RatingPoints_Avg ~ Age,
  data = peak_data,
  FUN = length
)

# rename count column
names(age_counts)[2] <- "N"

# display age counts
age_counts
##    Age   N
## 1   19 231
## 2   20 383
## 3   21 506
## 4   22 608
## 5   23 649
## 6   24 621
## 7   25 610
## 8   26 586
## 9   27 539
## 10  28 477
## 11  29 404
## 12  30 353
## 13  31 259
## 14  32 191
## 15  33 139
## 16  34  70
## 17  35  43
## 18  36  19
## 19  37   8
## 20  38   4
## 21  39   2

The average Player Rating is then calculated for each age.

# calculate average rating by age
age_ratings <- aggregate(
  RatingPoints_Avg ~ Age,
  data = peak_data,
  FUN = mean
)

# rename average rating column
names(age_ratings)[2] <- "AverageRating"

The average ratings and number of observations are then combined.

# combine age results
age_summary <- merge(
  age_ratings,
  age_counts,
  by = "Age"
)

# only keep ages with at least 20 observations
age_summary <- age_summary[
  age_summary$N >= 20,
]

# sort by age
age_summary <- age_summary[
  order(age_summary$Age),
]

# display age summary
age_summary
##    Age AverageRating   N
## 1   19      6.748597 231
## 2   20      7.573329 383
## 3   21      8.121115 506
## 4   22      8.722090 608
## 5   23      8.958313 649
## 6   24      9.513810 621
## 7   25      9.651289 610
## 8   26      9.661070 586
## 9   27      9.793699 539
## 10  28      9.790113 477
## 11  29      9.973765 404
## 12  30      9.868402 353
## 13  31      9.877286 259
## 14  32      9.402639 191
## 15  33      9.472705 139
## 16  34      9.622429  70
## 17  35      9.914023  43

A minimum of 20 observations is used so very small age groups do not have too much effect.

7 Finding the Highest Rated Age

The age with the highest average Player Rating is found.

# find age with highest average rating
best_age <- age_summary[
  which.max(age_summary$AverageRating),
]

# display highest rated age
best_age
##    Age AverageRating   N
## 11  29      9.973765 404

8 Plotting Player Rating by Age

The average Player Rating at each age is shown on a graph.

# create average rating by age plot
age_plot <- ggplot(
  age_summary,
  aes(
    x = Age,
    y = AverageRating
  )
) +
  geom_line() +
  geom_point() +
  labs(
    title = "Average AFL Player Rating by Age",
    x = "Age",
    y = "Average Player Rating"
  ) +
  theme_minimal()

# display age plot
age_plot

9 Quadratic Regression

A quadratic regression is used to model the relationship between age and Player Rating.

The squared age term allows the model to increase and then decrease instead of only fitting a straight line.

# fit quadratic regression
age_model <- lm(
  RatingPoints_Avg ~
    Age +
    I(Age^2),
  data = peak_data
)

# display model results
summary(age_model)
## 
## Call:
## lm(formula = RatingPoints_Avg ~ Age + I(Age^2), data = peak_data)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -8.1516 -1.9590 -0.2988  1.6926 12.2189 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -12.858468   1.329039  -9.675   <2e-16 ***
## Age           1.557667   0.102657  15.173   <2e-16 ***
## I(Age^2)     -0.026531   0.001953 -13.587   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 2.739 on 6699 degrees of freedom
## Multiple R-squared:  0.07616,    Adjusted R-squared:  0.07588 
## F-statistic: 276.1 on 2 and 6699 DF,  p-value: < 2.2e-16

10 Estimating Peak Age

The coefficients from the quadratic model are used to estimate the age where the curve reaches its highest point.

# save model coefficients
age_coefs <- coef(age_model)

# calculate predicted peak age
predicted_peak_age <- -
  age_coefs["Age"] /
  (2 * age_coefs["I(Age^2)"])

# display predicted peak age
predicted_peak_age
##      Age 
## 29.35612

A range of ages is then created so the regression curve can be plotted.

# create age prediction values
age_range <- data.frame(
  Age = seq(
    min(age_summary$Age),
    max(age_summary$Age),
    by = 0.1
  )
)

# predict rating across ages
age_range$PredictedRating <- predict(
  age_model,
  newdata = age_range
)

# find predicted maximum rating
predicted_peak_rating <- max(
  age_range$PredictedRating
)

# display predicted peak rating
predicted_peak_rating
## [1] 10.00501

11 Quadratic Regression Plot

The regression curve is plotted with the observed average ratings.

# create quadratic regression plot
regression_plot <- ggplot(
  age_summary,
  aes(
    x = Age,
    y = AverageRating
  )
) +
  geom_point() +
  geom_line(
    data = age_range,
    aes(
      x = Age,
      y = PredictedRating
    )
  ) +
  labs(
    title = "AFL Player Rating and Age",
    subtitle = "Quadratic regression curve",
    x = "Age",
    y = "Average Player Rating"
  ) +
  theme_minimal()

# display regression plot
regression_plot

12 Comparing Positions

Peak age is also compared between AFL positions.

Players without a recorded position are removed.

# create position dataset
position_data <- peak_data[
  !is.na(peak_data$Position) &
    peak_data$Position != "",
]

The number of observations for each age and position is calculated.

# calculate number of observations by age and position
position_counts <- aggregate(
  RatingPoints_Avg ~ Age + Position,
  data = position_data,
  FUN = length
)

# rename count column
names(position_counts)[3] <- "N"

The average Player Rating is then calculated for every age and position.

# calculate average rating by age and position
position_ratings <- aggregate(
  RatingPoints_Avg ~ Age + Position,
  data = position_data,
  FUN = mean
)

# rename rating column
names(position_ratings)[3] <- "AverageRating"

The results are combined.

Only age and position groups with at least 10 observations are kept.

# combine position results
position_summary <- merge(
  position_ratings,
  position_counts,
  by = c("Age", "Position")
)

# only keep groups with at least 10 observations
position_summary <- position_summary[
  position_summary$N >= 10,
]

# sort position results
position_summary <- position_summary[
  order(
    position_summary$Position,
    position_summary$Age
  ),
]

# display position results
position_summary
##     Age      Position AverageRating   N
## 1    19 Gen. Defender      6.934435  23
## 7    20 Gen. Defender      6.728825  40
## 14   21 Gen. Defender      7.586371  70
## 21   22 Gen. Defender      7.753122  74
## 28   23 Gen. Defender      8.064024  82
## 35   24 Gen. Defender      8.166943  70
## 42   25 Gen. Defender      9.081400  70
## 49   26 Gen. Defender      8.628753  81
## 56   27 Gen. Defender      8.478716  74
## 63   28 Gen. Defender      8.861134  67
## 70   29 Gen. Defender      8.856492  61
## 77   30 Gen. Defender      8.540000  50
## 84   31 Gen. Defender      9.027816  38
## 91   32 Gen. Defender      7.882750  28
## 98   33 Gen. Defender      8.218789  19
## 2    19  Gen. Forward      6.497703  37
## 8    20  Gen. Forward      6.684408  49
## 15   21  Gen. Forward      7.190161  62
## 22   22  Gen. Forward      7.175136  66
## 29   23  Gen. Forward      7.622439  82
## 36   24  Gen. Forward      7.938653  75
## 43   25  Gen. Forward      8.490438  64
## 50   26  Gen. Forward      8.182283  60
## 57   27  Gen. Forward      8.337880  50
## 64   28  Gen. Forward      8.410821  39
## 71   29  Gen. Forward      8.862447  38
## 78   30  Gen. Forward      8.184806  36
## 85   31  Gen. Forward      8.241350  20
## 92   32  Gen. Forward      8.577235  17
## 99   33  Gen. Forward      8.982167  12
## 9    20  Key Defender      7.066700  10
## 16   21  Key Defender      7.092923  13
## 23   22  Key Defender      8.032654  26
## 30   23  Key Defender      7.843511  47
## 37   24  Key Defender      8.708386  44
## 44   25  Key Defender      8.747896  48
## 51   26  Key Defender      8.228463  41
## 58   27  Key Defender      8.930347  49
## 65   28  Key Defender      8.276085  47
## 72   29  Key Defender      8.871667  45
## 79   30  Key Defender      8.524515  33
## 86   31  Key Defender      7.952520  25
## 93   32  Key Defender      8.046722  18
## 4    19   Key Forward      5.032000  13
## 10   20   Key Forward      6.107536  28
## 17   21   Key Forward      6.908722  36
## 24   22   Key Forward      7.150600  35
## 31   23   Key Forward      8.010250  36
## 38   24   Key Forward      7.729607  28
## 45   25   Key Forward      7.834135  37
## 52   26   Key Forward      8.383946  37
## 59   27   Key Forward      8.747226  31
## 66   28   Key Forward      7.952516  31
## 73   29   Key Forward      9.006458  24
## 80   30   Key Forward      8.351765  17
## 87   31   Key Forward      9.766643  14
## 94   32   Key Forward      9.048824  17
## 101  33   Key Forward      8.097000  16
## 108  34   Key Forward      8.939667  12
## 5    19   Mid-Forward      6.468562  16
## 11   20   Mid-Forward      8.090826  23
## 18   21   Mid-Forward      7.988423  26
## 25   22   Mid-Forward      7.907333  27
## 32   23   Mid-Forward      7.819750  28
## 39   24   Mid-Forward      9.930792  24
## 46   25   Mid-Forward      9.574850  20
## 53   26   Mid-Forward      9.664571  14
## 60   27   Mid-Forward     10.433833  18
## 67   28   Mid-Forward     10.400462  13
## 74   29   Mid-Forward      9.971600  10
## 81   30   Mid-Forward     10.593800  10
## 6    19    Midfielder      7.092250  36
## 12   20    Midfielder      8.192887  62
## 19   21    Midfielder      8.712352  91
## 26   22    Midfielder      9.564765 102
## 33   23    Midfielder     10.662200  95
## 40   24    Midfielder     10.570161  93
## 47   25    Midfielder     10.740144  97
## 54   26    Midfielder     10.852263  95
## 61   27    Midfielder     10.719330  88
## 68   28    Midfielder     11.069112  80
## 75   29    Midfielder     11.709629  70
## 82   30    Midfielder     11.123159  63
## 89   31    Midfielder     11.223157  51
## 96   32    Midfielder     10.203313  32
## 103  33    Midfielder     10.358417  24
## 110  34    Midfielder     10.918000  11
## 27   22          Ruck      8.034154  13
## 34   23          Ruck      9.558412  17
## 41   24          Ruck     10.926500  22
## 48   25          Ruck     11.386375  24
## 55   26          Ruck     11.432286  21
## 62   27          Ruck     11.040955  22
## 69   28          Ruck     11.575263  19
## 76   29          Ruck     11.643214  14
## 83   30          Ruck     12.529167  18
## 90   31          Ruck     11.368211  19
## 97   32          Ruck     11.702214  14
## 104  33          Ruck     10.937000  11

13 Finding Peak Age by Position

The age with the highest average Player Rating is found for each position.

# create empty peak position table
position_peaks <- data.frame()

# find peak age for each position
for (position in sort(
  unique(position_summary$Position)
)) {
  
  # select position
  current_position <- position_summary[
    position_summary$Position == position,
  ]
  
  # find highest rated age
  peak_row <- current_position[
    which.max(
      current_position$AverageRating
    ),
  ]
  
  # add result
  position_peaks <- rbind(
    position_peaks,
    data.frame(
      Position = position,
      PeakAge = peak_row$Age,
      PeakRating = peak_row$AverageRating,
      N = peak_row$N
    )
  )
}

# round ratings
position_peaks$PeakRating <- round(
  position_peaks$PeakRating,
  2
)

# display peak age by position
position_peaks
##        Position PeakAge PeakRating  N
## 1 Gen. Defender      25       9.08 70
## 2  Gen. Forward      33       8.98 12
## 3  Key Defender      27       8.93 49
## 4   Key Forward      31       9.77 14
## 5   Mid-Forward      30      10.59 10
## 6    Midfielder      29      11.71 70
## 7          Ruck      30      12.53 18

14 Plotting Age by Position

The relationship between age and Player Rating is shown separately for each position.

# create position plot
position_plot <- ggplot(
  position_summary,
  aes(
    x = Age,
    y = AverageRating
  )
) +
  geom_line() +
  geom_point() +
  facet_wrap(
    ~ Position
  ) +
  labs(
    title = "Average AFL Player Rating by Age and Position",
    x = "Age",
    y = "Average Player Rating"
  ) +
  theme_minimal()

# display position plot
position_plot

15 Final Tables

A small table is created with the main overall values.

# create final results
final_results <- data.frame(
  Result = c(
    "Highest Observed Average Age",
    "Quadratic Model Peak Age",
    "Quadratic Model Peak Rating"
  ),
  Value = c(
    best_age$Age,
    round(predicted_peak_age, 2),
    round(predicted_peak_rating, 2)
  )
)

# display final results
knitr::kable(
  final_results,
  caption = "Overall Peak Age Results"
)
Overall Peak Age Results
Result Value
Highest Observed Average Age 29.00
Quadratic Model Peak Age 29.36
Quadratic Model Peak Rating 10.01
# display position peak table
knitr::kable(
  position_peaks,
  caption = "Observed Peak Age by AFL Position"
)
Observed Peak Age by AFL Position
Position PeakAge PeakRating N
Gen. Defender 25 9.08 70
Gen. Forward 33 8.98 12
Key Defender 27 8.93 49
Key Forward 31 9.77 14
Mid-Forward 30 10.59 10
Midfielder 29 11.71 70
Ruck 30 12.53 18

16 Exporting the Results

The main tables can also be saved as CSV files.

# save age results
write.csv(
  age_summary,
  "afl_rating_by_age.csv",
  row.names = FALSE
)

# save position results
write.csv(
  position_peaks,
  "afl_peak_age_by_position.csv",
  row.names = FALSE
)