๐ NBA hoops with sportsdataverse-py
Welcome to the hardwood! ๐ In just a few lines of Python you're about to pull a whole season of NBA data โ teams, standings, rosters, play-by-play, box scores, schedules and statistical leaders โ straight from ESPN and the SportsDataverse data releases. Everything comes back as a tidy polars DataFrame that's ready to slice, model, and chart. ๐
We lead with the richest surface in the package: the espn_nba_* family,
backed by ESPN's site / web / core APIs. If you know the R package
hoopR, these names will feel like home.
Python neighbor for the raw NBA Stats endpoints:
nba_api. Let's lace 'em up! ๐
๐งฐ The toolboxโ
Here's the kit we'll reach for. The espn_nba_* wrappers (โญ our
premium source) hit ESPN live and parse the JSON into polars for you; the
load_nba_* loaders pull pre-built season parquets from the
sportsdataverse-data
releases โ fast and reliable. Click any name for the full reference.
| Function | What it gives you | Source |
|---|---|---|
espn_nba_teams | All 30 NBA teams (grab team_ids here) | โญ ESPN |
espn_nba_scoreboard | A day's slate โ scores, status, matchups | โญ ESPN |
espn_nba_schedule | Schedule for a date / date-range | โญ ESPN |
espn_nba_standings | Conference standings (W-L, win%, streak) | โญ ESPN |
espn_nba_team_roster | A team's active roster | โญ ESPN |
espn_nba_team_schedule | One team's full-season schedule | โญ ESPN |
espn_nba_player_gamelog | A player's game-by-game log | โญ ESPN |
espn_nba_leaders | League statistical leaders | โญ ESPN |
espn_nba_pbp | Full game payload (play-by-play, win prob, box) | โญ ESPN |
espn_nba_game_rosters | Both teams' rosters for one game | โญ ESPN |
load_nba_schedule | Multi-season schedule parquet | ๐ฆ release |
load_nba_player_boxscore | Player box scores, every game | ๐ฆ release |
load_nba_standings | Historical standings | ๐ฆ release |
espn_nba_injuries | League-wide injury report, one row per team | โญ ESPN |
load_nba_team_boxscore | Team box scores, every game (off/def, shooting) | ๐ฆ release |
load_nba_shots | Every made shot with court coordinates | ๐ฆ release |
most_recent_nba_season | The current season year helper | ๐งฎ util |
๐ Setupโ
pip install sportsdataverse
No API key needed โ ESPN's public endpoints and the data releases are open. ๐
import polars as pl
import sportsdataverse as sdv
from sportsdataverse.nba import most_recent_nba_season
pl.Config.set_tbl_rows(8)
SEASON = most_recent_nba_season()
print('current NBA season:', SEASON)
current NBA season: 2026
ESPN endpoints are live and seasonal, so we'll route every network call
through a tiny safe() helper. When the feed is up you get the frame; when
it's mid-offseason or briefly rate-limited you get a friendly one-liner
instead of a scary traceback. ๐
def safe(label, thunk):
try:
out = thunk()
n = out.height if isinstance(out, pl.DataFrame) else (len(out) if hasattr(out, '__len__') else '?')
print(f'โ
{label} โ {n} rows')
return out
except Exception as e: # noqa: BLE001 -- demo resilience
print(f'โญ๏ธ {label}: unavailable right now ({type(e).__name__})')
return None
๐๏ธ Teamsโ
Start with espn_nba_teams โ
one wide row per franchise. The team_id column is the key you'll pass into
roster, schedule and standings calls everywhere else.
teams = safe('teams', sdv.nba.espn_nba_teams)
(teams.select(['team_id', 'team_location', 'team_name', 'team_abbreviation', 'team_color']).head(10)
if teams is not None else 'teams feed unavailable')
โ
teams โ 30 rows
shape: (10, 5)
โโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ team_id โ team_location โ team_name โ team_abbreviation โ team_color โ
โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ str โ str โ str โ
โโโโโโโโโโโชโโโโโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโ โโโชโโโโโโโโโโโโโก
โ 1 โ Atlanta โ Hawks โ ATL โ c8102e โ
โ 2 โ Boston โ Celtics โ BOS โ 008348 โ
โ 17 โ Brooklyn โ Nets โ BKN โ 000000 โ
โ 30 โ Charlotte โ Hornets โ CHA โ 008ca8 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ 6 โ Dallas โ Mavericks โ DAL โ 0064b1 โ
โ 7 โ Denver โ Nuggets โ DEN โ 0e2240 โ
โ 8 โ Detroit โ Pistons โ DET โ 1d428a โ
โ 9 โ Golden State โ Warriors โ GS โ fdb927 โ
โโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโ
๐ Today on the slate (scoreboard)โ
espn_nba_scoreboard returns a
tidy frame of every game for a date โ final scores, live status, and
matchups. Pass dates='YYYYMMDD' for one day. Here's a slice of the 2024
NBA Finals opener.
sb = safe('scoreboard', lambda: sdv.nba.espn_nba_scoreboard(dates='20240606'))
keep = ['game_id', 'short_name', 'home_abbreviation', 'away_abbreviation',
'home_score', 'away_score', 'status_type_detail']
(sb.select([c for c in keep if c in sb.columns]).head()
if sb is not None and sb.height else 'no games on that date')
โ
scoreboard โ 1 rows
shape: (1, 7)
โโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ game_id โ short_name โ home_abbrevia โ away_abbrevia โ home_score โ away_score โ status_type_d โ
โ --- โ --- โ tion โ tion โ --- โ --- โ etail โ
โ str โ str โ --- โ --- โ str โ str โ --- โ
โ โ โ str โ str โ โ โ str โ
โโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโก
โ 401656359 โ DAL @ BOS โ BOS โ DAL โ 107 โ 89 โ Final โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโ
๐ Standingsโ
espn_nba_standings gives one
row per team with wins, losses, win%, point differential and current streak.
Pass season= (the end year of the season).
standings = safe('standings', lambda: sdv.nba.espn_nba_standings(season=SEASON))
cols = ['team_display_name', 'wins', 'losses', 'win_percent', 'games_behind',
'point_differential', 'streak']
(standings.select([c for c in cols if c in standings.columns])
.sort('win_percent', descending=True).head(10)
if standings is not None and standings.height else 'standings unavailable')
โ
standings โ 30 rows
shape: (10, 7)
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโ
โ team_display_name โ wins โ losses โ win_percent โ games_behind โ point_differential โ streak โ
โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ
โ str โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโชโโโโโโโโโชโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโก
โ Oklahoma City Thunder โ 64.0 โ 18.0 โ 0.7804878 โ 0.0 โ 914.0 โ -2.0 โ
โ San Antonio Spurs โ 62.0 โ 20.0 โ 0.756098 โ 2.0 โ 681.0 โ -1.0 โ
โ Detroit Pistons โ 60.0 โ 22.0 โ 0.731707 โ 0.0 โ 669.0 โ 3.0 โ
โ Boston Celtics โ 56.0 โ 26.0 โ 0.682927 โ 4.0 โ 631.0 โ 2.0 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ Los Angeles Lakers โ 53.0 โ 29.0 โ 0.646341 โ 11.0 โ 145.0 โ 3.0 โ
โ Cleveland Cavaliers โ 52.0 โ 30.0 โ 0.634146 โ 8.0 โ 336.0 โ 1.0 โ
โ Houston Rockets โ 52.0 โ 30.0 โ 0.634146 โ 12.0 โ 428.0 โ 1.0 โ
โ Minnesota โ 49.0 โ 33.0 โ 0.597561 โ 15.0 โ 275.0 โ 2.0 โ
โ Timberwolves โ โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโ
๐ณ Cookbook: common NBA tasksโ
Now the fun part โ a handful of recipes you'll reach for again and again.
Each one leans on the premium espn_nba_* wrappers.
Recipe 1 โ A team and its roster ๐ฅโ
Grab a team_id from espn_nba_teams,
then pull the active roster with
espn_nba_team_roster.
tid = None
if teams is not None and teams.height:
# Boston Celtics if present, else the first team
row = teams.filter(pl.col('team_abbreviation') == 'BOS')
tid = int((row if row.height else teams)['team_id'][0])
roster = safe(f'roster {tid}', lambda: sdv.nba.espn_nba_team_roster(team_id=tid)) if tid else None
cols = ['full_name', 'jersey', 'position_abbreviation', 'height', 'weight', 'age']
(roster.select([c for c in cols if c in roster.columns]).head(10)
if roster is not None and roster.height else 'roster unavailable')
โ
roster 2 โ 16 rows
shape: (10, 6)
โโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโฌโโโโโโ
โ full_name โ jersey โ position_abbreviation โ height โ weight โ age โ
โ --- โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ str โ f64 โ f64 โ i64 โ
โโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโชโโโโโโโโโชโโโโโโก
โ Dalano Banton โ 45 โ F โ 80.0 โ 203.0 โ 26 โ
โ Jaylen Brown โ 7 โ G โ 78.0 โ 223.0 โ 29 โ
โ Luka Garza โ 52 โ C โ 82.0 โ 243.0 โ 27 โ
โ Hugo Gonzalez โ 28 โ G โ 78.0 โ 200.0 โ 20 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ Payton Pritchard โ 11 โ G โ 73.0 โ 195.0 โ 28 โ
โ Neemias Queta โ 88 โ C โ 84.0 โ 248.0 โ 26 โ
โ Baylor Scheierman โ 55 โ G โ 78.0 โ 205.0 โ 25 โ
โ Max Shulga โ 44 โ G โ 76.0 โ 210.0 โ 23 โ
โโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโดโโโโโโ
Recipe 2 โ One team's season schedule ๐โ
espn_nba_team_schedule
returns every game on a team's calendar for a season โ perfect for building
a results table or a strength-of-schedule view.
tsched = safe(f'team schedule {tid}',
lambda: sdv.nba.espn_nba_team_schedule(team_id=tid, season=SEASON)) if tid else None
cols = ['id', 'date', 'name', 'short_name', 'season_year']
(tsched.select([c for c in cols if c in tsched.columns]).head()
if tsched is not None and tsched.height else 'team schedule unavailable')
โ
team schedule 2 โ 7 rows
shape: (5, 5)
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ id โ date โ name โ short_name โ season_year โ
โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ str โ str โ i64 โ
โโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโโก
โ 401869191 โ 2026-04-19T17:00Z โ Philadelphia 76ers at Boston Cโฆ โ PHI @ BOS โ 2026 โ
โ 401869396 โ 2026-04-21T23:00Z โ Philadelphia 76ers at Boston Cโฆ โ PHI @ BOS โ 2026 โ
โ 401869404 โ 2026-04-24T23:00Z โ Boston Celtics at Philadelphiaโฆ โ BOS @ PHI โ 2026 โ
โ 401869406 โ 2026-04-26T23:00Z โ Boston Celtics at Philadelphiaโฆ โ BOS @ PHI โ 2026 โ
โ 401869408 โ 2026-04-28T23:00Z โ Philadelphia 76ers at Boston Cโฆ โ PHI @ BOS โ 2026 โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
Recipe 3 โ A player's game log โน๏ธโ
espn_nba_player_gamelog
returns a game-by-game stat line for one athlete. The stat_* columns are
positional (the ordered ESPN box categories); pair them with the opponent
and result columns to see how the night went. (1966 = LeBron James.)
gamelog = safe('LeBron gamelog',
lambda: sdv.nba.espn_nba_player_gamelog(athlete_id=1966, season=SEASON))
cols = ['event_date', 'opponent_abbreviation', 'home_away', 'game_result', 'score',
'stat_0', 'stat_1', 'stat_2']
(gamelog.select([c for c in cols if c in gamelog.columns]).head()
if gamelog is not None and gamelog.height else 'gamelog unavailable')
โ
LeBron gamelog โ 73 rows
shape: (5, 8)
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโ
โ event_date โ opponent_abbreviation โ home_away โ game_result โ score โ stat_0 โ stat_1 โ stat_2 โ
โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ str โ str โ str โ str โ str โ str โ
โโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโโโชโโโโโโโโชโโโโโโโโโชโโโโโโโโโชโโโโโโโโโก
โ null โ null โ null โ null โ null โ 40 โ 8-18 โ 44.4 โ
โ null โ null โ null โ null โ null โ 37 โ 7-19 โ 36.8 โ
โ null โ null โ null โ null โ null โ 38 โ 9-18 โ 50.0 โ
โ null โ null โ null โ null โ null โ 36 โ 12-17 โ 70.6 โ
โ null โ null โ null โ null โ null โ 37 โ 10-25 โ 40.0 โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโโดโโโโโโโโโดโโโโโโโโโ
Recipe 4 โ Top scorers from the box-score release ๐ฅโ
For a whole-season leaderboard the
load_nba_player_boxscore
release is your friend โ it's a fast parquet download, no live API needed.
Here we average points per game and rank the top 10 scorers.
box = safe('player boxscore release', lambda: sdv.nba.load_nba_player_boxscore(seasons=[SEASON]))
if box is not None and box.height:
leaders = (
box.filter(pl.col('minutes') > 0)
.group_by(['athlete_display_name', 'team_abbreviation'])
.agg(pl.len().alias('gp'),
pl.col('points').mean().round(1).alias('ppg'),
pl.col('rebounds').mean().round(1).alias('rpg'),
pl.col('assists').mean().round(1).alias('apg'))
.filter(pl.col('gp') >= 20)
.sort('ppg', descending=True)
.head(10)
)
out = leaders
else:
out = 'box-score release unavailable'
out
โ
player boxscore release โ 34883 rows
shape: (10, 6)
โโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโฌโโโโโโโฌโโโโโโโฌโโโโโโโ
โ athlete_display_name โ team_abbreviation โ gp โ ppg โ rpg โ apg โ
โ --- โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ u32 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโช โโโโโโชโโโโโโโชโโโโโโโชโโโโโโโก
โ Luka Doncic โ LAL โ 64 โ 33.5 โ 7.7 โ 8.3 โ
โ Shai Gilgeous-Alexander โ OKC โ 83 โ 30.5 โ 4.0 โ 6.8 โ
โ Jaylen Brown โ BOS โ 78 โ 28.4 โ 6.8 โ 5.0 โ
โ Anthony Edwards โ MIN โ 71 โ 27.8 โ 5.1 โ 3.6 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ Giannis Antetokounmpo โ MIL โ 36 โ 27.6 โ 9.8 โ 5.4 โ
โ Donovan Mitchell โ CLE โ 88 โ 27.5 โ 4.6 โ 5.1 โ
โ Nikola Jokic โ DEN โ 71 โ 27.5 โ 12.9 โ 10.6 โ
โ Lauri Markkanen โ UTAH โ 42 โ 26.7 โ 6.9 โ 2.1 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโดโโโโโโโดโโโโโโโดโโโโโโโ
Recipe 5 โ Offense vs defense, every team ๐ก๏ธโ
The load_nba_team_boxscore
release has one row per team-game with both team_score and
opponent_team_score โ so points-for, points-against and net rating are a
single group_by away.
tbox = safe('team boxscore release', lambda: sdv.nba.load_nba_team_boxscore(seasons=[SEASON]))
if tbox is not None and tbox.height:
netrtg = (
tbox.group_by('team_abbreviation')
.agg(pl.len().alias('gp'),
pl.col('team_score').mean().round(1).alias('off_ppg'),
pl.col('opponent_team_score').mean().round(1).alias('def_ppg'))
.with_columns((pl.col('off_ppg') - pl.col('def_ppg')).round(1).alias('net'))
.sort('net', descending=True)
.head(10)
)
out = netrtg
else:
out = 'team box-score release unavailable'
out
โ
team boxscore release โ 2652 rows
shape: (10, 5)
โโโโโโโโโโโโโโโโโโโโโฌโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโ
โ team_abbreviation โ gp โ off_ppg โ def_ppg โ net โ
โ --- โ --- โ --- โ --- โ --- โ
โ str โ u32 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโโโโโโโโชโโโโโโชโโโโโโโโโโชโโโโโโโโโโชโโโโโโโก
โ OKC โ 97 โ 118.5 โ 108.0 โ 10.5 โ
โ STARS โ 3 โ 41.3 โ 32.7 โ 8.6 โ
โ NY โ 102 โ 116.4 โ 108.4 โ 8.0 โ
โ SA โ 106 โ 118.2 โ 110.2 โ 8.0 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ HOU โ 88 โ 114.1 โ 109.4 โ 4.7 โ
โ DEN โ 88 โ 121.1 โ 116.6 โ 4.5 โ
โ CHA โ 84 โ 115.8 โ 111.5 โ 4.3 โ
โ CLE โ 100 โ 117.4 โ 114.6 โ 2.8 โ
โโโโโโโโโโโโโโโโโโโโโดโโโโโโดโโโโโโโโโโดโโโโโโโโโโดโโโโโโโ
Recipe 6 โ Who lived behind the arc? ๐ฏโ
Sum makes and attempts across the season to get each team's true
three-point percentage (game-level percentages can't just be averaged).
Reuses the tbox frame from Recipe 5 โ no second download.
if tbox is not None and tbox.height:
three_pt = (
tbox.group_by('team_abbreviation')
.agg(pl.col('three_point_field_goals_made').sum().alias('made'),
pl.col('three_point_field_goals_attempted').sum().alias('att'))
.with_columns((100 * pl.col('made') / pl.col('att')).round(1).alias('three_pt_pct'))
.filter(pl.col('att') > 0)
.sort('three_pt_pct', descending=True)
.head(10)
)
out = three_pt
else:
out = 'team box-score release unavailable'
out
shape: (10, 4)
โโโโโโโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโฌโโโโโโโโโโโโโโโ
โ team_abbreviation โ made โ att โ three_pt_pct โ
โ --- โ --- โ --- โ --- โ
โ str โ i32 โ i32 โ f64 โ
โโโโโโโโโโโโโโโโโโโโโชโโโโโโโชโโโโโโโชโโโโโโโโโโโโโโโก
โ WORLD โ 11 โ 26 โ 42.3 โ
โ STRIPES โ 21 โ 52 โ 40.4 โ
โ DEN โ 1221 โ 3127 โ 39.0 โ
โ MIL โ 1240 โ 3205 โ 38.7 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ
โ LAC โ 1033 โ 2807 โ 36.8 โ
โ ATL โ 1269 โ 3455 โ 36.7 โ
โ MIN โ 1254 โ 3423 โ 36.6 โ
โ OKC โ 1336 โ 3662 โ 36.5 โ
โโโโโโโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโดโโโโโโโโโโโโโโโ
Recipe 7 โ Double-double machines ๐ชโ
A double-double is double digits in two of points / rebounds / assists.
Count the categories per player-game, keep the ones that cleared two, then
tally them up โ straight from
load_nba_player_boxscore.
pbox = safe('player boxscore release', lambda: sdv.nba.load_nba_player_boxscore(seasons=[SEASON]))
if pbox is not None and pbox.height:
dd = (
pbox.filter(pl.col('minutes') > 0)
.with_columns(
((pl.col('points') >= 10).cast(pl.Int8)
+ (pl.col('rebounds') >= 10).cast(pl.Int8)
+ (pl.col('assists') >= 10).cast(pl.Int8)).alias('cats10'))
.filter(pl.col('cats10') >= 2)
.group_by(['athlete_display_name', 'team_abbreviation'])
.agg(pl.len().alias('double_doubles'))
.sort('double_doubles', descending=True)
.head(10)
)
out = dd
else:
out = 'player box-score release unavailable'
out
โ
player boxscore release โ 34883 rows
shape: (10, 3)
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ athlete_display_name โ team_abbreviation โ double_doubles โ
โ --- โ --- โ --- โ
โ str โ str โ u32 โ
โโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโก
โ Karl-Anthony Towns โ NY โ 69 โ
โ Nikola Jokic โ DEN โ 61 โ
โ Victor Wembanyama โ SA โ 54 โ
โ Jalen Johnson โ ATL โ 51 โ
โ โฆ โ โฆ โ โฆ โ
โ Donovan Clingan โ POR โ 37 โ
โ Alperen Sengun โ HOU โ 37 โ
โ Rudy Gobert โ MIN โ 37 โ
โ Bam Adebayo โ MIA โ 35 โ
โโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
Recipe 8 โ A tidy standings table ๐โ
The load_nba_standings
release ships in long format (one row per team ร stat). Pivot the stats
you care about into columns to get a classic standings grid, sorted by
win percentage.
stload = safe('standings release', lambda: sdv.nba.load_nba_standings(seasons=[SEASON]))
wanted = ['wins', 'losses', 'winPercent', 'playoffSeed', 'pointDifferential']
if stload is not None and stload.height and {'stat_name', 'value'}.issubset(stload.columns):
table = (
stload.filter(pl.col('stat_name').is_in(wanted))
.select(['team_abbreviation', 'group_name', 'stat_name', 'value'])
.pivot(values='value', index=['team_abbreviation', 'group_name'], on='stat_name')
.sort('winPercent', descending=True)
.head(12)
)
out = table
else:
out = 'standings release unavailable'
out
โ
standings release โ 690 rows
shape: (12, 7)
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโ
โ team_abbreviation โ group_name โ losses โ playoffSeed โ pointDifferential โ winPercent โ wins โ
โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ f64 โ f64 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโชโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโก
โ OKC โ Western โ 18.0 โ 1.0 โ 914.0 โ 0.7804878 โ 64.0 โ
โ โ Conference โ โ โ โ โ โ
โ SA โ Western โ 20.0 โ 2.0 โ 681.0 โ 0.756098 โ 62.0 โ
โ โ Conference โ โ โ โ โ โ
โ DET โ Eastern โ 22.0 โ 1.0 โ 669.0 โ 0.731707 โ 60.0 โ
โ โ Conference โ โ โ โ โ โ
โ BOS โ Eastern โ 26.0 โ 2.0 โ 631.0 โ 0.682927 โ 56.0 โ
โ โ Conference โ โ โ โ โ โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ HOU โ Western โ 30.0 โ 5.0 โ 428.0 โ 0.634146 โ 52.0 โ
โ โ Conference โ โ โ โ โ โ
โ MIN โ Western โ 33.0 โ 6.0 โ 275.0 โ 0.597561 โ 49.0 โ
โ โ Conference โ โ โ โ โ โ
โ ATL โ Eastern โ 36.0 โ 6.0 โ 198.0 โ 0.5609756 โ 46.0 โ
โ โ Conference โ โ โ โ โ โ
โ TOR โ Eastern โ 36.0 โ 5.0 โ 232.0 โ 0.5609756 โ 46.0 โ
โ โ Conference โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโ
Recipe 9 โ Built on threes (shot release + a join) ๐งฑโ
load_nba_shots is one row per
made shot with a score_value. Tally points from twos vs threes per team,
then join team abbreviations from the box-score release to find who
leaned hardest on the long ball.
shots = safe('shots release', lambda: sdv.nba.load_nba_shots(seasons=[SEASON]))
if shots is not None and shots.height and tbox is not None and tbox.height:
fg = shots.filter(pl.col('score_value').is_in([2, 3]))
reliance = (
fg.group_by('team_id')
.agg(pl.col('score_value').filter(pl.col('score_value') == 3).len().alias('threes_made'),
pl.col('score_value').sum().alias('points_from_fg'))
.with_columns((3 * pl.col('threes_made')).alias('points_from_threes'))
.with_columns((100 * pl.col('points_from_threes') / pl.col('points_from_fg'))
.round(1).alias('pct_pts_from_3'))
.filter(pl.col('threes_made') >= 500) # drop All-Star / special rosters
)
abbr = tbox.select(['team_id', 'team_abbreviation']).unique()
out = (reliance.join(abbr, on='team_id', how='left')
.select(['team_abbreviation', 'threes_made', 'pct_pts_from_3'])
.sort('pct_pts_from_3', descending=True).head(10))
else:
out = 'shots / team box-score release unavailable'
out
โ
shots release โ 298411 rows
shape: (10, 3)
โโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ team_abbreviation โ threes_made โ pct_pts_from_3 โ
โ --- โ --- โ --- โ
โ str โ u32 โ f64 โ
โโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโก
โ CHA โ 1373 โ 50.0 โ
โ GS โ 1316 โ 48.2 โ
โ MIL โ 1240 โ 46.9 โ
โ BOS โ 1377 โ 46.8 โ
โ โฆ โ โฆ โ โฆ โ
โ BKN โ 1073 โ 44.6 โ
โ MEM โ 1143 โ 43.3 โ
โ ATL โ 1269 โ 43.0 โ
โ CHI โ 1144 โ 42.9 โ
โโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
Recipe 10 โ Head-to-head, game by game ๐คโ
Filter the team box-score release to one matchup and you get the full season series โ every meeting, the score, and who won. Swap the two abbreviations for any rivalry you like.
TEAM_A, TEAM_B = 'BOS', 'NY'
if tbox is not None and tbox.height and 'opponent_team_abbreviation' in tbox.columns:
series = (
tbox.filter((pl.col('team_abbreviation') == TEAM_A)
& (pl.col('opponent_team_abbreviation') == TEAM_B))
.select([c for c in ['game_date', 'team_home_away', 'team_score',
'opponent_team_score', 'team_winner']
if c in tbox.columns])
.sort('game_date')
)
out = series if series.height else f'no {TEAM_A} vs {TEAM_B} games in {SEASON}'
else:
out = 'team box-score release unavailable'
out
shape: (4, 5)
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ game_date โ team_home_away โ team_score โ opponent_team_score โ team_winner โ
โ --- โ --- โ --- โ --- โ --- โ
โ date โ str โ i32 โ i32 โ bool โ
โโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโก
โ 2025-10-24 โ away โ 95 โ 105 โ false โ
โ 2025-12-02 โ home โ 123 โ 117 โ true โ
โ 2026-02-08 โ home โ 89 โ 111 โ false โ
โ 2026-04-09 โ away โ 106 โ 112 โ false โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
Recipe 11 โ Who's banged up? ๐ฉน (pandas interop)โ
espn_nba_injuries hits ESPN
live for the league-wide injury report. Ask for a pandas frame with
return_as_pandas=True (a handy interop point), count the listed players
per team, then hand the result back to polars for the final sort.
import ast
inj = safe('injuries', lambda: sdv.nba.espn_nba_injuries(return_as_pandas=True))
if inj is not None and getattr(inj, 'shape', (0,))[0] and 'injuries' in inj.columns:
def _n_listed(s):
try:
v = ast.literal_eval(s) if isinstance(s, str) else s
return len(v) if isinstance(v, list) else 0
except Exception:
return 0
inj = inj.copy()
inj['players_listed'] = inj['injuries'].apply(_n_listed)
out = (pl.from_pandas(inj[['display_name', 'players_listed']])
.filter(pl.col('players_listed') > 0)
.sort('players_listed', descending=True)
.head(12))
else:
out = 'injury report unavailable (off-season or feed down)'
out
โ
injuries โ 27 rows
shape: (12, 2)
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ display_name โ players_listed โ
โ --- โ --- โ
โ str โ i64 โ
โโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโก
โ Memphis Grizzlies โ 13 โ
โ Brooklyn Nets โ 10 โ
โ Chicago Bulls โ 10 โ
โ Indiana Pacers โ 9 โ
โ โฆ โ โฆ โ
โ Washington Wizards โ 8 โ
โ New Orleans Pelicans โ 6 โ
โ Golden State Warriors โ 4 โ
โ Miami Heat โ 4 โ
โโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
๐ฌ Play-by-play & game rostersโ
Now for the granular stuff. espn_nba_pbp
returns the whole game payload as a dict โ play-by-play, win probability,
box score, and header โ keyed by game_id (an ESPN event id). Pair it with
espn_nba_game_rosters for who actually
suited up.
We'll use Game 1 of the 2024 Finals (game_id=401585660).
GAME_ID = 401585660
pbp = safe('pbp payload', lambda: sdv.nba.espn_nba_pbp(game_id=GAME_ID))
(list(pbp.keys())[:8] if isinstance(pbp, dict) else 'pbp unavailable')
โ
pbp payload โ 22 rows
['gameId',
'plays',
'winprobability',
'boxscore',
'header',
'format',
'broadcasts',
'videos']
plays = (pl.DataFrame(pbp['plays'], infer_schema_length=None)
if isinstance(pbp, dict) and pbp.get('plays') else None)
cols = ['period.number', 'clock.displayValue', 'text', 'homeScore', 'awayScore', 'scoringPlay']
(plays.select([c for c in cols if c in plays.columns]).head()
if plays is not None and plays.height else 'no plays parsed')
shape: (5, 6)
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ period.number โ clock.displayValue โ text โ homeScore โ awayScore โ scoringPlay โ
โ --- โ --- โ --- โ --- โ --- โ --- โ
โ i64 โ str โ str โ i64 โ i64 โ bool โ
โโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโโโก
โ 1 โ 12:00 โ Myles Turner vs. โ 0 โ 0 โ false โ
โ โ โ Anthony Davisโฆ โ โ โ โ
โ 1 โ 11:42 โ Aaron Nesmith makes โ 0 โ 3 โ true โ
โ โ โ 26-foot thโฆ โ โ โ โ
โ 1 โ 11:17 โ Austin Reaves misses โ 0 โ 3 โ false โ
โ โ โ driving lโฆ โ โ โ โ
โ 1 โ 11:14 โ Austin Reaves โ 0 โ 3 โ false โ
โ โ โ offensive rebounโฆ โ โ โ โ
โ 1 โ 11:12 โ Austin Reaves misses โ 0 โ 3 โ false โ
โ โ โ 14-foot tโฆ โ โ โ โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโโโ
Slice it: every 3-pointer in the game ๐ฏโ
The plays frame is just polars โ so a scoring slice is one filter away.
Here we pull made three-pointers in chronological order.
if plays is not None and plays.height:
threes = (
plays.filter(pl.col('scoringPlay') == True)
.filter(pl.col('text').str.contains('(?i)three point|3pt|three-point'))
.select([c for c in ['period.number', 'clock.displayValue', 'text',
'homeScore', 'awayScore'] if c in plays.columns])
)
out = threes.head(10) if threes.height else 'no three-pointers matched the text filter'
else:
out = 'no plays to slice'
out
shape: (10, 5)
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ period.number โ clock.displayValue โ text โ homeScore โ awayScore โ
โ --- โ --- โ --- โ --- โ --- โ
โ i64 โ str โ str โ i64 โ i64 โ
โโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโก
โ 1 โ 11:42 โ Aaron Nesmith makes 26-foot thโฆ โ 0 โ 3 โ
โ 1 โ 10:14 โ Andrew Nembhard makes 23-foot โฆ โ 2 โ 6 โ
โ 1 โ 9:58 โ LeBron James makes 26-foot thrโฆ โ 5 โ 6 โ
โ 1 โ 5:44 โ Myles Turner makes 25-foot thrโฆ โ 15 โ 19 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ 2 โ 10:05 โ Max Christie makes 25-foot thrโฆ โ 41 โ 40 โ
โ 2 โ 9:39 โ Obi Toppin makes 27-foot threeโฆ โ 41 โ 43 โ
โ 2 โ 8:44 โ Aaron Nesmith makes 26-foot thโฆ โ 41 โ 49 โ
โ 2 โ 7:02 โ Rui Hachimura makes 22-foot thโฆ โ 51 โ 51 โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโ
Who played? Game rosters ๐โ
espn_nba_game_rosters returns both teams'
rosters for a single game, one row per athlete โ including the starter
flag and jersey number.
grosters = safe('game rosters', lambda: sdv.nba.espn_nba_game_rosters(game_id=GAME_ID))
cols = ['athlete_display_name', 'team_abbreviation', 'starter', 'jersey', 'position_name']
(grosters.select([c for c in cols if c in grosters.columns]).head(10)
if grosters is not None and grosters.height else 'game rosters unavailable')
โ
game rosters โ 26 rows
shape: (10, 4)
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโ
โ athlete_display_name โ team_abbreviation โ starter โ jersey โ
โ --- โ --- โ --- โ --- โ
โ str โ str โ bool โ str โ
โโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโ โโโชโโโโโโโโโโชโโโโโโโโโก
โ LeBron James โ LAL โ true โ 23 โ
โ Anthony Davis โ LAL โ true โ 23 โ
โ Rui Hachimura โ LAL โ true โ 28 โ
โ Spencer Dinwiddie โ LAL โ true โ 26 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ
โ Cam Reddish โ LAL โ false โ 5 โ
โ Jaxson Hayes โ LAL โ false โ 11 โ
โ Max Christie โ LAL โ false โ 00 โ
โ Harry Giles III โ LAL โ false โ 20 โ
โโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโ
๐ฆ Bulk season data with the loadersโ
When you want everything for a season at once โ not one game at a time โ
the load_nba_* loaders pull pre-built parquet releases. They're fast,
reliable, and don't depend on a live API being up.
| Loader | Grain |
|---|---|
load_nba_schedule | one row per game |
load_nba_player_boxscore | one row per player-game |
load_nba_standings | one row per team-season |
sched = safe('schedule release', lambda: sdv.nba.load_nba_schedule(seasons=[SEASON]))
cols = ['id', 'date', 'home_display_name', 'away_display_name', 'home_score', 'away_score']
(sched.select([c for c in cols if c in sched.columns]).head()
if sched is not None and sched.height else 'schedule release unavailable')
โ
schedule release โ 1330 rows
shape: (5, 6)
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ id โ date โ home_display_name โ away_display_name โ home_score โ away_score โ
โ --- โ --- โ --- โ --- โ --- โ --- โ
โ i32 โ str โ str โ str โ i32 โ i32 โ
โโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโก
โ 401859967 โ 2026-06-14T00:30Z โ San Antonio Spurs โ New York Knicks โ 90 โ 94 โ
โ 401859966 โ 2026-06-11T00:30Z โ New York Knicks โ San Antonio Spurs โ 107 โ 106 โ
โ 401859965 โ 2026-06-09T00:30Z โ New York Knicks โ San Antonio Spurs โ 111 โ 115 โ
โ 401859964 โ 2026-06-06T00:30Z โ San Antonio Spurs โ New York Knicks โ 104 โ 105 โ
โ 401859963 โ 2026-06-04T00:30Z โ San Antonio Spurs โ New York Knicks โ 95 โ 105 โ
โโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโ
Pipeline: the highest-scoring games of the season ๐ฅโ
With the schedule release in hand, a combined-points leaderboard is a quick polars pipeline โ cast the scores, sum them, sort descending.
if sched is not None and sched.height and {'home_score', 'away_score'}.issubset(sched.columns):
hot = (
sched.with_columns(
(pl.col('home_score').cast(pl.Int64, strict=False)
+ pl.col('away_score').cast(pl.Int64, strict=False)).alias('total_points')
)
.filter(pl.col('total_points').is_not_null())
.sort('total_points', descending=True)
.select([c for c in ['date', 'home_display_name', 'away_display_name',
'home_score', 'away_score', 'total_points'] if c in sched.columns])
.head(10)
)
out = hot
else:
out = 'schedule release unavailable'
out
shape: (10, 5)
โโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ date โ home_display_name โ away_display_name โ home_score โ away_score โ
โ --- โ --- โ --- โ --- โ --- โ
โ str โ str โ str โ i32 โ i32 โ
โโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโก
โ 2025-12-21T20:30Z โ Atlanta Hawks โ Chicago Bulls โ 150 โ 152 โ
โ 2025-11-17T01:00Z โ Utah Jazz โ Chicago Bulls โ 150 โ 147 โ
โ 2026-03-25T23:00Z โ Philadelphia 76ers โ Chicago Bulls โ 157 โ 137 โ
โ 2026-04-08T00:00Z โ New Orleans Pelicans โ Utah Jazz โ 156 โ 137 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ 2026-04-01T23:00Z โ Washington Wizards โ Philadelphia 76ers โ 131 โ 153 โ
โ 2026-03-12T02:30Z โ LA Clippers โ Minnesota Timberwolves โ 153 โ 128 โ
โ 2025-11-15T01:00Z โ Milwaukee Bucks โ Charlotte Hornets โ 147 โ 134 โ
โ 2026-01-10T18:00Z โ Cleveland Cavaliers โ Minnesota Timberwolves โ 146 โ 134 โ
โโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโ
๐ Where to nextโ
You just toured the premium espn_nba_* surface plus the season
loaders โ teams, scoreboard, standings, rosters, schedules, player game
logs, play-by-play, and bulk box scores. A few parting tips:
- Pass
return_as_pandas=Trueto any wrapper for a pandas frame instead of polars. - ESPN
espn_nba_*wrappers also acceptreturn_parsed=Falsefor the raw JSON dict. - Full reference lives in the NBA section of the sidebar: ESPN site API ยท ESPN web API ยท ESPN core API ยท additional functions ยท loaders
- R user? The same surface lives in hoopR.
- Need raw NBA Stats endpoints? See nba_api.
Now go break down some film โ and may your jumper always find the bottom of the net! ๐๐ฅ