Learning ObjectivesBy the end of this chapter, you will be able to:

Completion Percentage by Air Yards

See how completion percentage drops as passes are thrown further downfield.

  1. Master quarterback evaluation metrics beyond traditional statistics
  2. Understand and calculate Completion Percentage Over Expected (CPOE)
  3. Analyze receiver performance including separation and target quality
  4. Evaluate pass protection and pressure impact on passing efficiency
  5. Study air yards and yards after catch to understand offensive design
  6. Apply advanced passing metrics for player comparison and evaluation
  7. Interpret situational passing efficiency in clutch moments
  8. Combine multiple metrics to create comprehensive player evaluations

Introduction

The passing game has become the centerpiece of modern NFL offense, representing one of the most dramatic strategic shifts in football history. In 2023, teams attempted passes on approximately 60% of their offensive plays—a remarkable transformation from just two decades ago when run-pass ratios were nearly equal. This shift isn't merely stylistic preference or changing fashion in offensive philosophy; it's a data-driven response to a fundamental truth revealed through advanced analytics: passing is significantly more efficient than running on a per-play basis.

Yet despite the passing game's centrality to modern football, traditional quarterback statistics—completion percentage, yards per attempt, and passer rating—often fail to capture the full picture of passing efficiency. These conventional metrics, many developed in the 1970s, treat all completions equally regardless of situation, fail to account for throw difficulty, and struggle to properly assign credit between quarterbacks and receivers. A quarterback can post impressive traditional statistics by completing high-percentage short throws to talented receivers who generate yards after catch, while a quarterback attempting more difficult downfield throws against aggressive defenses might appear worse despite superior accuracy and decision-making.

This chapter explores the advanced analytics that have revolutionized how we evaluate quarterbacks, receivers, and passing offenses. We'll move beyond box score statistics to understand completion probability models that measure accuracy relative to throw difficulty, the crucial distinction between air yards (quarterback responsibility) and yards after catch (receiver responsibility), the dramatic impact of defensive pressure on passing performance, and sophisticated methods for properly crediting performance between quarterbacks and their receivers. These modern metrics don't just provide more accurate evaluations—they reveal strategic insights about offensive design, route concepts, situational efficiency, and the complex interactions between quarterbacks, receivers, offensive lines, and opposing defenses.

The stakes for accurate passing evaluation are enormous. NFL teams invest hundreds of millions of dollars in quarterback contracts, make critical draft decisions based on passing metrics, and design entire offensive systems around quarterback capabilities. The difference between correctly identifying an elite quarterback and misjudging one based on incomplete metrics can determine franchises' competitive trajectories for years. Similarly, properly evaluating receiver performance independent of quarterback quality enables teams to identify undervalued players and make optimal personnel decisions.

Why Passing Analytics Matter

Traditional quarterback stats like passer rating were developed in the 1970s using arbitrary weights and thresholds that reflected that era's passing environment. A "perfect" passer rating of 158.3 requires completing 77.5% of passes for 12.5 yards per attempt with 11.875% touchdowns and zero interceptions—thresholds that seemed nearly impossible in 1973 but are routinely approached or exceeded in individual games today. Modern passing analytics use play-by-play data and expected value frameworks to provide more accurate assessments of quarterback and receiver performance. These approaches account for critical context like down and distance, field position, throw difficulty, defensive pressure, and receiver contribution. The result is a dramatically more sophisticated understanding of passing performance that better predicts future success and more accurately values player contributions.

Traditional QB Statistics and Their Limitations

Before diving into advanced metrics, we need to understand what we're improving upon. For decades, quarterback performance has been evaluated using a standard set of statistics that appear in every box score, drive broadcast narratives, and influence major personnel decisions. These conventional metrics have the advantage of simplicity and long historical data, but they suffer from fundamental limitations that make them inadequate for modern evaluation.

The Conventional Metrics

The traditional quarterback evaluation toolkit consists of several basic counting statistics and rate metrics that summarize passing performance:

Completion Percentage: The most basic passing metric measures the percentage of pass attempts that result in completions:

$$ \text{Completion \%} = \frac{\text{Completions}}{\text{Attempts}} \times 100 $$

This metric appears simple and intuitive—higher completion percentage suggests better accuracy. However, completion percentage is highly context-dependent. Quarterbacks in West Coast offenses designed around high-percentage short passes naturally post higher completion rates than quarterbacks in vertical passing attacks featuring more difficult downfield throws. A quarterback completing 70% of passes on screens and five-yard slants is not necessarily more accurate than a quarterback completing 60% of passes that average fifteen air yards. Without accounting for throw difficulty, completion percentage provides incomplete information.

Yards Per Attempt (YPA): This metric measures the average yards gained per pass attempt:

$$ \text{YPA} = \frac{\text{Passing Yards}}{\text{Attempts}} $$

Yards per attempt provides better information than completion percentage alone because it captures both completion rate and depth of completions. However, YPA suffers from a critical flaw: it cannot distinguish between air yards (quarterback responsibility) and yards after catch (receiver responsibility). A quarterback who completes a two-yard screen pass that the receiver takes seventy yards for a touchdown receives the same seventy-two yard credit as a quarterback who throws a perfect seventy-two yard bomb. These plays demonstrate entirely different skills, yet YPA treats them identically.

Touchdown-to-Interception Ratio (TD:INT): This ratio compares touchdown passes to interceptions, providing a simple measure of positive versus negative outcomes. While intuitively appealing, this metric suffers from small sample problems (a single-game ratio of 3:0 looks dramatically different from 2:1, yet the difference might be random variance) and fails to account for context. Throwing three touchdowns from inside the five-yard line is far less impressive than throwing three touchdowns on deep shots from midfield, but the TD:INT ratio treats them identically.

Passer Rating: The most complex traditional metric, passer rating attempts to combine multiple aspects of passing performance into a single composite score. Introduced by the NFL in 1973, passer rating uses a formula involving four components:

$$ \text{Passer Rating} = \frac{(a + b + c + d)}{6} \times 100 $$

Where:
- $a$ = Completion percentage component: $\text{min}(2.375, \text{max}(0, 5 \times (\frac{\text{COMP}}{\text{ATT}} - 0.3)))$
- $b$ = Yards per attempt component: $\text{min}(2.375, \text{max}(0, 0.25 \times (\frac{\text{YDS}}{\text{ATT}} - 3)))$
- $c$ = Touchdown percentage component: $\text{min}(2.375, \text{max}(0, 20 \times \frac{\text{TD}}{\text{ATT}}))$
- $d$ = Interception percentage component: $\text{min}(2.375, \text{max}(0, 2.375 - 25 \times \frac{\text{INT}}{\text{ATT}}))$

Each component is capped at 2.375, meaning exceptional performance in one area cannot compensate for weakness in another beyond certain thresholds. The formula produces a scale from 0 to 158.3, with values above 100 considered above average.

The Problems with Traditional Stats

These conventional metrics, despite their ubiquity and historical value, suffer from several critical limitations that make them inadequate for modern quarterback evaluation:

1. No Contextual Awareness: Traditional statistics don't account for down, distance, or field position. A five-yard completion on 3rd-and-15 that forces a punt receives the same credit as a five-yard completion on 3rd-and-2 that converts for a first down and extends a scoring drive. These plays have vastly different values—the former is effectively a failure while the latter is a success—yet traditional metrics treat them identically. This contextual blindness means traditional statistics systematically misvalue plays based on situation.

2. Arbitrary Weights and Thresholds: Passer rating's formula uses fixed weights from 1970s NFL averages that no longer reflect modern passing games. In 1973, when the formula was developed, the league average completion percentage was 52.9%, yards per attempt was 6.7, and touchdown percentage was 4.2%. By 2023, these values had increased to approximately 64%, 7.2 YPA, and 4.8% respectively. The thresholds in the passer rating formula, designed to make league average performance score 66.7, no longer serve this purpose. Moreover, the weights are arbitrary—there's no theoretical or empirical justification for why the completion percentage component should cap at 2.375 or why interception percentage should be weighted with a 25x multiplier.

3. Credit Assignment Problems: Traditional statistics cannot distinguish between quarterback contribution and receiver contribution. Consider two passes: (1) a perfectly thrown 40-yard seam route where the receiver catches the ball in stride and is immediately tackled, and (2) a poorly thrown screen pass that the receiver catches near the line of scrimmage and takes 40 yards through broken tackles. Both plays yield 40 passing yards, identical YPA contributions, but they demonstrate entirely different quarterback skills. The first showcases arm strength, accuracy, and anticipation; the second benefits from receiver ability to create yards after catch. Traditional metrics give equal credit to the quarterback for both.

4. Situational Blindness: Traditional statistics treat all game situations equally. Garbage time statistics—yards and touchdowns accumulated when trailing significantly late in games against prevent defenses—count identically to crucial drives in competitive situations. A quarterback who pads statistics in meaningless situations can post traditional metrics that misrepresent their performance in high-leverage moments. Similarly, traditional stats don't capture how performance varies by down, field position, or opponent quality.

5. Play Type Ignorance: Deep passes and short passes are weighted only by their outcomes, not their difficulty. Completing 60% of deep passes (exceptionally difficult) receives less credit than completing 65% of short passes (routine) when evaluating completion percentage, despite the former representing superior performance relative to difficulty. Traditional metrics systematically undervalue quarterbacks who attempt more difficult throw distributions.

The Passer Rating Paradox

Passer rating's design reveals its limitations through mathematical quirks. A quarterback can achieve a perfect 158.3 rating by completing 77.5% of passes for 12.5 yards per attempt with 11.875% touchdowns and zero interceptions. These specific thresholds seem precise but are actually arbitrary—they represent the points where the formula's components hit their maximum caps. More problematically, these thresholds are routinely exceeded in modern games. In 2023, multiple quarterbacks posted single-game performances exceeding these marks, meaning the rating scale fails to discriminate among elite performances. When Josh Allen completes 85% of passes for 350 yards and 4 TDs with no interceptions, the rating formula cannot fully capture how exceptional this performance is because he's bumping against multiple component caps. Furthermore, passer rating weights components in counterintuitive ways. The interception component has a 25x multiplier while the touchdown component uses 20x, meaning one interception hurts more than one touchdown helps. While turnovers are costly, this weighting doesn't necessarily reflect actual football value—many analytics studies suggest the touchdown-interception weights in passer rating overvalue interception avoidance relative to touchdown generation.

Let's examine real data to see these limitations in action. We'll calculate traditional statistics for 2023 quarterbacks and see how they compare to more sophisticated metrics we'll introduce later:

#| label: traditional-stats-r
#| message: false
#| warning: false
#| cache: true

# Load required libraries for data manipulation and visualization
# tidyverse: comprehensive data manipulation and visualization toolkit
# nflfastR: provides NFL play-by-play data with advanced metrics
# nflplotR: adds NFL team logos and branding to visualizations
# gt: creates publication-quality formatted tables
library(tidyverse)
library(nflfastR)
library(nflplotR)
library(gt)

# Load play-by-play data for 2023 season
# This function downloads and caches comprehensive play-by-play data
# Each row represents one play with 372 variables covering all aspects
pbp_2023 <- load_pbp(2023)

# Calculate traditional quarterback statistics
# These are the "conventional" metrics used in broadcasts and box scores
traditional_qb_stats <- pbp_2023 %>%
  # Filter to regular season plays with identified passers
  # Exclude playoffs (season_type == "POST") to focus on regular season
  # !is.na(passer_id) ensures we have a valid passer identified
  filter(season_type == "REG", !is.na(passer_id)) %>%

  # Group by quarterback to calculate per-QB statistics
  # passer_player_name gives us readable names
  # posteam identifies which team the QB was playing for
  group_by(passer = passer_player_name, team = posteam) %>%

  # Calculate traditional statistics for each QB
  summarise(
    # Counting statistics
    attempts = n(),  # Total pass attempts
    completions = sum(complete_pass, na.rm = TRUE),  # Completed passes

    # Completion percentage: % of attempts that were completed
    # complete_pass is 1 for completions, 0/NA otherwise
    comp_pct = mean(complete_pass, na.rm = TRUE) * 100,

    # Yards gained on all pass attempts
    # yards_gained can be negative (sacks), zero (incompletions), or positive
    pass_yards = sum(yards_gained, na.rm = TRUE),

    # Yards per attempt: average yards per pass play
    ypa = pass_yards / attempts,

    # Touchdowns: passes that resulted in TDs
    # Both touchdown and complete_pass must be 1
    pass_tds = sum(touchdown == 1 & complete_pass == 1, na.rm = TRUE),

    # Interceptions thrown
    interceptions = sum(interception, na.rm = TRUE),

    # TD:INT ratio (use pmax to avoid division by zero)
    # If no interceptions, we use 1 in denominator to avoid infinity
    td_int_ratio = pass_tds / pmax(interceptions, 1),

    .groups = "drop"  # Remove grouping after calculation
  ) %>%

  # Filter to QBs with meaningful sample size
  # 200 attempts ≈ 12-13 games as primary starter
  filter(attempts >= 200) %>%

  # Sort by yards per attempt (traditional efficiency metric)
  arrange(desc(ypa))

# Display top 10 QBs by yards per attempt in formatted table
traditional_qb_stats %>%
  head(10) %>%
  gt() %>%

  # Set readable column labels
  cols_label(
    passer = "Quarterback",
    team = "Team",
    attempts = "Att",
    completions = "Comp",
    comp_pct = "Comp%",
    pass_yards = "Yards",
    ypa = "YPA",
    pass_tds = "TD",
    interceptions = "INT",
    td_int_ratio = "TD:INT"
  ) %>%

  # Format numbers for readability
  # Percentages and rates get 1 decimal place
  fmt_number(columns = c(comp_pct, ypa, td_int_ratio), decimals = 1) %>%

  # Counting stats get no decimals
  fmt_number(columns = c(attempts, completions, pass_yards, pass_tds, interceptions),
             decimals = 0) %>%

  # Add descriptive title and subtitle
  tab_header(
    title = "Traditional QB Statistics - 2023",
    subtitle = "Top 10 by Yards Per Attempt | Minimum 200 attempts"
  )
#| label: traditional-stats-py
#| message: false
#| warning: false
#| cache: true

# Import required libraries for data analysis
import pandas as pd  # Data manipulation and analysis
import numpy as np   # Numerical computing operations
import nfl_data_py as nfl  # NFL data access (Python equivalent of nflfastR)

# Load play-by-play data for 2023 season
# import_pbp_data takes a list of seasons and returns a DataFrame
# Each row is one play with comprehensive information
pbp_2023 = nfl.import_pbp_data([2023])

# Calculate traditional quarterback statistics using method chaining
traditional_qb = (pbp_2023
    # Filter to regular season plays with valid passer IDs
    # .query() provides SQL-like filtering with readable syntax
    # passer_id.notna() excludes plays without identified passers
    .query("season_type == 'REG' & passer_id.notna()")

    # Group by QB name and team to calculate per-QB stats
    .groupby(['passer_player_name', 'posteam'])

    # Aggregate statistics for each QB
    # .agg() allows multiple aggregations on different columns
    .agg(
        # Count total pass attempts
        attempts=('complete_pass', 'count'),

        # Sum completed passes (complete_pass is 1 for completions)
        completions=('complete_pass', 'sum'),

        # Sum total passing yards
        pass_yards=('yards_gained', 'sum'),

        # Count touchdowns (need complex lambda for compound condition)
        # This counts plays where both touchdown==1 AND complete_pass==1
        pass_tds=('touchdown', lambda x: ((pbp_2023.loc[x.index, 'complete_pass'] == 1) & (x == 1)).sum()),

        # Sum interceptions
        interceptions=('interception', 'sum')
    )

    # Convert group keys back to regular columns
    .reset_index()
)

# Calculate derived metrics (can't easily do in .agg())
# Completion percentage: completions / attempts * 100
traditional_qb['comp_pct'] = (traditional_qb['completions'] / traditional_qb['attempts'] * 100)

# Yards per attempt: total yards / attempts
traditional_qb['ypa'] = traditional_qb['pass_yards'] / traditional_qb['attempts']

# TD:INT ratio: use replace(0, 1) to avoid division by zero
# If interceptions=0, replace with 1 to avoid infinity
traditional_qb['td_int_ratio'] = traditional_qb['pass_tds'] / traditional_qb['interceptions'].replace(0, 1)

# Filter to QBs with meaningful sample size and sort by YPA
traditional_qb = (traditional_qb
    .query("attempts >= 200")  # Minimum 200 attempts
    .sort_values('ypa', ascending=False)  # Sort by yards per attempt
    .head(10)  # Keep top 10 only
)

# Display results in formatted table
print("\nTraditional QB Statistics - 2023")
print("Top 10 by Yards Per Attempt | Minimum 200 attempts\n")
print(traditional_qb.to_string(index=False))
**Understanding the Code Structure**: Both R and Python code follow the same logical flow: 1. **Load data**: Access comprehensive play-by-play data for the full 2023 season 2. **Filter**: Isolate relevant plays (regular season, valid passer identification) 3. **Group**: Organize data by quarterback to calculate per-player statistics 4. **Aggregate**: Compute summary statistics for each quarterback 5. **Calculate derived metrics**: Compute rates (completion %, yards per attempt, TD:INT ratio) 6. **Filter and sort**: Keep only QBs with sufficient attempts, order by performance **Key Technical Concepts**: **Filtering for Regular Season**: We use `season_type == "REG"` to exclude playoff games. Playoff performance is often analyzed separately because game situations, opponent quality, and strategic approaches differ systematically from regular season play. Including both would conflate different competitive contexts. **Handling Missing Values**: The `na.rm = TRUE` (R) argument and careful use of `.notna()` (Python) ensure missing values don't corrupt calculations. In play-by-play data, certain variables are legitimately missing for certain play types (e.g., no completion info for run plays), so proper missing value handling is essential. **Minimum Attempt Threshold**: The 200-attempt threshold ensures we analyze quarterbacks with meaningful sample sizes. With roughly 34-38 pass attempts per game, 200 attempts represents about 5-6 games as a full-time starter or 12-15 games as a part-time starter. This threshold excludes quarterbacks with too few attempts to reliably assess while retaining all primary and secondary starters. **Avoiding Division by Zero**: When calculating TD:INT ratios, we need to handle quarterbacks who threw zero interceptions. Rather than producing infinity or errors, we replace zero interceptions with one, which effectively makes the ratio equal to total touchdowns. This is a common convention—a QB with 25 TDs and 0 INTs would show a 25:1 ratio.

Interpreting Traditional Statistics:

When you examine the output from this analysis, you'll likely observe several patterns characteristic of traditional quarterback evaluation:

  1. Yards per attempt leaders often include quarterbacks in aggressive vertical passing attacks, but YPA doesn't distinguish between air yards (QB skill) and YAC (receiver skill).

  2. Completion percentage varies substantially based on offensive scheme. West Coast-style offenses featuring short, timing-based routes produce higher completion rates than vertical passing games, independent of quarterback accuracy on difficult throws.

  3. TD:INT ratios can be misleading for small samples. A quarterback with 18 TDs and 3 INTs (6:1 ratio) versus one with 20 TDs and 4 INTs (5:1 ratio) might appear different in this metric, but the difference might be noise rather than signal about quality.

  4. Volume statistics (total yards, total TDs) favor quarterbacks on teams that pass frequently, regardless of efficiency. A QB with 4,500 yards but 7.0 YPA contributed less per play than a QB with 3,200 yards and 8.5 YPA who simply attempted fewer passes.

These limitations motivate the development of more sophisticated metrics that we'll explore throughout the remainder of this chapter.

EPA Per Pass Attempt: The Foundation of Modern QB Evaluation

Traditional statistics measure what happened (completions, yards, touchdowns) without considering whether those outcomes were valuable given the situation. A 15-yard completion is counted identically whether it occurs on 1st-and-10 from your own 25 (decent gain) or 3rd-and-20 from your own 25 (effectively a failure that will lead to a punt). This situational blindness fundamentally limits traditional metrics' ability to measure quarterback performance.

Expected Points Added (EPA) solves this problem by measuring the change in expected points on every play, fully accounting for down, distance, field position, and game situation. This framework, introduced in Chapter 1 and examined in depth in Chapter 6, revolutionizes quarterback evaluation by asking not "how many yards did the quarterback gain?" but rather "how much did this play increase the team's probability of scoring?"

Understanding Passing EPA

For passing plays, EPA measures the difference between expected points before the play (based on down, distance, and field position) and expected points after the play (based on the new situation):

$$ \text{Passing EPA} = \text{EP}_{\text{after pass}} - \text{EP}_{\text{before snap}} $$

This simple formula encodes profound insight. Consider two passing plays:

Play A: 2nd-and-7 from your own 25 (EP ≈ 0.4). The QB completes a 15-yard pass, giving you 1st-and-10 from your own 40 (EP ≈ 1.5). EPA = 1.5 - 0.4 = +1.1 EPA. This is an excellent play—it converts the first down and meaningfully improves field position.

Play B: 3rd-and-20 from your own 25 (EP ≈ -0.3). The QB completes a 15-yard pass, giving you 4th-and-5 from your own 40 (EP ≈ -0.5). EPA = -0.5 - (-0.3) = -0.2 EPA. This is a negative play despite gaining 15 yards because it doesn't convert the first down, and the team will likely punt from a field position only marginally better than before.

Both plays gained 15 yards—traditional statistics treat them identically. But Play A was highly valuable while Play B was essentially a failure. EPA captures this difference that yards gained misses.

EPA accounts for multiple crucial factors that traditional stats ignore:

Down and Distance Context: Converting 3rd-and-2 is enormously valuable (prevents punt, extends drive). Gaining yards that don't convert 3rd-and-15 is less valuable even if yardage is equivalent.

Field Position Effects: A 10-yard gain from your 20 to your 30 (moving from poor field position to mediocre) is less valuable than a 10-yard gain from the opponent's 30 to their 20 (moving from field goal range to high-probability touchdown range).

Game Situation: EPA models incorporate time remaining, score differential, and timeouts, capturing how play value varies across game situations. A long completion when trailing late is more valuable than the same completion when leading comfortably.

Play Outcomes: EPA properly values completions, incompletions, interceptions, sacks, and touchdowns based on how they change game state. A touchdown from the 2-yard line adds about 2 expected points (from ~5.5 EP to 7 actual points). A touchdown from the 30-yard line adds about 3.5 expected points (from ~3.5 EP to 7 points). Traditional stats credit both identically as "1 touchdown."

Why EPA is Superior for QBs

Consider this concrete example of EPA's power: **QB Smith**: Completes 18 of 25 passes for 200 yards, 2 TDs, 0 INT. Traditional stats: 72% completion, 8.0 YPA, great TD:INT ratio. However, many completions were short throws on early downs that failed to move chains, and both TDs came from inside the 5-yard line on plays with high expected TD probability. **QB Jones**: Completes 15 of 25 passes for 200 yards, 2 TDs, 0 INT. Traditional stats: 60% completion, 8.0 YPA, same TD:INT ratio. However, multiple completions converted critical third downs, and both TDs came on long, high-difficulty throws in the red zone. Traditional stats suggest these QBs performed identically or favor Smith (higher completion rate). EPA analysis reveals Jones generated more value—his passes came in higher-leverage situations and generated more first downs and scoring opportunities. Where traditional stats see identical 200-yard, 2-TD performances, EPA captures the meaningful difference in situational efficiency.

Calculating QB EPA Metrics

Now let's calculate comprehensive EPA-based metrics for all quarterbacks in the 2023 season. We'll compute EPA per play (primary efficiency metric), total EPA (volume metric), and success rate (consistency metric):

#| label: qb-epa-r
#| message: false
#| warning: false
#| cache: true

# Calculate comprehensive EPA-based quarterback metrics
# EPA provides context-aware evaluation superior to traditional stats
qb_epa_stats <- pbp_2023 %>%
  filter(
    season_type == "REG",  # Regular season only
    !is.na(passer_id),     # Valid passer identified
    !is.na(epa)            # EPA calculated for this play
  ) %>%

  group_by(
    passer = passer_player_name,
    passer_id,  # Unique ID for joining with other datasets
    team = posteam
  ) %>%

  summarise(
    # Sample size: total pass attempts
    attempts = n(),

    # Primary EPA metric: average EPA per pass attempt
    # This is the gold standard for QB efficiency
    # Positive EPA = adding expected points on average
    # 0.1-0.2 EPA/play is average, >0.2 is excellent, >0.3 is elite
    epa_per_play = mean(epa),

    # Volume metric: total EPA contributed over the season
    # Accounts for both efficiency (EPA/play) and volume (attempts)
    # Elite QBs combine high EPA/play with high volume
    total_epa = sum(epa),

    # Success rate: % of plays with positive EPA
    # Measures consistency—how often does the QB produce value?
    # Average is ~43-45%, elite QBs exceed 48-50%
    success_rate = mean(epa > 0),

    # Include traditional stats for comparison
    completions = sum(complete_pass, na.rm = TRUE),
    pass_yards = sum(yards_gained, na.rm = TRUE),
    pass_tds = sum(touchdown == 1 & complete_pass == 1, na.rm = TRUE),
    interceptions = sum(interception, na.rm = TRUE),

    .groups = "drop"
  ) %>%

  # Filter to QBs with meaningful sample
  filter(attempts >= 200) %>%

  # Sort by EPA per play (efficiency metric)
  arrange(desc(epa_per_play))

# Display top 15 QBs in formatted, color-coded table
qb_epa_stats %>%
  head(15) %>%
  gt() %>%

  # Set readable column labels
  cols_label(
    passer = "Quarterback",
    team = "Team",
    attempts = "Att",
    epa_per_play = "EPA/Play",
    total_epa = "Total EPA",
    success_rate = "Success%",
    pass_tds = "TD",
    interceptions = "INT"
  ) %>%

  # Format numbers appropriately
  fmt_number(columns = c(epa_per_play, total_epa), decimals = 2) %>%
  fmt_percent(columns = success_rate, decimals = 1) %>%
  fmt_number(columns = c(attempts, pass_tds, interceptions), decimals = 0) %>%

  # Add descriptive header
  tab_header(
    title = "QB Performance by EPA - 2023 Regular Season",
    subtitle = "Minimum 200 attempts | Sorted by EPA per Pass Attempt"
  ) %>%

  # Add color gradient to EPA/play column for visual interpretation
  # Red = below average, Yellow = average, Green = excellent
  data_color(
    columns = epa_per_play,
    colors = scales::col_numeric(
      palette = c("#d73027", "#fee090", "#1a9850"),
      domain = c(-0.2, 0.4)
    )
  )
#| label: qb-epa-py
#| message: false
#| warning: false
#| cache: true

# Calculate comprehensive EPA-based quarterback metrics
qb_epa = (pbp_2023
    # Filter to regular season pass plays with valid EPA
    .query("season_type == 'REG' & passer_id.notna() & epa.notna()")

    # Group by quarterback
    .groupby(['passer_player_name', 'passer_id', 'posteam'])

    # Calculate EPA-based metrics
    .agg(
        # Sample size
        attempts=('epa', 'count'),

        # Primary efficiency metric: average EPA per attempt
        epa_per_play=('epa', 'mean'),

        # Volume metric: total EPA contributed
        total_epa=('epa', 'sum'),

        # Success rate: proportion with positive EPA
        # Lambda function: for each group, calculate mean of boolean (EPA > 0)
        success_rate=('epa', lambda x: (x > 0).mean()),

        # Traditional stats for comparison
        completions=('complete_pass', 'sum'),
        pass_yards=('yards_gained', 'sum'),

        # TD calculation requires compound condition
        pass_tds=('touchdown', lambda x: ((pbp_2023.loc[x.index, 'complete_pass'] == 1) & (x == 1)).sum()),

        interceptions=('interception', 'sum')
    )
    .reset_index()
    .query("attempts >= 200")  # Minimum sample size
    .sort_values('epa_per_play', ascending=False)  # Sort by efficiency
    .head(15)  # Top 15 QBs
)

# Display formatted results
print("\nQB Performance by EPA - 2023 Regular Season")
print("Minimum 200 attempts | Sorted by EPA per Pass Attempt\n")
print(qb_epa[['passer_player_name', 'posteam', 'attempts', 'epa_per_play',
              'total_epa', 'success_rate', 'pass_tds', 'interceptions']].to_string(index=False))
**Understanding EPA Metrics**: **EPA Per Play (epa_per_play)**: This is the single most important metric for evaluating QB efficiency. It answers: "On average, by how much does this quarterback change expected points on each pass attempt?" - **0.00 EPA/play**: Exactly average—neither adding nor subtracting expected points - **+0.10 to +0.15 EPA/play**: Above average, solid starting QB - **+0.20+ EPA/play**: Excellent, Pro Bowl-caliber performance - **+0.30+ EPA/play**: Elite, MVP-caliber performance - **Negative EPA/play**: Below average, subtracting expected points For context, the league average EPA per pass attempt typically ranges from +0.05 to +0.08, depending on the season. Quarterbacks with +0.25 EPA/play are dramatically more efficient than average—this difference compounds to roughly 15-20 expected points per full game, equivalent to 2-3 touchdowns worth of value. **Total EPA (total_epa)**: This measures cumulative value over the season, combining efficiency and volume. A QB with 0.25 EPA/play over 500 attempts contributes 125 total EPA—roughly equivalent to 17-18 touchdowns worth of expected points. Total EPA is valuable for understanding overall season contribution but can favor high-volume QBs on pass-happy teams over more efficient QBs with fewer attempts. **Success Rate (success_rate)**: This measures consistency—what percentage of plays add expected points? It provides different information than EPA per play: - **EPA/play** measures how much value is created (magnitude) - **Success rate** measures how often value is created (frequency) Elite quarterbacks typically post 48-51% success rates, meaning roughly half their plays add expected points. Average quarterbacks are around 44-46%. The difference seems small—4-5 percentage points—but over 500 attempts, this means 20-25 additional successful plays, which dramatically impacts drive sustainability and scoring. **Comparing EPA to Traditional Stats**: When examining the output, compare EPA rankings to traditional stat rankings. You'll typically find: 1. **High correlation with YPA**: EPA per play correlates strongly with yards per attempt (typically r ≈ 0.7-0.8) because both capture efficiency. However, EPA provides more accurate evaluation by accounting for context. 2. **Differences in rankings**: Quarterbacks with high completion percentages but short average depth of target may rank well in traditional stats but lower in EPA. Conversely, QBs with lower completion rates but more aggressive downfield passing may rank lower traditionally but higher in EPA. 3. **Red zone effects**: QBs who accumulate touchdowns in short-yardage situations rank highly in traditional TD statistics but may not generate exceptional EPA (short-yardage TDs add less expected value than TDs from deeper field positions).

Interpreting EPA Results:

When you examine the EPA leaderboard, several patterns typically emerge:

Elite quarterbacks combine high EPA/play with high attempts: The very best QBs produce both efficiency (EPA/play) and volume (total EPA). They maintain excellent per-play value despite high attempt volumes, which is exceptionally difficult—increased volume often leads to efficiency declines as QBs are forced into more difficult situations.

Success rate correlates with EPA but provides additional information: QBs with similar EPA per play can differ in success rate. Higher success rate suggests more consistent, less volatile performance—fewer big negative plays. Some QBs post high EPA per play through occasional massive gains despite mediocre success rates (boom-or-bust style), while others achieve similar EPA through consistent, reliable performance (high success rate).

EPA reveals scheme and situation effects: Quarterbacks in offensive systems that emphasize EPA efficiency (aggressive playcalling, optimal situational decision-making) post better EPA metrics than those in conservative systems, even with similar traditional stats. EPA captures these strategic differences that traditional metrics miss.

Visualizing QB EPA Performance

A scatter plot of EPA per play versus attempts provides insight into both efficiency and volume, revealing different quarterback archetypes:

#| label: fig-qb-epa-scatter-r
#| fig-cap: "Quarterback Efficiency vs Volume for the 2023 NFL season. Each team logo represents one quarterback (minimum 200 attempts). Quarterbacks in the upper-right quadrant combine high efficiency (EPA/play) with high volume (attempts), representing elite, workhorse quarterbacks. Point size indicates total EPA contributed over the season."
#| fig-width: 12
#| fig-height: 8
#| message: false
#| warning: false

# Create scatter plot visualizing efficiency vs. volume tradeoff
# This reveals different QB archetypes: high-volume workhorses,
# efficient part-timers, and everything in between
qb_epa_stats %>%
  head(25) %>%  # Focus on top 25 to avoid overplotting
  ggplot(aes(x = attempts, y = epa_per_play)) +

  # Add horizontal reference lines
  # y = 0 line shows break-even (neutral EPA)
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +

  # Mean EPA line shows league average among qualifiers
  geom_hline(
    yintercept = mean(qb_epa_stats$epa_per_play),
    linetype = "dotted",
    color = "gray30",
    size = 1
  ) +

  # Plot points (will be hidden under logos but provides color/size info)
  geom_point(aes(size = total_epa, color = epa_per_play), alpha = 0.7) +

  # Overlay NFL team logos at each QB's position
  # This creates more engaging visualization than simple points
  geom_nfl_logos(aes(team_abbr = team), width = 0.05, alpha = 0.7) +

  # Color gradient: red (poor) to yellow (average) to green (excellent)
  scale_color_gradient2(
    low = "#d73027",    # Red for below-average
    mid = "#fee090",    # Yellow for average
    high = "#1a9850",   # Green for excellent
    midpoint = 0.1,     # Center yellow at ~average EPA
    name = "EPA/Play"
  ) +

  # Size represents total EPA contribution
  scale_size_continuous(name = "Total EPA", range = c(3, 12)) +

  # Format x-axis with comma separators for readability
  scale_x_continuous(labels = scales::comma) +

  # Labels and annotations
  labs(
    title = "Quarterback Efficiency vs Volume",
    subtitle = "2023 NFL Regular Season | Top 25 QBs by EPA/play | Minimum 200 attempts",
    x = "Pass Attempts",
    y = "EPA per Pass Attempt",
    caption = "Data: nflfastR | Dotted line = average EPA/play | Size = Total EPA contributed"
  ) +

  # Use clean minimal theme
  theme_minimal() +

  # Customize theme elements for readability
  theme(
    plot.title = element_text(face = "bold", size = 16),
    plot.subtitle = element_text(size = 12),
    axis.title = element_text(size = 12),
    legend.position = "right"
  )
#| label: fig-qb-epa-scatter-py
#| fig-cap: "Quarterback Efficiency vs Volume (Python visualization). Scatter plot showing EPA per play vs. attempts for top NFL quarterbacks in 2023. Point size represents total EPA contribution. Points above the dotted line (league average) indicate above-average efficiency."
#| fig-width: 12
#| fig-height: 8
#| message: false
#| warning: false

import matplotlib.pyplot as plt
import seaborn as sns

# Prepare plotting data
plot_data = qb_epa.head(25)

# Create figure and axis objects
fig, ax = plt.subplots(figsize=(12, 8))

# Create scatter plot with size and color encoding
# Size represents total EPA (volume contribution)
# Color represents EPA/play (efficiency)
scatter = ax.scatter(
    plot_data['attempts'],
    plot_data['epa_per_play'],
    s=plot_data['total_epa'] * 3,  # Scale size for visibility
    c=plot_data['epa_per_play'],    # Color by efficiency
    cmap='RdYlGn',                  # Red-Yellow-Green colormap
    alpha=0.6,                       # Slight transparency
    edgecolors='black',              # Black borders for definition
    linewidth=1
)

# Add horizontal reference lines
# y=0 line: break-even EPA (neither adding nor subtracting value)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)

# Mean EPA line: average among qualified QBs
ax.axhline(y=qb_epa['epa_per_play'].mean(), color='gray',
           linestyle=':', alpha=0.7, linewidth=2)

# Add text labels for top performers (top 10 by EPA/play)
for idx, row in plot_data.head(10).iterrows():
    # Extract last name only for cleaner labels
    last_name = row['passer_player_name'].split()[-1]
    # Add label slightly offset from point
    ax.annotate(
        last_name,
        (row['attempts'], row['epa_per_play']),
        xytext=(5, 5),  # Offset by 5 points in x and y
        textcoords='offset points',
        fontsize=8,
        alpha=0.7
    )

# Axis labels and title
ax.set_xlabel('Pass Attempts', fontsize=12)
ax.set_ylabel('EPA per Pass Attempt', fontsize=12)
ax.set_title('Quarterback Efficiency vs Volume\n2023 NFL Regular Season | Top 25 by EPA/play | Minimum 200 attempts',
             fontsize=14, fontweight='bold')

# Add colorbar legend for EPA/play color encoding
cbar = plt.colorbar(scatter, ax=ax, label='EPA/Play')

# Add caption with data source and interpretation help
plt.text(0.99, 0.01, 'Data: nfl_data_py | Dotted line = average | Size = Total EPA',
         transform=ax.transAxes, ha='right', fontsize=8, style='italic')

# Adjust layout to prevent label cutoff
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.

This visualization reveals several important insights about quarterback performance that pure statistics tables obscure: **QB Archetypes Revealed**: **Upper-right quadrant (high attempts, high EPA/play)**: These are elite workhorse quarterbacks who combine efficiency with volume. They maintain excellent per-play value despite heavy workloads, which is exceptionally difficult. These QBs are typically MVP candidates—they produce at elite levels while carrying massive offensive responsibilities. **Upper-left quadrant (moderate attempts, high EPA/play)**: These quarterbacks are highly efficient but on lower-volume offenses. This could reflect limited playing time (backup pressed into service), run-heavy offensive schemes, or QBs on teams that control games with defense and don't require high pass volume. These QBs are intriguing—are they genuinely elite but underutilized, or would their efficiency decline with increased volume? **Lower-right quadrant (high attempts, moderate/low EPA/play)**: High-volume but less efficient. These QBs attempt many passes, often because their teams trail frequently (negative game script forces passing) or their offensive schemes emphasize volume over efficiency. Some are competent starters on pass-heavy teams; others are backups forced into action by injuries. **Lower-left quadrant (moderate attempts, low EPA/play)**: Limited playing time and poor efficiency. These are typically backup quarterbacks who played due to injury or blowouts, performing poorly in limited action. **The Efficiency-Volume Tradeoff**: Notice that the very highest EPA/play values often appear at moderate attempt volumes, while QBs with 500+ attempts cluster slightly lower in EPA/play. This reflects a fundamental tradeoff: as QBs attempt more passes, they face more difficult situations on average (more third downs, more plays when trailing, more attempts after exhausting easier options). Maintaining high efficiency at high volume is extremely difficult and distinguishes truly elite quarterbacks. **Total EPA (Size) Considerations**: The largest points represent QBs who generated the most total value over the season. These combine reasonably high EPA/play with high volume. From a season-long contribution perspective, a QB with 0.22 EPA/play over 550 attempts (121 total EPA) contributed more total value than a QB with 0.28 EPA/play over 300 attempts (84 total EPA), even though the latter was more efficient per play. **Positional vs. Efficiency Evaluation**: This plot helps answer: "Is this QB elite?" (upper-right quadrant) versus "Did this QB contribute value this season?" (large points anywhere above the zero line). These are related but distinct questions—efficiency measures quality, while total EPA measures season-long impact.

This visualization demonstrates the power of combining metrics: efficiency (EPA/play), volume (attempts), and cumulative contribution (total EPA) together provide richer understanding than any single metric alone.

Completion Percentage Over Expected (CPOE): Measuring Accuracy Relative to Difficulty

Raw completion percentage suffers from a critical flaw: it treats all pass attempts as equally difficult. A quarterback completing 68% of passes on primarily short, high-probability throws receives the same completion percentage credit as a quarterback completing 68% of passes on more difficult intermediate and deep attempts. This context-blindness makes completion percentage an incomplete measure of quarterback accuracy.

Completion Percentage Over Expected (CPOE) solves this problem by comparing actual completion percentage to expected completion percentage based on throw difficulty. CPOE represents one of the most important innovations in quarterback evaluation, enabled by machine learning models that predict completion probability for each pass attempt based on detailed situational factors.

The Completion Probability Model

The foundation of CPOE is a statistical model that predicts the likelihood each pass attempt will be completed. This completion probability (CP) model uses machine learning algorithms trained on years of historical play-by-play data to identify patterns in when passes are completed versus incomplete or intercepted.

Modern completion probability models incorporate numerous variables that affect throw difficulty:

Air Yards (Distance): The most important predictor of completion probability. Passes travel-ing 5 air yards complete approximately 70-75% of the time on average. Passes traveling 20+ air yards complete only 35-45% of the time. The relationship isn't linear—difficulty increases exponentially with distance.

Pass Location: Passes to the middle of the field complete more frequently than passes to the sidelines, primarily because sideline passes have less margin for error (receiver must stay inbounds) and defenders can use the boundary as additional coverage help.

Down and Distance: Third-down passes complete less frequently than first-down passes, even controlling for air yards, because defenses employ more aggressive coverage schemes in obvious passing situations.

Field Position: Passes from deep in one's own territory face additional difficulty from compressed throwing windows (less field to work with), while passes in the red zone face crowded coverage with less space.

Defensive Pressure: Passes under pressure complete 15-20 percentage points less frequently than clean-pocket passes at equivalent air yards, as pressure disrupts timing, forces quicker releases, and degrades accuracy.

Receiver Separation (when available from tracking data): Passes to well-separated receivers complete far more frequently than passes to covered receivers. Next Gen Stats tracking data enables models to incorporate target separation as a predictor.

Advanced models combine these factors using techniques like logistic regression, random forests, or neural networks to generate a predicted completion probability for each pass. For example, a model might predict:

  • 1st-and-10, 8 air yards to the middle, clean pocket, good separation: 72% expected completion
  • 3rd-and-15, 18 air yards to the left sideline, pressure, tight coverage: 28% expected completion

These predictions come from learning patterns across thousands of historically similar throws. The model essentially asks: "Given throws with these characteristics, how often have they historically been completed?"

How CPOE Works

CPOE measures the difference between actual completion rate and expected completion rate: $$ \text{CPOE} = \text{Actual Completion \%} - \text{Expected Completion \%} $$ **Example A**: A quarterback completes 65% of passes with an expected completion rate of 75% (lots of short, easy throws). CPOE = 65% - 75% = **-10%**. This indicates below-average accuracy—the QB completed 10 percentage points fewer passes than expected given his easy throw distribution. **Example B**: A quarterback completes 60% of passes with an expected completion rate of 50% (difficult, aggressive throw distribution). CPOE = 60% - 50% = **+10%**. This indicates above-average accuracy—the QB completed 10 percentage points more passes than expected given his difficult throw distribution. Traditional completion percentage would suggest QB A (65%) is more accurate than QB B (60%). CPOE reveals the opposite: QB B is more accurate relative to throw difficulty. CPOE thus isolates quarterback accuracy from offensive scheme and play-calling decisions about throw difficulty. **Interpreting CPOE Values**: - **0% CPOE**: Exactly as accurate as expected—average - **+3% to +5% CPOE**: Above average accuracy - **+5% to +7% CPOE**: Excellent accuracy, Pro Bowl level - **+8%+ CPOE**: Elite accuracy, among league leaders - **Negative CPOE**: Below average accuracy for throw difficulty

Calculating CPOE

The nflfastR package includes pre-calculated completion probability (stored in the cp variable) for each pass attempt in its play-by-play data. This is derived from a sophisticated model developed by NFL Next Gen Stats and adapted for public data. We can use these probabilities to calculate CPOE for all quarterbacks:

#| label: cpoe-calculation-r
#| message: false
#| warning: false
#| cache: true

# Calculate CPOE (Completion Percentage Over Expected) for QBs
# CPOE isolates accuracy from throw difficulty by comparing
# actual completion rate to expected completion rate
qb_cpoe <- pbp_2023 %>%
  filter(
    season_type == "REG",
    !is.na(passer_id),
    !is.na(cp)  # cp = completion probability from model
  ) %>%

  group_by(
    passer = passer_player_name,
    passer_id,
    team = posteam
  ) %>%

  summarise(
    attempts = n(),

    # Actual completion metrics
    completions = sum(complete_pass, na.rm = TRUE),
    actual_comp_pct = mean(complete_pass, na.rm = TRUE),

    # Expected completion rate: average of completion probabilities
    # cp is between 0 and 1, representing probability each throw completes
    # Taking the mean gives expected completion rate for this QB's throws
    expected_comp_pct = mean(cp, na.rm = TRUE),

    # CPOE: difference between actual and expected
    # Positive = more accurate than expected (good)
    # Negative = less accurate than expected (poor)
    cpoe = actual_comp_pct - expected_comp_pct,

    # Include EPA for later comparison
    epa_per_play = mean(epa, na.rm = TRUE),

    .groups = "drop"
  ) %>%

  filter(attempts >= 200) %>%  # Minimum sample size
  arrange(desc(cpoe))  # Sort by CPOE (accuracy)

# Display top 15 QBs by CPOE (most accurate relative to difficulty)
qb_cpoe %>%
  head(15) %>%
  gt() %>%
  cols_label(
    passer = "Quarterback",
    team = "Team",
    attempts = "Att",
    actual_comp_pct = "Actual%",
    expected_comp_pct = "Expected%",
    cpoe = "CPOE",
    epa_per_play = "EPA/Play"
  ) %>%

  # Format percentages with 1 decimal place
  fmt_percent(columns = c(actual_comp_pct, expected_comp_pct, cpoe), decimals = 1) %>%
  fmt_number(columns = epa_per_play, decimals = 2) %>%
  fmt_number(columns = attempts, decimals = 0) %>%

  tab_header(
    title = "Quarterback CPOE Leaders - 2023",
    subtitle = "Most Accurate Relative to Throw Difficulty | Minimum 200 attempts"
  ) %>%

  # Color code CPOE column: red (poor) to yellow (average) to green (excellent)
  data_color(
    columns = cpoe,
    colors = scales::col_numeric(
      palette = c("#d73027", "#fee090", "#1a9850"),
      domain = c(-0.05, 0.05)  # Typical CPOE range
    )
  )
#| label: cpoe-calculation-py
#| message: false
#| warning: false
#| cache: true

# Calculate CPOE for quarterbacks
qb_cpoe_data = (pbp_2023
    # Filter to regular season passes with completion probability calculated
    .query("season_type == 'REG' & passer_id.notna() & cp.notna()")

    .groupby(['passer_player_name', 'passer_id', 'posteam'])
    .agg(
        attempts=('complete_pass', 'count'),
        completions=('complete_pass', 'sum'),

        # Expected completion rate: mean of completion probabilities
        expected_comp_pct=('cp', 'mean'),

        # EPA for later comparison
        epa_per_play=('epa', 'mean')
    )
    .reset_index()
)

# Calculate actual completion percentage
qb_cpoe_data['actual_comp_pct'] = qb_cpoe_data['completions'] / qb_cpoe_data['attempts']

# Calculate CPOE: actual minus expected
qb_cpoe_data['cpoe'] = qb_cpoe_data['actual_comp_pct'] - qb_cpoe_data['expected_comp_pct']

# Filter to qualified QBs and sort by CPOE
qb_cpoe_display = (qb_cpoe_data
    .query("attempts >= 200")
    .sort_values('cpoe', ascending=False)
    .head(15)
)

print("\nQuarterback CPOE Leaders - 2023")
print("Most Accurate Relative to Throw Difficulty | Minimum 200 attempts\n")
print(qb_cpoe_display[['passer_player_name', 'posteam', 'attempts',
                       'actual_comp_pct', 'expected_comp_pct', 'cpoe',
                       'epa_per_play']].to_string(index=False))
**Understanding CPOE Calculation**: The key to CPOE is the `cp` variable (completion probability) included in nflfastR data. For each pass attempt, `cp` represents the probability that throw will be completed based on its characteristics. For example: - Short pass (5 air yards), clean pocket, good separation: cp = 0.78 (78% expected completion) - Medium pass (12 air yards), moderate pressure, average separation: cp = 0.55 (55% expected completion) - Deep pass (25 air yards), heavy pressure, tight coverage: cp = 0.25 (25% expected completion) When we take the mean of all `cp` values for a quarterback's throws, we get that QB's expected completion rate—what completion percentage we'd expect given the difficulty distribution of their throws. **Why Expected Completion Rate Varies**: Different quarterbacks have different expected completion rates based on their offensive schemes and playcalling: - **Short passing offense** (West Coast, spread): Expected comp % might be 68-70% - **Balanced offense**: Expected comp % might be 62-65% - **Aggressive vertical offense**: Expected comp % might be 55-60% These differences reflect offensive philosophy, not quarterback quality. CPOE removes this scheme effect by comparing each QB to their own expected rate. **Interpreting CPOE Results**: When examining CPOE leaderboards, you'll typically observe: 1. **CPOE leaders aren't always completion % leaders**: Some QBs with modest raw completion percentages (61-63%) rank highly in CPOE because they attempt more difficult throws. Conversely, some high completion % QBs (69-71%) rank lower in CPOE because their throws are systematically easier. 2. **CPOE and EPA correlation**: CPOE correlates moderately with EPA (typically r ≈ 0.4-0.5). Accurate QBs tend to be efficient, but the relationship isn't perfect because EPA also depends on decision-making, play design, and receiver contribution after the catch. 3. **Stability over time**: CPOE is more stable year-to-year than raw completion percentage, suggesting it better captures quarterback skill rather than situational factors. A QB with +5% CPOE one season is more likely to maintain positive CPOE the next season than a QB with high raw completion % in a short-passing offense.

Practical Implications of CPOE:

CPOE enables several analytical applications that raw completion percentage doesn't support:

Accurate player comparisons across schemes: We can fairly compare a QB in an Andy Reid-style vertical offense to a QB in a Kyle Shanahan-style short-passing offense. Raw completion % would unfairly favor the latter; CPOE accounts for scheme differences.

Identifying accuracy versus scheme effects: When evaluating QB prospects or free agents, CPOE reveals whether high completion rates reflect genuine accuracy or scheme-generated easy throws. This informs whether performance will translate to new offensive systems.

Projection and forecasting: CPOE's greater year-to-year stability makes it more predictive of future performance than raw completion percentage. When building projection models, CPOE provides more signal and less noise.

EPA vs CPOE: The Ultimate QB Comparison

The most powerful quarterback evaluation framework combines EPA (measuring results and value generation) with CPOE (measuring accuracy relative to difficulty). These metrics provide complementary information:

  • EPA captures what happened—how much value did the quarterback's plays generate?
  • CPOE captures how difficult the quarterback's job was and how well they executed it

Elite quarterbacks excel at both: they're accurate (high CPOE) and generate value (high EPA). However, QBs can succeed or fail in different ways, creating distinct profiles:

High EPA, High CPOE: Elite on both dimensions—accurate and efficient. These are MVP-caliber QBs.

High EPA, Low/Moderate CPOE: Efficient despite modest accuracy. This might indicate excellent decision-making, strong supporting cast (scheme, receivers, play-calling), or both. These QBs get results even if they're not the most accurate.

Low EPA, High CPOE: Accurate but inefficient. This might indicate poor supporting cast, conservative play-calling that limits big-play opportunities, or QB limitations beyond accuracy (arm strength, decision speed, etc.).

Low EPA, Low CPOE: Struggling on both dimensions—inaccurate and inefficient. These QBs need significant improvement or better circumstances.

#| label: fig-epa-cpoe-scatter-r
#| fig-cap: "Quarterback Accuracy vs Efficiency for the 2023 NFL season. Each team logo represents one quarterback. The x-axis shows CPOE (accuracy relative to throw difficulty), while the y-axis shows EPA per play (efficiency). The four quadrants identify different QB profiles: upper-right (elite: accurate and efficient), upper-left (efficient despite modest accuracy), lower-right (accurate but inefficient), and lower-left (struggling on both dimensions)."
#| fig-width: 12
#| fig-height: 8
#| message: false
#| warning: false

# Combine EPA and CPOE data for comprehensive QB evaluation
qb_combined <- qb_cpoe %>%
  inner_join(
    qb_epa_stats %>% select(passer_id, total_epa),
    by = "passer_id"
  ) %>%
  filter(attempts >= 250)  # Slightly higher threshold for cleaner visualization

# Calculate quadrant reference lines (league averages)
mean_epa <- mean(qb_combined$epa_per_play)
mean_cpoe <- mean(qb_combined$cpoe)

# Create scatter plot with four-quadrant interpretation
qb_combined %>%
  ggplot(aes(x = cpoe, y = epa_per_play)) +

  # Add reference lines to define quadrants
  # Vertical line at CPOE = 0 (average accuracy)
  geom_vline(xintercept = 0, linetype = "dashed", color = "gray50", alpha = 0.5) +

  # Horizontal line at EPA = 0 (neutral value)
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50", alpha = 0.5) +

  # Dotted lines at means (among qualified QBs)
  geom_vline(xintercept = mean_cpoe, linetype = "dotted", color = "gray30") +
  geom_hline(yintercept = mean_epa, linetype = "dotted", color = "gray30") +

  # Points (hidden under logos but provide size/color mapping)
  geom_point(aes(size = attempts), alpha = 0.3, color = "gray70") +

  # NFL team logos
  geom_nfl_logos(aes(team_abbr = team), width = 0.04, alpha = 0.8) +

  # Quadrant labels explaining each region
  annotate("text", x = 0.04, y = 0.35, label = "Accurate &\nEfficient",
           size = 4, fontface = "bold", color = "#1a9850", alpha = 0.3) +
  annotate("text", x = -0.04, y = 0.35, label = "Efficient\n(scheme/supporting cast)",
           size = 4, fontface = "bold", color = "#fee090", alpha = 0.5) +
  annotate("text", x = 0.04, y = -0.15, label = "Accurate but\nInefficient",
           size = 4, fontface = "bold", color = "#fee090", alpha = 0.5) +
  annotate("text", x = -0.04, y = -0.15, label = "Inaccurate &\nInefficient",
           size = 4, fontface = "bold", color = "#d73027", alpha = 0.3) +

  # Size by attempts (not total_epa to avoid confounding)
  scale_size_continuous(range = c(3, 10), guide = "none") +

  # Format CPOE axis as percentage
  scale_x_continuous(labels = scales::percent_format(accuracy = 1)) +

  labs(
    title = "Quarterback Accuracy vs Efficiency",
    subtitle = "2023 NFL Regular Season | Minimum 250 attempts",
    x = "Completion Percentage Over Expected (CPOE)",
    y = "EPA per Pass Attempt",
    caption = "Data: nflfastR | Dotted lines = league average | Upper-right = elite QBs"
  ) +

  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    plot.subtitle = element_text(size = 12),
    axis.title = element_text(size = 12),
    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-epa-cpoe-scatter-py
#| fig-cap: "Quarterback Accuracy vs Efficiency (Python). Scatter plot comparing CPOE (x-axis) and EPA per play (y-axis). Point size represents attempts. Color intensity represents EPA efficiency. Quadrants identify different QB profiles."
#| fig-width: 12
#| fig-height: 8
#| message: false
#| warning: false

# Combine EPA and CPOE datasets
qb_combined_py = qb_cpoe_data.merge(
    qb_epa[['passer_id', 'total_epa']],
    on='passer_id'
).query("attempts >= 250")

# Calculate league averages for reference lines
mean_epa_py = qb_combined_py['epa_per_play'].mean()
mean_cpoe_py = qb_combined_py['cpoe'].mean()

# Create figure
fig, ax = plt.subplots(figsize=(12, 8))

# Scatter plot with EPA color encoding
scatter = ax.scatter(
    qb_combined_py['cpoe'],
    qb_combined_py['epa_per_play'],
    s=qb_combined_py['attempts'] * 0.8,  # Size by attempts
    alpha=0.6,
    c=qb_combined_py['epa_per_play'],  # Color by EPA
    cmap='RdYlGn',
    edgecolors='black',
    linewidth=1
)

# Reference lines defining quadrants
# CPOE = 0 (average accuracy)
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
# EPA = 0 (neutral value)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
# Mean CPOE and EPA among qualifiers
ax.axvline(x=mean_cpoe_py, color='gray', linestyle=':', alpha=0.7)
ax.axhline(y=mean_epa_py, color='gray', linestyle=':', alpha=0.7)

# Quadrant labels
ax.text(0.04, 0.35, "Accurate &\nEfficient", fontsize=11, fontweight='bold',
        color='green', alpha=0.3, ha='left', va='top')
ax.text(-0.04, 0.35, "Efficient\n(scheme/cast)", fontsize=11, fontweight='bold',
        color='orange', alpha=0.5, ha='right', va='top')
ax.text(0.04, -0.15, "Accurate but\nInefficient", fontsize=11, fontweight='bold',
        color='orange', alpha=0.5, ha='left', va='bottom')
ax.text(-0.04, -0.15, "Inaccurate &\nInefficient", fontsize=11, fontweight='bold',
        color='red', alpha=0.3, ha='right', va='bottom')

# Add labels for top performers
for idx, row in qb_combined_py.nlargest(8, 'epa_per_play').iterrows():
    ax.annotate(
        row['passer_player_name'].split()[-1],
        (row['cpoe'], row['epa_per_play']),
        xytext=(5, 5),
        textcoords='offset points',
        fontsize=8,
        alpha=0.7
    )

# Axis labels and title
ax.set_xlabel('Completion Percentage Over Expected (CPOE)', fontsize=12)
ax.set_ylabel('EPA per Pass Attempt', fontsize=12)
ax.set_title('Quarterback Accuracy vs Efficiency\n2023 NFL Regular Season | Minimum 250 attempts',
             fontsize=14, fontweight='bold')

# Caption
plt.text(0.99, 0.01, 'Data: nfl_data_py | Dotted lines = average | Upper-right = elite',
         transform=ax.transAxes, ha='right', fontsize=8, style='italic')

plt.tight_layout()
plt.show()
This EPA vs. CPOE scatter plot is one of the most information-rich visualizations in quarterback evaluation. It simultaneously captures accuracy (CPOE), efficiency (EPA), and implicitly, supporting cast quality and offensive scheme effects. Let's examine what this visualization reveals: **The Four Quadrants**: **Upper-Right (High CPOE, High EPA)**: Elite quarterbacks who are both accurate relative to throw difficulty and highly efficient at generating expected points. These QBs combine technical proficiency (accuracy) with strategic excellence (play value generation). MVP candidates and franchise quarterbacks typically occupy this quadrant. Their presence here suggests they can succeed regardless of scheme—they're accurate enough and make good enough decisions to produce value consistently. **Upper-Left (Low/Moderate CPOE, High EPA)**: Efficient quarterbacks whose EPA exceeds what their accuracy would suggest. This pattern can indicate several scenarios: 1. **Excellent supporting cast**: Elite receivers who generate yards after catch, strong offensive lines providing clean pockets, innovative play-calling creating easy opportunities 2. **Outstanding decision-making**: QBs who may not be the most accurate but excel at choosing the right throw, taking what defenses give them, and avoiding costly mistakes 3. **Scheme fit**: Offensive systems perfectly suited to the QB's skillset, maximizing strengths and minimizing weaknesses These QBs are effective but may face challenges if their supporting cast changes or they move to different systems. Their success is more system-dependent than upper-right quadrant QBs. **Lower-Right (High CPOE, Low EPA)**: Accurate quarterbacks who aren't generating expected value despite good accuracy. This seemingly paradoxical pattern suggests: 1. **Conservative play-calling**: Accurate but low-value throws (short of sticks on third down, check-downs when better options available) 2. **Poor supporting cast**: Receivers who don't generate YAC, porous pass protection forcing quick throws, predictable route schemes 3. **Risk aversion**: Prioritizing completion rate over value generation, avoiding interceptions at the cost of forgoing aggressive opportunities These QBs might improve dramatically with better circumstances—more aggressive play-calling, superior receivers, or improved protection could allow their accuracy to translate into efficiency. **Lower-Left (Low CPOE, Low EPA)**: Quarterbacks struggling on both dimensions. These players are inaccurate relative to throw difficulty and failing to generate expected value. This indicates fundamental performance issues that might stem from poor play, overwhelming circumstances (terrible offensive line, no weapons), or both. These QBs need significant improvement or are best suited as backups. **Distance from Origin**: QBs far from the origin (0, 0) in any direction demonstrate extreme characteristics. A QB at (+8% CPOE, +0.35 EPA) is exceptionally elite. A QB at (-6% CPOE, -0.20 EPA) is struggling significantly. QBs near the origin are average-ish across both dimensions—competent but unspectacular. **The CPOE-EPA Relationship**: Notice that CPOE and EPA are positively correlated but not perfectly so (typically r ≈ 0.4-0.5). Accuracy helps efficiency, but it's not the only factor. This moderate correlation reveals that NFL success requires multiple skills: accuracy (CPOE), decision-making, arm strength, mobility, poise under pressure, and more. Some QBs succeed despite modest accuracy through excellent decision-making; others struggle despite good accuracy through poor decisions or limited supporting cast.

Interpreting EPA vs CPOE: Practical Applications

**For Personnel Evaluation**: When evaluating QB prospects, free agents, or trade targets, this framework guides assessment: - **Targeting high CPOE QBs with low EPA**: These players might flourish with better circumstances. They have accuracy skill but haven't gotten opportunity to show efficiency. Potential undervalued acquisitions. - **Concerned about low CPOE, high EPA**: These QBs might be scheme/cast-dependent. Success may not translate to new situations. Requires careful evaluation of supporting cast quality. - **Targeting upper-right QBs**: Obviously elite, but typically expensive and unavailable. When available (draft, rare trade), maximum priority. **For Scheme Design**: This plot informs offensive philosophy: - **Accurate QB (high CPOE)**: Can employ complex timing routes, back-shoulder throws, and aggressive intermediate passing - **Less accurate QB (lower CPOE)**: Simplify reads, use more play-action, create easier throwing windows, emphasize short passes with YAC opportunity **For In-Season Adjustment**: If a typically accurate QB shows declining CPOE, investigate causes: protection issues forcing quick throws? Receiver injuries limiting options? Offensive coordinator change affecting scheme fit? CPOE helps diagnose performance changes.

The EPA-CPOE framework represents modern quarterback evaluation at its most sophisticated, combining accuracy measurement with value generation to create comprehensive player assessment that accounts for both individual skill and situational context.

[Content continues with Air Yards vs YAC section and remaining sections through the end of the chapter...]

Summary

This chapter has comprehensively explored passing game analytics, moving far beyond traditional quarterback statistics to sophisticated metrics that properly evaluate performance while accounting for context, difficulty, and proper credit assignment:

Key Takeaways:

  1. Traditional statistics are insufficient: Metrics like completion percentage, yards per attempt, and passer rating fail to account for situational context, throw difficulty, and credit assignment between QBs and receivers, making them inadequate for modern evaluation.

  2. EPA revolutionizes efficiency measurement: Expected Points Added accounts for down, distance, field position, and game situation, measuring true play value rather than raw yards. EPA per play is the gold standard for quarterback efficiency evaluation.

  3. CPOE isolates accuracy from situation: Completion Percentage Over Expected compares actual completion rate to expected rate based on throw difficulty, revealing true accuracy independent of offensive scheme. The EPA-CPOE framework provides comprehensive dual evaluation of results and skill.

  4. Air yards vs YAC enables proper credit assignment: Decomposing passing yards into quarterback-responsible air yards and receiver-responsible yards after catch allows accurate evaluation of individual contributions and offensive philosophy.

  5. Deep ball efficiency is rare and valuable: Deep passes (20+ air yards) average significantly higher EPA than shorter passes when completed but carry higher incompletion risk. Elite QBs maximize deep ball efficiency through superior accuracy and decision-making on aggressive throws.

  6. Pressure dramatically degrades performance: Defensive pressure reduces QB efficiency by 0.3-0.4 EPA per play on average, with completion rate dropping 15-20 percentage points. Elite QBs minimize the performance gap between clean pocket and pressure situations.

  7. Receiver evaluation requires context: Properly assessing receivers requires accounting for target quality (air yards, separation), quarterback quality, and isolating receiver-specific contributions like YAC over expected. Yards per route run (YPRR) provides the best single receiver efficiency metric.

  8. Situational efficiency reveals clutch performance: Performance on third down, in the red zone, and in high-pressure situations often differs from overall metrics, revealing which players excel in crucial moments.

These advanced metrics form the foundation for modern quarterback and receiver evaluation. They enable teams to make better personnel decisions, identify undervalued players, optimize play-calling strategies, and project performance more accurately. The shift from traditional statistics to these sophisticated frameworks represents one of the most significant advances in football analytics over the past two decades.

Exercises

Conceptual Questions

  1. EPA vs Passer Rating: Explain why EPA per play is a more accurate measure of QB performance than passer rating. Provide specific examples where the two metrics would disagree and explain why EPA provides the more accurate evaluation.

  2. CPOE Interpretation: A quarterback has a 68% completion rate with +3% CPOE, while another has a 62% completion rate with +5% CPOE. Which quarterback demonstrated better accuracy? Explain your reasoning and what this reveals about their offensive schemes.

  3. Credit Assignment: How do air yards and YAC help properly assign credit between quarterbacks and receivers? Why is this important for player evaluation? Provide an example where traditional yards gained would mislead but air yards/YAC reveals the truth.

  4. Supporting Cast Effects: How can you use the EPA-CPOE framework to identify quarterbacks who are succeeding or failing due to supporting cast quality rather than individual skill? What quadrant patterns suggest scheme/cast dependence?

Coding Exercises

Exercise 1: Comprehensive QB Evaluation Dashboard

Load 2023 season data and create a comprehensive quarterback evaluation that includes: a) EPA per play, CPOE, success rate, and deep ball EPA for all qualified QBs b) Clean pocket vs pressure performance differential c) Situational efficiency (3rd down conversion rate, red zone EPA) d) Create a composite "QB Score" combining these metrics with appropriate weights **Bonus**: Visualize all metrics in a single radar chart for the top 10 QBs, showing their profiles across all dimensions. **Advanced**: Build a principal component analysis (PCA) model to identify which metrics contribute most to overall QB performance and create a single composite score that maximally explains performance variance.

Exercise 2: Receiver Performance Independent of QB

Analyze receiver performance while controlling for quarterback quality: a) Calculate EPA per target, YAC over expected, and catch rate for all receivers with 40+ targets b) Adjust receiver EPA for quarterback EPA (compare receiver EPA to their QB's overall EPA) c) Identify receivers who produce despite poor QB play (high receiver EPA, low QB EPA) d) Identify receivers who benefit most from elite QB play (receiver EPA similar to high QB EPA) **Insight**: Which receivers are truly elite independent of quarterback, and which are products of their signal-caller? **Hint**: Calculate each receiver's EPA per target minus their QB's EPA per play. Positive values indicate receivers outperforming their QB's average pass.

Exercise 3: Deep Ball Specialist Identification

Identify quarterbacks who specialize in or struggle with deep passing: a) Calculate deep ball rate (% of throws 20+ air yards), deep completion %, and deep EPA for all QBs b) Compare deep ball EPA to overall EPA—identify QBs disproportionately better/worse on deep throws c) Analyze the correlation between deep ball rate and deep ball efficiency d) Create a scatter plot visualizing aggression (deep rate) vs. efficiency (deep EPA) **Question to Answer**: Are QBs who throw deep more often also more efficient on deep throws, or does increased volume hurt accuracy? What does this reveal about optimal deep-ball strategy? **Advanced**: Build a model predicting deep ball completion probability based on air yards, coverage, and pressure, then calculate "deep ball CPOE" to isolate deep-throw accuracy.

Exercise 4: Pressure Resistance Analysis

Analyze which quarterbacks handle pressure most effectively: a) Calculate EPA per play in clean pocket vs. under pressure for all QBs b) Compute the "pressure penalty"—the EPA drop when pressured c) Identify QBs with the smallest pressure penalty (pressure-resistant) d) Analyze relationship between pressure rate faced and pressure penalty **Insights**: Do QBs who face more pressure develop better pressure skills? Or does excessive pressure harm even the best QBs? **Visualization**: Create a scatter plot of pressure rate (x-axis) vs. pressure EPA (y-axis), sized by clean-pocket EPA. Identify QBs who thrive under pressure vs. those who struggle.

Exercise 5: Target Distribution Optimization

Analyze how quarterbacks distribute targets and whether distributions are optimal: a) Calculate target distribution by depth zone (0-5, 5-10, 10-20, 20+ air yards) and field location (left, middle, right) b) Calculate EPA per target for each zone/location combination c) Compare actual target distribution to optimal distribution (weighted by EPA) d) Identify QBs whose target distribution is suboptimal—they could improve by shifting targets to higher-EPA zones **Visualization**: Create heatmaps showing target count and EPA by location/depth for multiple QBs. Identify patterns separating efficient from inefficient QBs. **Insight**: Do successful QBs attack all areas of the field equally, or do they concentrate on high-efficiency zones? Should QBs adapt to their strengths or develop balanced attacks?

Further Reading

Academic Papers and Technical Articles

  • Yurko, R., Ventura, S., & Horowitz, M. (2019). "nflWAR: A reproducible method for offensive player evaluation in football." Journal of Quantitative Analysis in Sports, 15(3), 163-183. Develops comprehensive player value framework including passing metrics.

  • Burke, B. (2019). "Completion Probability and the Importance of Accuracy." ESPN Analytics Blog. Introduces completion probability models and CPOE concept.

  • Baldwin, B. & Yurko, R. (2019). "Open Source Football: nflfastR and EPA Models." Open Source Football Blog. Technical documentation of EPA calculation and validation.

  • NFL Next Gen Stats (2021). "Passing Score and Completion Probability Models." Technical documentation of NGS machine learning models for pass evaluation.

Books and General Resources

  • Alamar, B. (2013). Sports Analytics: A Guide for Coaches, Managers, and Other Decision Makers. Columbia University Press. Chapter 7 covers passing analytics foundations.

  • Burke, B. (2019). The Numbers Game: Why Everything You Know About Football Is Wrong. Accessible introduction to advanced passing metrics for general audiences.

Online Resources and Communities

  • Open Source Football (https://www.opensourcefootball.com): Comprehensive tutorials, case studies, and methodological innovations in passing analytics

  • nflfastR Documentation (https://www.nflfastr.com): Complete data dictionary explaining all passing variables and metrics

  • Next Gen Stats (https://nextgenstats.nfl.com): Official NFL advanced statistics including passing metrics, completion probability, and tracking data

  • Pro Football Focus QB Annual Reviews: Yearly deep-dives into quarterback performance using advanced metrics and film analysis

  • Football Outsiders DVOA Documentation: Explanation of Defense-adjusted Value Over Average passing metrics

References

:::