Tutorial: Player Comparison¶
Build a tool to compare NFL player statistics side-by-side.
What You'll Build¶
A player comparison tool that:
- Fetches stats for multiple players
- Normalizes data for comparison
- Creates formatted comparison tables
- Identifies statistical advantages
Prerequisites¶
- Completed the First API Call tutorial
- Python 3.14+ with griddy installed
Step 1: Project Setup¶
Step 2: Create the Comparison Class¶
#!/usr/bin/env python3
"""NFL Player Comparison Tool."""
import os
from dataclasses import dataclass
from typing import Dict, List, Optional, Any
from griddy.nfl import GriddyNFL
from griddy.core.exceptions import GriddyError
@dataclass
class PlayerStats:
"""Container for player statistics."""
name: str
team: str
position: str
stats: Dict[str, Any]
class PlayerComparison:
"""Compare NFL player statistics."""
def __init__(self, nfl: GriddyNFL):
self.nfl = nfl
def find_player_stats(
self,
player_name: str,
season: int,
stat_type: str = "passing"
) -> Optional[PlayerStats]:
"""Find stats for a specific player."""
# Get stats based on type
if stat_type == "passing":
data = self.nfl.stats.passing.get_passing_stats_by_season(season=season)
players = data.players
elif stat_type == "rushing":
data = self.nfl.stats.rushing.get_rushing_stats_by_season(season=season)
players = data.players
elif stat_type == "receiving":
data = self.nfl.stats.receiving.get_receiving_stats_by_season(season=season)
players = data.players
else:
return None
# Find the player
for player in players:
if player.player_name.lower() == player_name.lower():
return PlayerStats(
name=player.player_name,
team=player.team_abbreviation,
position=stat_type.upper()[:2],
stats=self._extract_stats(player, stat_type)
)
return None
def _extract_stats(self, player, stat_type: str) -> Dict[str, Any]:
"""Extract relevant stats based on position."""
if stat_type == "passing":
return {
"Games": getattr(player, 'games', 0),
"Completions": getattr(player, 'completions', 0),
"Attempts": getattr(player, 'attempts', 0),
"Comp %": getattr(player, 'completion_percentage', 0),
"Yards": getattr(player, 'passing_yards', 0),
"TD": getattr(player, 'passing_touchdowns', 0),
"INT": getattr(player, 'interceptions', 0),
"Rating": getattr(player, 'passer_rating', 0),
"YPA": getattr(player, 'yards_per_attempt', 0),
}
elif stat_type == "rushing":
return {
"Games": getattr(player, 'games', 0),
"Attempts": getattr(player, 'rushing_attempts', 0),
"Yards": getattr(player, 'rushing_yards', 0),
"TD": getattr(player, 'rushing_touchdowns', 0),
"YPC": getattr(player, 'yards_per_attempt', 0),
"Long": getattr(player, 'longest_rush', 0),
"Fumbles": getattr(player, 'fumbles', 0),
}
elif stat_type == "receiving":
return {
"Games": getattr(player, 'games', 0),
"Targets": getattr(player, 'targets', 0),
"Receptions": getattr(player, 'receptions', 0),
"Yards": getattr(player, 'receiving_yards', 0),
"TD": getattr(player, 'receiving_touchdowns', 0),
"YPR": getattr(player, 'yards_per_reception', 0),
"Long": getattr(player, 'longest_reception', 0),
}
return {}
def compare(
self,
player_names: List[str],
season: int,
stat_type: str = "passing"
) -> List[PlayerStats]:
"""Compare multiple players."""
results = []
for name in player_names:
stats = self.find_player_stats(name, season, stat_type)
if stats:
results.append(stats)
else:
print(f"Warning: Could not find stats for {name}")
return results
Step 3: Create Display Functions¶
def display_comparison(players: List[PlayerStats]):
"""Display comparison table."""
if not players:
print("No players to compare")
return
# Get all stat keys
all_stats = set()
for player in players:
all_stats.update(player.stats.keys())
all_stats = sorted(all_stats)
# Header
print("\n" + "=" * 70)
print(" PLAYER COMPARISON ".center(70, "="))
print("=" * 70)
# Player names header
header = f"{'Stat':15}"
for player in players:
header += f"{player.name[:15]:>18}"
print(header)
print("-" * 70)
# Stats rows
for stat in all_stats:
row = f"{stat:15}"
values = []
for player in players:
val = player.stats.get(stat, "N/A")
if isinstance(val, float):
row += f"{val:>18.1f}"
else:
row += f"{val:>18}"
values.append(val if val != "N/A" else 0)
# Highlight winner (higher is better for most stats)
if len(values) > 1 and all(isinstance(v, (int, float)) for v in values):
max_val = max(values)
# For negative stats (INT, Fumbles), lower is better
if stat in ["INT", "Fumbles"]:
max_val = min(values)
print(row)
print("=" * 70 + "\n")
def display_advantage_summary(players: List[PlayerStats]):
"""Show which player has advantage in each category."""
if len(players) != 2:
return
p1, p2 = players
print("ADVANTAGE SUMMARY")
print("-" * 40)
p1_advantages = []
p2_advantages = []
# Stats where higher is better
higher_better = ["Yards", "TD", "Rating", "Comp %", "YPA", "YPC", "YPR",
"Receptions", "Long", "Completions", "Attempts", "Targets"]
# Stats where lower is better
lower_better = ["INT", "Fumbles"]
for stat in p1.stats:
v1 = p1.stats.get(stat, 0)
v2 = p2.stats.get(stat, 0)
if not isinstance(v1, (int, float)) or not isinstance(v2, (int, float)):
continue
if stat in higher_better:
if v1 > v2:
p1_advantages.append(stat)
elif v2 > v1:
p2_advantages.append(stat)
elif stat in lower_better:
if v1 < v2:
p1_advantages.append(stat)
elif v2 < v1:
p2_advantages.append(stat)
print(f"{p1.name}: {', '.join(p1_advantages) or 'None'}")
print(f"{p2.name}: {', '.join(p2_advantages) or 'None'}")
print()
Step 4: Add Chart Visualization (Optional)¶
def display_bar_chart(players: List[PlayerStats], stat: str, width: int = 40):
"""Display a simple text-based bar chart for a stat."""
values = [(p.name, p.stats.get(stat, 0)) for p in players]
max_val = max(v for _, v in values) if values else 1
print(f"\n{stat}:")
for name, val in values:
bar_len = int((val / max_val) * width) if max_val > 0 else 0
bar = "█" * bar_len
print(f" {name:15} {bar} {val}")
Step 5: Main Application¶
def main():
import argparse
parser = argparse.ArgumentParser(description="NFL Player Comparison")
parser.add_argument("players", nargs="+", help="Player names to compare")
parser.add_argument("--season", type=int, default=2024)
parser.add_argument("--type", choices=["passing", "rushing", "receiving"],
default="passing", help="Stat type to compare")
parser.add_argument("--chart", help="Stat to show as bar chart")
args = parser.parse_args()
# Get token
token = os.environ.get("NFL_ACCESS_TOKEN")
if not token:
print("Please set NFL_ACCESS_TOKEN environment variable")
return
try:
nfl = GriddyNFL(nfl_auth={"accessToken": token})
comparison = PlayerComparison(nfl)
print(f"\nComparing {args.type} stats for {args.season} season...")
players = comparison.compare(args.players, args.season, args.type)
if players:
display_comparison(players)
if len(players) == 2:
display_advantage_summary(players)
if args.chart and players:
display_bar_chart(players, args.chart)
except GriddyError as e:
print(f"API Error: {e.message}")
if __name__ == "__main__":
main()
Step 6: Run Comparisons¶
# Compare two quarterbacks
python compare.py "Patrick Mahomes" "Josh Allen" --type passing
# Compare running backs
python compare.py "Derrick Henry" "Saquon Barkley" --type rushing
# Compare with chart
python compare.py "Patrick Mahomes" "Josh Allen" --chart Yards
# Three-way comparison
python compare.py "Patrick Mahomes" "Josh Allen" "Lamar Jackson"
Sample Output¶
Comparing passing stats for 2024 season...
======================================================================
====================== PLAYER COMPARISON ======================
======================================================================
Stat Patrick Mahomes Josh Allen
----------------------------------------------------------------------
Attempts 612 587
Comp % 66.2 65.8
Completions 405 386
Games 17 17
INT 11 10
Rating 98.4 101.2
TD 26 29
YPA 8.2 8.5
Yards 5027 4990
======================================================================
ADVANTAGE SUMMARY
----------------------------------------
Patrick Mahomes: Yards, Comp %, Completions
Josh Allen: TD, Rating, YPA, INT
Enhancements¶
Per-Game Stats¶
def normalize_to_per_game(stats: Dict[str, Any], games: int) -> Dict[str, Any]:
"""Convert counting stats to per-game averages."""
counting_stats = ["Yards", "TD", "Attempts", "Completions", "Receptions", "Targets"]
normalized = {}
for stat, val in stats.items():
if stat in counting_stats and isinstance(val, (int, float)) and games > 0:
normalized[f"{stat}/G"] = round(val / games, 1)
else:
normalized[stat] = val
return normalized
Export to CSV¶
def export_to_csv(players: List[PlayerStats], filename: str):
"""Export comparison to CSV."""
import csv
with open(filename, "w", newline="") as f:
writer = csv.writer(f)
# Header
header = ["Stat"] + [p.name for p in players]
writer.writerow(header)
# Stats
all_stats = set()
for p in players:
all_stats.update(p.stats.keys())
for stat in sorted(all_stats):
row = [stat] + [p.stats.get(stat, "") for p in players]
writer.writerow(row)
print(f"Exported to {filename}")
Next Steps¶
- Add historical season comparisons
- Include Next Gen Stats metrics
- Create web interface with interactive charts