4 min read

Not all plays matter equally. A first down run in the second quarter of a blowout has far less impact than a third down pass in the final two minutes of a tie game. Understanding how teams perform in high-leverage situations reveals clutch...

Chapter 14: Situational Football

Analyzing performance in critical game situations


Introduction

Not all plays matter equally. A first down run in the second quarter of a blowout has far less impact than a third down pass in the final two minutes of a tie game. Understanding how teams perform in high-leverage situations reveals clutch performance, coaching acumen, and true competitive quality.

This chapter explores: - Red zone efficiency - scoring inside the opponent's 20 - Third down conversions - the critical "money down" - Two-minute offense - hurry-up execution - Goal-to-go situations - short-field scoring - Late and close games - performance under pressure - Clutch vs choke patterns - does "clutch" exist?

These situational metrics help identify teams that consistently execute when it matters most.


Red Zone Efficiency

Defining the Red Zone

The "red zone" traditionally refers to the area inside the opponent's 20-yard line. Once an offense reaches this territory, the field compresses, defensive coverage tightens, and scoring becomes the primary objective.

import pandas as pd
import numpy as np
import nfl_data_py as nfl

# Load data
pbp = nfl.import_pbp_data([2023])

# Identify red zone plays
red_zone_plays = pbp[
    (pbp['yardline_100'] <= 20) &
    (pbp['play_type'].isin(['pass', 'run'])) &
    (pbp['epa'].notna())
].copy()

print(f"Red zone plays: {len(red_zone_plays):,}")
print(f"Percentage of all plays: {len(red_zone_plays)/len(pbp)*100:.1f}%")

Red Zone Scoring Rate

The primary red zone metric is touchdown conversion rate:

def calculate_red_zone_efficiency(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate comprehensive red zone metrics for a team."""

    # Get all drives that reached the red zone
    drives = pbp[
        (pbp['posteam'] == team) &
        (pbp['yardline_100'] <= 20)
    ].groupby(['game_id', 'drive']).agg(
        reached_rz=('yardline_100', 'min'),
        scored_td=('touchdown', 'max'),
        scored_fg=('field_goal_result', lambda x: (x == 'made').any()),
        turnover=('interception', lambda x: (x == 1).any() | (pbp.loc[x.index, 'fumble_lost'] == 1).any())
    ).reset_index()

    # Filter to drives that actually reached RZ
    rz_drives = drives[drives['reached_rz'] <= 20]

    total_trips = len(rz_drives)
    touchdowns = rz_drives['scored_td'].sum()
    field_goals = rz_drives['scored_fg'].sum()
    turnovers = rz_drives['turnover'].sum()

    # Points per trip
    points = touchdowns * 7 + field_goals * 3
    points_per_trip = points / total_trips if total_trips > 0 else 0

    return {
        'red_zone_trips': total_trips,
        'touchdowns': touchdowns,
        'td_rate': touchdowns / total_trips if total_trips > 0 else 0,
        'field_goals': field_goals,
        'fg_rate': field_goals / total_trips if total_trips > 0 else 0,
        'points_per_trip': points_per_trip,
        'turnovers': turnovers,
        'turnover_rate': turnovers / total_trips if total_trips > 0 else 0
    }

# Example
kc_rz = calculate_red_zone_efficiency(pbp, 'KC')
print("KC Red Zone Efficiency:")
for key, value in kc_rz.items():
    if 'rate' in key:
        print(f"  {key}: {value:.1%}")
    else:
        print(f"  {key}: {value:.2f}")

Red Zone Benchmarks

Metric League Average Good Elite
TD Rate 55-58% 60-65% 65%+
Points/Trip 4.0-4.3 4.5-5.0 5.0+
Turnover Rate 5-7% 3-5% < 3%

Red Zone EPA

Beyond scoring rates, EPA provides efficiency context:

def calculate_red_zone_epa(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate red zone EPA metrics."""

    rz_plays = pbp[
        (pbp['posteam'] == team) &
        (pbp['yardline_100'] <= 20) &
        (pbp['play_type'].isin(['pass', 'run'])) &
        (pbp['epa'].notna())
    ]

    overall_epa = rz_plays['epa'].mean()
    pass_plays = rz_plays[rz_plays['play_type'] == 'pass']
    rush_plays = rz_plays[rz_plays['play_type'] == 'run']

    pass_epa = pass_plays['epa'].mean() if len(pass_plays) > 0 else 0
    rush_epa = rush_plays['epa'].mean() if len(rush_plays) > 0 else 0
    success_rate = (rz_plays['epa'] > 0).mean()

    return {
        'rz_epa': overall_epa,
        'rz_pass_epa': pass_epa,
        'rz_rush_epa': rush_epa,
        'rz_success_rate': success_rate,
        'rz_pass_rate': len(pass_plays) / len(rz_plays) if len(rz_plays) > 0 else 0
    }

Key Insight: Red zone EPA tends to be lower than overall EPA because: 1. Field compression limits big plays 2. Defenses tighten coverage 3. Each yard is more valuable (steeper EP curve)


Third Down Analysis

The Money Down

Third down is often called the "money down" because it determines whether drives continue or stall. Third down conversion rate is one of the most discussed metrics in football.

def calculate_third_down_efficiency(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate third down conversion metrics."""

    third_downs = pbp[
        (pbp['posteam'] == team) &
        (pbp['down'] == 3) &
        (pbp['play_type'].isin(['pass', 'run']))
    ]

    # Overall conversion rate
    conversions = (
        (third_downs['first_down'] == 1) |
        (third_downs['touchdown'] == 1)
    ).sum()

    conversion_rate = conversions / len(third_downs) if len(third_downs) > 0 else 0

    # By distance
    short = third_downs[third_downs['ydstogo'] <= 3]
    medium = third_downs[(third_downs['ydstogo'] > 3) & (third_downs['ydstogo'] <= 6)]
    long = third_downs[third_downs['ydstogo'] > 6]

    def conv_rate(df):
        if len(df) == 0:
            return 0
        return ((df['first_down'] == 1) | (df['touchdown'] == 1)).sum() / len(df)

    return {
        'total_third_downs': len(third_downs),
        'conversions': conversions,
        'conversion_rate': conversion_rate,
        'short_rate': conv_rate(short),  # 1-3 yards
        'medium_rate': conv_rate(medium),  # 4-6 yards
        'long_rate': conv_rate(long),  # 7+ yards
        'third_down_epa': third_downs['epa'].mean()
    }

kc_3rd = calculate_third_down_efficiency(pbp, 'KC')
print("\nKC Third Down Efficiency:")
print(f"  Overall: {kc_3rd['conversion_rate']:.1%}")
print(f"  3rd & Short (1-3): {kc_3rd['short_rate']:.1%}")
print(f"  3rd & Medium (4-6): {kc_3rd['medium_rate']:.1%}")
print(f"  3rd & Long (7+): {kc_3rd['long_rate']:.1%}")

Third Down Benchmarks

Distance League Average Good Elite
Overall 38-42% 43-47% 48%+
Short (1-3) 62-68% 70-75% 76%+
Medium (4-6) 40-45% 48-52% 53%+
Long (7+) 25-30% 32-37% 38%+

Third Down EPA Context

def analyze_third_down_context(pbp: pd.DataFrame, team: str) -> pd.DataFrame:
    """Detailed third down analysis by situation."""

    third_downs = pbp[
        (pbp['posteam'] == team) &
        (pbp['down'] == 3) &
        (pbp['play_type'].isin(['pass', 'run'])) &
        (pbp['epa'].notna())
    ]

    # Create distance buckets
    def distance_bucket(ytg):
        if ytg <= 2:
            return "1-2"
        elif ytg <= 4:
            return "3-4"
        elif ytg <= 6:
            return "5-6"
        elif ytg <= 9:
            return "7-9"
        else:
            return "10+"

    third_downs['distance_bucket'] = third_downs['ydstogo'].apply(distance_bucket)

    result = third_downs.groupby('distance_bucket').agg(
        attempts=('epa', 'count'),
        conversion_rate=('first_down', lambda x: ((x == 1) | (third_downs.loc[x.index, 'touchdown'] == 1)).mean()),
        epa=('epa', 'mean'),
        pass_rate=('play_type', lambda x: (x == 'pass').mean())
    ).reset_index()

    return result

third_analysis = analyze_third_down_context(pbp, 'KC')
print("\nKC Third Down by Distance:")
print(third_analysis.to_string(index=False))

Two-Minute Offense

Definition and Importance

The two-minute offense refers to drives at the end of halves (or games) where teams operate with urgency. These situations test: - Clock management skills - No-huddle execution - Decision-making under pressure - Sideline communication

def identify_two_minute_drives(pbp: pd.DataFrame) -> pd.DataFrame:
    """Identify two-minute drill situations."""

    two_min_plays = pbp[
        # End of half/game
        ((pbp['qtr'] == 2) & (pbp['game_seconds_remaining'] <= 120 + 1800)) |  # End of 1st half
        ((pbp['qtr'] == 4) & (pbp['game_seconds_remaining'] <= 120))  # End of game
    ].copy()

    # Filter to meaningful situations (not garbage time)
    two_min_plays = two_min_plays[
        (two_min_plays['score_differential'].abs() <= 16) &
        (two_min_plays['play_type'].isin(['pass', 'run', 'field_goal']))
    ]

    return two_min_plays

two_min = identify_two_minute_drives(pbp)
print(f"Two-minute plays identified: {len(two_min):,}")

Two-Minute Efficiency Metrics

def calculate_two_minute_efficiency(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate two-minute drill effectiveness."""

    two_min = identify_two_minute_drives(pbp)
    team_two_min = two_min[two_min['posteam'] == team]

    if len(team_two_min) == 0:
        return {'no_data': True}

    # Plays
    plays = team_two_min[team_two_min['play_type'].isin(['pass', 'run'])]

    # Scoring
    drives = team_two_min.groupby(['game_id', 'drive']).agg(
        scored_td=('touchdown', 'max'),
        scored_fg=('field_goal_result', lambda x: (x == 'made').any()),
        plays=('play_type', 'count')
    ).reset_index()

    scoring_drives = ((drives['scored_td'] == 1) | (drives['scored_fg'] == True)).sum()
    total_drives = len(drives)

    return {
        'two_min_drives': total_drives,
        'scoring_drives': scoring_drives,
        'scoring_rate': scoring_drives / total_drives if total_drives > 0 else 0,
        'epa_per_play': plays['epa'].mean() if len(plays) > 0 else 0,
        'pass_rate': (plays['play_type'] == 'pass').mean() if len(plays) > 0 else 0,
        'success_rate': (plays['epa'] > 0).mean() if len(plays) > 0 else 0
    }

kc_2min = calculate_two_minute_efficiency(pbp, 'KC')
print("\nKC Two-Minute Drill Efficiency:")
for key, value in kc_2min.items():
    if isinstance(value, float):
        print(f"  {key}: {value:.3f}")
    else:
        print(f"  {key}: {value}")

Two-Minute Benchmarks

Metric League Average Good Elite
Scoring Rate 35-40% 45-50% 55%+
EPA/Play 0.05-0.10 0.12-0.18 0.20+
Success Rate 45-48% 50-55% 56%+

Goal-to-Go Situations

Inside the 10

Goal-to-go situations (less than 10 yards from the end zone) represent the highest-value plays:

def calculate_goal_to_go_efficiency(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate goal-to-go efficiency."""

    goal_to_go = pbp[
        (pbp['posteam'] == team) &
        (pbp['yardline_100'] <= 10) &
        (pbp['goal_to_go'] == 1) &
        (pbp['play_type'].isin(['pass', 'run']))
    ]

    if len(goal_to_go) == 0:
        return {'no_data': True}

    td_rate = (goal_to_go['touchdown'] == 1).mean()
    epa = goal_to_go['epa'].mean()
    pass_rate = (goal_to_go['play_type'] == 'pass').mean()

    # By distance
    inside_5 = goal_to_go[goal_to_go['yardline_100'] <= 5]
    inside_5_td = (inside_5['touchdown'] == 1).mean() if len(inside_5) > 0 else 0

    return {
        'goal_to_go_plays': len(goal_to_go),
        'td_rate': td_rate,
        'epa': epa,
        'pass_rate': pass_rate,
        'inside_5_td_rate': inside_5_td
    }

Goal Line (1-2 Yards)

The true goal line is the ultimate short-yardage test:

def calculate_goal_line_efficiency(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate efficiency from the 1-2 yard line."""

    goal_line = pbp[
        (pbp['posteam'] == team) &
        (pbp['yardline_100'] <= 2) &
        (pbp['play_type'].isin(['pass', 'run']))
    ]

    if len(goal_line) == 0:
        return {'no_data': True}

    td_rate = (goal_line['touchdown'] == 1).mean()
    rush_plays = goal_line[goal_line['play_type'] == 'run']
    pass_plays = goal_line[goal_line['play_type'] == 'pass']

    rush_td = (rush_plays['touchdown'] == 1).mean() if len(rush_plays) > 0 else 0
    pass_td = (pass_plays['touchdown'] == 1).mean() if len(pass_plays) > 0 else 0

    return {
        'goal_line_plays': len(goal_line),
        'overall_td_rate': td_rate,
        'rush_td_rate': rush_td,
        'pass_td_rate': pass_td,
        'pass_rate': len(pass_plays) / len(goal_line) if len(goal_line) > 0 else 0
    }

Late and Close Games

Defining High-Leverage Situations

Games are most influenced by plays in close, late situations:

def identify_late_close_plays(pbp: pd.DataFrame) -> pd.DataFrame:
    """
    Identify plays in late and close game situations.

    Definition: 4th quarter (or OT), score within 8 points.
    """
    late_close = pbp[
        (pbp['qtr'] >= 4) &
        (pbp['score_differential'].abs() <= 8) &
        (pbp['play_type'].isin(['pass', 'run'])) &
        (pbp['epa'].notna())
    ].copy()

    return late_close

late_close = identify_late_close_plays(pbp)
print(f"Late & close plays: {len(late_close):,}")
print(f"Percentage of all plays: {len(late_close)/len(pbp)*100:.1f}%")

Late and Close Efficiency

def calculate_late_close_efficiency(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate efficiency in late and close situations."""

    late_close = identify_late_close_plays(pbp)
    team_plays = late_close[late_close['posteam'] == team]

    if len(team_plays) == 0:
        return {'no_data': True}

    # Compare to overall performance
    all_plays = pbp[
        (pbp['posteam'] == team) &
        (pbp['play_type'].isin(['pass', 'run'])) &
        (pbp['epa'].notna())
    ]

    overall_epa = all_plays['epa'].mean()
    late_close_epa = team_plays['epa'].mean()

    overall_success = (all_plays['epa'] > 0).mean()
    late_close_success = (team_plays['epa'] > 0).mean()

    return {
        'late_close_plays': len(team_plays),
        'late_close_epa': late_close_epa,
        'overall_epa': overall_epa,
        'epa_difference': late_close_epa - overall_epa,
        'late_close_success': late_close_success,
        'overall_success': overall_success,
        'success_difference': late_close_success - overall_success
    }

kc_late = calculate_late_close_efficiency(pbp, 'KC')
print("\nKC Late & Close Performance:")
print(f"  Late/Close EPA: {kc_late['late_close_epa']:.3f}")
print(f"  Overall EPA: {kc_late['overall_epa']:.3f}")
print(f"  Difference: {kc_late['epa_difference']:+.3f}")

Win Probability-Weighted Performance

For a more sophisticated view, weight plays by leverage:

def calculate_leverage_weighted_epa(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate leverage-weighted EPA."""

    team_plays = pbp[
        (pbp['posteam'] == team) &
        (pbp['play_type'].isin(['pass', 'run'])) &
        (pbp['epa'].notna()) &
        (pbp['wp'].notna())
    ].copy()

    # Leverage = how much WP can change
    # Highest when WP is near 50%
    team_plays['leverage'] = 4 * team_plays['wp'] * (1 - team_plays['wp'])

    # Weighted EPA
    weighted_epa = (team_plays['epa'] * team_plays['leverage']).sum() / team_plays['leverage'].sum()
    unweighted_epa = team_plays['epa'].mean()

    return {
        'unweighted_epa': unweighted_epa,
        'leverage_weighted_epa': weighted_epa,
        'difference': weighted_epa - unweighted_epa
    }

Defensive Situational Performance

Red Zone Defense

def calculate_red_zone_defense(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate defensive red zone efficiency."""

    rz_defense = pbp[
        (pbp['defteam'] == team) &
        (pbp['yardline_100'] <= 20) &
        (pbp['play_type'].isin(['pass', 'run']))
    ]

    # Drives defended
    drives = pbp[
        (pbp['defteam'] == team) &
        (pbp['yardline_100'] <= 20)
    ].groupby(['game_id', 'drive']).agg(
        allowed_td=('touchdown', 'max')
    ).reset_index()

    td_allowed = drives['allowed_td'].sum()
    total_trips = len(drives)

    return {
        'rz_trips_allowed': total_trips,
        'tds_allowed': td_allowed,
        'td_rate_allowed': td_allowed / total_trips if total_trips > 0 else 0,
        'rz_def_epa': rz_defense['epa'].mean() if len(rz_defense) > 0 else 0
    }

Third Down Defense

def calculate_third_down_defense(pbp: pd.DataFrame, team: str) -> dict:
    """Calculate third down defensive efficiency."""

    third_defense = pbp[
        (pbp['defteam'] == team) &
        (pbp['down'] == 3) &
        (pbp['play_type'].isin(['pass', 'run']))
    ]

    conversions_allowed = (
        (third_defense['first_down'] == 1) |
        (third_defense['touchdown'] == 1)
    ).sum()

    conversion_rate_allowed = conversions_allowed / len(third_defense) if len(third_defense) > 0 else 0

    return {
        'third_downs_faced': len(third_defense),
        'conversions_allowed': conversions_allowed,
        'conversion_rate_allowed': conversion_rate_allowed,
        'third_down_def_epa': third_defense['epa'].mean() if len(third_defense) > 0 else 0
    }

Does "Clutch" Exist?

The Statistical Debate

A persistent question in sports analytics: Is "clutch" performance a repeatable skill?

def analyze_clutch_consistency(pbp_multiple_years: dict, team: str) -> dict:
    """
    Analyze whether clutch performance is consistent across seasons.

    Args:
        pbp_multiple_years: Dict of {year: pbp_dataframe}
        team: Team to analyze

    Returns:
        Year-over-year clutch performance correlation
    """
    yearly_clutch = []

    for year, pbp in pbp_multiple_years.items():
        late_close = identify_late_close_plays(pbp)
        team_plays = late_close[late_close['posteam'] == team]

        if len(team_plays) >= 50:  # Minimum sample
            clutch_epa = team_plays['epa'].mean()

            all_plays = pbp[
                (pbp['posteam'] == team) &
                (pbp['play_type'].isin(['pass', 'run'])) &
                (pbp['epa'].notna())
            ]
            overall_epa = all_plays['epa'].mean()

            yearly_clutch.append({
                'year': year,
                'clutch_epa': clutch_epa,
                'overall_epa': overall_epa,
                'clutch_diff': clutch_epa - overall_epa
            })

    if len(yearly_clutch) < 3:
        return {'insufficient_data': True}

    df = pd.DataFrame(yearly_clutch)

    # Year-to-year correlation of clutch differential
    df['prev_clutch_diff'] = df['clutch_diff'].shift(1)
    correlation = df['clutch_diff'].corr(df['prev_clutch_diff'])

    return {
        'yearly_data': df.to_dict('records'),
        'yoy_correlation': correlation,
        'interpretation': 'Clutch is repeatable' if correlation > 0.3 else 'Clutch appears random'
    }

Research Finding: Year-to-year correlation of "clutch" performance is typically very low (r ≈ 0.05-0.15), suggesting clutch performance is largely random variance rather than a stable skill.


Building a Situational Analyzer

from dataclasses import dataclass

@dataclass
class SituationalReport:
    """Comprehensive situational performance report."""

    team: str
    season: int

    # Red Zone
    rz_trips: int
    rz_td_rate: float
    rz_points_per_trip: float
    rz_epa: float

    # Third Down
    third_down_rate: float
    third_short_rate: float
    third_long_rate: float
    third_down_epa: float

    # Two Minute
    two_min_scoring_rate: float
    two_min_epa: float

    # Late & Close
    late_close_epa: float
    late_close_vs_overall: float

    # Defense
    rz_def_td_rate: float
    third_down_def_rate: float


class SituationalAnalyzer:
    """Comprehensive situational football analyzer."""

    def __init__(self, pbp: pd.DataFrame, season: int = 2023):
        self.pbp = pbp
        self.season = season
        self.plays = pbp[
            (pbp['play_type'].isin(['pass', 'run'])) &
            (pbp['epa'].notna())
        ].copy()

    def analyze_team(self, team: str) -> SituationalReport:
        """Generate complete situational report for a team."""

        # Red zone offense
        rz = calculate_red_zone_efficiency(self.pbp, team)
        rz_epa = calculate_red_zone_epa(self.pbp, team)

        # Third down
        third = calculate_third_down_efficiency(self.pbp, team)

        # Two minute
        two_min = calculate_two_minute_efficiency(self.pbp, team)

        # Late and close
        late_close = calculate_late_close_efficiency(self.pbp, team)

        # Defense
        rz_def = calculate_red_zone_defense(self.pbp, team)
        third_def = calculate_third_down_defense(self.pbp, team)

        return SituationalReport(
            team=team,
            season=self.season,
            rz_trips=rz.get('red_zone_trips', 0),
            rz_td_rate=rz.get('td_rate', 0),
            rz_points_per_trip=rz.get('points_per_trip', 0),
            rz_epa=rz_epa.get('rz_epa', 0),
            third_down_rate=third.get('conversion_rate', 0),
            third_short_rate=third.get('short_rate', 0),
            third_long_rate=third.get('long_rate', 0),
            third_down_epa=third.get('third_down_epa', 0),
            two_min_scoring_rate=two_min.get('scoring_rate', 0) if not two_min.get('no_data') else 0,
            two_min_epa=two_min.get('epa_per_play', 0) if not two_min.get('no_data') else 0,
            late_close_epa=late_close.get('late_close_epa', 0) if not late_close.get('no_data') else 0,
            late_close_vs_overall=late_close.get('epa_difference', 0) if not late_close.get('no_data') else 0,
            rz_def_td_rate=rz_def.get('td_rate_allowed', 0),
            third_down_def_rate=third_def.get('conversion_rate_allowed', 0)
        )

    def rank_all_teams(self, metric: str = 'rz_td_rate') -> pd.DataFrame:
        """Rank teams by a situational metric."""

        results = []
        for team in self.plays['posteam'].unique():
            report = self.analyze_team(team)
            results.append({
                'team': team,
                'rz_td_rate': report.rz_td_rate,
                'third_down_rate': report.third_down_rate,
                'two_min_scoring_rate': report.two_min_scoring_rate,
                'late_close_epa': report.late_close_epa
            })

        df = pd.DataFrame(results)
        return df.sort_values(metric, ascending=False)

Key Takeaways

Situational Performance Matters

  1. Red zone efficiency - converting opportunities to touchdowns
  2. Third down conversion - sustaining drives
  3. Two-minute execution - capitalizing on late-half opportunities
  4. Late & close performance - winning when it matters most

Benchmarks to Remember

  • Elite RZ TD rate: 65%+
  • Elite 3rd down rate: 48%+
  • Elite two-minute scoring: 55%+

Analytical Caveats

  1. Sample sizes - situational stats have fewer plays
  2. Context - opponent quality matters
  3. Clutch - largely not a repeatable skill
  4. Regression - extreme situational performance often regresses

Practice Exercises

  1. Calculate red zone efficiency for all teams and identify the top 5
  2. Analyze third down conversion rates by pass vs rush
  3. Identify teams that perform significantly better (or worse) in late/close situations
  4. Compare offensive vs defensive situational performance
  5. Test whether "clutch" performance persists year-to-year

Summary

Situational football analysis reveals how teams perform when stakes are highest. Key metrics include:

  • Red zone TD rate for scoring efficiency
  • Third down conversion for drive sustainability
  • Two-minute performance for late-half execution
  • Late and close EPA for clutch performance

While these metrics are valuable, analysts should account for sample size limitations and the largely random nature of "clutch" performance. Situational excellence often reflects overall team quality more than unique situational skill.


Preview: Chapter 15

Next, we'll explore Home Field Advantage - quantifying the value of playing at home and what factors drive it.