Revisiting NFL Kicker Accuracy: Long Shots are Becoming Routine

Twenty-five years ago, NFL kickers made fewer than one in four field goal attempts from 55–59 yards. Now they’re making nearly two in three.

After reading my January 2025 post on kicker accuracy, which first laid out that improvement, a reader reached out to me and requested another visual: put time on the x-axis, make rate on the y-axis, and give each distance range its own line. With football season underway, I’ve finally gotten back to that idea and have a fresh new plot to share with you all - I think it turned out nicely!

Visualizations

The chart below follows four distance ranges, from 40–44 yards through 55–59 yards, on a rolling five-year basis. Each year on the x-axis marks the end of a five-season period: the 2025 point includes attempts from 2021–2025, for example.

NFL field goal make rates for 40–44, 45–49, 50–54 and 55–59 yard kicks, on a rolling five-year basis, ending in each NFL season from 2003 through 2025.

What Stands Out

  • Success on 55–59 yard kicks has climbed over time and continues to rise slowly: they’ve gone from desperation attempts to something kickers can realistically make
  • The 45–49 and 50–54 yard lines have increased by roughly the same amount over the time window, showing a general improvement in accuracy
  • To me, the contrast suggests the improvement from 55–59 yards is more about gains in the capacity to hit that distance, while the shorter kicks are about accuracy gains at distances kickers could already reach

Code Reference

I used nflreadr to quickly pull in the play-by-play data - kudos to nflverse for making this accessible.

Load the Data

library(dplyr)
library(purrr)
library(ggplot2)

all_kicks <- map_dfr(1999:2025, function(year) {
  pbp <- nflreadr::load_pbp(year, file_type = "csv")
  if (!"play_deleted" %in% names(pbp)) pbp$play_deleted <- 0

  pbp %>%
    filter(field_goal_attempt == 1) %>%
    select(season, season_type, field_goal_result, kick_distance,
           play_deleted, desc)
})

Prepare the Attempts

kicks <- all_kicks %>%
  filter(season_type %in% c("REG", "POST"),
         field_goal_result %in% c("made", "missed", "blocked"),
         !is.na(kick_distance), coalesce(play_deleted, 0) != 1,
         !grepl("No Play", coalesce(desc, ""), ignore.case = TRUE)) %>%
  mutate(made = field_goal_result == "made",
         distance = floor(kick_distance / 5) * 5) %>%
  filter(distance %in% c(40, 45, 50, 55))

Rolling Five Year Make Rates

rolling <- map_dfr(2003:2025, function(end_season) {
  kicks %>%
    filter(between(season, end_season - 4, end_season)) %>%
    group_by(distance) %>%
    summarise(attempts = n(), makes = sum(made), .groups = "drop") %>%
    mutate(year = end_season, make_rate = makes / attempts)
}) %>%
  mutate(distance = factor(paste0(distance, "–", distance + 4, " yards")))

Build the Chart

p <- ggplot(rolling, aes(year, make_rate, colour = distance)) +
  geom_line(linewidth = 0.9) +
  scale_colour_manual(
    values = c("#003f5c", "#955196", "#dd5182", "#ffa600"),
    name = "Distance") +
  scale_x_continuous(breaks = c(2003, 2007, 2011, 2015, 2019, 2023, 2025),
                     limits = c(2003, 2025), expand = expansion(mult = 0.02)) +
  scale_y_continuous(labels = scales::label_percent(accuracy = 1),
                     breaks = seq(0, 1, 0.2), limits = c(0, 1),
                     expand = expansion(mult = c(0, 0.02))) +
  theme_minimal(base_size = 13, base_family = "sans") +
  theme(
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(colour = "grey92", linewidth = 0.45),
    plot.title = element_text(size = 22, face = "bold", colour = "black"),
    plot.subtitle = element_text(size = 13, margin = margin(b = 16)),
    plot.caption = element_text(size = 10, colour = "grey50", hjust = 1,
                                lineheight = 1.25, margin = margin(t = 15)),
    plot.title.position = "plot", axis.title.x = element_text(margin = margin(t = 14)),
    axis.title.y = element_text(margin = margin(r = 12)),
    legend.title = element_text(size = 13), legend.text = element_text(size = 12),
    legend.key.width = grid::unit(1.2, "cm"),
    legend.spacing.y = grid::unit(0.2, "cm"),
    plot.margin = margin(16, 16, 16, 16)) +
  labs(
    x = "Five-year period ending in",
    y = "% of Field Goal Attempts Made",
    title = "The 55-Yard Field Goal Is Becoming Routine",
    subtitle = paste0("55\u201359 yard kicks have improved the most, now going through nearly two-thirds of the time.\n",
                      "Rolling five-year make rates; 2025 includes the 2021\u20132025 NFL seasons"),
    caption = paste0("Regular season + playoffs. Blocked kicks count as misses.\n",
                     "Source: nflverse | conormclaughlin.net"))

p