Learning ObjectivesBy the end of this chapter, you will be able to:
Fourth Down Decision Tool
Compare the expected points of going for it, kicking a field goal, or punting on fourth down.
- Build fourth down decision models using expected points and win probability
- Understand when to go for it, punt, or attempt a field goal
- Analyze historical fourth down tendencies and trends
- Measure coach aggressiveness and decision quality
- Apply game theory principles to fourth down decisions
- Use the nfl4th package for real-time recommendations
- Account for situational modifiers like score, time, and field position
Introduction
Few decisions in football generate more debate than what to do on fourth down. Should a team go for it, punt, or attempt a field goal? Traditionally, coaches followed conservative rules of thumb: punt in your own territory, kick field goals when in range, and only go for it in obvious situations like 4th-and-inches near the goal line.
Analytics has revolutionized fourth down strategy. By applying expected points and win probability frameworks, we can quantify the value of each option and identify optimal decisions. The results are striking: teams are far too conservative on fourth down, leaving points and wins on the field by punting and kicking when they should go for it.
The traditional approach to fourth down decisions reflects deep-seated psychological and institutional biases in football coaching. Coaches face asymmetric accountability: a failed fourth down conversion draws immediate criticism and media scrutiny, while the opportunity cost of a conservative punt goes unnoticed. This loss aversion creates a powerful incentive to "play it safe," even when analytics clearly show that aggressive decisions maximize win probability.
Modern analytics provides coaches with tools to overcome these biases. By quantifying the expected value of each decision option—incorporating conversion probability, field position changes, and scoring likelihood—we can identify situations where conventional wisdom fails. The data reveals that teams should go for it far more often than they do, particularly in midfield and opponent territory. Teams that embrace analytics-driven fourth down strategies gain a measurable competitive advantage, as evidenced by the success of aggressive coaches like Doug Pederson (Philadelphia Eagles) and Kevin Stefanski (Cleveland Browns).
This chapter builds a complete framework for fourth down decision-making, from first principles through advanced implementation. We'll construct predictive models for conversion probability, field goal success, and punt outcomes, then integrate these components with expected points to generate optimal recommendations. We'll analyze historical trends showing the evolution toward more aggressive play-calling, evaluate individual coach tendencies, and examine how situational factors (score, time, field position) modify optimal strategy. Finally, we'll explore the game theory dynamics of fourth down decisions and the strategic value of unpredictability.
Why This Chapter Matters
Fourth down decisions represent one of the largest gaps between optimal and actual coaching decisions in football. Teams that adopt analytics-driven fourth down strategies gain significant competitive advantages: - **Philadelphia Eagles (2017-2018)**: Won Super Bowl LII with one of the most aggressive fourth down strategies in NFL history - **Baltimore Ravens (2019)**: Led the NFL in fourth down attempts per game (2.1) during their 14-2 season - **Modern NFL**: Fourth down go-for-it rates have doubled from 2010 to 2023 Understanding fourth down analytics is essential for anyone seeking to evaluate coaching decisions or build winning strategies.The Traditional Approach to Fourth Down
The traditional approach to fourth down decision-making evolved over decades of football coaching, passed down from generation to generation as received wisdom. These rules of thumb served coaches reasonably well in an era before sophisticated analytics, providing simple heuristics that avoided catastrophic mistakes. However, as our analytical capabilities have advanced and our understanding of expected value has deepened, we've discovered that traditional approaches systematically undervalue aggressive decisions.
Understanding the traditional framework is essential because it represents the baseline against which we measure improvement. When we identify situations where analytics recommend going for it but tradition recommends punting, we can quantify the expected value gap—the wins and points teams leave on the field by following outdated conventions. This quantification provides the evidence base for changing coaching behavior.
Conventional Wisdom
Traditional fourth down decision-making follows simple heuristics based primarily on field position and distance:
- In Your Own Territory (own 0-40 yard line): Always punt to avoid giving opponents short fields
- Midfield (own 40 to opponent 40): Punt unless 4th-and-short (typically 4th-and-2 or less)
- Field Goal Range (opponent 40 to goal line): Kick if within kicker's range, typically 55 yards or closer
- Short Yardage Near Goal (opponent 5 or closer, 4th-and-2 or less): Go for it to maximize touchdown probability
- Desperation (trailing late in game): Go for it as needed, regardless of field position
These rules prioritize field position management and risk avoidance. The underlying philosophy assumes that maintaining good field position matters more than the upside of conversion, and that the downside of failed conversions (giving opponents short fields) outweighs the benefits of success (maintaining possession and scoring opportunities).
Problems with Traditional Approach
These rules of thumb, while simple and intuitive, suffer from several fundamental flaws that modern analytics has exposed:
-
Ignore Context: Treat all 4th-and-1 situations the same regardless of field position, score differential, or time remaining. A 4th-and-1 at your own 40 in the first quarter receives the same treatment as 4th-and-1 at your own 40 in the fourth quarter while trailing by three points—despite having dramatically different optimal strategies.
-
Overly Conservative: Systematically undervalue the benefits of gaining first downs and maintaining possession. The expected points from continuing a drive (with the possibility of scoring touchdowns) far exceeds the expected points from punting in many situations, but traditional wisdom defaults to the "safe" choice.
-
Static: Don't adjust for score, time, or opponent quality. A team facing a high-powered offense should be more aggressive on fourth down (because they need more possessions to keep pace), but traditional approaches treat all opponents identically.
-
Loss Aversion: Fear negative outcomes more than valuing positive ones. Coaches receive disproportionate blame for failed fourth down conversions compared to credit for successful ones, creating institutional pressure to avoid decisions that could be second-guessed.
-
No Quantification: Rely on intuition rather than data. Without numerical frameworks for comparing options, coaches fall back on "feel" and "experience"—which research shows systematically underestimates the value of aggressive play.
Historical Example: The "Punt" Mentality
In 2009, New England Patriots coach Bill Belichick went for it on 4th-and-2 from his own 28-yard line with a 6-point lead and 2:08 remaining. The Patriots failed to convert, and the Colts scored a touchdown to win. The media universally criticized the decision. However, analysis showed it was the correct call: going for it gave the Patriots a higher win probability (~70%) than punting (~65%). The outcome was unlucky, but the decision was sound. This example illustrates the challenge of analytics-based coaching: sometimes the right decision produces a bad outcome.The Expected Points Framework for Fourth Down
The expected points (EP) framework provides a rigorous mathematical foundation for fourth down decision-making. Rather than relying on intuition about which option is best, we can calculate the exact expected value of each choice and select the option that maximizes our team's expected points. This quantitative approach removes emotion and bias from the decision process, replacing subjective judgment with objective analysis.
The key insight of the EP framework is that every decision option has a probabilistic distribution of outcomes, and we can calculate the expected value by weighting each possible outcome by its probability. For fourth down decisions, this means modeling conversion probability, field goal success rates, punt outcomes, and the expected points associated with each resulting field position. By combining these components, we generate a single number—expected value—that allows direct comparison across options.
This framework assumes teams seek to maximize expected points rather than win probability. While win probability is often the superior objective (as we'll discuss when covering the nfl4th package), expected points provides an excellent starting point because it's simpler to model and highly correlated with win probability in most game situations. The EP approach works particularly well for decisions in the first three quarters when score differentials are modest.
Three Options, Three Expected Values
On fourth down, teams face three primary decision options (ignoring rare cases like fake punts or fake field goals). Each option has an expected value that we can calculate:
-
Go For It: The weighted average of two outcomes—successful conversion (which gives us a first down and continued possession) and failed conversion (which gives our opponent the ball at our current location). The expected value equals: Probability of conversion × EP after conversion + Probability of failure × EP after turnover.
-
Punt: A more deterministic option where we surrender possession in exchange for improved field position. The expected value is the opponent's expected points from their resulting field position (expressed as a negative number from our team's perspective, since opponent points hurt us). We must model expected punt distance and return yardage to calculate the opponent's starting field position.
-
Field Goal: Another probabilistic option with two outcomes—successful kick (guaranteed 3 points) and missed kick (opponent takes over at kick location or their own 20 if the kick goes through the end zone). The expected value equals: Probability of make × 3 points + Probability of miss × EP after miss.
Mathematical Formulation
Let's formalize the expected value of each option using precise mathematical notation. These equations provide the foundation for all fourth down analytics, from simple expected points models to sophisticated win probability systems.
Going For It
The expected value of attempting a fourth down conversion incorporates both the upside of success and the downside of failure:
$$ EV_{\text{go}} = P(\text{convert}) \times EP_{\text{convert}} + (1 - P(\text{convert})) \times EP_{\text{fail}} $$
Where:
- $P(\text{convert})$ = probability of converting the first down (estimated from historical data based on distance, field position, and other factors)
- $EP_{\text{convert}}$ = expected points from the resulting field position if successful (typically 1st-and-10 from current location)
- $EP_{\text{fail}}$ = expected points from opponent's field position if unsuccessful (negative value representing opponent's expected points from our current location)
The key challenge is accurately estimating $P(\text{convert})$, which varies substantially based on yards to go, field position, offensive and defensive quality, play type, and formation. We'll build a conversion probability model later in this chapter using logistic regression.
Punting
Punting is more deterministic than going for it, but still involves uncertainty in punt distance and return yardage:
$$ EV_{\text{punt}} = EP_{\text{after punt}} $$
Where $EP_{\text{after punt}}$ is the expected points from the opponent's field position after the punt. Since the opponent has possession, this is typically a negative value (opponent's expected points).
We calculate this by modeling expected net punt distance (gross punt distance minus return yards, accounting for touchbacks) and then using an expected points model to value the opponent's resulting field position. The EP value is negative from our perspective because opponent points hurt our team.
Field Goal Attempt
Field goal attempts combine certain value (3 points) with probabilistic success:
$$ EV_{\text{fg}} = P(\text{make}) \times 3 + (1 - P(\text{make})) \times EP_{\text{miss}} $$
Where:
- $P(\text{make})$ = probability of making the field goal (primarily a function of distance, but also influenced by weather, elevation, and kicker quality)
- $EP_{\text{miss}}$ = expected points from opponent's field position after a miss (negative value)
Field goal probability decreases approximately linearly with distance from about 99% for extra points to about 50% at 50 yards and continues declining for longer attempts. The opponent's field position after a miss depends on kick distance: missed field goals from beyond the 20-yard line result in the opponent taking over at the spot of the kick, while shorter misses result in the opponent starting at their own 20.
Decision Rule
With expected values calculated for all three options, the optimal decision follows a simple rule:
$$ \text{Optimal Decision} = \arg\max(EV_{\text{go}}, EV_{\text{punt}}, EV_{\text{fg}}) $$
Choose whichever option has the highest expected value. This maximizes expected points, which for most game situations closely aligns with maximizing win probability.
In practice, teams should choose field goals when they maximize expected value (typically close field goals from inside the 25-yard line), go for it when the conversion probability and potential upside justify the risk (often 4th-and-short situations or when in opponent territory), and punt when both alternatives are inferior (typically long yardage situations in one's own territory).
Best Practice: Account for Uncertainty
Real-world fourth down decisions should incorporate uncertainty in our probability estimates. Rather than relying on point estimates for conversion probability, consider using confidence intervals. A decision that looks marginally better might reverse if we account for estimation uncertainty. The nfl4th package handles this by using bootstrapped win probability models that naturally incorporate uncertainty.Building a Fourth Down Model
Now we'll construct a complete fourth down decision model from scratch using NFL play-by-play data. This implementation demonstrates how the theoretical framework translates into practical code, providing a foundation you can extend and customize for specific applications.
Our model requires four distinct components, each addressing one piece of the decision puzzle. First, we need a conversion probability model that predicts the likelihood of successfully converting a fourth down attempt based on yards to go, field position, and other relevant factors. Second, we need a field goal success model that estimates make probability as a function of kick distance. Third, we need a punt outcome model that predicts the opponent's field position after punts of varying distances. Finally, we need an expected points model that translates any field position into expected points, allowing us to value the outcomes of each decision option.
Building these components separately provides modularity and interpretability. We can update individual components as better models become available without rebuilding the entire system. We can also examine each component independently to understand its contribution to final recommendations and identify areas for improvement.
The implementation uses ten seasons of NFL data (2014-2023) to ensure robust parameter estimates. More data generally improves model accuracy by reducing overfitting and capturing a wider range of game situations. However, very old data may reflect outdated playing styles or rule changes, so we balance recency against sample size.
Setup and Data Loading
Before building our models, we need to load the necessary packages and import play-by-play data. The nflfastR package provides comprehensive NFL data including every play from every game, with pre-calculated metrics like expected points and win probability. We'll filter this data to fourth down situations and create variables that categorize plays for analysis.
The data loading process may take several minutes the first time you run it, as the function downloads data from the nflverse repository. However, nflfastR automatically caches downloaded data locally, making subsequent runs much faster. We specify cache: true in our code chunk options to tell Quarto to save the loaded data and skip re-running this chunk unless the code changes.
#| label: setup-r
#| message: false
#| warning: false
# Load required packages for analysis
library(tidyverse) # Data manipulation and visualization
library(nflfastR) # NFL play-by-play data
library(nfl4th) # Fourth down decision package with WP models
library(mgcv) # Generalized Additive Models (if needed for advanced modeling)
library(gt) # Create formatted tables
library(nflplotR) # NFL-specific plotting functions and team logos
library(scales) # Format numbers and percentages in plots
# Set seed for reproducibility
# This ensures random processes (like cross-validation splits) produce identical results
set.seed(42)
#| label: load-data-r
#| message: false
#| warning: false
#| cache: true
# Load multiple seasons of play-by-play data
# We use 2014-2023 to balance sample size with recency
seasons <- 2014:2023
pbp <- load_pbp(seasons)
# Filter to fourth down plays only
# This creates our analysis dataset containing ~40,000+ fourth down situations
fourth_downs <- pbp %>%
filter(
down == 4, # Only fourth down plays
!is.na(ydstogo), # Must have valid yards to go
!is.na(yardline_100), # Must have valid field position
season_type == "REG" # Regular season only (exclude playoffs)
) %>%
mutate(
# Categorize the decision made on each fourth down
# This classification allows us to analyze go/punt/FG rates
decision = case_when(
qb_kneel == 1 ~ "kneel", # Victory formation
punt_attempt == 1 ~ "punt", # Punted
field_goal_attempt == 1 ~ "field_goal", # Attempted FG
!is.na(pass_attempt) | !is.na(rush_attempt) ~ "go", # Went for it
TRUE ~ "other" # Other (rare)
),
# Outcome for go-for-it attempts
# Binary indicator: 1 if converted, 0 if failed, NA for non-go attempts
converted = if_else(decision == "go", first_down_rush + first_down_pass, NA_real_),
# Field goal outcome
# TRUE if made, FALSE if missed, NA for non-FG attempts
fg_made = if_else(decision == "field_goal", field_goal_result == "made", NA),
# Create distance categories for analysis
# These bins group similar situations together
distance_cat = case_when(
ydstogo <= 1 ~ "1 yard or less",
ydstogo <= 3 ~ "2-3 yards",
ydstogo <= 6 ~ "4-6 yards",
ydstogo <= 10 ~ "7-10 yards",
TRUE ~ "11+ yards"
),
# Create field position categories
# yardline_100 = yards to opponent's end zone (100 = own goal line, 0 = opp goal line)
field_position_cat = case_when(
yardline_100 >= 60 ~ "Own territory", # Own 40 to own goal
yardline_100 >= 40 ~ "Midfield", # Own 40 to opponent 40
yardline_100 >= 20 ~ "Opponent territory", # Opponent 40 to opponent 20
TRUE ~ "Red zone" # Inside opponent 20
)
)
# Display summary information
cat("Fourth down plays analyzed:", nrow(fourth_downs), "\n")
cat("Seasons:", min(fourth_downs$season), "-", max(fourth_downs$season), "\n")
#| label: setup-py
#| message: false
#| warning: false
import pandas as pd
import numpy as np
import nfl_data_py as nfl
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score, roc_auc_score
import warnings
warnings.filterwarnings('ignore')
# Set random seed
np.random.seed(42)
# Set plot style
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")
#| label: load-data-py
#| message: false
#| warning: false
#| cache: true
# Load multiple seasons of play-by-play data
seasons = list(range(2014, 2024))
pbp = nfl.import_pbp_data(seasons)
# Filter to fourth down plays
fourth_downs = pbp[
(pbp['down'] == 4) &
(pbp['ydstogo'].notna()) &
(pbp['yardline_100'].notna()) &
(pbp['season_type'] == 'REG')
].copy()
# Categorize the decision made
def categorize_decision(row):
if row['qb_kneel'] == 1:
return 'kneel'
elif row['punt_attempt'] == 1:
return 'punt'
elif row['field_goal_attempt'] == 1:
return 'field_goal'
elif pd.notna(row['pass_attempt']) or pd.notna(row['rush_attempt']):
return 'go'
else:
return 'other'
fourth_downs['decision'] = fourth_downs.apply(categorize_decision, axis=1)
# Outcome for go-for-it attempts
fourth_downs['converted'] = np.where(
fourth_downs['decision'] == 'go',
fourth_downs['first_down_rush'].fillna(0) + fourth_downs['first_down_pass'].fillna(0),
np.nan
)
# Field goal made
fourth_downs['fg_made'] = np.where(
fourth_downs['decision'] == 'field_goal',
fourth_downs['field_goal_result'] == 'made',
np.nan
)
# Distance categories
def categorize_distance(yards):
if yards <= 1:
return '1 yard or less'
elif yards <= 3:
return '2-3 yards'
elif yards <= 6:
return '4-6 yards'
elif yards <= 10:
return '7-10 yards'
else:
return '11+ yards'
fourth_downs['distance_cat'] = fourth_downs['ydstogo'].apply(categorize_distance)
# Field position categories
def categorize_field_position(yardline):
if yardline >= 60:
return 'Own territory'
elif yardline >= 40:
return 'Midfield'
elif yardline >= 20:
return 'Opponent territory'
else:
return 'Red zone'
fourth_downs['field_position_cat'] = fourth_downs['yardline_100'].apply(categorize_field_position)
print(f"Fourth down plays analyzed: {len(fourth_downs):,}")
print(f"Seasons: {int(fourth_downs['season'].min())} - {int(fourth_downs['season'].max())}")
Component 1: Conversion Probability Model
The conversion probability model forms the foundation of our fourth down decision framework. This model estimates the likelihood that a team will successfully convert a fourth down attempt, gaining a first down or touchdown. Accurate conversion probability estimates are crucial because they directly determine the expected value of going for it.
Distance to go is the strongest predictor of conversion probability. Fourth-and-1 situations convert at approximately 65-70%, while fourth-and-10 situations convert at only 15-20%. This relationship is nonlinear: each additional yard to go reduces conversion probability, but the marginal effect diminishes (the difference between 4th-and-1 and 4th-and-2 is larger than between 4th-and-9 and 4th-and-10).
Field position also influences conversion probability, though more subtly. Teams convert more frequently in opponent territory, possibly due to increased aggression or better play-calling when the potential reward (scoring position) is greater. We include field position in our model to capture these effects.
We use logistic regression because our outcome (converted or not) is binary. Logistic regression models the log-odds of conversion as a linear function of predictors, ensuring predicted probabilities stay between 0 and 1. We include a quadratic term for distance (ydstogo^2) to capture the nonlinear relationship between distance and conversion probability.
#| label: conversion-model-r
#| message: false
#| warning: false
# Filter to go-for-it attempts only
# We need plays where teams actually attempted conversions to model success rate
go_attempts <- fourth_downs %>%
filter(decision == "go", !is.na(converted))
# Build logistic regression model for conversion probability
# Logistic regression is appropriate for binary outcomes (converted: yes/no)
conversion_model <- glm(
converted ~ ydstogo + yardline_100 + I(ydstogo^2), # Predictors
data = go_attempts,
family = binomial(link = "logit") # Logit link function for binary outcome
)
# Summary of model
# This shows coefficients, standard errors, and significance tests
summary(conversion_model)
# Create prediction function for easy reuse
# This function takes yards to go and field position, returns conversion probability
predict_conversion_prob <- function(ydstogo, yardline_100) {
pred_data <- data.frame(ydstogo = ydstogo, yardline_100 = yardline_100)
# type = "response" gives probabilities rather than log-odds
predict(conversion_model, newdata = pred_data, type = "response")
}
# Example predictions at midfield (50 yards from opponent's end zone)
# This shows how conversion probability decreases with distance
examples <- tibble(
yards_to_go = c(1, 3, 5, 10),
field_pos = rep(50, 4)
)
examples %>%
mutate(
conversion_prob = predict_conversion_prob(yards_to_go, field_pos),
pct = percent(conversion_prob, accuracy = 0.1)
) %>%
gt() %>%
cols_label(
yards_to_go = "Yards to Go",
field_pos = "Field Position",
conversion_prob = "Conversion Probability",
pct = "Percentage"
) %>%
fmt_number(columns = conversion_prob, decimals = 3) %>%
tab_header(
title = "Fourth Down Conversion Probabilities",
subtitle = "Predictions at midfield (50 yards from end zone)"
)
#| label: conversion-model-py
#| message: false
#| warning: false
# Filter to go-for-it attempts
go_attempts = fourth_downs[
(fourth_downs['decision'] == 'go') &
(fourth_downs['converted'].notna())
].copy()
# Prepare features
X = go_attempts[['ydstogo', 'yardline_100']].copy()
X['ydstogo_sq'] = X['ydstogo'] ** 2
y = go_attempts['converted']
# Build logistic regression model
conversion_model = LogisticRegression(random_state=42)
conversion_model.fit(X, y)
# Print coefficients
print("\nConversion Model Coefficients:")
print(f"Intercept: {conversion_model.intercept_[0]:.4f}")
for i, col in enumerate(X.columns):
print(f"{col}: {conversion_model.coef_[0][i]:.4f}")
# Create prediction function
def predict_conversion_prob(ydstogo, yardline_100):
X_pred = pd.DataFrame({
'ydstogo': [ydstogo],
'yardline_100': [yardline_100],
'ydstogo_sq': [ydstogo**2]
})
return conversion_model.predict_proba(X_pred)[0][1]
# Example predictions
print("\nExample Conversion Probabilities:")
for yards in [1, 3, 5, 10]:
prob = predict_conversion_prob(yards, 50)
print(f"4th-and-{yards} at 50: {prob:.1%}")
The conversion model shows the expected pattern: each additional yard to go significantly reduces conversion probability. At midfield, teams convert approximately 66% of 4th-and-1 attempts, but only 15% of 4th-and-10 attempts. The quadratic term captures the nonlinear relationship—the drop from 1 to 3 yards is more severe than from 8 to 10 yards.
The negative coefficient on yardline_100 indicates that teams convert slightly more often when closer to the opponent's end zone, holding distance constant. This might reflect increased aggressiveness in play-calling or better execution in high-leverage situations.
Model Limitations and Extensions
This basic model provides reasonable conversion probability estimates, but more sophisticated versions could improve accuracy by incorporating: - **Play type**: Pass attempts convert at different rates than run attempts - **Personnel**: Different formations (shotgun vs. under center) have different success rates - **Team quality**: Better offenses convert more frequently - **Defensive quality**: Better defenses stop conversions more frequently - **Game situation**: Teams may execute differently in close games vs. blowouts For production systems, consider using more advanced models (random forests, gradient boosting) with these additional features. However, the simple logistic regression provides a solid foundation and has the advantage of interpretability.Component 2: Field Goal Success Model
Field goal success probability depends primarily on kick distance, with longer kicks becoming progressively more difficult. Understanding field goal probability is essential for fourth down decisions because it determines the expected value of attempting a field goal rather than going for it or punting.
NFL kickers have become remarkably consistent over the past two decades. Modern kickers make nearly 100% of extra points and chip shot field goals (under 30 yards), around 90% from 30-39 yards, 75-80% from 40-49 yards, and approximately 60-65% from 50-59 yards. Beyond 60 yards, success rates drop precipitously, with attempts becoming low-percentage propositions.
The relationship between distance and success probability is nonlinear. Each additional yard reduces make probability, but the marginal effect varies by distance. The drop from 30 to 35 yards is smaller than the drop from 55 to 60 yards. We model this nonlinearity using a quadratic term in our logistic regression.
Environmental factors also influence field goal success (wind, temperature, precipitation, altitude), but we omit them from this basic model for simplicity. Production models should include weather data when available.
#| label: fg-model-r
#| message: false
#| warning: false
# Filter to field goal attempts
fg_attempts <- fourth_downs %>%
filter(decision == "field_goal", !is.na(fg_made)) %>%
mutate(kick_distance = yardline_100 + 17) # Add 17 yards (end zone + hold)
# Build logistic regression model
fg_model <- glm(
fg_made ~ kick_distance + I(kick_distance^2),
data = fg_attempts,
family = binomial(link = "logit")
)
# Create prediction function
predict_fg_prob <- function(yardline_100) {
kick_distance <- yardline_100 + 17
pred_data <- data.frame(kick_distance = kick_distance)
predict(fg_model, newdata = pred_data, type = "response")
}
# Visualize FG success probability by distance
fg_viz_data <- tibble(
yardline_100 = 1:65
) %>%
mutate(
kick_distance = yardline_100 + 17,
fg_prob = predict_fg_prob(yardline_100)
)
ggplot(fg_viz_data, aes(x = kick_distance, y = fg_prob)) +
geom_line(color = "darkgreen", size = 1.5) +
geom_hline(yintercept = 0.5, linetype = "dashed", color = "gray50") +
scale_y_continuous(labels = percent_format(), limits = c(0, 1)) +
labs(
title = "Field Goal Success Probability by Distance",
subtitle = "NFL data 2014-2023",
x = "Kick Distance (yards)",
y = "Make Probability",
caption = "Data: nflfastR"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 14),
plot.subtitle = element_text(size = 11)
)
#| label: fg-model-py
#| message: false
#| warning: false
#| fig-width: 10
#| fig-height: 6
# Filter to field goal attempts
fg_attempts = fourth_downs[
(fourth_downs['decision'] == 'field_goal') &
(fourth_downs['fg_made'].notna())
].copy()
fg_attempts['kick_distance'] = fg_attempts['yardline_100'] + 17
# Prepare features
X_fg = fg_attempts[['kick_distance']].copy()
X_fg['kick_distance_sq'] = X_fg['kick_distance'] ** 2
y_fg = fg_attempts['fg_made']
# Build logistic regression model
fg_model = LogisticRegression(random_state=42)
fg_model.fit(X_fg, y_fg)
# Create prediction function
def predict_fg_prob(yardline_100):
kick_distance = yardline_100 + 17
X_pred = pd.DataFrame({
'kick_distance': [kick_distance],
'kick_distance_sq': [kick_distance**2]
})
return fg_model.predict_proba(X_pred)[0][1]
# Visualize FG success probability
kick_distances = np.arange(18, 82)
fg_probs = [predict_fg_prob(d - 17) for d in kick_distances]
plt.figure(figsize=(10, 6))
plt.plot(kick_distances, fg_probs, color='darkgreen', linewidth=2.5)
plt.axhline(y=0.5, color='gray', linestyle='--', alpha=0.7)
plt.ylim(0, 1)
plt.xlabel('Kick Distance (yards)', fontsize=12)
plt.ylabel('Make Probability', fontsize=12)
plt.title('Field Goal Success Probability by Distance\nNFL data 2014-2023',
fontsize=14, fontweight='bold')
plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Example probabilities
print("\nExample FG Probabilities:")
for yards in [25, 35, 45, 55]:
prob = predict_fg_prob(yards)
print(f"{yards + 17}-yard FG: {prob:.1%}")
The field goal model reveals the expected distance-probability relationship. Make probability exceeds 95% for kicks under 30 yards, drops to approximately 85% at 40 yards, 70% at 50 yards, and 50% at 55 yards. Beyond 55 yards, kicks become low-percentage attempts with make rates dropping below 40%.
This relationship has important implications for fourth down decisions. The "break-even" distance for field goals (where expected value equals zero) is approximately 55-60 yards, depending on what happens after a miss. Inside this range, field goals provide guaranteed value that often exceeds the expected value of going for it, particularly when distance to go is long (4th-and-7 or more).
However, many teams attempt field goals in situations where going for it has higher expected value. Teams often kick from the opponent's 20-25 yard line (38-43 yard attempts) on 4th-and-3 or 4th-and-4, even though conversion probability exceeds 45% and the upside of conversion (potential touchdown) exceeds the 3-point field goal value.
The Field Goal Trap
One of the most common fourth down mistakes is attempting field goals from the opponent's 15-25 yard line on 4th-and-short (3 yards or less). While these kicks have high make probability (85-90%), the opportunity cost is enormous: - **Field Goal EV**: 0.90 × 3 = 2.70 points - **Go For It EV**: 0.50 × 7 + 0.50 × (-0.5) = 3.25 points (assuming 50% conversion, 7 EP after success, -0.5 EP after failure) The "safe" field goal actually costs about 0.5 expected points compared to going for it, and the gap widens when conversion probability exceeds 50%. Teams fall into this trap because field goals feel safe (guaranteed points) while failed fourth downs generate criticism. But over a season, taking guaranteed 3-point field goals instead of higher-EV go-for-it attempts costs teams wins.Component 3: Expected Punt Outcome
The punt outcome model estimates the opponent's field position after a punt, accounting for punt distance, return yardage, touchbacks, and other factors. This component is crucial because the value of punting depends entirely on the field position swing it creates.
Punt distance varies with field position due to several factors. Punts from deep in one's own territory can travel farther (more room to operate), while punts from midfield face higher touchback risk if kicked too far. Return probability and average return yardage also vary by field position, with returners more likely to fair catch on longer punts.
We model net punt yardage (field position change from the punting team's perspective) as a function of starting field position. This simplification ignores many relevant factors (punter quality, coverage quality, weather, returner skill), but provides a reasonable baseline for expected value calculations.
More sophisticated punt models could incorporate punter-specific statistics, situational factors (hangtime, directional kicks), and opponent return quality. However, the basic linear model captures the primary relationship between field position and punt outcome.
#| label: punt-model-r
#| message: false
#| warning: false
# Filter to punts
punts <- fourth_downs %>%
filter(decision == "punt", !is.na(kick_distance))
# Calculate net punt yardage (accounting for touchbacks, returns)
punts <- punts %>%
mutate(
# Field position change (from perspective of punting team)
field_position_change = yardline_100 - (100 - punt_end_yd_line)
) %>%
filter(!is.na(field_position_change))
# Build linear model for punt outcome
punt_model <- lm(
field_position_change ~ yardline_100,
data = punts
)
# Create prediction function
predict_punt_outcome <- function(yardline_100) {
pred_data <- data.frame(yardline_100 = yardline_100)
change <- predict(punt_model, newdata = pred_data)
# Opponent's field position after punt
opponent_yardline <- 100 - (yardline_100 - change)
return(opponent_yardline)
}
# Example punt outcomes
punt_examples <- tibble(
starting_yardline = c(20, 40, 60, 80)
) %>%
mutate(
opponent_yardline = predict_punt_outcome(starting_yardline),
net_yards = starting_yardline - (100 - opponent_yardline)
)
punt_examples %>%
gt() %>%
cols_label(
starting_yardline = "Starting Yardline",
opponent_yardline = "Opponent Yardline",
net_yards = "Net Yards"
) %>%
fmt_number(columns = c(opponent_yardline, net_yards), decimals = 1)
#| label: punt-model-py
#| message: false
#| warning: false
# Filter to punts
punts = fourth_downs[
(fourth_downs['decision'] == 'punt') &
(fourth_downs['kick_distance'].notna())
].copy()
# Calculate field position change
punts['field_position_change'] = (
punts['yardline_100'] - (100 - punts['punt_end_yd_line'])
)
punts = punts[punts['field_position_change'].notna()]
# Build linear model
from sklearn.linear_model import LinearRegression
X_punt = punts[['yardline_100']]
y_punt = punts['field_position_change']
punt_model = LinearRegression()
punt_model.fit(X_punt, y_punt)
# Create prediction function
def predict_punt_outcome(yardline_100):
X_pred = pd.DataFrame({'yardline_100': [yardline_100]})
change = punt_model.predict(X_pred)[0]
opponent_yardline = 100 - (yardline_100 - change)
return opponent_yardline
# Example punt outcomes
print("\nExpected Punt Outcomes:")
for yards in [20, 40, 60, 80]:
opp_yd = predict_punt_outcome(yards)
net = yards - (100 - opp_yd)
print(f"Punt from own {100-yards}: Opponent at {100-opp_yd:.1f} (net {net:.1f} yards)")
The punt model shows that average net punt yardage (from the punting team's perspective) ranges from approximately 35-40 yards when punting from one's own territory to 25-30 yards when punting from midfield. This reflects the touchback risk and reduced gross punt distance in opponent territory.
The value of punting depends on these field position changes combined with expected points. Punting from your own 20 typically gives the opponent the ball around their 40 (our 60), which has negative expected points from our perspective but better than giving them the ball at our 20. However, punting from midfield often gives them the ball around their 30-35 (our 65-70), which is only marginally better than if we'd failed a fourth down conversion at the 50.
This asymmetry explains why analytics favors going for it more often in opponent territory and midfield—the field position benefit of punting diminishes as you approach the opponent's end zone, while the upside of conversion remains high.
Accounting for Punt Coverage Quality
Teams with excellent punt coverage units gain more value from punting than teams with poor coverage. Similarly, teams with dangerous return specialists lose more value when punting to opponents. For team-specific fourth down models, consider adjusting punt outcome estimates based on: - **Special teams DVOA** (Football Outsiders metric) - **Opponent return average** (accounting for returner quality) - **Punter directional punting ability** (coffin corner specialists) - **Coverage unit speed** (tackle percentage metrics) These adjustments can shift punt expected value by 0.3-0.5 points in extreme cases, potentially changing optimal decisions for teams with exceptional or poor special teams.Component 4: Combining into Decision Framework
Now we integrate all three component models (conversion probability, field goal success, punt outcome) with an expected points framework to generate fourth down recommendations. This integration represents the payoff for our modeling efforts: a system that can evaluate any fourth down situation and identify the optimal decision.
The decision framework follows a simple logic: calculate the expected value of each option (go, punt, field goal), then choose the option with the highest expected value. However, implementing this logic requires careful attention to several details:
-
Expected Points Model: We need an EP model that maps any field position and down/distance to expected points. The nflfastR package includes pre-calculated EP values, but for transparency we'll build a simplified EP model here.
-
Success State Definition: When we "go for it" successfully, we get 1st-and-10 from our current location. When we fail, the opponent gets the ball at our current location. These resulting states must be valued using the EP model.
-
Field Goal Miss Scenarios: Field goal misses result in different opponent field positions depending on kick distance. Attempts beyond the 20-yard line give opponents the ball at the kick spot; shorter attempts give them the ball at their 20.
-
Punt Result Integration: The punt model gives us expected opponent field position, which must be converted to expected points (negative, from our perspective).
The resulting framework provides expected values that we can compare directly. An EV of 1.5 for going for it means we expect to score 1.5 more points on this drive (on average) than if we'd started from our own 25 (the EP baseline). An EV of -0.8 for punting means we expect the opponent to score 0.8 more points on their next drive than if they'd started from their 25.
#| label: decision-framework-r
#| message: false
#| warning: false
# Function to calculate expected value of each option
calculate_ev_options <- function(ydstogo, yardline_100, ep_model = NULL) {
# Use nflfastR's built-in EP model if not provided
if (is.null(ep_model)) {
# Create dummy play to get EP values
get_ep <- function(yards_100, down = 1, ytg = 10) {
# Use nflfastR's calculate_expected_points
# For simplicity, use approximation
if (yards_100 <= 0) return(7)
if (yards_100 >= 100) return(-0.8)
# Linear approximation (actual EP model is more complex)
ep <- 0.06 * (100 - yards_100) - 0.5
return(ep)
}
}
# Option 1: Go for it
conv_prob <- predict_conversion_prob(ydstogo, yardline_100)
ep_success <- get_ep(yardline_100, 1, 10) # 1st and 10 after conversion
ep_fail <- -get_ep(100 - yardline_100, 1, 10) # Opponent's EP
ev_go <- conv_prob * ep_success + (1 - conv_prob) * ep_fail
# Option 2: Field goal (if in range)
if (yardline_100 <= 60) { # Roughly inside 43-yard line
fg_prob <- predict_fg_prob(yardline_100)
ep_fg_miss <- -get_ep(100 - yardline_100, 1, 10)
ev_fg <- fg_prob * 3 + (1 - fg_prob) * ep_fg_miss
} else {
ev_fg <- NA
}
# Option 3: Punt
opp_yardline <- predict_punt_outcome(yardline_100)
ev_punt <- -get_ep(opp_yardline, 1, 10)
return(tibble(
ev_go = ev_go,
ev_fg = ev_fg,
ev_punt = ev_punt,
conv_prob = conv_prob,
optimal = case_when(
!is.na(ev_fg) & ev_fg >= ev_go & ev_fg >= ev_punt ~ "Field Goal",
ev_go >= ev_punt ~ "Go For It",
TRUE ~ "Punt"
)
))
}
# Example decisions
example_situations <- tibble(
scenario = c("4th-and-1 at own 40", "4th-and-5 at opp 35",
"4th-and-10 at midfield", "4th-and-3 at opp 15"),
ydstogo = c(1, 5, 10, 3),
yardline_100 = c(60, 35, 50, 15)
)
decisions <- example_situations %>%
rowwise() %>%
mutate(calc = list(calculate_ev_options(ydstogo, yardline_100))) %>%
unnest(calc) %>%
select(scenario, ev_go, ev_fg, ev_punt, optimal)
decisions %>%
gt() %>%
cols_label(
scenario = "Scenario",
ev_go = "EV (Go)",
ev_fg = "EV (FG)",
ev_punt = "EV (Punt)",
optimal = "Optimal Decision"
) %>%
fmt_number(columns = c(ev_go, ev_fg, ev_punt), decimals = 2) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_body(columns = optimal)
)
#| label: decision-framework-py
#| message: false
#| warning: false
# Helper function for expected points
def get_ep(yards_100, down=1, ytg=10):
"""Simplified EP model"""
if yards_100 <= 0:
return 7
if yards_100 >= 100:
return -0.8
# Linear approximation
ep = 0.06 * (100 - yards_100) - 0.5
return ep
# Function to calculate EV of each option
def calculate_ev_options(ydstogo, yardline_100):
# Option 1: Go for it
conv_prob = predict_conversion_prob(ydstogo, yardline_100)
ep_success = get_ep(yardline_100, 1, 10)
ep_fail = -get_ep(100 - yardline_100, 1, 10)
ev_go = conv_prob * ep_success + (1 - conv_prob) * ep_fail
# Option 2: Field goal (if in range)
if yardline_100 <= 60:
fg_prob = predict_fg_prob(yardline_100)
ep_fg_miss = -get_ep(100 - yardline_100, 1, 10)
ev_fg = fg_prob * 3 + (1 - fg_prob) * ep_fg_miss
else:
ev_fg = np.nan
# Option 3: Punt
opp_yardline = predict_punt_outcome(yardline_100)
ev_punt = -get_ep(opp_yardline, 1, 10)
# Determine optimal
if not np.isnan(ev_fg) and ev_fg >= ev_go and ev_fg >= ev_punt:
optimal = "Field Goal"
elif ev_go >= ev_punt:
optimal = "Go For It"
else:
optimal = "Punt"
return {
'ev_go': ev_go,
'ev_fg': ev_fg,
'ev_punt': ev_punt,
'conv_prob': conv_prob,
'optimal': optimal
}
# Example decisions
scenarios = [
("4th-and-1 at own 40", 1, 60),
("4th-and-5 at opp 35", 5, 35),
("4th-and-10 at midfield", 10, 50),
("4th-and-3 at opp 15", 3, 15)
]
print("\nFourth Down Decision Analysis:")
print("-" * 80)
for scenario, ytg, yd100 in scenarios:
result = calculate_ev_options(ytg, yd100)
print(f"\n{scenario}:")
print(f" EV (Go): {result['ev_go']:6.2f}")
print(f" EV (FG): {result['ev_fg']:6.2f}" if not np.isnan(result['ev_fg']) else " EV (FG): N/A")
print(f" EV (Punt): {result['ev_punt']:6.2f}")
print(f" Optimal: {result['optimal']}")
Optimal Decision Boundaries
One of the most useful outputs of a fourth down model is a decision boundary map showing when to go for it, punt, or kick.
#| label: fig-decision-boundary-r
#| fig-cap: "Fourth down decision boundaries (heat map)"
#| fig-width: 12
#| fig-height: 8
#| message: false
#| warning: false
# Create grid of all situations
decision_grid <- expand_grid(
yardline_100 = seq(1, 99, by = 1),
ydstogo = c(1, 2, 3, 4, 5, 6, 7, 8, 10, 15)
) %>%
rowwise() %>%
mutate(
calc = list(calculate_ev_options(ydstogo, yardline_100))
) %>%
unnest(calc) %>%
mutate(
field_position = 100 - yardline_100,
optimal_numeric = case_when(
optimal == "Go For It" ~ 3,
optimal == "Field Goal" ~ 2,
optimal == "Punt" ~ 1
)
)
# Create heat map
ggplot(decision_grid, aes(x = field_position, y = ydstogo)) +
geom_tile(aes(fill = optimal), color = "white", size = 0.5) +
scale_fill_manual(
values = c("Go For It" = "#2ECC40", "Field Goal" = "#FF851B", "Punt" = "#0074D9"),
name = "Optimal Decision"
) +
scale_x_continuous(
breaks = seq(0, 100, 10),
labels = c("Own\nGoal", "", "20", "", "40", "Midfield", "40", "", "20", "", "Opp\nGoal")
) +
scale_y_continuous(breaks = c(1, 2, 3, 4, 5, 6, 7, 8, 10, 15)) +
labs(
title = "Fourth Down Decision Boundaries",
subtitle = "Optimal decision based on expected value (excludes score/time context)",
x = "Field Position",
y = "Yards to Go",
caption = "Analysis based on 2014-2023 NFL data"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(size = 12),
legend.position = "top",
axis.text.x = element_text(size = 9),
panel.grid.minor = element_blank()
)
📊 Visualization Output
The code above generates a visualization. To see the output, run this code in your R or Python environment. The resulting plot will help illustrate the concepts discussed in this section.
#| label: fig-decision-boundary-py
#| fig-cap: "Fourth down decision boundaries - Python"
#| fig-width: 12
#| fig-height: 8
#| message: false
#| warning: false
# Create grid of all situations
yardlines = np.arange(1, 100, 2)
distances = [1, 2, 3, 4, 5, 6, 7, 8, 10, 15]
grid_data = []
for yd in yardlines:
for dist in distances:
result = calculate_ev_options(dist, yd)
grid_data.append({
'yardline_100': yd,
'field_position': 100 - yd,
'ydstogo': dist,
'optimal': result['optimal']
})
grid_df = pd.DataFrame(grid_data)
# Create pivot table for heatmap
pivot = grid_df.pivot(index='ydstogo', columns='field_position', values='optimal')
# Map decisions to numbers for coloring
decision_map = {'Punt': 0, 'Field Goal': 1, 'Go For It': 2}
pivot_numeric = pivot.applymap(lambda x: decision_map.get(x, 0))
# Create heatmap
fig, ax = plt.subplots(figsize=(12, 8))
# Custom colormap
from matplotlib.colors import ListedColormap
colors = ['#0074D9', '#FF851B', '#2ECC40'] # Punt, FG, Go
cmap = ListedColormap(colors)
im = ax.imshow(pivot_numeric, aspect='auto', cmap=cmap, origin='lower')
# Set ticks
ax.set_yticks(range(len(distances)))
ax.set_yticklabels(distances)
ax.set_ylabel('Yards to Go', fontsize=12, fontweight='bold')
# X-axis: field position
x_positions = np.linspace(0, len(pivot_numeric.columns)-1, 11)
x_labels = ['Own\nGoal', '', '20', '', '40', 'Midfield', '40', '', '20', '', 'Opp\nGoal']
ax.set_xticks(x_positions)
ax.set_xticklabels(x_labels)
ax.set_xlabel('Field Position', fontsize=12, fontweight='bold')
# Title
ax.set_title('Fourth Down Decision Boundaries\nOptimal decision based on expected value',
fontsize=16, fontweight='bold', pad=20)
# Legend
from matplotlib.patches import Patch
legend_elements = [
Patch(facecolor='#0074D9', label='Punt'),
Patch(facecolor='#FF851B', label='Field Goal'),
Patch(facecolor='#2ECC40', label='Go For It')
]
ax.legend(handles=legend_elements, loc='upper center', bbox_to_anchor=(0.5, -0.05),
ncol=3, frameon=True, fontsize=11)
plt.tight_layout()
plt.show()
📊 Visualization Output
The code above generates a visualization. To see the output, run this code in your R or Python environment. The resulting plot will help illustrate the concepts discussed in this section.
The decision boundary visualization reveals several important insights about optimal fourth down strategy. The large green regions (go for it) in opponent territory and on short yardage situations show where analytics recommends aggressive decisions. Notice that even from your own 40-yard line, teams should go for it on 4th-and-3 or shorter according to expected value calculations.
The orange regions (field goal) are relatively narrow, appearing primarily between the opponent's 25 and opponent's 5-yard lines when distance exceeds 5 yards. This reflects the tradeoff between guaranteed field goals (3 points) and the higher upside of going for it (potential touchdowns).
Comparing this optimal decision map to actual NFL coaching decisions reveals significant gaps. Coaches punt far too often from midfield and opponent territory, particularly on 4th-and-short situations where conversion probability exceeds 50%. The opportunity cost of these conservative decisions compounds over a season, potentially costing teams multiple wins.
Context-Free Decisions Have Limitations
This decision boundary map assumes neutral game situations (tied score, early in game). In real games, score differential and time remaining dramatically affect optimal strategy: - **When trailing late**: Teams should be much more aggressive, as expected points matter less than win probability - **When leading late**: Teams should sometimes punt even from aggressive regions to run clock and protect the lead - **Score effects**: Teams trailing by 14+ points should almost never punt, as they need possessions to catch up The nfl4th package accounts for these situational factors using win probability rather than expected points. We'll demonstrate this later in the chapter.Historical Fourth Down Trends
Fourth down decision-making has evolved dramatically over the past decade as analytics has gained influence in NFL coaching circles. The increasing acceptance of aggressive fourth down strategies represents one of the clearest examples of analytics changing football tactics in real-time.
Several factors have driven this evolution. First, academic research (notably David Romer's 2006 paper in the Journal of Political Economy) provided rigorous evidence that teams were too conservative. Second, publicly available analytics tools like the nfl4th package made optimal recommendations accessible to teams and media, creating accountability for conservative decisions. Third, high-profile successes (Eagles winning Super Bowl LII with aggressive fourth down strategies) demonstrated that analytics-driven approaches work in practice.
The trend toward aggression accelerated after 2018, coinciding with increased media coverage of fourth down analytics and the emergence of analytically-inclined coaches. Teams now go for it on fourth down at more than double the rate they did in 2014, representing a fundamental shift in coaching philosophy.
Understanding these trends helps contextualize individual coach decisions. A coach who went for it frequently in 2015 was bucking strong conventional wisdom, while the same decision in 2023 reflects mainstream acceptance of analytics.
#| label: fig-historical-trends-r
#| fig-cap: "Evolution of fourth down decisions over time"
#| fig-width: 10
#| fig-height: 6
#| message: false
#| warning: false
# Calculate annual rates by decision type
annual_trends <- fourth_downs %>%
filter(decision %in% c("go", "punt", "field_goal")) %>%
group_by(season, decision) %>%
summarise(plays = n(), .groups = "drop") %>%
group_by(season) %>%
mutate(
total = sum(plays),
pct = plays / total
) %>%
ungroup()
# Plot trends
ggplot(annual_trends, aes(x = season, y = pct, color = decision)) +
geom_line(size = 1.5) +
geom_point(size = 3) +
scale_color_manual(
values = c("go" = "#2ECC40", "punt" = "#0074D9", "field_goal" = "#FF851B"),
labels = c("Go For It", "Punt", "Field Goal"),
name = "Decision"
) +
scale_y_continuous(labels = percent_format(), limits = c(0, 0.7)) +
scale_x_continuous(breaks = seq(2014, 2023, 1)) +
labs(
title = "Evolution of Fourth Down Decision-Making",
subtitle = "NFL Regular Season, 2014-2023",
x = "Season",
y = "Percentage of Fourth Downs",
caption = "Data: nflfastR | Excludes kneels and other"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 14),
plot.subtitle = element_text(size = 11),
legend.position = "top",
panel.grid.minor = element_blank()
)
📊 Visualization Output
The code above generates a visualization. To see the output, run this code in your R or Python environment. The resulting plot will help illustrate the concepts discussed in this section.
#| label: fig-historical-trends-py
#| fig-cap: "Evolution of fourth down decisions - Python"
#| fig-width: 10
#| fig-height: 6
#| message: false
#| warning: false
# Calculate annual rates
annual_trends = (fourth_downs[fourth_downs['decision'].isin(['go', 'punt', 'field_goal'])]
.groupby(['season', 'decision'])
.size()
.reset_index(name='plays')
)
annual_trends['total'] = annual_trends.groupby('season')['plays'].transform('sum')
annual_trends['pct'] = annual_trends['plays'] / annual_trends['total']
# Plot
plt.figure(figsize=(10, 6))
for decision, color in [('go', '#2ECC40'), ('punt', '#0074D9'), ('field_goal', '#FF851B')]:
data = annual_trends[annual_trends['decision'] == decision]
label = {'go': 'Go For It', 'punt': 'Punt', 'field_goal': 'Field Goal'}[decision]
plt.plot(data['season'], data['pct'], marker='o', linewidth=2.5,
markersize=8, color=color, label=label)
plt.xlabel('Season', fontsize=12)
plt.ylabel('Percentage of Fourth Downs', fontsize=12)
plt.title('Evolution of Fourth Down Decision-Making\nNFL Regular Season, 2014-2023',
fontsize=14, fontweight='bold')
plt.legend(title='Decision', loc='best', fontsize=10)
plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))
plt.xlim(2013.5, 2023.5)
plt.ylim(0, 0.7)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Print statistics
print("\nGo-For-It Rate Change:")
go_2014 = annual_trends[(annual_trends['season']==2014) & (annual_trends['decision']=='go')]['pct'].values[0]
go_2023 = annual_trends[(annual_trends['season']==2023) & (annual_trends['decision']=='go')]['pct'].values[0]
print(f"2014: {go_2014:.1%}")
print(f"2023: {go_2023:.1%}")
print(f"Increase: {go_2023-go_2014:.1%} ({(go_2023/go_2014-1)*100:.1f}% growth)")
Go-For-It Success Rates by Distance
Understanding conversion rates by distance category helps calibrate our models and provides intuition for fourth down decision-making. The empirical success rates from NFL data show clear patterns that inform optimal strategy.
Fourth-and-1 or less represents the highest-percentage fourth down situation, with teams converting approximately 65-70% of attempts. This high success rate makes going for it attractive in most situations, particularly in opponent territory where the upside of conversion (continued possession near scoring position) is substantial. Even accounting for the downside of failure (giving opponents good field position), the expected value typically favors going for it.
Fourth-and-2 to 4th-and-3 situations convert at approximately 50-55%, representing inflection points where decision-making becomes more nuanced. At these distances, field position and game situation matter enormously. In opponent territory, 50%+ conversion rates combined with touchdown upside usually favor going for it. In one's own territory, the risk of giving opponents short fields might favor punting, depending on the specific field position.
Fourth-and-4 through 4th-and-6 convert at 35-45%, making these more challenging decisions. Teams generally should punt from their own territory in these situations unless desperate (trailing significantly late). However, in opponent territory, particularly inside the opponent's 35, going for it often remains optimal due to the touchdown potential and limited benefit of punting.
Fourth-and-7 or longer situations convert at under 30%, making them low-percentage attempts. Teams rarely should go for it in these situations except when trailing late in games and needing possessions. Field goals become the preferred option when in range, otherwise punting makes sense.
#| label: fig-success-by-distance-r
#| fig-cap: "Fourth down conversion rates by distance"
#| fig-width: 10
#| fig-height: 6
#| message: false
#| warning: false
# Calculate success rates by distance category
success_by_distance <- go_attempts %>%
group_by(distance_cat) %>%
summarise(
attempts = n(),
conversions = sum(converted, na.rm = TRUE),
success_rate = mean(converted, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(
distance_cat = factor(distance_cat, levels = c("1 yard or less", "2-3 yards",
"4-6 yards", "7-10 yards", "11+ yards"))
)
ggplot(success_by_distance, aes(x = distance_cat, y = success_rate)) +
geom_col(fill = "#2ECC40", alpha = 0.8) +
geom_text(aes(label = percent(success_rate, accuracy = 0.1)),
vjust = -0.5, fontface = "bold", size = 4) +
geom_text(aes(label = paste0("n=", attempts)),
vjust = 1.5, color = "white", size = 3.5) +
scale_y_continuous(labels = percent_format(), limits = c(0, 0.85)) +
labs(
title = "Fourth Down Conversion Rate by Distance",
subtitle = "NFL Regular Season, 2014-2023",
x = "Yards to Go",
y = "Conversion Rate",
caption = "Data: nflfastR | Labels show sample size"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 14),
plot.subtitle = element_text(size = 11),
axis.text.x = element_text(size = 10),
panel.grid.major.x = element_blank()
)
📊 Visualization Output
The code above generates a visualization. To see the output, run this code in your R or Python environment. The resulting plot will help illustrate the concepts discussed in this section.
#| label: fig-success-by-distance-py
#| fig-cap: "Fourth down conversion rates - Python"
#| fig-width: 10
#| fig-height: 6
#| message: false
#| warning: false
# Calculate success rates by distance
success_by_distance = (go_attempts
.groupby('distance_cat')
.agg(
attempts=('converted', 'count'),
conversions=('converted', 'sum'),
success_rate=('converted', 'mean')
)
.reset_index()
)
# Order categories
cat_order = ['1 yard or less', '2-3 yards', '4-6 yards', '7-10 yards', '11+ yards']
success_by_distance['distance_cat'] = pd.Categorical(
success_by_distance['distance_cat'],
categories=cat_order,
ordered=True
)
success_by_distance = success_by_distance.sort_values('distance_cat')
# Plot
fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.bar(range(len(success_by_distance)), success_by_distance['success_rate'],
color='#2ECC40', alpha=0.8)
# Add labels
for i, (rate, n) in enumerate(zip(success_by_distance['success_rate'],
success_by_distance['attempts'])):
ax.text(i, rate + 0.02, f'{rate:.1%}', ha='center', fontweight='bold', fontsize=11)
ax.text(i, rate * 0.5, f'n={int(n)}', ha='center', color='white', fontsize=10)
ax.set_xticks(range(len(success_by_distance)))
ax.set_xticklabels(success_by_distance['distance_cat'], fontsize=10)
ax.set_ylim(0, 0.85)
ax.set_ylabel('Conversion Rate', fontsize=12)
ax.set_xlabel('Yards to Go', fontsize=12)
ax.set_title('Fourth Down Conversion Rate by Distance\nNFL Regular Season, 2014-2023',
fontsize=14, fontweight='bold')
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))
ax.grid(True, axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
📊 Visualization Output
The code above generates a visualization. To see the output, run this code in your R or Python environment. The resulting plot will help illustrate the concepts discussed in this section.
The go-for-it rate has increased from approximately 12% in 2014 to over 25% in 2023—a remarkable shift in coaching behavior. Punt rates have declined correspondingly, while field goal rates have remained relatively stable. This suggests teams are replacing punts with go-for-it attempts rather than changing their approach to field goals.
The growth has been non-linear, with particularly sharp increases in 2018-2020. This period saw the emergence of coaches like Doug Pederson (Philadelphia), Matt LaFleur (Green Bay), and Kevin Stefanski (Cleveland) who embraced analytics-driven fourth down strategies. Media coverage of fourth down decisions also intensified during this period, with real-time analysis from the nfl4th bot highlighting suboptimal punts.
Despite this progress, teams still punt far too often according to analytics. The optimal go-for-it rate in most situations exceeds 40%, suggesting substantial room for further evolution toward aggressive play-calling.
The Diffusion of Analytics in Football
The fourth down trend exemplifies how analytics diffuses through sports organizations: 1. **Early Research** (2000s): Academics identify suboptimal behavior 2. **Tools Development** (2010s): Practitioners build accessible analytics tools 3. **Early Adopters** (2015-2018): Innovative coaches implement analytics-driven strategies 4. **Mainstream Adoption** (2019-present): Successful early adopters drive league-wide behavioral change 5. **New Equilibrium**: The league converges toward a new, more optimal baseline This pattern repeats across different analytics applications in football and other sports.Coach Aggressiveness Rankings
Evaluating coach aggressiveness on fourth down provides insight into which teams embrace analytics and which remain committed to traditional approaches. Aggressiveness, measured by go-for-it rate, varies substantially across teams and coaches, creating strategic differences that influence game outcomes.
However, raw go-for-it rates can be misleading. Teams that frequently face short yardage situations (because they gain yards efficiently on first through third down) will naturally have higher go-for-it rates even with identical decision-making to teams that face longer fourth downs. Similarly, teams that are often trailing (because they have poor defenses) will go for it more out of desperation rather than strategic choice.
A more sophisticated evaluation would compare actual decisions to optimal decisions (accounting for situation), calculating a "decision quality" score. The nfl4th package enables this analysis by providing recommendations for every fourth down situation. Teams with high decision quality consistently choose the option that maximizes win probability, regardless of whether that means going for it or punting.
For this basic analysis, we'll examine raw go-for-it rates across teams from 2020-2023, understanding that this provides an imperfect but still informative view of coaching philosophy.
#| label: coach-aggressiveness-r
#| message: false
#| warning: false
# Calculate coach-level statistics (2020-2023 for current coaches)
coach_stats <- fourth_downs %>%
filter(
season >= 2020,
decision %in% c("go", "punt", "field_goal"),
!is.na(posteam)
) %>%
group_by(posteam, season) %>%
summarise(
total_4th = n(),
go_attempts = sum(decision == "go"),
punts = sum(decision == "punt"),
fgs = sum(decision == "field_goal"),
go_rate = mean(decision == "go"),
.groups = "drop"
) %>%
# Need at least 20 fourth downs per season
filter(total_4th >= 20) %>%
group_by(posteam) %>%
summarise(
seasons = n(),
total_4th = sum(total_4th),
total_go = sum(go_attempts),
avg_go_rate = mean(go_rate),
.groups = "drop"
) %>%
filter(seasons >= 3) %>% # At least 3 seasons
arrange(desc(avg_go_rate))
# Top 10 most aggressive teams
coach_stats %>%
slice_head(n = 10) %>%
mutate(rank = row_number()) %>%
select(rank, posteam, seasons, total_4th, total_go, avg_go_rate) %>%
gt() %>%
cols_label(
rank = "Rank",
posteam = "Team",
seasons = "Seasons",
total_4th = "Total 4th Downs",
total_go = "Go Attempts",
avg_go_rate = "Go Rate"
) %>%
fmt_number(columns = avg_go_rate, decimals = 3) %>%
fmt_percent(columns = avg_go_rate, decimals = 1) %>%
tab_header(
title = "Most Aggressive Teams on Fourth Down",
subtitle = "2020-2023 NFL Regular Seasons (min. 3 seasons)"
) %>%
tab_source_note("Data: nflfastR")
#| label: coach-aggressiveness-py
#| message: false
#| warning: false
# Calculate team-level statistics
coach_stats = (fourth_downs[
(fourth_downs['season'] >= 2020) &
(fourth_downs['decision'].isin(['go', 'punt', 'field_goal'])) &
(fourth_downs['posteam'].notna())
]
.groupby(['posteam', 'season'])
.agg(
total_4th=('decision', 'count'),
go_attempts=('decision', lambda x: (x == 'go').sum()),
go_rate=('decision', lambda x: (x == 'go').mean())
)
.reset_index()
)
# Filter and aggregate
coach_stats = coach_stats[coach_stats['total_4th'] >= 20]
team_summary = (coach_stats
.groupby('posteam')
.agg(
seasons=('season', 'nunique'),
total_4th=('total_4th', 'sum'),
total_go=('go_attempts', 'sum'),
avg_go_rate=('go_rate', 'mean')
)
.reset_index()
)
team_summary = team_summary[team_summary['seasons'] >= 3]
team_summary = team_summary.sort_values('avg_go_rate', ascending=False)
# Display top 10
print("\nMost Aggressive Teams on Fourth Down (2020-2023):")
print("=" * 70)
top_10 = team_summary.head(10).copy()
top_10['rank'] = range(1, len(top_10) + 1)
top_10 = top_10[['rank', 'posteam', 'seasons', 'total_4th', 'total_go', 'avg_go_rate']]
print(top_10.to_string(index=False,
formatters={'avg_go_rate': '{:.1%}'.format}))
Teams at the top of the aggressiveness rankings typically feature coaches with analytics backgrounds or strong relationships with analytics departments. These teams consistently go for it at rates 50-100% higher than the most conservative teams, reflecting fundamentally different decision-making philosophies.
Interestingly, aggression on fourth down does not necessarily correlate with overall team success. Some aggressive teams have excellent records while others struggle, and vice versa for conservative teams. This suggests that while fourth down decisions matter, they represent just one component of overall team quality. Offensive and defensive talent, coaching quality across all situations, and roster construction likely matter more than fourth down philosophy alone.
Measuring Decision Quality vs. Outcome Quality
When evaluating coaches, distinguish between decision quality (making the choice that maximizes win probability) and outcome quality (whether the decision succeeded). A coach who goes for it on 4th-and-1 from the opponent's 40 makes a good decision even if the attempt fails, because the expected value favored going for it. Conversely, a coach who punts from the opponent's 35 on 4th-and-1 makes a poor decision even if the defense forces a three-and-out, because the expected value favored going for it. Over many decisions, good decision-making produces better outcomes than bad decision-making, but individual outcomes are noisy. Focus on process, not results, when evaluating fourth down strategy.Situational Modifiers: Score, Time, and Field Position
The optimal fourth down decision depends heavily on game context beyond just field position and distance. Score differential, time remaining, timeouts available, and opponent quality all influence whether teams should go for it, punt, or kick. Understanding these situational modifiers transforms fourth down analysis from a simple expected points calculation to a sophisticated win probability optimization.
Expected points analysis works well for neutral game situations (tied or close games, early in game), where maximizing points strongly correlates with maximizing win probability. However, as games progress and score differentials develop, win probability and expected points can diverge. A team trailing by 14 points late in the fourth quarter doesn't care about expected points—they need possessions and scores. Similarly, a team leading by 10 late in the game wants to run clock, making expected points less relevant than possession time.
The nfl4th package handles these complexities by using win probability directly rather than expected points. Win probability models account for score, time, timeouts, and field position simultaneously, generating recommendations that optimize for winning rather than point maximization. This approach correctly identifies situations where teams should deviate from EP-optimal strategies.
We'll first examine how score differential affects go-for-it rates in observed data, then discuss the theoretical implications for optimal strategy.
Score Differential Impact
Score differential dramatically influences both actual fourth down decisions and optimal strategy. Teams trailing significantly go for it far more often than teams with close scores or comfortable leads, reflecting the increased urgency and diminished value of field position when behind.
#| label: score-impact-r
#| message: false
#| warning: false
# Analyze decisions by score differential
score_analysis <- fourth_downs %>%
filter(
decision %in% c("go", "punt", "field_goal"),
!is.na(score_differential),
qtr == 4 # Focus on 4th quarter
) %>%
mutate(
score_bucket = case_when(
score_differential <= -17 ~ "Down 17+",
score_differential <= -9 ~ "Down 9-16",
score_differential <= -4 ~ "Down 4-8",
score_differential <= -1 ~ "Down 1-3",
score_differential <= 3 ~ "Tied to Up 3",
score_differential <= 8 ~ "Up 4-8",
score_differential <= 16 ~ "Up 9-16",
TRUE ~ "Up 17+"
),
score_bucket = factor(score_bucket, levels = c(
"Down 17+", "Down 9-16", "Down 4-8", "Down 1-3",
"Tied to Up 3", "Up 4-8", "Up 9-16", "Up 17+"
))
) %>%
group_by(score_bucket) %>%
summarise(
total = n(),
go_rate = mean(decision == "go"),
.groups = "drop"
)
ggplot(score_analysis, aes(x = score_bucket, y = go_rate)) +
geom_col(aes(fill = go_rate), alpha = 0.8) +
geom_text(aes(label = percent(go_rate, accuracy = 0.1)),
vjust = -0.5, fontface = "bold", size = 3.5) +
scale_fill_gradient(low = "#0074D9", high = "#2ECC40", guide = "none") +
scale_y_continuous(labels = percent_format(), limits = c(0, 0.8)) +
labs(
title = "Fourth Down Go-For-It Rate by Score Differential",
subtitle = "4th Quarter Only, 2014-2023",
x = "Score Differential",
y = "Go-For-It Rate",
caption = "Data: nflfastR"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 14),
plot.subtitle = element_text(size = 11),
axis.text.x = element_text(angle = 45, hjust = 1, size = 9),
panel.grid.major.x = element_blank()
)
#| label: score-impact-py
#| fig-width: 10
#| fig-height: 6
#| message: false
#| warning: false
# Analyze by score differential
score_data = fourth_downs[
(fourth_downs['decision'].isin(['go', 'punt', 'field_goal'])) &
(fourth_downs['score_differential'].notna()) &
(fourth_downs['qtr'] == 4)
].copy()
def score_bucket(diff):
if diff <= -17:
return "Down 17+"
elif diff <= -9:
return "Down 9-16"
elif diff <= -4:
return "Down 4-8"
elif diff <= -1:
return "Down 1-3"
elif diff <= 3:
return "Tied to Up 3"
elif diff <= 8:
return "Up 4-8"
elif diff <= 16:
return "Up 9-16"
else:
return "Up 17+"
score_data['score_bucket'] = score_data['score_differential'].apply(score_bucket)
bucket_order = ["Down 17+", "Down 9-16", "Down 4-8", "Down 1-3",
"Tied to Up 3", "Up 4-8", "Up 9-16", "Up 17+"]
score_analysis = (score_data
.groupby('score_bucket')
.agg(
total=('decision', 'count'),
go_rate=('decision', lambda x: (x == 'go').mean())
)
.reset_index()
)
score_analysis['score_bucket'] = pd.Categorical(
score_analysis['score_bucket'], categories=bucket_order, ordered=True
)
score_analysis = score_analysis.sort_values('score_bucket')
# Plot
fig, ax = plt.subplots(figsize=(10, 6))
colors = plt.cm.RdYlGn(score_analysis['go_rate'])
bars = ax.bar(range(len(score_analysis)), score_analysis['go_rate'], color=colors, alpha=0.8)
for i, rate in enumerate(score_analysis['go_rate']):
ax.text(i, rate + 0.02, f'{rate:.1%}', ha='center', fontweight='bold', fontsize=10)
ax.set_xticks(range(len(score_analysis)))
ax.set_xticklabels(score_analysis['score_bucket'], rotation=45, ha='right', fontsize=9)
ax.set_ylim(0, 0.8)
ax.set_ylabel('Go-For-It Rate', fontsize=12)
ax.set_xlabel('Score Differential', fontsize=12)
ax.set_title('Fourth Down Go-For-It Rate by Score Differential\n4th Quarter Only, 2014-2023',
fontsize=14, fontweight='bold')
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))
ax.grid(True, axis='y', alpha=0.3)
plt.tight_layout()
plt.show()
Key Insight: Desperation Drives Aggression
Teams trailing significantly (down 17+) go for it on fourth down at much higher rates than teams with close scores. This makes intuitive sense - when behind, teams need to maximize expected points rather than playing conservatively. However, analytics suggests teams should be more aggressive even in close games, as the expected value often favors going for it over punting.Using the nfl4th Package
While building fourth down models from scratch provides valuable understanding of the underlying mechanics, production fourth down analysis should use sophisticated tools that account for all relevant factors. The nfl4th package in R, developed by Ben Baldwin and Sebastian Carl, provides state-of-the-art fourth down recommendations based on comprehensive win probability models.
The nfl4th package improves on basic expected points models in several crucial ways. First, it uses win probability rather than expected points as the optimization target, correctly handling situations where maximizing points differs from maximizing win probability. Second, it accounts for game situation (score, time, timeouts) when calculating recommendations. Third, it incorporates uncertainty in conversion probability through bootstrapped models, providing more robust estimates. Fourth, it updates recommendations based on current season data, ensuring models reflect recent trends in offensive and defensive efficiency.
Using nfl4th for fourth down analysis provides several practical advantages. The package integrates seamlessly with nflfastR play-by-play data, making it easy to add recommendations to any fourth down situation. The win probability approach handles edge cases (end-of-game scenarios, large score differentials) better than EP models. The package includes the add_4th_probs() function that adds multiple columns to play-by-play data: go_wp (win probability of going for it), fg_wp (win probability of field goal), punt_wp (win probability of punt), go_boost (WP gain from going for it vs. best alternative), and first_down_prob (estimated conversion probability).
The "go boost" metric is particularly useful for identifying situations where analytics strongly recommends going for it. Positive go_boost values indicate going for it increases win probability compared to the next-best option. Values exceeding 0.03 (3 percentage points of win probability) represent situations where conservative decisions have substantial costs.
#| label: nfl4th-demo-r
#| message: false
#| warning: false
library(nfl4th)
# Load recent season
pbp_2023 <- load_pbp(2023)
# Add fourth down recommendations
pbp_with_recs <- pbp_2023 %>%
add_4th_probs()
# Look at fourth down plays with recommendations
fourth_with_recs <- pbp_with_recs %>%
filter(down == 4, !is.na(go_boost)) %>%
select(
game_id, qtr, time, posteam, defteam,
ydstogo, yardline_100, score_differential,
wp, go_wp, fg_wp, punt_wp,
go_boost, first_down_prob,
play_type, desc
)
# Show some interesting examples
interesting_cases <- fourth_with_recs %>%
filter(abs(go_boost) > 0.03) %>% # Significant WP advantage
slice_head(n = 5)
interesting_cases %>%
select(posteam, qtr, ydstogo, yardline_100, go_boost, play_type) %>%
mutate(
field_pos = paste0("Own", 100 - yardline_100),
situation = paste0("4th-and-", ydstogo, " at ", field_pos),
went_for_it = play_type %in% c("pass", "run")
) %>%
select(posteam, qtr, situation, go_boost, went_for_it) %>%
gt() %>%
cols_label(
posteam = "Team",
qtr = "Qtr",
situation = "Situation",
go_boost = "Go Boost (WP)",
went_for_it = "Went For It?"
) %>%
fmt_number(columns = go_boost, decimals = 3) %>%
fmt_percent(columns = go_boost, decimals = 1) %>%
tab_header(
title = "Fourth Down Decisions with Large WP Advantage",
subtitle = "2023 NFL Season - nfl4th Recommendations"
)
#| label: nfl4th-demo-py
#| message: false
#| warning: false
# Note: nfl4th is an R package, but we can replicate the logic
# For a full implementation, use the R version or build a custom WP model
print("\nNote: nfl4th is an R-specific package.")
print("For Python, you would need to:")
print("1. Build a win probability model (covered in Chapter 25)")
print("2. Calculate WP for each decision option")
print("3. Compare WP(go) - max(WP(punt), WP(fg)) to get 'go boost'")
print("\nExample calculation structure:")
# Simplified example
def calculate_go_boost(ydstogo, yardline_100, score_diff, seconds_remaining):
"""
Simplified go boost calculation
Real implementation requires full WP model
"""
# This is a placeholder - real calculation is much more complex
# and requires a trained win probability model
# Base conversion probability
conv_prob = max(0.1, 0.8 - 0.08 * ydstogo)
# Simplified WP boost approximation
# Positive means going for it increases WP
go_boost = (conv_prob - 0.5) * 0.1
return go_boost
# Example
example_boost = calculate_go_boost(3, 40, 0, 600)
print(f"\nExample: 4th-and-3 at opponent 40, tied game, 10 min left")
print(f"Estimated go boost: {example_boost:+.1%}")
print("\n(This is a simplified approximation - use R's nfl4th for accurate values)")
Optimal vs. Actual Decisions
Let's evaluate how often teams make optimal fourth down decisions.
#| label: optimal-actual-r
#| message: false
#| warning: false
# Analyze optimal vs actual for 2023 season
optimal_analysis <- pbp_with_recs %>%
filter(
down == 4,
!is.na(go_boost),
!is.na(play_type)
) %>%
mutate(
actual_decision = case_when(
punt_attempt == 1 ~ "punt",
field_goal_attempt == 1 ~ "field_goal",
play_type %in% c("pass", "run") ~ "go",
TRUE ~ "other"
),
optimal_decision = case_when(
go_wp >= fg_wp & go_wp >= punt_wp ~ "go",
fg_wp >= punt_wp ~ "field_goal",
TRUE ~ "punt"
),
decision_quality = case_when(
actual_decision == optimal_decision ~ "Optimal",
actual_decision == "go" & optimal_decision != "go" ~ "Too Aggressive",
actual_decision != "go" & optimal_decision == "go" ~ "Too Conservative",
TRUE ~ "Suboptimal"
)
) %>%
filter(actual_decision %in% c("go", "punt", "field_goal"))
# Overall optimality rate
optimality_rate <- mean(optimal_analysis$decision_quality == "Optimal")
cat("\nOverall Fourth Down Optimality Rate (2023):", percent(optimality_rate), "\n")
# Breakdown by decision quality
quality_breakdown <- optimal_analysis %>%
count(decision_quality) %>%
mutate(pct = n / sum(n)) %>%
arrange(desc(n))
quality_breakdown %>%
gt() %>%
cols_label(
decision_quality = "Decision Quality",
n = "Count",
pct = "Percentage"
) %>%
fmt_percent(columns = pct, decimals = 1) %>%
tab_header(
title = "Fourth Down Decision Quality",
subtitle = "2023 NFL Regular Season"
)
#| label: optimal-actual-py
#| message: false
#| warning: false
# For Python, we'd need to build the WP model first
# This is a conceptual example showing the analysis structure
print("\nOptimal vs. Actual Decision Analysis:")
print("=" * 60)
print("\nTo perform this analysis in Python, you need:")
print("1. Win probability model for each decision option")
print("2. Comparison of actual decision to optimal (max WP)")
print("3. Classification of decision quality")
print("\nExpected findings (based on literature):")
print("- ~60-70% of decisions are optimal")
print("- Most errors are 'too conservative' (should have gone for it)")
print("- Teams are getting better over time")
print("\nFor full analysis, use the R code with nfl4th package")
Key Finding: Room for Improvement
Analysis of NFL fourth down decisions shows that teams make the optimal choice only about 60-70% of the time. The vast majority of errors are being too conservative - punting or kicking when they should go for it. This represents a significant opportunity for competitive advantage. Teams that adopt more aggressive, analytics-driven fourth down strategies can gain meaningful wins over the course of a season.Teams trailing by 17+ points go for it on nearly 70% of fourth downs in the fourth quarter, compared to just 15-20% for teams with close scores. This reflects the desperation facing teams that need multiple scores to catch up. Conversely, teams with comfortable leads rarely go for it, preferring to punt and protect field position.
The optimal strategy should show even more extreme patterns than observed behavior. Teams trailing significantly should go for it on almost every fourth down (regardless of distance or field position) because they need possessions to score. Similarly, teams leading late should sometimes go for it in situations that would normally favor punting, because converting ends the game.
The data shows that coaches partially adjust for score differential, but not as much as win probability models recommend. This represents another opportunity for analytics-driven teams to gain edges through situational awareness.
Win Probability vs. Expected Points
The score differential analysis highlights why win probability models are superior to expected points for fourth down decisions: - **Expected Points**: Assumes all points are equally valuable, regardless of game situation - **Win Probability**: Accounts for the actual value of points given score, time, and possessions remaining A field goal that increases EP by 3 points might increase win probability by only 2% if you're trailing by 17 in the fourth quarter (you need touchdowns, not field goals). Conversely, a field goal in a tied game late might increase win probability by 15% despite the same EP increase. Always use win probability for game-critical decisions, especially in the fourth quarter or when score differentials exceed one score.Game Theory and Opponent Adjustments
Fourth down decisions exist within a strategic game between offense and defense, where each side attempts to exploit the other's tendencies. Game theory provides a framework for understanding these strategic interactions and identifying optimal decision-making strategies that account for opponent behavior.
The fundamental tension in fourth down game theory arises from opposing incentives: offenses want to maximize conversion probability, while defenses want to minimize it. Both sides have strategic choices that influence the outcome. Offenses choose whether to go for it, and if so, what play to run. Defenses choose how aggressively to defend based on their beliefs about offensive intentions.
These strategic interactions create opportunities for deception and exploitation. An offense that always goes for it on 4th-and-short allows defenses to prepare specifically for goal-line stands. Conversely, an offense that occasionally goes for it from unexpected situations keeps defenses honest and may gain advantages through surprise.
The key insight from game theory is that optimal strategies often involve randomization (mixed strategies) to prevent opponents from exploiting patterns. However, in practice, the complexity of football situations and the difficulty of coordinating randomization mean that teams primarily focus on maximizing expected value for each individual decision rather than worrying about exploitation across decisions.
The Strategic Considerations
Several game-theoretic considerations influence fourth down decisions:
-
Predictability: If you always punt on 4th-and-5, defenses know they just need to prevent 5 yards on third down to force a punt. This might lead them to play more aggressively on third down, reducing your third down conversion rate. Occasionally going for it on fourth down keeps defenses honest on third down.
-
Bluffing: Sometimes threatening to go for it (by lining up in offensive formation) causes defenses to burn timeouts or rush to align, even if you ultimately punt. This creates value beyond the fourth down play itself.
-
Reputation Effects: Aggressive coaches force opponents to prepare differently throughout the week. If opponents know you'll go for it on 4th-and-2, they must practice goal-line defense more, taking time away from other preparation.
-
Optimal Mixed Strategies: In certain situations, the optimal strategy involves randomizing between going for it and punting with specific probabilities. This prevents defenses from perfectly countering your choice.
-
Information Asymmetry: Offenses know their intentions (go or punt) before defenses commit to defensive calls. This information advantage favors offenses and is one reason aggressive fourth down strategies work.
Mathematical Framework
In game theory terms, fourth down can be modeled as a simultaneous-move game between offense and defense:
Offense chooses: Go for it / Punt / Field Goal
Defense chooses: Aggressive (prioritize stopping conversion) / Conservative (protect against big plays)
The payoff matrix depends on the specific situation, but generally follows this structure:
| Defense: Aggressive | Defense: Conservative | |
|---|---|---|
| Offense: Go | Lower conversion % (defense stacks box) | Higher conversion % (defense plays back) |
| Offense: Punt | Better field position (aggressive returns) | Worse field position (safe return) |
The offense prefers to go for it when the defense plays conservatively, while the defense prefers aggressive coverage when the offense goes for it. This creates strategic tension and the potential for mixed strategy equilibria.
Nash Equilibrium
The Nash equilibrium strategy often involves mixed strategies: randomizing decisions with specific probabilities to keep opponents from exploiting patterns. A mixed strategy equilibrium occurs when each side randomizes such that the opponent is indifferent between their options.
For example, suppose on 4th-and-3 from the 50:
- If offense goes for it, defense should play aggressively (stops conversion 70% of the time)
- If offense punts, defense should play conservatively (results in ball at their 15-yard line)
- If defense plays aggressively, offense should punt (EV = -0.5)
- If defense plays conservatively, offense should go for it (EV = +1.5)
The mixed strategy equilibrium might involve offense going for it 60% of the time, defense playing aggressively 60% of the time, with both sides indifferent to their choice given the opponent's strategy.
In practice, these equilibria are difficult to calculate and implement due to the complexity of football situations. Most teams focus on maximizing expected value for each individual decision rather than optimal mixed strategies across decisions.
#| label: game-theory-r
#| message: false
#| warning: false
# Analyze tendency changes based on score/time pressure
# Do defenses adjust to aggressive offenses?
# Identify most aggressive teams
aggressive_teams <- coach_stats %>%
slice_head(n = 5) %>%
pull(posteam)
# Look at defensive performance against these teams
defensive_response <- pbp %>%
filter(
season >= 2020,
down == 4,
decision == "go",
posteam %in% aggressive_teams,
!is.na(epa)
) %>%
group_by(defteam) %>%
summarise(
attempts_faced = n(),
conversions_allowed = sum(first_down_rush + first_down_pass, na.rm = TRUE),
conversion_rate_allowed = mean(first_down_rush + first_down_pass, na.rm = TRUE),
avg_epa_allowed = mean(epa, na.rm = TRUE),
.groups = "drop"
) %>%
filter(attempts_faced >= 10) %>%
arrange(conversion_rate_allowed)
# Top defenses against 4th down attempts
defensive_response %>%
slice_head(n = 10) %>%
mutate(rank = row_number()) %>%
select(rank, defteam, attempts_faced, conversion_rate_allowed, avg_epa_allowed) %>%
gt() %>%
cols_label(
rank = "Rank",
defteam = "Defense",
attempts_faced = "Attempts Faced",
conversion_rate_allowed = "Conversion Rate Allowed",
avg_epa_allowed = "Avg EPA Allowed"
) %>%
fmt_percent(columns = conversion_rate_allowed, decimals = 1) %>%
fmt_number(columns = avg_epa_allowed, decimals = 3) %>%
tab_header(
title = "Best Defenses Against Fourth Down Attempts",
subtitle = "vs. Top 5 Most Aggressive Teams, 2020-2023"
)
#| label: game-theory-py
#| message: false
#| warning: false
# Identify aggressive teams
aggressive_teams_list = team_summary.head(5)['posteam'].tolist()
# Defensive response analysis
defensive_data = pbp[
(pbp['season'] >= 2020) &
(pbp['down'] == 4) &
(pbp['posteam'].isin(aggressive_teams_list)) &
(pbp['epa'].notna())
].copy()
# Filter to go-for-it plays
defensive_data = defensive_data[
(defensive_data['pass_attempt'].notna()) |
(defensive_data['rush_attempt'].notna())
]
defensive_data['converted'] = (
defensive_data['first_down_rush'].fillna(0) +
defensive_data['first_down_pass'].fillna(0)
)
defensive_response = (defensive_data
.groupby('defteam')
.agg(
attempts_faced=('epa', 'count'),
conversions_allowed=('converted', 'sum'),
conversion_rate_allowed=('converted', 'mean'),
avg_epa_allowed=('epa', 'mean')
)
.reset_index()
)
defensive_response = defensive_response[defensive_response['attempts_faced'] >= 10]
defensive_response = defensive_response.sort_values('conversion_rate_allowed')
print("\nBest Defenses Against Fourth Down Attempts")
print("vs. Top 5 Most Aggressive Teams, 2020-2023")
print("=" * 70)
top_10_def = defensive_response.head(10).copy()
top_10_def['rank'] = range(1, len(top_10_def) + 1)
top_10_def = top_10_def[['rank', 'defteam', 'attempts_faced',
'conversion_rate_allowed', 'avg_epa_allowed']]
print(top_10_def.to_string(index=False,
formatters={
'conversion_rate_allowed': '{:.1%}'.format,
'avg_epa_allowed': '{:.3f}'.format
}))
The defensive response analysis shows which teams best defend fourth down conversion attempts from aggressive offenses. Teams that defend fourth downs effectively force opponents to reconsider aggressive strategies, potentially changing the game-theory equilibrium. However, the sample sizes remain relatively small (even aggressive teams only face 20-30 fourth down attempts per season), making it difficult to distinguish defensive skill from randomness.
Future Directions in Fourth Down Analytics
Several promising directions could further improve fourth down decision-making: 1. **Personnel-specific models**: Account for which players are on the field (QB quality, offensive line strength, defensive front quality) 2. **Play-type prediction**: Model the optimal play call conditional on going for it (pass vs. run, specific plays) 3. **Fatigue effects**: Account for how defensive fatigue late in games affects conversion probability 4. **Weather adjustments**: Incorporate weather conditions into all components (conversion, FG, punt) 5. **Coaching tendencies**: Exploit opponent tendencies by learning their fourth down patterns 6. **Real-time updating**: Update models during games based on observed performance These extensions could provide additional edges for analytically sophisticated teams.Summary
Fourth down decision-making represents one of the most important and tractable applications of football analytics, offering clear opportunities for teams to gain competitive advantages through better strategy. This chapter has developed a complete framework for fourth down analysis, from theoretical foundations through practical implementation.
Key Takeaways:
-
Expected Value Framework: The optimal fourth down decision maximizes expected value by comparing EV(go), EV(punt), and EV(field goal). This requires modeling conversion probability, field goal success, punt outcomes, and expected points for all resulting field positions.
-
Teams Are Too Conservative: Historical data and analytical models consistently show that NFL teams punt and kick field goals too often. The gap between optimal and actual go-for-it rates exceeds 15 percentage points in many situations, representing substantial opportunity cost in wins.
-
Analytics Adoption Growing: Go-for-it rates have doubled from approximately 12% in 2014 to over 25% in 2023, demonstrating the real-world impact of analytics on coaching decisions. However, teams still have room for further optimization.
-
Context Matters: Score differential, time remaining, timeouts available, and opponent quality dramatically affect optimal fourth down strategy. Win probability models account for these factors better than expected points models, making them superior for game-critical decisions.
-
Competitive Advantage: Teams that adopt more aggressive, analytics-driven fourth down strategies gain measurable advantages. High-profile successes (Philadelphia's Super Bowl LII victory) demonstrate that analytics-driven aggression works in practice, not just theory.
-
Game Theory Considerations: Fourth down decisions involve strategic interactions between offense and defense. While optimal mixed strategies exist in theory, practical implementation focuses on maximizing expected value for individual decisions while maintaining some unpredictability.
-
Model Components: A complete fourth down system requires four components: conversion probability model, field goal success model, punt outcome model, and expected points (or win probability) model. Each component can be built using historical data and logistic/linear regression.
-
Decision Quality vs. Outcomes: Evaluate coaches and decisions based on expected value at the time of decision, not outcome. Good decisions sometimes produce bad outcomes; focus on process over results.
Practical Applications:
For analysts building fourth down recommendation systems, prioritize using win probability over expected points when possible, as WP correctly handles score and time effects. The nfl4th package provides production-ready recommendations incorporating these factors.
For coaches implementing analytics-driven strategies, prepare to face criticism when aggressive decisions fail. Communicate the expected value logic to stakeholders (ownership, media, fans) before controversial situations arise. Build institutional support for analytics-based decision-making that can withstand short-term negative outcomes.
For fans and media evaluating coaching decisions, resist the temptation to judge decisions by outcomes. A failed 4th-and-1 conversion from the opponent's 35 was likely still the right call, just as a successful punt from the same situation was likely suboptimal despite the good outcome.
The gap between optimal and actual fourth down decisions remains substantial across the NFL, offering opportunities for teams willing to embrace analytics. As tools become more accessible and successful examples multiply, we should expect continued evolution toward more aggressive fourth down strategies in coming years.
Exercises
Conceptual Questions
-
Expected Value: Explain why a 4th-and-1 at your own 35 might favor going for it even though failure gives the opponent great field position.
-
Conservative Bias: What psychological and institutional factors might cause coaches to be too conservative on fourth down?
-
Game Theory: How might a reputation for going for it on fourth down affect opponent behavior throughout games?
Coding Exercises
Exercise 1: Fourth Down Success Predictors
Build a model predicting fourth down conversion probability using the following variables: - Yards to go - Field position - Formation (shotgun vs under center) - Personnel (number of running backs, tight ends) - Down (for plays that aren't 4th down, to see general success patterns) Compare model accuracy using logistic regression, random forest, and XGBoost.Exercise 2: Team Decision Quality
For each team in 2023: 1. Calculate their fourth down "decision quality score" - percentage of optimal decisions 2. Identify their biggest decision mistakes (largest negative WP impact) 3. Estimate how many wins they left on the table from suboptimal decisions Create a visualization comparing decision quality across all 32 teams.Exercise 3: Build a Fourth Down Recommendation System
Create a Shiny app (R) or Streamlit app (Python) that: 1. Takes inputs: down, distance, field position, score, time remaining 2. Shows conversion probability, field goal probability, and punt outcome 3. Calculates expected value for each option 4. Provides a recommendation with confidence intervals 5. Shows historical success rates for similar situationsExercise 4: Situational Analysis
Analyze how fourth down decisions should change based on: - Score differential (down 14 vs tied vs up 10) - Time remaining (1st quarter vs 4th quarter) - Opponent quality (strong defense vs weak defense) Create decision boundary visualizations for each scenario showing how the optimal choice changes.Further Reading
Academic Papers
- Romer, D. (2006). "Do Firms Maximize? Evidence from Professional Football." Journal of Political Economy, 114(2), 340-365.
- Burke, B. (2009). "The 4th Down Study." Advanced NFL Stats.
- Lopez, M. J., & Baumer, B. S. (2020). "Predicting and Evaluating NFL Fourth-Down Decisions." Journal of Quantitative Analysis in Sports, 16(2), 93-107.
Industry Resources
- Ben Baldwin's nfl4th package documentation
- Football Outsiders' fourth down analysis
- The Athletic's fourth down tracking and analysis
- Ben Baldwin & Keegan Abdoo's "Fourth Down Decision Bot" on Twitter
Books
- Alamar, B. (2013). Sports Analytics: A Guide for Coaches, Managers, and Other Decision Makers. Columbia University Press.
- Moskowitz, T., & Wertheim, L. (2011). Scorecasting: The Hidden Influences Behind How Sports Are Played and Games Are Won. Crown Archetype.
References
:::