Game-Planning Analytics
Beginner
10 min read
0 views
Nov 27, 2025
# Game-Planning Analytics
## Introduction
Game-planning analytics involves analyzing opponent tendencies, strengths, and weaknesses to develop targeted strategies. Effective game planning combines statistical analysis with film study to create competitive advantages.
## Game Planning Framework
### Preparation Components
1. **Opponent Tendency Analysis**: Play-calling patterns by situation
2. **Personnel Matchups**: Identifying favorable individual matchups
3. **Scheme Weaknesses**: Coverage/run defense vulnerabilities
4. **Key Player Impact**: Effect of stars on opponent performance
5. **Situational Success**: Third down, red zone, two-minute efficiency
### Analytics Integration
- Historical performance data
- Advanced metrics (EPA, success rate)
- Play-by-play tendency reports
- Personnel grouping analysis
- Game script scenarios
## R Analysis with nflfastR
```r
library(nflfastR)
library(dplyr)
library(ggplot2)
library(tidyr)
# Load play-by-play data
pbp <- load_pbp(2023)
# Filter to analysis plays
plays <- pbp %>%
filter(
!is.na(posteam),
!is.na(defteam),
play_type %in% c("run", "pass")
)
# Function to create opponent scouting report
create_scouting_report <- function(opponent_defense) {
# Filter to opponent defensive plays
opponent_plays <- plays %>%
filter(defteam == opponent_defense)
# Overall defensive efficiency
overall_defense <- opponent_plays %>%
summarise(
total_plays = n(),
epa_allowed = mean(epa, na.rm = TRUE),
success_rate_allowed = mean(success, na.rm = TRUE),
yards_per_play = mean(yards_gained, na.rm = TRUE),
explosive_rate = mean(yards_gained >= 15, na.rm = TRUE)
)
print(paste("=== SCOUTING REPORT:", opponent_defense, "DEFENSE ===\n"))
print("Overall Defensive Performance:")
print(overall_defense)
# Pass defense breakdown
pass_defense <- opponent_plays %>%
filter(play_type == "pass") %>%
summarise(
dropbacks = n(),
epa_per_dropback = mean(epa, na.rm = TRUE),
completion_rate = mean(complete_pass, na.rm = TRUE),
yards_per_attempt = mean(yards_gained, na.rm = TRUE),
sack_rate = mean(sack == 1, na.rm = TRUE),
pressure_rate = mean(qb_hit == 1 | sack == 1, na.rm = TRUE)
)
print("\nPass Defense:")
print(pass_defense)
# Run defense breakdown
run_defense <- opponent_plays %>%
filter(play_type == "run") %>%
summarise(
carries = n(),
epa_per_carry = mean(epa, na.rm = TRUE),
yards_per_carry = mean(yards_gained, na.rm = TRUE),
stuff_rate = mean(yards_gained <= 0, na.rm = TRUE),
explosive_rate = mean(yards_gained >= 10, na.rm = TRUE)
)
print("\nRun Defense:")
print(run_defense)
# Situational analysis
third_down <- opponent_plays %>%
filter(down == 3) %>%
summarise(
attempts = n(),
conversion_rate = mean(third_down_converted, na.rm = TRUE),
avg_distance = mean(ydstogo, na.rm = TRUE)
)
print("\nThird Down Defense:")
print(third_down)
# Red zone defense
redzone <- opponent_plays %>%
filter(yardline_100 <= 20) %>%
summarise(
plays = n(),
epa_per_play = mean(epa, na.rm = TRUE),
td_rate = mean(touchdown == 1, na.rm = TRUE)
)
print("\nRed Zone Defense:")
print(redzone)
# Tendency by down
tendencies <- opponent_plays %>%
mutate(
situation = case_when(
down == 1 ~ "1st Down",
down == 2 & ydstogo <= 5 ~ "2nd & Short",
down == 2 & ydstogo > 5 ~ "2nd & Long",
down == 3 & ydstogo <= 3 ~ "3rd & Short",
down == 3 & ydstogo > 7 ~ "3rd & Long",
TRUE ~ "Other"
)
) %>%
filter(situation != "Other") %>%
group_by(situation, play_type) %>%
summarise(
plays = n(),
epa_allowed = mean(epa, na.rm = TRUE),
.groups = "drop"
) %>%
group_by(situation) %>%
mutate(
total_plays = sum(plays),
play_pct = plays / total_plays * 100
)
print("\nDefensive Performance by Situation:")
print(tendencies)
# Return full report as list
return(list(
overall = overall_defense,
pass_def = pass_defense,
run_def = run_defense,
third_down = third_down,
redzone = redzone,
tendencies = tendencies
))
}
# Example: Scout Kansas City Chiefs defense
chiefs_report <- create_scouting_report("KC")
# Matchup analysis: Your offense vs opponent defense
analyze_matchup <- function(your_offense, opponent_defense) {
# Your offensive tendencies
your_offense_plays <- plays %>%
filter(posteam == your_offense)
your_tendencies <- your_offense_plays %>%
group_by(play_type) %>%
summarise(
plays = n(),
epa_per_play = mean(epa, na.rm = TRUE),
success_rate = mean(success, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(play_pct = plays / sum(plays) * 100)
print(paste("=== MATCHUP ANALYSIS:", your_offense, "OFF vs", opponent_defense, "DEF ===\n"))
print("Your Offensive Tendencies:")
print(your_tendencies)
# Opponent defensive weaknesses
opponent_defense_plays <- plays %>%
filter(defteam == opponent_defense)
opponent_weaknesses <- opponent_defense_plays %>%
group_by(play_type) %>%
summarise(
plays = n(),
epa_allowed = mean(epa, na.rm = TRUE),
success_allowed = mean(success, na.rm = TRUE),
.groups = "drop"
)
print("\nOpponent Defensive Weaknesses:")
print(opponent_weaknesses)
# Identify exploitation opportunities
print("\n=== GAME PLAN RECOMMENDATIONS ===")
if (opponent_weaknesses$epa_allowed[opponent_weaknesses$play_type == "pass"] >
opponent_weaknesses$epa_allowed[opponent_weaknesses$play_type == "run"]) {
print("ATTACK THROUGH THE AIR: Opponent's pass defense is weaker")
print(paste("Pass EPA allowed:",
round(opponent_weaknesses$epa_allowed[opponent_weaknesses$play_type == "pass"], 3)))
} else {
print("ESTABLISH THE RUN: Opponent's run defense is vulnerable")
print(paste("Run EPA allowed:",
round(opponent_weaknesses$epa_allowed[opponent_weaknesses$play_type == "run"], 3)))
}
# Third down analysis
opp_third_down <- opponent_defense_plays %>%
filter(down == 3) %>%
summarise(conv_rate = mean(third_down_converted, na.rm = TRUE))
print(paste("\nOpponent 3rd Down Conv Rate Allowed:",
round(opp_third_down$conv_rate * 100, 1), "%"))
if (opp_third_down$conv_rate > 0.42) {
print("OPPORTUNITY: Opponent struggles on third down")
}
return(list(
your_tendencies = your_tendencies,
opp_weaknesses = opponent_weaknesses
))
}
# Example: Buffalo offense vs Kansas City defense
matchup <- analyze_matchup("BUF", "KC")
# Weekly game plan summary
create_game_plan <- function(your_team, opponent) {
# Offensive game plan
print(paste("=== GAME PLAN:", your_team, "vs", opponent, "===\n"))
# Your offense vs their defense
off_matchup <- analyze_matchup(your_team, opponent)
# Your defense vs their offense
def_matchup <- analyze_matchup(opponent, your_team)
# Key matchups
your_off <- plays %>% filter(posteam == your_team)
opp_def <- plays %>% filter(defteam == opponent)
# Explosive play comparison
your_explosive <- your_off %>%
summarise(explosive_rate = mean(yards_gained >= 15, na.rm = TRUE))
opp_explosive_allowed <- opp_def %>%
summarise(explosive_allowed = mean(yards_gained >= 15, na.rm = TRUE))
print("\n=== KEY FACTORS ===")
print(paste("Your explosive play rate:", round(your_explosive$explosive_rate * 100, 1), "%"))
print(paste("Opponent explosive rate allowed:",
round(opp_explosive_allowed$explosive_allowed * 100, 1), "%"))
if (your_explosive$explosive_rate > opp_explosive_allowed$explosive_allowed) {
print("ADVANTAGE: Your explosive offense vs their vulnerability")
}
}
# Generate full game plan
create_game_plan("BUF", "KC")
# Personnel matchup analysis
personnel_matchups <- plays %>%
filter(posteam == "BUF", defteam == "KC") %>%
mutate(
off_personnel = case_when(
grepl("1 RB.*3 WR", personnel) ~ "11 Personnel",
grepl("1 RB.*2 TE", personnel) ~ "12 Personnel",
TRUE ~ "Other"
)
) %>%
filter(off_personnel != "Other") %>%
group_by(off_personnel, play_type) %>%
summarise(
plays = n(),
epa_per_play = mean(epa, na.rm = TRUE),
success_rate = mean(success, na.rm = TRUE),
.groups = "drop"
)
print("\nPersonnel Matchup Performance:")
print(personnel_matchups)
# Visualize game plan key metrics
opponent <- "KC"
opp_defense <- plays %>% filter(defteam == opponent)
situation_performance <- opp_defense %>%
mutate(
situation = case_when(
down == 1 ~ "1st",
down == 2 & ydstogo <= 5 ~ "2nd Short",
down == 2 & ydstogo > 5 ~ "2nd Long",
down == 3 & ydstogo <= 3 ~ "3rd Short",
down == 3 & ydstogo <= 7 ~ "3rd Med",
down == 3 ~ "3rd Long",
TRUE ~ "Other"
)
) %>%
filter(situation != "Other") %>%
group_by(situation) %>%
summarise(
plays = n(),
epa_allowed = mean(epa, na.rm = TRUE),
.groups = "drop"
) %>%
filter(plays >= 30)
ggplot(situation_performance, aes(x = reorder(situation, epa_allowed),
y = epa_allowed)) +
geom_col(aes(fill = epa_allowed > 0), show.legend = FALSE) +
scale_fill_manual(values = c("darkgreen", "darkred")) +
coord_flip() +
geom_hline(yintercept = 0, linetype = "dashed") +
labs(
title = paste(opponent, "Defense - EPA Allowed by Situation"),
subtitle = "Positive EPA = Weakness to Attack",
x = "Situation",
y = "EPA Allowed per Play"
) +
theme_minimal()
```
## Python Implementation
```python
import nfl_data_py as nfl
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Load play-by-play data
pbp = nfl.import_pbp_data([2023])
# Filter plays
plays = pbp[
(pbp['play_type'].isin(['run', 'pass'])) &
(pbp['posteam'].notna()) &
(pbp['defteam'].notna())
].copy()
def create_scouting_report(opponent_defense):
"""Generate comprehensive scouting report for opponent defense"""
opp_plays = plays[plays['defteam'] == opponent_defense].copy()
print(f"=== SCOUTING REPORT: {opponent_defense} DEFENSE ===\n")
# Overall defense
overall = opp_plays.agg({
'play_id': 'count',
'epa': 'mean',
'success': 'mean',
'yards_gained': 'mean'
})
print("Overall Defensive Efficiency:")
print(f"Total Plays: {overall['play_id']}")
print(f"EPA Allowed: {overall['epa']:.3f}")
print(f"Success Rate Allowed: {overall['success']:.1%}")
print(f"Yards per Play: {overall['yards_gained']:.2f}\n")
# Pass defense
pass_plays = opp_plays[opp_plays['play_type'] == 'pass']
print("Pass Defense:")
print(f"Dropbacks: {len(pass_plays)}")
print(f"EPA per Dropback: {pass_plays['epa'].mean():.3f}")
print(f"Yards per Attempt: {pass_plays['yards_gained'].mean():.2f}")
print(f"Completion %: {pass_plays['complete_pass'].mean():.1%}\n")
# Run defense
run_plays = opp_plays[opp_plays['play_type'] == 'run']
print("Run Defense:")
print(f"Carries: {len(run_plays)}")
print(f"EPA per Carry: {run_plays['epa'].mean():.3f}")
print(f"Yards per Carry: {run_plays['yards_gained'].mean():.2f}")
print(f"Stuff Rate: {(run_plays['yards_gained'] <= 0).mean():.1%}\n")
# Third down
third_downs = opp_plays[opp_plays['down'] == 3]
print("Third Down Defense:")
print(f"Attempts: {len(third_downs)}")
print(f"Conversion Rate: {third_downs['third_down_converted'].mean():.1%}\n")
# Red zone
redzone = opp_plays[opp_plays['yardline_100'] <= 20]
print("Red Zone Defense:")
print(f"Plays: {len(redzone)}")
print(f"EPA per Play: {redzone['epa'].mean():.3f}")
print(f"TD Rate: {redzone['touchdown'].mean():.1%}\n")
return {
'overall': overall,
'pass': pass_plays,
'run': run_plays,
'third_down': third_downs,
'redzone': redzone
}
def analyze_matchup(your_offense, opponent_defense):
"""Analyze offensive vs defensive matchup"""
print(f"=== MATCHUP: {your_offense} OFF vs {opponent_defense} DEF ===\n")
# Your offense
your_plays = plays[plays['posteam'] == your_offense]
your_summary = your_plays.groupby('play_type').agg({
'play_id': 'count',
'epa': 'mean',
'success': 'mean'
}).rename(columns={'play_id': 'plays'})
print("Your Offensive Profile:")
print(your_summary)
print()
# Opponent defense
opp_plays = plays[plays['defteam'] == opponent_defense]
opp_summary = opp_plays.groupby('play_type').agg({
'play_id': 'count',
'epa': 'mean',
'success': 'mean'
}).rename(columns={'play_id': 'plays', 'epa': 'epa_allowed'})
print("Opponent Defensive Profile:")
print(opp_summary)
print()
# Recommendations
print("=== GAME PLAN RECOMMENDATIONS ===")
pass_epa_allowed = opp_summary.loc['pass', 'epa_allowed']
run_epa_allowed = opp_summary.loc['run', 'epa_allowed']
if pass_epa_allowed > run_epa_allowed:
print(f"ATTACK: Pass game (EPA allowed: {pass_epa_allowed:.3f})")
else:
print(f"ATTACK: Run game (EPA allowed: {run_epa_allowed:.3f})")
# Third down
opp_3rd = opp_plays[opp_plays['down'] == 3]
conv_rate = opp_3rd['third_down_converted'].mean()
print(f"\n3rd Down Conv Rate Allowed: {conv_rate:.1%}")
if conv_rate > 0.42:
print("OPPORTUNITY: Opponent vulnerable on 3rd down")
return your_summary, opp_summary
# Example usage
chiefs_report = create_scouting_report('KC')
print("\n" + "="*60 + "\n")
matchup_analysis = analyze_matchup('BUF', 'KC')
# Visualize game plan
def visualize_game_plan(opponent_defense):
"""Create game plan visualizations"""
opp_plays = plays[plays['defteam'] == opponent_defense].copy()
# Categorize situations
def categorize_situation(row):
if row['down'] == 1:
return '1st Down'
elif row['down'] == 2 and row['ydstogo'] <= 5:
return '2nd & Short'
elif row['down'] == 2 and row['ydstogo'] > 5:
return '2nd & Long'
elif row['down'] == 3 and row['ydstogo'] <= 3:
return '3rd & Short'
elif row['down'] == 3 and row['ydstogo'] <= 7:
return '3rd & Med'
elif row['down'] == 3:
return '3rd & Long'
else:
return 'Other'
opp_plays['situation'] = opp_plays.apply(categorize_situation, axis=1)
opp_plays = opp_plays[opp_plays['situation'] != 'Other']
situation_summary = opp_plays.groupby('situation').agg({
'epa': 'mean',
'success': 'mean',
'play_id': 'count'
}).rename(columns={'play_id': 'plays'})
situation_summary = situation_summary[situation_summary['plays'] >= 30]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# EPA by situation
situation_summary = situation_summary.sort_values('epa', ascending=False)
colors = ['darkred' if x > 0 else 'darkgreen' for x in situation_summary['epa']]
axes[0].barh(range(len(situation_summary)), situation_summary['epa'],
color=colors, alpha=0.7)
axes[0].set_yticks(range(len(situation_summary)))
axes[0].set_yticklabels(situation_summary.index)
axes[0].set_xlabel('EPA Allowed per Play')
axes[0].set_title(f'{opponent_defense} Defense - Situational Weaknesses')
axes[0].axvline(0, color='black', linestyle='--', alpha=0.5)
axes[0].invert_yaxis()
# Pass vs Run
play_type_summary = opp_plays.groupby('play_type').agg({
'epa': 'mean',
'success': 'mean'
})
x = np.arange(len(play_type_summary))
width = 0.35
axes[1].bar(x - width/2, play_type_summary['epa'], width,
label='EPA Allowed', alpha=0.7, color='steelblue')
axes[1].bar(x + width/2, play_type_summary['success'], width,
label='Success Rate', alpha=0.7, color='orange')
axes[1].set_xticks(x)
axes[1].set_xticklabels(play_type_summary.index)
axes[1].set_ylabel('Value')
axes[1].set_title(f'{opponent_defense} - Pass vs Run Defense')
axes[1].legend()
axes[1].axhline(0, color='black', linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()
# Generate visualizations
visualize_game_plan('KC')
```
## Key Insights
### Game Planning Principles
- Target opponent's worst situational defense
- Exploit personnel mismatches in key downs
- Attack weak coverage zones consistently
- Adjust based on in-game performance
### Critical Metrics
- Third down defense (<40% conversion = attack)
- Red zone efficiency (TD rate > 55% = vulnerable)
- Explosive plays allowed (>12% = deep shot opportunity)
- Pressure rate (<25% = time for deep routes)
## Resources
- [nflfastR documentation](https://www.nflfastr.com/)
- [NFL Next Gen Stats](https://nextgenstats.nfl.com/)
- [Pro Football Focus](https://www.pff.com/)
Discussion
Have questions or feedback? Join our community discussion on
Discord or
GitHub Discussions.
Table of Contents
Related Topics
Quick Actions