The purpose of this project is to investigate which NFL kickers were trusted to attempt longer field goals during the 2018–2020 seasons. Rather than examining accuracy alone, I wanted to compare the total distance of a kicker’s field goal attempts, average kick distance, number of attempts, and success rate.
The main research question is: Who were the best kickers based on the distance they were trusted to attempt?
To find the answer to this question, I summarized field goal attempts by kicker, visualized the relationship between total kick distance and the number of attempts, and created a heatmap showing successful and unsuccessful attempts across distance categories. I also develop a combined score using average kick distance and success rate to produce a Top 10 ranking to not clutter the heatmap with a bunch of random low ranking kickers.
I use the NFL Big Data Bowl data from the 2018, 2019, and 2020
seasons. The plays.csv file contains information about
field goal attempts, including kick distance and result. The
players.csv file identifies the kickers, while
games.csv provides season information.
library(data.table)
library(dplyr)
library(ggplot2)
library(scales)
library(knitr)
library(kableExtra)
# Set the folder containing the CSV files
setwd("~/Rstudio/nfl-big-data-bowl-2022")
# Load the data
plays <- fread("plays.csv")
players <- fread("players.csv")
games <- fread("games.csv")
# Join the data so each play includes the kicker's name and season
plays_df <- plays |>
left_join(
players |> select(nflId, displayName),
by = c("kickerId" = "nflId")
) |>
left_join(
games |> select(gameId, season),
by = "gameId"
)
# Keep completed field goal attempts with recorded results
perf_summary <- plays_df |>
filter(
specialTeamsPlayType == "Field Goal",
specialTeamsResult %in% c(
"Kick Attempt Good",
"Kick Attempt No Good"
),
!is.na(kickLength),
!is.na(displayName)
) |>
data.frame()
# Check the data
head(perf_summary)
## gameId playId
## 1 2018090600 658
## 2 2018090600 1368
## 3 2018090600 1587
## 4 2018090900 1226
## 5 2018090900 1613
## 6 2018090900 1973
## playDescription
## 1 (5:03) M.Bryant 21 yard field goal is GOOD, Center-J.Overbaugh, Holder-M.Bosher.
## 2 (6:12) J.Elliott 26 yard field goal is GOOD, Center-R.Lovato, Holder-C.Johnston.
## 3 (2:13) M.Bryant 52 yard field goal is GOOD, Center-J.Overbaugh, Holder-M.Bosher.
## 4 (13:18) (Field Goal formation) J.Tucker 41 yard field goal is GOOD, Center-M.Cox, Holder-S.Koch.
## 5 (8:48) (Field Goal formation) S.Hauschka 52 yard field goal is No Good, Short, Center-R.Ferguson, Holder-C.Bojorquez.
## 6 (4:20) (Field Goal formation) J.Tucker 39 yard field goal is GOOD, Center-M.Cox, Holder-S.Koch.
## quarter down yardsToGo possessionTeam specialTeamsPlayType
## 1 1 4 3 ATL Field Goal
## 2 2 4 8 PHI Field Goal
## 3 2 4 17 ATL Field Goal
## 4 2 4 13 BAL Field Goal
## 5 2 4 9 BUF Field Goal
## 6 2 4 2 BAL Field Goal
## specialTeamsResult kickerId returnerId kickBlockerId yardlineSide
## 1 Kick Attempt Good 27091 <NA> NA PHI
## 2 Kick Attempt Good 44966 <NA> NA ATL
## 3 Kick Attempt Good 27091 <NA> NA PHI
## 4 Kick Attempt Good 39470 <NA> NA BUF
## 5 Kick Attempt No Good 33469 <NA> NA BAL
## 6 Kick Attempt Good 39470 <NA> NA BUF
## yardlineNumber gameClock penaltyCodes penaltyJerseyNumbers penaltyYards
## 1 3 05:03:00 <NA> <NA> NA
## 2 8 06:12:00 <NA> <NA> NA
## 3 34 02:13:00 <NA> <NA> NA
## 4 23 13:18:00 <NA> <NA> NA
## 5 34 08:48:00 <NA> <NA> NA
## 6 21 04:20:00 <NA> <NA> NA
## preSnapHomeScore preSnapVisitorScore passResult kickLength kickReturnYardage
## 1 0 0 <NA> 21 NA
## 2 0 3 <NA> 26 NA
## 3 3 3 <NA> 52 NA
## 4 14 0 <NA> 41 NA
## 5 17 0 <NA> 52 NA
## 6 17 0 <NA> 39 NA
## playResult absoluteYardlineNumber displayName season
## 1 0 13 Matt Bryant 2018
## 2 0 18 Jake Elliott 2018
## 3 0 76 Matt Bryant 2018
## 4 0 33 Justin Tucker 2018
## 5 -8 76 Stephen Hauschka 2018
## 6 0 31 Justin Tucker 2018
I calculate the number of field goal attempts, total kick distance, average kick distance, number of successful attempts, number of missed attempts, and success rate for each kicker.
To avoid ranking kickers with very few attempts, I include only kickers with at least 10 recorded field goal attempts.
# Calculate performance statistics for each kicker
kicker_summary <- perf_summary |>
group_by(displayName) |>
summarise(
kicks = n(),
total_kick_length = sum(kickLength, na.rm = TRUE),
average_kick_length = mean(kickLength, na.rm = TRUE),
made = sum(specialTeamsResult == "Kick Attempt Good"),
missed = sum(specialTeamsResult == "Kick Attempt No Good"),
success_rate = made / kicks,
.groups = "drop"
) |>
filter(kicks >= 10)
# Calculate standardized scores for average distance and success rate
kicker_summary <- kicker_summary |>
mutate(
distance_score = as.numeric(scale(average_kick_length)),
success_score = as.numeric(scale(success_rate)),
# Give equal weight to distance and success rate
kicker_score = distance_score + success_score
) |>
arrange(desc(kicker_score))
# Display the summary
kicker_summary
## # A tibble: 48 × 10
## displayName kicks total_kick_length average_kick_length made missed
## <chr> <int> <int> <dbl> <int> <int>
## 1 Graham Gano 45 1796 39.9 42 3
## 2 Brandon McManus 82 3344 40.8 72 10
## 3 Jason Myers 80 3149 39.4 73 7
## 4 Justin Tucker 92 3493 38.0 87 5
## 5 Matt Bryant 34 1406 41.4 28 6
## 6 Josh Lambo 57 2153 37.8 54 3
## 7 Mason Crosby 71 2803 39.5 63 8
## 8 Sebastian Janikowski 24 967 40.3 20 4
## 9 Dustin Hopkins 85 3321 39.1 73 12
## 10 Randy Bullock 77 3012 39.1 66 11
## # ℹ 38 more rows
## # ℹ 4 more variables: success_rate <dbl>, distance_score <dbl>,
## # success_score <dbl>, kicker_score <dbl>
The combined score gives equal weight to average kick distance and success rate after standardizing both measures. A higher score indicates a combination of longer average attempts and a higher success rate. This is a custom scoring method for this project rather than an official NFL statistic.
Total kick distance can be influenced by how many opportunities a kicker receives. So I compare total kick distance with the number of attempts and also consider average kick distance and success rate in the subsequent analysis.
# Plot total kick distance against number of attempts
ggplot(
kicker_summary,
aes(
x = kicks,
y = total_kick_length
)
) +
geom_point(alpha = 0.7) +
geom_smooth(method = "lm", se = FALSE) +
labs(
title = "Total Kick Distance vs. Number of Attempts",
x = "Number of Field Goal Attempts",
y = "Total Kick Distance (yards)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5)
)
This scatterplot helps show whether kickers with more attempts also accumulate greater total kick distance. The number of attempts is important when interpreting total distance because a kicker with more opportunities may accumulate a larger total even if their average attempts are shorter.
I group field goal attempts into five distance categories. The heatmap displays the number of successful and unsuccessful attempts for each kicker across these categories. For readability, it focuses on the Top 10 kickers identified by the combined score.
# Create categories for field goal distances
perf_summary2 <- perf_summary |>
mutate(
kick_range = cut(
kickLength,
breaks = c(0, 30, 40, 50, 60, Inf),
labels = c(
"Under 30",
"30-39",
"40-49",
"50-59",
"60+"
),
right = FALSE
)
) |>
filter(!is.na(kick_range))
# Get the names of the Top 10 kickers
top_10_kickers <- kicker_summary |>
slice_head(n = 10)
# Keep only the Top 10 kickers for the heatmap
heatmap_data <- perf_summary2 |>
filter(displayName %in% top_10_kickers$displayName) |>
group_by(displayName, kick_range, specialTeamsResult) |>
summarise(
kicks = n(),
.groups = "drop"
)
# Create the heatmap
ggplot(
heatmap_data,
aes(
x = kick_range,
y = displayName,
fill = kicks
)
) +
geom_tile(color = "white") +
facet_wrap(~specialTeamsResult) +
labs(
title = "Top 10 Kickers: Attempts by Distance and Result",
x = "Field Goal Distance (yards)",
y = "Kicker",
fill = "Number of Kicks"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.y = element_text(size = 8)
)
The heatmap helps identify the distance ranges in which these kickers attempted the most field goals. Comparing the panels also shows how successful and unsuccessful attempts are distributed across distance categories.
The following table ranks kickers by the combined score. It also shows the number of attempts, total distance, average distance, and success rate so that the ranking can be interpreted alongside the underlying statistics.
# Prepare the Top 10 table
top_10_table <- top_10_kickers |>
transmute(
Rank = row_number(),
Kicker = displayName,
Attempts = kicks,
`Total Distance (yards)` = round(total_kick_length, 1),
`Average Distance (yards)` = round(average_kick_length, 1),
`Successful Kicks` = made,
`Missed Kicks` = missed,
`Success Rate` = percent(success_rate, accuracy = 0.1),
`Combined Score` = round(kicker_score, 2)
)
# Display the table
kable(
top_10_table,
format = "html",
caption = "Top 10 NFL Kickers by Combined Score",
align = "c"
) |>
kable_styling(
bootstrap_options = c("striped", "hover", "condensed"),
full_width = TRUE,
position = "center"
)
| Rank | Kicker | Attempts | Total Distance (yards) | Average Distance (yards) | Successful Kicks | Missed Kicks | Success Rate | Combined Score |
|---|---|---|---|---|---|---|---|---|
| 1 | Graham Gano | 45 | 1796 | 39.9 | 42 | 3 | 93.3% | 2.64 |
| 2 | Brandon McManus | 82 | 3344 | 40.8 | 72 | 10 | 87.8% | 2.21 |
| 3 | Jason Myers | 80 | 3149 | 39.4 | 73 | 7 | 91.2% | 1.96 |
| 4 | Justin Tucker | 92 | 3493 | 38.0 | 87 | 5 | 94.6% | 1.69 |
| 5 | Matt Bryant | 34 | 1406 | 41.4 | 28 | 6 | 82.4% | 1.62 |
| 6 | Josh Lambo | 57 | 2153 | 37.8 | 54 | 3 | 94.7% | 1.60 |
| 7 | Mason Crosby | 71 | 2803 | 39.5 | 63 | 8 | 88.7% | 1.60 |
| 8 | Sebastian Janikowski | 24 | 967 | 40.3 | 20 | 4 | 83.3% | 1.16 |
| 9 | Dustin Hopkins | 85 | 3321 | 39.1 | 73 | 12 | 85.9% | 0.87 |
| 10 | Randy Bullock | 77 | 3012 | 39.1 | 66 | 11 | 85.7% | 0.87 |
# Compare the combined scores of the Top 10 kickers
ggplot(
top_10_kickers,
aes(
x = reorder(displayName, kicker_score),
y = kicker_score
)
) +
geom_col() +
coord_flip() +
labs(
title = "Top 10 NFL Kickers by Combined Score",
x = "Kicker",
y = "Combined Score"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5)
)
The table and ranking graph identify the kickers with the highest combined scores among those with at least 10 recorded attempts. The scatterplot provides context about the relationship between total kick distance and number of opportunities, while the heatmap shows the distribution of attempts by distance and result.
This project examines NFL field goal kickers from 2018 through 2020 by considering the distances they attempted, their number of attempts, and their success rates. The visualizations provide different perspectives on kicker performance: the scatterplot compares total distance with opportunities, the heatmap compares attempts across distance categories, and the Top 10 table and graph summarize the combined ranking.
The main takeaway is that identifying the best kickers depends on how performance is measured. A kicker who attempts longer field goals may be trusted with more difficult opportunities, but distance alone does not guarantee success. Considering attempt volume, average distance, and success rate together provides a broader basis for comparing kickers.
I have came to the conclusion that Graham Gano is the best Kicker based on my analysis on longest field goals.