Yesterday I wrote a quick blog post discussing how the distribution of field goal attempts by distance has shifted quite a bit over the last twenty five years: teams are attempting fewer short field goals and more long field goals.
In this post, I’ll look to round out my analysis, focusing not on attempts, but the actual make rate of kicks by distance, and how that has evolved over time.
Visualizations
What we find when we slice the data by time is that kickers have NFL kickers have become more accurate at every level of accuracy since 1999. We find that kickers in 2024 are:
- 12 ppt more accurate on kicks of 30-34 yards than 1999-2003
- 10 ppt more accurate on kicks of 40-44 yards than 1999-2003
- 20 ppt more accurate on kicks of 50-54 yards than 1999-2003
- 40 ppt more accurate on kicks of 55-59 yards than 1999-2003
The huge improvements in the 50-59 yard range have meaningfully changed how the game is played, allowing teams to attempt more long kicks instead of punting.
I’ve made two charts to visualize this data: one showing field goal conversion probability per year cohort as a decaying line chart, and another summarizing the accuracy per cohort and distance as a heatmap.
Kick Conversion as a Function of Distance
As Tile Chart
Code Reference
Play-by-Play Data Acquisition
library(tidyverse)
library(lubridate)
library(nflreadr)
# Initialize an empty data frame to store results
all_kicks <- tibble()
# Loop through each year from 1999 to 2024
for (year in 1999:2024) {
# Load play-by-play data for the specific year
pbp <- load_pbp(year)
# Filter and process the data
kicks <- pbp %>%
filter(!is.na(field_goal_result)) %>%
select(game_date, field_goal_result, kick_distance, kicker_player_name) %>%
mutate(year = year(ymd(game_date)))
# Append the processed kicks data to the main dataset
all_kicks <- bind_rows(all_kicks, kicks)
# Print progress to monitor the loop
print(paste("Processed year:", year))
}
Charting Code
Line Chart
all_kicks %>%
mutate(kick_distance = floor(kick_distance / 5) * 5) %>%
mutate(kick_distance = ifelse(kick_distance > 60, 60, kick_distance)) %>%
group_by(year_range, kick_distance) %>%
summarize(
fga = n(),
fgm_pct = sum(fgm) / n()
) %>%
ggplot(aes(x = kick_distance, y = fgm_pct, group = year_range, color = year_range)) +
geom_line() +
geom_point(size = 0.8) +
scale_color_manual(
values = c("#003f5c", "#444e86", "#955196", "#dd5182", "#ff6e54", "#ffa600"),
name = "Years"
) +
scale_x_continuous(limits = c(20, 60), breaks = seq(20, 60, 5)) +
scale_y_continuous(labels = scales::percent_format(), breaks = pretty_breaks(n = 5)) +
coord_cartesian(clip = "off") +
theme_minimal() +
theme(
panel.grid.minor.x = element_blank(),
plot.title = element_text(size = 20, face = "bold"),
plot.subtitle = element_text(size = 12),
plot.caption = element_text(colour = "grey60"),
plot.title.position = "plot"
) +
labs(
x = "\nField Goal Distance (Rounded Down to Nearest 5 Yards)",
y = "% of Field Goals Attempts Made\n",
title = "NFL Kicker Accuracy as a Function of\nDistance and Year",
subtitle = "Kickers are making significantly more kicks at every distance than they did\n25 years ago, with the most pronounced improvement visible >50 yards.",
caption = "conormclaughlin.net"
)
Tile Chart
all_kicks %>%
mutate(kick_distance = floor(kick_distance / 5) * 5) %>%
mutate(kick_distance = ifelse(kick_distance > 60, 60, kick_distance)) %>%
filter(kick_distance >= 20) %>%
group_by(year_range, rounded_year, kick_distance) %>%
summarize(
fga = n(),
fgm_pct = sum(fgm) / n()
) %>%
ggplot(aes(x = kick_distance, y = reorder(year_range, -rounded_year), fill = fgm_pct)) +
geom_tile() +
geom_text(
size = 3,
fontface = "bold",
aes(
label = scales::percent(fgm_pct, accuracy = 1),
colour = ifelse(fgm_pct < 0.4, "white", "black")
)
) +
scale_colour_identity() + # Use the specified colors without a legend
scale_fill_viridis(option = "C", guide = FALSE) +
scale_x_continuous(breaks = seq(20, 60, 5)) +
theme_minimal() +
theme(
panel.grid.minor.x = element_blank(),
plot.title = element_text(size = 20, face = "bold"),
plot.subtitle = element_text(size = 12),
plot.caption = element_text(colour = "grey60"),
plot.title.position = "plot"
) +
labs(
x = "\nField Goal Distance (Rounded Down to Nearest 5 Yards)",
y = "",
title = "NFL Kicker Accuracy as a Function of\nDistance and Year",
subtitle = "Kickers are making significantly more kicks at every distance than they did\n25 years ago, with the most pronounced improvement visible >50 yards.",
caption = "conormclaughlin.net"
)