Lesson 8 · Pandas Projects
Sports Analytics with Python Pandas: Player & Team Stats
A complete, standalone tutorial: build a synthetic season of basketball box scores, then rank, track, and compare players with pandas. No prior pandas…
- CoursePandas Projects
- Lesson8 of 10
- Video19 min
- FormatJupyter notebook · 18 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbPandas for Sports Analytics: Analyzing a Basketball Season#
- A complete, standalone tutorial: build a synthetic season of basketball box scores, then rank, track, and compare players with pandas.
- No prior pandas experience needed. Let's jump straight in.
Before You Start#
- Open a new Jupyter Notebook in VS Code and select your Python interpreter as the kernel.
- If pandas isn't installed yet, open a terminal in VS Code and run: pip install pandas
Part 1: Building the Dataset#
import pandas as pd
import numpy as np
print(pd.__version__)
Generating a Synthetic Season#
rng = np.random.default_rng(seed=22)
teams = {
'Comets': ['Rylan', 'Tho', 'Bea', 'Omar', 'Nia'],
'Falcons': ['Dara', 'Mo', 'Ines', 'Kwame', 'Suri']
}
games_per_player = 20
print(teams)
rows = []
for team, players in teams.items():
for player in players:
for game_num in range(1, games_per_player + 1):
minutes = round(float(rng.uniform(15, 38)), 1)
points = int(rng.poisson(14))
rebounds = int(rng.poisson(5))
assists = int(rng.poisson(4))
rows.append([team, player, game_num, minutes, points, rebounds, assists])
games = pd.DataFrame(rows, columns=['team', 'player', 'game_num', 'minutes', 'points', 'rebounds', 'assists'])
print(games.shape)
games.to_csv('season_box_scores.csv', index=False)
games = pd.read_csv('season_box_scores.csv')
games.head()
Part 2: First Look at the Data#
print(games.shape)
games.info()
games[['minutes', 'points', 'rebounds', 'assists']].describe()
games['team'].value_counts()
Part 3: Season Totals and Ranking#
season_totals = games.groupby('player').agg(
team=('team', 'first'),
games_played=('game_num', 'count'),
total_points=('points', 'sum'),
total_rebounds=('rebounds', 'sum'),
total_assists=('assists', 'sum')
).sort_values('total_points', ascending=False)
season_totals
Ranking with rank()#
season_totals['points_rank'] = season_totals['total_points'].rank(ascending=False).astype(int)
season_totals[['team', 'total_points', 'points_rank']]
Per-Game Averages#
per_game_avg = games.groupby('player')[['points', 'rebounds', 'assists']].mean().round(1)
per_game_avg = per_game_avg.sort_values('points', ascending=False)
per_game_avg
Part 4: A Simple Efficiency Metric#
games['efficiency'] = (games['points'] + games['rebounds'] + games['assists']) / games['minutes']
games[['player', 'game_num', 'minutes', 'efficiency']].head()
avg_efficiency = games.groupby('player')['efficiency'].mean().sort_values(ascending=False)
avg_efficiency.round(3)
Part 5: Cumulative and Rolling Stats#
games = games.sort_values(['player', 'game_num'])
games['cumulative_points'] = games.groupby('player')['points'].cumsum()
games[games['player'] == 'Rylan'][['game_num', 'points', 'cumulative_points']].tail()
games['rolling_5g_avg'] = games.groupby('player')['points'].transform(lambda s: s.rolling(window=5).mean())
games[games['player'] == 'Dara'][['game_num', 'points', 'rolling_5g_avg']].tail(8)
Part 6: Comparing Teams#
team_comparison = games.groupby('team').agg(
avg_points=('points', 'mean'),
avg_rebounds=('rebounds', 'mean'),
avg_assists=('assists', 'mean'),
avg_efficiency=('efficiency', 'mean')
).round(2)
team_comparison
Pivot Table: Player Scoring by Game Range#
games['season_third'] = pd.cut(games['game_num'], bins=[0, 7, 14, 20], labels=['Games 1-7', 'Games 8-14', 'Games 15-20'])
scoring_trend = pd.pivot_table(games, values='points', index='player', columns='season_third', aggfunc='mean', observed=True).round(1)
scoring_trend
Part 7: Visualizing the Results#
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5))
per_game_avg['points'].plot(kind='bar', ax=ax, color='darkorange')
ax.set_title('Points Per Game by Player')
ax.set_ylabel('Points Per Game')
plt.tight_layout()
plt.savefig('points_per_game.png', dpi=150)
plt.close(fig)
print('Saved points_per_game.png')
rylan_games = games[games['player'] == 'Rylan']
fig, ax = plt.subplots(figsize=(9, 5))
ax.plot(rylan_games['game_num'], rylan_games['points'], color='gray', alpha=0.5, marker='o', label='Points per game')
ax.plot(rylan_games['game_num'], rylan_games['rolling_5g_avg'], color='crimson', linewidth=2, label='5-game rolling average')
ax.set_title("Rylan's Scoring Trend This Season")
ax.set_xlabel('Game Number')
ax.set_ylabel('Points')
ax.legend()
plt.tight_layout()
plt.savefig('player_scoring_trend.png', dpi=150)
plt.close(fig)
print('Saved player_scoring_trend.png')
Wrap-Up: What You Learned#
- Generating a realistic synthetic season of box scores with NumPy's Poisson distribution, then saving and reloading with to_csv and read_csv.
- First-look exploration: shape, info, describe, and value_counts.
- Season totals with a multi-statistic agg, plus rank() for a real leaderboard.
- Building your own derived metric, like our per-minute efficiency score.
- Cumulative totals with cumsum, and per-player rolling averages with groupby plus transform.
- Team-level comparisons, and a pivot_table binned by season third to spot trends over time.
- You went from raw synthetic box scores to a full season analysis with rankings, trends, and charts. If you want the next dataset in this series to land in your feed automatically, subscribing is the move see you in the next one.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



