Chapter 13: Key Takeaways - Play-by-Play Visualization

Quick Reference Guide

The Core Principle

Play-by-play visualization transforms raw game data into narrative. Each play is a data point; the visualization's job is to reveal the story those data points tell about momentum, key moments, and game flow.


1. Play-by-Play Data Structure

Essential Fields

play = {
    # Pre-play state
    'quarter': 1,
    'time': '10:30',
    'down': 2,
    'distance': 7,
    'yard_line': 35,  # 1-99 scale

    # Action and result
    'play_type': 'pass',
    'yards_gained': 12,

    # Advanced metrics
    'epa': 0.85,       # Expected Points Added
    'wp_before': 0.52, # Win Probability before
    'wp_after': 0.58,  # Win Probability after
    'wpa': 0.06        # Win Probability Added
}

Yard Line Convention

  • 1-50: Own territory (1 = own goal line)
  • 51-99: Opponent territory (99 = opponent's goal line)
  • Yard line 75 = opponent's 25-yard line

2. Key Metrics

EPA (Expected Points Added)

EPA = EP_after - EP_before
  • Positive EPA: Play improved scoring expectancy
  • Negative EPA: Play hurt scoring expectancy
  • Context-dependent: 3-yard gain can be positive (3rd & 2) or negative (3rd & 8)

WPA (Win Probability Added)

WPA = WP_after - WP_before
  • Measures game impact: Same play has more WPA in close game
  • Ranges from -1 to +1: Theoretically
  • Typical big play: ±5-15%
  • Game-changing play: ±15-30%

Success Rate

  • 1st down: 40% of distance
  • 2nd down: 50% of distance
  • 3rd/4th down: 100% of distance (first down)

3. Drive Chart Types

Traditional Drive Chart

  • Shows field position progression
  • Horizontal representation of the field
  • Color-coded by result (TD, FG, punt, turnover)

EPA-Annotated Drive Chart

  • Adds value context to each play
  • Color scale: Green (positive) → Red (negative)
  • Cumulative EPA shows drive value

Multi-Drive Summary

  • Stacked horizontal bars
  • One row per drive
  • Quick comparison of all possessions

4. Win Probability Visualization

Key Design Elements

Element Purpose
Fill areas Show which team is favored
50% line Reference for "even" game
Quarter markers Time context
Key play annotations Identify turning points
Team colors Immediate identification

Best Practices

  1. Start at 50% for neutral site games
  2. Limit annotations to 5-10 key moments
  3. Use fills to emphasize advantage magnitude
  4. Show overtime as extension of timeline

5. Situational Analysis

Down × Distance Matrix

         | 1-3    | 4-6    | 7-10   | 11+
---------|--------|--------|--------|--------
1st Down | +0.25  | +0.18  | +0.12  | +0.05
2nd Down | +0.30  | +0.15  | +0.02  | -0.15
3rd Down | +0.45  | +0.22  | -0.08  | -0.28

Visualization Guidelines

  • Use diverging colormap centered at 0
  • Include sample sizes for reliability
  • Add cell annotations for exact values

6. Chart Type Quick Reference

Visualization Use Case Key Feature
Drive chart Single possession Field position
WP curve Full game flow Momentum shifts
EPA bar chart Play-by-play value Individual contributions
Heatmap Situational analysis Pattern identification
Sequence diagram Detailed drive review Full context
Waterfall Cumulative contributions Running totals

7. Code Templates

Basic Drive Chart

def create_drive_chart(plays):
    fig, ax = plt.subplots(figsize=(14, 4))

    # Draw field
    ax.axhspan(0.3, 0.7, facecolor='#2e5a1c')

    # Plot plays
    current_yl = plays[0]['yard_line']
    for play in plays:
        yards = play['yards_gained']
        next_yl = current_yl + yards
        ax.plot([current_yl, next_yl], [0.5, 0.5],
               linewidth=4, solid_capstyle='round')
        current_yl = next_yl

    ax.set_xlim(0, 100)
    ax.axis('off')
    return fig

Win Probability Chart

def create_wp_chart(plays, home_team, away_team):
    fig, ax = plt.subplots(figsize=(14, 6))

    times = [convert_to_game_time(p) for p in plays]
    wps = [p['home_wp'] for p in plays]

    ax.fill_between(times, wps, 0.5,
                   where=[wp >= 0.5 for wp in wps],
                   color='green', alpha=0.3)
    ax.fill_between(times, wps, 0.5,
                   where=[wp < 0.5 for wp in wps],
                   color='red', alpha=0.3)
    ax.plot(times, wps, linewidth=2)
    ax.axhline(0.5, linestyle='--', alpha=0.5)

    return fig

EPA Color Function

def epa_color(epa):
    if epa >= 1.0:
        return '#1a9641'   # Big positive
    elif epa >= 0.3:
        return '#a6d96a'   # Positive
    elif epa >= -0.3:
        return '#ffffbf'   # Neutral
    elif epa >= -1.0:
        return '#fdae61'   # Negative
    else:
        return '#d7191c'   # Big negative

8. Audience-Specific Design

Broadcast (5-8 seconds)

  • Large, clear numbers
  • Minimal text
  • Team colors prominent
  • Update animations

Coaching Staff (extended study)

  • Full detail
  • All situational breakdowns
  • Links to video
  • Print-ready

Social Media (2-3 seconds)

  • Single key insight
  • Bold visual
  • Call to action
  • Mobile-optimized (square/vertical)

9. Common Mistakes to Avoid

Drive Charts

  • ❌ Too many play labels (cluttered)
  • ❌ Inconsistent color scales
  • ❌ Missing drive result indicator
  • ✅ Clear start/end markers
  • ✅ Appropriate detail level

Win Probability

  • ❌ Too many annotations
  • ❌ No team identification
  • ❌ Missing 50% reference
  • ✅ Clear fills showing advantage
  • ✅ Key moments highlighted

Heatmaps

  • ❌ Using sequential colormap for EPA
  • ❌ Missing sample sizes
  • ❌ Unclear axis labels
  • ✅ Diverging colormap centered at 0
  • ✅ Cell annotations

10. Pre-Visualization Checklist

Before Creating

  • [ ] What story does the data tell?
  • [ ] Who is the audience?
  • [ ] What is the appropriate detail level?
  • [ ] What time frame do viewers have?

During Creation

  • [ ] Are colors meaningful?
  • [ ] Is there a clear visual hierarchy?
  • [ ] Are key moments identified?
  • [ ] Is context provided?

Before Publishing

  • [ ] Is it accurate?
  • [ ] Is it accessible?
  • [ ] Does it tell the intended story?
  • [ ] Would a non-expert understand it?

Formulas Quick Reference

Game Time Conversion

game_minutes = (quarter - 1) * 15 + (15 - minutes) - seconds/60

EPA from EP Values

epa = ep_after - ep_before

Drive Efficiency

drive_epa_per_play = sum(play_epas) / len(plays)

Success Rate

success_rate = successful_plays / total_plays

Essential Resources

Python Libraries

import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.animation import FuncAnimation
import numpy as np

Color Palettes

# EPA diverging
EPA_COLORS = ['#d7191c', '#fdae61', '#ffffbf', '#a6d96a', '#1a9641']

# Drive results
RESULT_COLORS = {
    'touchdown': '#2a9d8f',
    'field_goal': '#e9c46a',
    'turnover': '#e76f51',
    'punt': '#8d99ae',
    'downs': '#e76f51'
}

Chapter Summary in One Sentence

Play-by-play visualization transforms game data into visual narratives using drive charts, win probability curves, and situational analysis to reveal the story of how games unfold play by play.