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.