After an injury plagued 2025 season, 2026 has generally been a return to form for the Kansas City Chiefs. Patrick Mahomes has looked spry in his return from knee surgery, Travis Kelce seems like he is running in slightly less mud than usual, and Kenneth Walker has been a godsend in terms of balancing the offense. While they’ve clearly lacked a downfield receiving threat, forcing Mahomes to operate more in the short-to-intermediate space, the offense has still been productive, and they currently sit at 6-2 in the AFC West, a half game behind the Broncos.
Those wins have come very much in spite of long-time kicker Harrison Butker, who is 2 for his last 6 attempts from the field and just 12 for 19 (63%) on the season. To be honest, I’m shocked that Butker continues to be on the roster - beyond the numbers, the eye test says that he clearly doesn’t have the leg from long distance, and I suspect he’s not going to be able to turn it around with just more time.
Butker’s History
Why go out of my way to write this post about a struggling kicker? Let’s just say that Butker has never been a friend of this blog… he has a long track record of espousing bigoted views under the guise of his own religiosity.
For those who aren’t familiar, please see the following remarks he made at the 2024 Benedictine College commencement:
“Our own nation is led by a man who publicly and proudly proclaims his Catholic faith, but at the same time is delusional enough to make the Sign of the Cross during a pro- abortion rally. He has been so vocal in his support for the murder of innocent babies that I’m sure to many people it appears that you can be both Catholic and pro-choice.”
“While COVID might have played a large role throughout your formative years, it is not unique. Bad policies and poor leadership have negatively impacted major life issues. Things like abortion, IVF, surrogacy, euthanasia, as well as a growing support for degenerate cultural values in media, all stem from the pervasiveness of disorder.”
“For the ladies present today, congratulations on an amazing accomplishment. You should be proud of all that you have achieved to this point in your young lives. I want to speak directly to you briefly because I think it is you, the women, who have had the most diabolical lies told to you. How many of you are sitting here now about to cross this stage and are thinking about all the promotions and titles you are going to get in your career? Some of you may go on to lead successful careers in the world, but I would venture to guess that the majority of you are most excited about your marriage and the children you will bring into this world.”
Visualizations
With this context in mind, I thought it would be fun to build a chart or two to illustrate how and why Butker’s performance has fallen off this season!
As the data shows us, he’s not missing everything, but he is struggling significantly from long range at a time when other kickers around the league continue to get more and more accurate from those distances.
Rolling Field Goal Performance

This chart trends Butker’s points-over-expectation against a league average kicker over a rolling 30 kicks. Butker generally maintained average to above-average performance until the 2025 season, where his performance started to erode.
Those wobbles have turned into a full-on dive, and thus far in 2026 we’ve seen poor performance from distance torpedo his overall performance. While he’s been solid inside of 50 yards, that production isn’t nearly enough to offset how many points he’s cost the team from further out.
Computing Points over Expectations
To compute the counterfactual of “average NFL kicker”, we just fit a tiny logistic model that adjusts for distance and season based how the rest of the league performed.
model <- glm(made ~ splines::ns(distance, 4) + factor(season),
data = filter(fg, !is_butker), family = binomial())
fg$expected <- predict(model, newdata = fg, type = 'response')
b <- fg %>% filter(is_butker) %>% arrange(game_date, game_id, play_id) %>%
mutate(attempt = row_number(), pae = 3 * (made - expected))
Where the Long Kicks Are Going

I thought this chart, which borrows the layout of baseball spray charts, does a good job showing that he has been missing wide right more and more with time - delightful schadenfreude considering that’s where his personal views are also missing.
Data and Methods
Thank you to nflverse - I used their play-by-play data to fetch regular season kicks since 2020 to power these charts.
Code Reference
Fetching Kick Data
library(arrow)
library(dplyr)
kicks <- lapply(2020:2026, function(season) {
url <- sprintf(
"https://github.com/nflverse/nflverse-data/releases/download/pbp/play_by_play_%s.parquet",
season
)
file <- tempfile(fileext = ".parquet")
download.file(url, file, mode = "wb", quiet = TRUE)
read_parquet(file) |>
filter(
season_type == "REG",
field_goal_attempt == 1,
as.Date(game_date) <= as.Date("2026-11-11")
) |>
select(
season, game_date, game_id, play_id,
kicker_player_id, kicker_player_name,
kick_distance, field_goal_result, desc
)
}) |>
bind_rows()
Rolling Performance
rolling <- bind_rows(lapply(30:nrow(b), function(i) {
w <- b[(i - 29):i, ]
tibble(attempt = i, range = c('All FG', '50+', 'Under 50'),
points = c(sum(w$pae), sum(w$pae[w$distance >= 50]),
sum(w$pae[w$distance < 50])))
}))
ticks <- b |> group_by(season) |>
summarise(attempt = min(attempt), .groups = 'drop')
last <- filter(rolling, attempt == max(attempt))
range_colors <- c('All FG' = '#373D43', '50+' = '#B51C35', 'Under 50' = '#237D8D')
rolling_plot <- ggplot(rolling, aes(attempt, points)) +
geom_hline(yintercept = 0, colour = 'grey60', linewidth = .4) +
geom_line(data = filter(rolling, range != 'All FG'),
aes(colour = range, linetype = range), alpha = .5, linewidth = .75) +
geom_line(data = filter(rolling, range == 'All FG'),
colour = '#373D43', linewidth = 1) +
geom_text(data = last,
aes(label = sprintf('%s: %+.1f', range, points), colour = range),
nudge_x = 2, hjust = 0, size = 3.2, fontface = 'bold') +
scale_colour_manual(values = range_colors, guide = 'none') +
scale_linetype_manual(
values = c('50+' = 'solid', 'Under 50' = 'longdash'), guide = 'none') +
scale_x_continuous(breaks = ticks$attempt, labels = ticks$season,
limits = c(1, max(b$attempt) + 35)) +
labs(title = paste0("After Years of Strong Performance, Butker's Production",
'\nhas Plummeted in 2026'),
subtitle = paste('Butker has fallen sharply below league-average production in 2026,',
'driven largely by\nstruggles from 50+ yards; shorter kicks remain solid.'),
x = '\nSeason (Last 30 Kicks, Rolling)',
y = 'Points above NFL expectation', caption = caption) + blog_theme
rolling_plot
Long-range Outcomes
d <- b |> filter(season >= 2023, distance >= 50) |>
mutate(
column = case_when(miss_direction == 'Miss left' ~ 1,
miss_direction == 'Miss right' ~ 3, TRUE ~ 2),
outcome = case_when(made == 1 ~ 'Made',
miss_direction == 'Miss short' ~ 'Short', TRUE ~ 'Miss left or right')) |>
group_by(season, column, distance) |>
arrange(game_date, game_id, play_id, .by_group = TRUE) |>
mutate(x = column + .22 * (row_number() - (n() + 1) / 2)) |> ungroup()
counts <- d |> group_by(season) |>
summarise(label = paste0(sum(made), '/', n(), ' made'), .groups = 'drop')
colors <- c('Made' = '#237D8D', 'Miss left or right' = '#B51C35', 'Short' = '#B47C20')
direction_plot <- ggplot(d,
aes(x, distance, colour = outcome, fill = outcome, shape = outcome)) +
annotate('rect', xmin = 1.6, xmax = 2.4, ymin = 48, ymax = 66,
fill = '#F4F6F6', colour = NA) +
geom_hline(yintercept = seq(50, 65, 5), colour = 'grey90', linewidth = .35) +
geom_point(size = 3.6, stroke = .65) +
geom_text(data = counts, aes(x = 2, y = 67, label = label),
inherit.aes = FALSE, size = 3.5, fontface = 'bold') +
facet_grid(. ~ season) +
scale_colour_manual(values = colors, name = NULL) +
scale_fill_manual(values = colors, name = NULL) +
scale_shape_manual(
values = c('Made' = 16, 'Miss left or right' = 16, 'Short' = 25), name = NULL) +
scale_x_continuous(breaks = 1:3, labels = c('Left', 'Middle', 'Right'),
limits = c(.55, 3.45)) +
scale_y_continuous(breaks = seq(50, 65, 5), limits = c(48, 68)) +
labs(title = "Where Butker's Long Kicks Are Missing", x = NULL,
subtitle = paste('Butker is spraying his long kicks left, right and short:',
'after going 5-for-5 from 50+ yards in 2023,\nhe’s just 2-for-8 in 2026.'),
y = 'Field-goal distance (yards)', caption = caption) +
blog_theme + theme(panel.grid = element_blank(), legend.position = 'bottom',
strip.text = element_text(face = 'bold'))
direction_plot