Case Study: The Fourth Down Revolution

How analytics changed the most conservative decision in football


Introduction

For decades, NFL coaches followed a simple fourth down philosophy: punt or kick field goals except in obvious situations (4th & 1 on the opponent's goal line, trailing late). This conservative approach persisted despite mathematical evidence that teams were leaving points on the table.

Then, gradually, the analytics revolution arrived. A few pioneering coaches began going for it more often. Their success—and the growing influence of analytics departments—shifted league-wide behavior. This case study examines that transformation through data.


The Historical Context

The Old Philosophy

Traditional fourth down wisdom held that: 1. Field position matters most - "Don't give them a short field" 2. Take the points - A field goal is "certain" points 3. Trust the defense - Pin them deep and let your D work 4. Avoid embarrassment - Failed 4th downs look bad on film

These principles led to extremely conservative decision-making: - Punt on 4th & 2 from the opponent's 45 - Kick 50+ yard field goals rather than go for it - Never go for it in your own territory

The Analytics Challenge

Starting in the early 2000s, researchers demonstrated that this conservatism was costing teams wins. Key findings:

  1. Conversion rates are high - Teams convert 4th & 1 at ~73%, 4th & 2 at ~63%
  2. Punts don't gain much - Net 35-40 yards on average
  3. Field position value is overrated - Difference between own 20 and own 35 is small
  4. Failed attempts aren't catastrophic - Opponent EP increase is manageable

Quantifying the Opportunity

Expected Value Model

import pandas as pd
import numpy as np

def fourth_down_ev(
    field_position: int,  # Yards from opponent's end zone
    distance: int,        # Yards to go
    conversion_prob: float = None
) -> dict:
    """
    Calculate expected value for all 4th down options.

    Returns EV for: go for it, field goal, punt
    """
    # Default conversion probabilities by distance
    default_conv = {
        1: 0.73, 2: 0.63, 3: 0.55, 4: 0.48,
        5: 0.44, 6: 0.40, 7: 0.36, 8: 0.33
    }

    conv_prob = conversion_prob or default_conv.get(distance, 0.30)

    # Expected points by field position (simplified)
    def ep(yards_from_opp_goal):
        return 7 * (1 - yards_from_opp_goal / 100) - 1

    # FG probability by distance
    fg_distance = field_position + 17
    if fg_distance <= 30:
        fg_prob = 0.93
    elif fg_distance <= 40:
        fg_prob = 0.85
    elif fg_distance <= 50:
        fg_prob = 0.70
    elif fg_distance <= 55:
        fg_prob = 0.55
    else:
        fg_prob = 0.35

    # Expected values
    # Go for it
    ep_success = ep(field_position - distance)  # First down
    ep_failure = -ep(100 - field_position)      # Turnover on downs
    ev_go = conv_prob * ep_success + (1 - conv_prob) * ep_failure

    # Field goal
    ep_miss_recovery = -ep(100 - field_position + 7)  # Opp gets ball at spot
    ev_fg = fg_prob * 3 + (1 - fg_prob) * ep_miss_recovery

    # Punt (assume 45 yard gross, touchback at 20)
    net_punt = min(45, field_position - 20)
    punt_result_position = max(20, field_position - net_punt)
    ev_punt = -ep(punt_result_position)

    return {
        'go_ev': round(ev_go, 2),
        'fg_ev': round(ev_fg, 2) if field_position <= 50 else None,
        'punt_ev': round(ev_punt, 2),
        'best_option': max(
            [('go', ev_go),
             ('fg', ev_fg if field_position <= 50 else -999),
             ('punt', ev_punt)],
            key=lambda x: x[1]
        )[0],
        'go_advantage': round(ev_go - max(ev_fg if field_position <= 50 else -999, ev_punt), 2)
    }

# Test key situations
situations = [
    (50, 1, "Midfield, 4th & 1"),
    (40, 2, "Opp 40, 4th & 2"),
    (35, 3, "Opp 35, 4th & 3"),
    (45, 4, "Opp 45, 4th & 4"),
]

print("Fourth Down Expected Value Analysis")
print("=" * 60)
for fp, dist, desc in situations:
    result = fourth_down_ev(fp, dist)
    print(f"\n{desc}")
    print(f"  Go for it EV: {result['go_ev']:+.2f}")
    if result['fg_ev']:
        print(f"  Field goal EV: {result['fg_ev']:+.2f}")
    print(f"  Punt EV: {result['punt_ev']:+.2f}")
    print(f"  Best option: {result['best_option'].upper()}")
    print(f"  Go advantage: {result['go_advantage']:+.2f}")

Key Finding: Going for it is optimal in far more situations than teams historically attempted.


The Data: How Teams Actually Decided

Historical Go-For-It Rates

def analyze_fourth_down_history(pbp_data: list) -> pd.DataFrame:
    """Analyze 4th down decisions across seasons."""

    results = []
    for year, pbp in pbp_data.items():
        fourth_downs = pbp[
            (pbp['down'] == 4) &
            (pbp['yardline_100'] <= 60) &  # Opponent's side or midfield
            (pbp['ydstogo'] <= 5)          # Short yardage
        ]

        go_plays = fourth_downs[
            fourth_downs['play_type'].isin(['pass', 'run'])
        ]

        go_rate = len(go_plays) / len(fourth_downs) if len(fourth_downs) > 0 else 0

        results.append({
            'season': year,
            'total_opportunities': len(fourth_downs),
            'go_attempts': len(go_plays),
            'go_rate': go_rate
        })

    return pd.DataFrame(results)

# Example output (illustrative data)
historical_rates = {
    2010: 0.08,
    2012: 0.09,
    2014: 0.11,
    2016: 0.14,
    2018: 0.18,
    2020: 0.23,
    2022: 0.28,
    2023: 0.32
}

print("\nLeague-Wide Go-For-It Rate (4th & 1-5, opp territory)")
print("=" * 50)
for year, rate in historical_rates.items():
    bar = "█" * int(rate * 100)
    print(f"{year}: {rate:.0%} {bar}")

The Trend: Go-for-it rates have nearly quadrupled since 2010.

Team-Level Variation

def rank_teams_by_aggressiveness(pbp: pd.DataFrame, season: int) -> pd.DataFrame:
    """Rank teams by 4th down aggressiveness."""

    fourth_downs = pbp[
        (pbp['down'] == 4) &
        (pbp['yardline_100'] <= 60) &
        (pbp['ydstogo'] <= 5)
    ]

    team_data = []
    for team in fourth_downs['posteam'].unique():
        team_4th = fourth_downs[fourth_downs['posteam'] == team]
        go_plays = team_4th[team_4th['play_type'].isin(['pass', 'run'])]

        team_data.append({
            'team': team,
            'opportunities': len(team_4th),
            'go_attempts': len(go_plays),
            'go_rate': len(go_plays) / len(team_4th) if len(team_4th) > 0 else 0,
            'conversions': (go_plays['first_down'] == 1).sum() if len(go_plays) > 0 else 0
        })

    df = pd.DataFrame(team_data)
    df['conversion_rate'] = df['conversions'] / df['go_attempts']
    df['conversion_rate'] = df['conversion_rate'].fillna(0)

    return df.sort_values('go_rate', ascending=False)

# Illustrative 2023 data
print("\n2023 Most Aggressive 4th Down Teams")
print("=" * 50)
aggressive_teams = [
    ('PHI', 0.42, 0.68),
    ('BAL', 0.38, 0.72),
    ('DET', 0.36, 0.65),
    ('SF', 0.34, 0.70),
    ('MIA', 0.32, 0.67),
]

for team, go_rate, conv_rate in aggressive_teams:
    print(f"{team}: Go rate {go_rate:.0%}, Conversion rate {conv_rate:.0%}")

The Pioneers

Doug Pederson Era Eagles (2016-2020)

Doug Pederson, influenced by analytics department recommendations, became notably aggressive:

# Pederson's Eagles 4th down profile
pederson_eagles = {
    '2017': {'go_rate': 0.28, 'success_rate': 0.71, 'result': 'Super Bowl Win'},
    '2018': {'go_rate': 0.31, 'success_rate': 0.68, 'result': 'Playoffs'},
    '2019': {'go_rate': 0.33, 'success_rate': 0.65, 'result': 'Playoffs'},
    '2020': {'go_rate': 0.25, 'success_rate': 0.62, 'result': 'Last place'},
}

print("\nDoug Pederson's Eagles: 4th Down Profile")
print("=" * 55)
for year, data in pederson_eagles.items():
    print(f"{year}: Go {data['go_rate']:.0%}, Success {data['success_rate']:.0%} - {data['result']}")

The 2017 Eagles famously went for it on 4th & 1 from their own 45 trailing in the Super Bowl—a decision vindicated by both analytics and outcome.

Kevin Stefanski's Browns

Stefanski brought aggressive 4th down philosophy to Cleveland:

# Key decisions that paid off
stefanski_decisions = [
    {
        'game': 'vs PIT Wildcard 2020',
        'situation': '4th & 1, own 46, down 12',
        'decision': 'Go for it',
        'result': 'Converted, scored TD, won game'
    },
    {
        'game': 'vs BAL 2021',
        'situation': '4th & 2, opp 35, tied',
        'decision': 'Go for it',
        'result': 'Converted, eventual FG'
    }
]

Quantifying the Impact

Decision Value Added

def calculate_decision_value(
    actual_decision: str,
    optimal_decision: str,
    ev_go: float,
    ev_fg: float,
    ev_punt: float
) -> float:
    """
    Calculate the expected points gained/lost from a decision.

    Positive = good decision, Negative = suboptimal
    """
    evs = {'go': ev_go, 'fg': ev_fg, 'punt': ev_punt}
    optimal_ev = evs[optimal_decision]
    actual_ev = evs[actual_decision]

    return actual_ev - optimal_ev  # 0 if optimal, negative if suboptimal

def audit_team_decisions(pbp: pd.DataFrame, team: str) -> dict:
    """
    Audit all 4th down decisions for a team.

    Returns aggregate decision value added.
    """
    fourth_downs = pbp[
        (pbp['posteam'] == team) &
        (pbp['down'] == 4)
    ]

    total_value = 0
    decisions_audited = 0

    for _, play in fourth_downs.iterrows():
        fp = play['yardline_100']
        dist = play['ydstogo']

        # Get expected values
        ev = fourth_down_ev(fp, dist)

        # Determine actual decision
        if play['play_type'] in ['pass', 'run']:
            actual = 'go'
        elif play['play_type'] == 'field_goal':
            actual = 'fg'
        elif play['play_type'] == 'punt':
            actual = 'punt'
        else:
            continue

        # Calculate value
        value = calculate_decision_value(
            actual, ev['best_option'],
            ev['go_ev'], ev['fg_ev'] or -99, ev['punt_ev']
        )

        total_value += value
        decisions_audited += 1

    return {
        'team': team,
        'decisions_audited': decisions_audited,
        'total_decision_value': round(total_value, 2),
        'avg_decision_value': round(total_value / decisions_audited, 3) if decisions_audited > 0 else 0
    }

# Example output
print("\n4th Down Decision Value Added (2023)")
print("=" * 50)
sample_results = [
    ('PHI', 48, 3.2, 0.067),
    ('BAL', 45, 2.8, 0.062),
    ('DET', 42, 2.1, 0.050),
    ('NYG', 38, -1.5, -0.039),
    ('CHI', 35, -2.8, -0.080),
]

for team, decisions, total_val, avg_val in sample_results:
    sign = '+' if total_val >= 0 else ''
    print(f"{team}: {decisions} decisions, {sign}{total_val:.1f} total EP, {sign}{avg_val:.3f} per decision")

Season-Long Impact

Over a 17-game season, aggressive fourth down decisions can add multiple wins:

# EPA added from optimal decisions
def estimate_wins_from_decisions(
    decision_ep_added: float,
    games: int = 17
) -> float:
    """
    Estimate wins added from 4th down decision-making.

    Uses ~0.3 win probability per expected point relationship.
    """
    per_game = decision_ep_added / games
    win_prob_added_per_game = per_game * 0.03  # ~3% per EP
    wins_added = win_prob_added_per_game * games

    return wins_added

# Example
ep_added = 5.0  # Above-average decision maker
wins_added = estimate_wins_from_decisions(ep_added)
print(f"\nWith {ep_added} EP added from 4th down decisions:")
print(f"Estimated wins added: {wins_added:.1f}")

The Holdouts

Despite the evidence, some teams remained conservative:

conservative_teams_2023 = [
    ('Team A', 0.15, 'Traditional coach, veteran QB'),
    ('Team B', 0.18, 'Defensive-minded coach'),
    ('Team C', 0.19, 'Risk-averse ownership pressure'),
]

print("\nMost Conservative Teams (2023)")
print("=" * 50)
for team, rate, reason in conservative_teams_2023:
    print(f"{team}: {rate:.0%} go rate - {reason}")

Why Resist the Evidence?

  1. Job security - Failed 4th downs generate criticism
  2. Outcome bias - Failed attempts are remembered more than punts
  3. Trust issues - Coaches don't fully trust analytics
  4. Sample size concerns - "Our team is different"
  5. Competitive balance - If everyone goes for it, advantage disappears

The Results

Aggressive Teams Win More

# Correlation analysis
correlation_data = {
    'metric': 'Go Rate vs Win %',
    'correlation': 0.35,
    'interpretation': 'Moderate positive - aggressive teams win more'
}

# But causation is complex
print("\nCaution: Correlation vs Causation")
print("=" * 50)
print("- Better teams CAN afford to be aggressive (good QBs, offensive lines)")
print("- Aggressive decisions may reflect coaching quality broadly")
print("- Sample sizes remain small for definitive claims")

Conversion Rates Validate the Math

actual_conversion_rates = {
    '4th & 1': 0.71,
    '4th & 2': 0.61,
    '4th & 3': 0.53,
    '4th & 4': 0.46,
    '4th & 5': 0.42,
}

print("\n2023 Actual Conversion Rates")
print("=" * 40)
for situation, rate in actual_conversion_rates.items():
    print(f"{situation}: {rate:.0%}")
print("\nThese validate the expected value models used to recommend decisions.")

Lessons Learned

1. Evidence Can Change Behavior

The NFL's fourth down revolution shows that analytical evidence, consistently presented, can overcome decades of conventional wisdom.

2. Pioneers Take Risks

Early adopters like Pederson faced criticism when attempts failed but were vindicated by aggregate success.

3. The Market Adjusts

As more teams go for it, defensive strategies may adapt, potentially changing the optimal calculus.

4. Context Still Matters

While analytics provides the framework, factors like personnel, weather, and momentum affect individual decisions.


Your Turn

Exercise: Analyze a recent NFL game and evaluate every 4th down decision:

  1. Calculate expected value for each option
  2. Determine if the actual decision was optimal
  3. Sum up total decision value added/lost
  4. Compare to league average

Bonus: Track how conversion attempts on "go" decisions compared to expected rates.


Summary

The fourth down revolution demonstrates analytics' power to change deeply entrenched behavior:

  • Mathematical evidence showed teams were too conservative
  • Pioneering coaches proved aggressive strategies could work
  • League-wide adoption increased go-for-it rates from ~8% to ~32%
  • Results validated the analytical recommendations

The transformation isn't complete—many coaches remain conservative—but fourth down decision-making has fundamentally changed due to analytical insights.