Skip to main content

๐Ÿ€ NBA hoops with sportsdataverse-py

This page is the executed notebook 04_nba_intro.ipynb: download it to run it yourself.

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.

FunctionWhat it gives youSource
espn_nba_teamsAll 30 NBA teams (grab team_ids here)โญ ESPN
espn_nba_scoreboardA day's slate โ€” scores, status, matchupsโญ ESPN
espn_nba_scheduleSchedule for a date / date-rangeโญ ESPN
espn_nba_standingsConference standings (W-L, win%, streak)โญ ESPN
espn_nba_team_rosterA team's active rosterโญ ESPN
espn_nba_team_scheduleOne team's full-season scheduleโญ ESPN
espn_nba_player_gamelogA player's game-by-game logโญ ESPN
espn_nba_leadersLeague statistical leadersโญ ESPN
espn_nba_pbpFull game payload (play-by-play, win prob, box)โญ ESPN
espn_nba_game_rostersBoth teams' rosters for one gameโญ ESPN
load_nba_scheduleMulti-season schedule parquet๐Ÿ“ฆ release
load_nba_player_boxscorePlayer box scores, every game๐Ÿ“ฆ release
load_nba_standingsHistorical standings๐Ÿ“ฆ release
espn_nba_injuriesLeague-wide injury report, one row per teamโญ ESPN
load_nba_team_boxscoreTeam box scores, every game (off/def, shooting)๐Ÿ“ฆ release
load_nba_shotsEvery made shot with court coordinates๐Ÿ“ฆ release
most_recent_nba_seasonThe 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: 2027

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. ๐Ÿ›Ÿ

from sportsdataverse.errors import AssetFetchError, NoDataError

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 (NoDataError, AssetFetchError) as e:
print(f"\u23ed\ufe0f {label}: {type(e).__name__}: {e}")
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 โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† 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 โ”‚
โ”‚ i64 โ”† 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 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ Detroit Pistons โ”† 3.0 โ”† 0.0 โ”† 1.0 โ”† 0.0 โ”† 38.0 โ”† 3.0 โ”‚
โ”‚ Portland Trail โ”† 2.0 โ”† 0.0 โ”† 1.0 โ”† 0.5 โ”† 49.0 โ”† 2.0 โ”‚
โ”‚ Blazers โ”† โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ Minnesota โ”† 3.0 โ”† 0.0 โ”† 1.0 โ”† 0.0 โ”† 70.0 โ”† 3.0 โ”‚
โ”‚ Timberwolves โ”† โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ Oklahoma City Thunder โ”† 3.0 โ”† 0.0 โ”† 1.0 โ”† 0.0 โ”† 31.0 โ”† 3.0 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ Houston Rockets โ”† 3.0 โ”† 0.0 โ”† 1.0 โ”† 0.0 โ”† 29.0 โ”† 3.0 โ”‚
โ”‚ Philadelphia 76ers โ”† 3.0 โ”† 1.0 โ”† 0.75 โ”† 0.5 โ”† 12.0 โ”† -1.0 โ”‚
โ”‚ Atlanta Hawks โ”† 2.0 โ”† 1.0 โ”† 0.6666667 โ”† 1.0 โ”† 47.0 โ”† -1.0 โ”‚
โ”‚ Toronto Raptors โ”† 2.0 โ”† 1.0 โ”† 0.6666667 โ”† 1.0 โ”† 30.0 โ”† -1.0 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿณ 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 โ€” 17 rows





shape: (10, 6)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”
โ”‚ full_name โ”† jersey โ”† position_abbreviation โ”† height โ”† weight โ”† age โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† str โ”† str โ”† f64 โ”† f64 โ”† i64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ก
โ”‚ Devin Carter โ”† 99 โ”† G โ”† 74.0 โ”† 188.0 โ”† 24 โ”‚
โ”‚ Chris Cenac Jr. โ”† 12 โ”† F โ”† 83.0 โ”† 240.0 โ”† 19 โ”‚
โ”‚ Mike Conley โ”† 45 โ”† G โ”† 73.0 โ”† 175.0 โ”† 39 โ”‚
โ”‚ Luka Garza โ”† 52 โ”† C โ”† 82.0 โ”† 243.0 โ”† 27 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ Ron Harper Jr. โ”† 8 โ”† G โ”† 77.0 โ”† 233.0 โ”† 26 โ”‚
โ”‚ Sam Hauser โ”† 30 โ”† F โ”† 80.0 โ”† 221.0 โ”† 28 โ”‚
โ”‚ Dillon Mitchell โ”† 20 โ”† F โ”† 80.0 โ”† 210.0 โ”† 23 โ”‚
โ”‚ Payton Pritchard โ”† 11 โ”† G โ”† 74.0 โ”† 211.0 โ”† 28 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜

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 โ€” 80 rows





shape: (5, 5)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ id โ”† date โ”† name โ”† short_name โ”† season_year โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† str โ”† str โ”† str โ”† i64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 401909088 โ”† 2026-10-20T19:00Z โ”† Boston Celtics at Detroit Pistโ€ฆ โ”† BOS @ DET โ”† 2027 โ”‚
โ”‚ 401909095 โ”† 2026-10-23T23:00Z โ”† New York Knicks at Boston Celtโ€ฆ โ”† NY @ BOS โ”† 2027 โ”‚
โ”‚ 401909870 โ”† 2026-10-26T23:30Z โ”† Chicago Bulls at Boston Celticโ€ฆ โ”† CHI @ BOS โ”† 2027 โ”‚
โ”‚ 401909877 โ”† 2026-10-27T23:30Z โ”† Brooklyn Nets at Boston Celticโ€ฆ โ”† BKN @ BOS โ”† 2027 โ”‚
โ”‚ 401909278 โ”† 2026-10-30T23:30Z โ”† Chicago Bulls at Boston Celticโ€ฆ โ”† CHI @ BOS โ”† 2027 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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 โ€” 5 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 โ”† 33 โ”† 8-15 โ”† 53.3 โ”‚
โ”‚ null โ”† null โ”† null โ”† null โ”† null โ”† 33 โ”† 5-14 โ”† 35.7 โ”‚
โ”‚ null โ”† null โ”† null โ”† null โ”† null โ”† 34 โ”† 5-13 โ”† 38.5 โ”‚
โ”‚ null โ”† null โ”† null โ”† null โ”† null โ”† 25 โ”† 4-9 โ”† 44.4 โ”‚
โ”‚ null โ”† null โ”† null โ”† null โ”† null โ”† 18 โ”† 1-1 โ”† 100.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 โ€” 995 rows





shape: (0, 6)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”
โ”‚ athlete_display_name โ”† team_abbreviation โ”† gp โ”† ppg โ”† rpg โ”† apg โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† str โ”† u32 โ”† f64 โ”† f64 โ”† f64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ก
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜

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 โ€” 72 rows





shape: (10, 5)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ team_abbreviation โ”† gp โ”† off_ppg โ”† def_ppg โ”† net โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† u32 โ”† f64 โ”† f64 โ”† f64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ก
โ”‚ TOR โ”† 2 โ”† 123.5 โ”† 97.0 โ”† 26.5 โ”‚
โ”‚ POR โ”† 2 โ”† 126.0 โ”† 101.5 โ”† 24.5 โ”‚
โ”‚ MIN โ”† 2 โ”† 123.0 โ”† 99.5 โ”† 23.5 โ”‚
โ”‚ ATL โ”† 3 โ”† 124.7 โ”† 109.0 โ”† 15.7 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ OKC โ”† 2 โ”† 106.0 โ”† 94.0 โ”† 12.0 โ”‚
โ”‚ MEM โ”† 2 โ”† 117.0 โ”† 105.5 โ”† 11.5 โ”‚
โ”‚ HOU โ”† 3 โ”† 121.0 โ”† 111.3 โ”† 9.7 โ”‚
โ”‚ LAC โ”† 2 โ”† 119.0 โ”† 110.5 โ”† 8.5 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ DET โ”† 33 โ”† 78 โ”† 42.3 โ”‚
โ”‚ HOU โ”† 40 โ”† 96 โ”† 41.7 โ”‚
โ”‚ LAL โ”† 25 โ”† 60 โ”† 41.7 โ”‚
โ”‚ LAC โ”† 30 โ”† 72 โ”† 41.7 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ BOS โ”† 32 โ”† 81 โ”† 39.5 โ”‚
โ”‚ MEM โ”† 28 โ”† 73 โ”† 38.4 โ”‚
โ”‚ MIL โ”† 45 โ”† 121 โ”† 37.2 โ”‚
โ”‚ NY โ”† 27 โ”† 73 โ”† 37.0 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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 โ€” 995 rows





shape: (10, 3)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ athlete_display_name โ”† team_abbreviation โ”† double_doubles โ”‚
โ”‚ --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† str โ”† u32 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ Amen Thompson โ”† HOU โ”† 3 โ”‚
โ”‚ Trae Young โ”† WSH โ”† 2 โ”‚
โ”‚ Karl-Anthony Towns โ”† NY โ”† 2 โ”‚
โ”‚ Domantas Sabonis โ”† SAC โ”† 2 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ Victor Wembanyama โ”† SA โ”† 2 โ”‚
โ”‚ Davion Mitchell โ”† MIA โ”† 2 โ”‚
โ”‚ Jalen Johnson โ”† ATL โ”† 2 โ”‚
โ”‚ Paul Reed โ”† DET โ”† 1 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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 โ€” 0 rows





'standings release unavailable'

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 โ€” 8390 rows





shape: (0, 3)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ team_abbreviation โ”† threes_made โ”† pct_pts_from_3 โ”‚
โ”‚ --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† u32 โ”† f64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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: (1, 5)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ game_date โ”† team_home_away โ”† team_score โ”† opponent_team_score โ”† team_winner โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ date โ”† str โ”† i32 โ”† i32 โ”† bool โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 2026-10-23 โ”† home โ”† 107 โ”† 103 โ”† true โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ Dallas Mavericks โ”† 6 โ”‚
โ”‚ Golden State Warriors โ”† 6 โ”‚
โ”‚ Oklahoma City Thunder โ”† 6 โ”‚
โ”‚ Philadelphia 76ers โ”† 6 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ Indiana Pacers โ”† 4 โ”‚
โ”‚ Sacramento Kings โ”† 4 โ”‚
โ”‚ Chicago Bulls โ”† 3 โ”‚
โ”‚ Detroit Pistons โ”† 3 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŽฌ 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, 5)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ athlete_display_name โ”† team_abbreviation โ”† starter โ”† jersey โ”† position_name โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† str โ”† bool โ”† str โ”† str โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ LeBron James โ”† LAL โ”† true โ”† 23 โ”† Forward โ”‚
โ”‚ Anthony Davis โ”† LAL โ”† true โ”† 23 โ”† Forward โ”‚
โ”‚ Rui Hachimura โ”† LAL โ”† true โ”† 28 โ”† Forward โ”‚
โ”‚ Spencer Dinwiddie โ”† LAL โ”† true โ”† 26 โ”† Point Guard โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ Cam Reddish โ”† LAL โ”† false โ”† 5 โ”† Small Forward โ”‚
โ”‚ Jaxson Hayes โ”† LAL โ”† false โ”† 11 โ”† Center โ”‚
โ”‚ Max Christie โ”† LAL โ”† false โ”† 0 โ”† Guard โ”‚
โ”‚ Harry Giles III โ”† LAL โ”† false โ”† 20 โ”† Power Forward โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“ฆ 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.

LoaderGrain
load_nba_scheduleone row per game
load_nba_player_boxscoreone row per player-game
load_nba_standingsone 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 โ€” 1206 rows





shape: (5, 6)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ id โ”† date โ”† home_display_name โ”† away_display_name โ”† home_score โ”† away_score โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ i32 โ”† str โ”† str โ”† str โ”† i32 โ”† i32 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 401910945 โ”† 2027-04-12T00:30Z โ”† Dallas Mavericks โ”† Memphis Grizzlies โ”† 0 โ”† 0 โ”‚
โ”‚ 401910946 โ”† 2027-04-12T00:30Z โ”† Minnesota โ”† Houston Rockets โ”† 0 โ”† 0 โ”‚
โ”‚ โ”† โ”† Timberwolves โ”† โ”† โ”† โ”‚
โ”‚ 401910947 โ”† 2027-04-12T00:30Z โ”† San Antonio Spurs โ”† Portland Trail โ”† 0 โ”† 0 โ”‚
โ”‚ โ”† โ”† โ”† Blazers โ”† โ”† โ”‚
โ”‚ 401910948 โ”† 2027-04-12T00:30Z โ”† Denver Nuggets โ”† Oklahoma City โ”† 0 โ”† 0 โ”‚
โ”‚ โ”† โ”† โ”† Thunder โ”† โ”† โ”‚
โ”‚ 401910949 โ”† 2027-04-12T00:30Z โ”† Golden State โ”† Utah Jazz โ”† 0 โ”† 0 โ”‚
โ”‚ โ”† โ”† Warriors โ”† โ”† โ”† โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

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.

stats.nba.com surface (nba_stats_*)โ€‹

Beyond the ESPN wrappers, sportsdataverse.nba also ships 128 wrappers for the official stats.nba.com API โ€” the same source powering nba_api. The module is nba_stats and every function is named nba_stats_<slug>. These are the capture-confirmed live, non-deprecated endpoints; deprecated and never-populated endpoints are excluded (see ENDPOINT_HEALTH.md in sdv-internal-refs for the full active/dying/barren/dead matrix).

League routing is a single league_id parameter: "00" โ†’ NBA, "20" โ†’ G-League, "15" โ†’ Summer League. One stem, one host, league chosen per call.

curl_cffi required for live calls: stats.nba.com uses TLS/JA3 fingerprinting that silently blocks plain requests. Install the optional transport: pip install "sportsdataverse[all]" (or pip install curl_cffi). The wrappers run fully offline when fed a fixture โ€” live calls simply need the extra installed.

Wrappers return a tidy polars DataFrame by default (parsed via the generic parse_nba_stats_result_sets parser โ€” which also flattens the shot-location endpoints' grouped 2-level headers into composite columns and unrolls the scoreboardv3 game feed). Pass return_parsed=False for the raw Dict or return_as_pandas=True for pandas. Multi-result-set endpoints (e.g. playercareerstats) return dict[str, DataFrame].

from sportsdataverse.nba import nba_stats

# NBA player dashboard (polars DataFrame by default)
# Live calls require: pip install "sportsdataverse[all]" (curl_cffi transport)
df = safe('nba_stats_leaguedashplayerstats (NBA)',
lambda: nba_stats.nba_stats_leaguedashplayerstats(league_id="00"))

# Same endpoint, other leagues via league_id on the SAME stem:
gleague = safe('nba_stats_leaguedashplayerstats (G-League)',
lambda: nba_stats.nba_stats_leaguedashplayerstats(league_id="20"))
summer = safe('nba_stats_leaguedashplayerstats (Summer League)',
lambda: nba_stats.nba_stats_leaguedashplayerstats(league_id="15"))

# Multi result-set endpoint -> dict[str, polars.DataFrame]
career = safe('nba_stats_playercareerstats (LeBron)',
lambda: nba_stats.nba_stats_playercareerstats(player_id="2544"))

print('career result sets:', list(career.keys()) if isinstance(career, dict) else career)
df
โœ… nba_stats_leaguedashplayerstats (NBA) โ€” 582 rows


โœ… nba_stats_leaguedashplayerstats (G-League) โ€” 526 rows


โœ… nba_stats_leaguedashplayerstats (Summer League) โ€” 450 rows


โœ… nba_stats_playercareerstats (LeBron) โ€” 14 rows
career result sets: ['SeasonTotalsRegularSeason', 'CareerTotalsRegularSeason', 'SeasonTotalsPostSeason', 'CareerTotalsPostSeason', 'SeasonTotalsAllStarSeason', 'CareerTotalsAllStarSeason', 'SeasonTotalsCollegeSeason', 'CareerTotalsCollegeSeason', 'SeasonTotalsShowcaseSeason', 'CareerTotalsShowcaseSeason', 'SeasonRankingsRegularSeason', 'SeasonRankingsPostSeason', 'SeasonHighs', 'CareerHighs']





shape: (582, 69)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ player_id โ”† player_nam โ”† nickname โ”† team_id โ”† โ€ฆ โ”† td3_rank โ”† wnba_fant โ”† fp_high_s โ”† team_coun โ”‚
โ”‚ --- โ”† e โ”† --- โ”† --- โ”† โ”† --- โ”† asy_pts_r โ”† core_rank โ”† t โ”‚
โ”‚ i64 โ”† --- โ”† str โ”† i64 โ”† โ”† i64 โ”† ank โ”† --- โ”† --- โ”‚
โ”‚ โ”† str โ”† โ”† โ”† โ”† โ”† --- โ”† i64 โ”† i64 โ”‚
โ”‚ โ”† โ”† โ”† โ”† โ”† โ”† i64 โ”† โ”† โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1630639 โ”† A.J. โ”† A.J. โ”† 161061276 โ”† โ€ฆ โ”† 38 โ”† 449 โ”† 454 โ”† 1 โ”‚
โ”‚ โ”† Lawson โ”† โ”† 1 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 1631260 โ”† AJ Green โ”† AJ โ”† 161061274 โ”† โ€ฆ โ”† 38 โ”† 139 โ”† 167 โ”† 1 โ”‚
โ”‚ โ”† โ”† โ”† 9 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 1642358 โ”† AJ Johnson โ”† AJ โ”† 161061274 โ”† โ€ฆ โ”† 38 โ”† 413 โ”† 412 โ”† 2 โ”‚
โ”‚ โ”† โ”† โ”† 2 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 203932 โ”† Aaron โ”† Aaron โ”† 161061274 โ”† โ€ฆ โ”† 38 โ”† 241 โ”† 250 โ”† 1 โ”‚
โ”‚ โ”† Gordon โ”† โ”† 3 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ 1630192 โ”† Zeke Nnaji โ”† Zeke โ”† 161061274 โ”† โ€ฆ โ”† 38 โ”† 371 โ”† 373 โ”† 1 โ”‚
โ”‚ โ”† โ”† โ”† 3 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 1630533 โ”† Ziaire โ”† Ziaire โ”† 161061275 โ”† โ€ฆ โ”† 38 โ”† 233 โ”† 244 โ”† 1 โ”‚
โ”‚ โ”† Williams โ”† โ”† 1 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 1629627 โ”† Zion โ”† Zion โ”† 161061274 โ”† โ€ฆ โ”† 38 โ”† 70 โ”† 65 โ”† 1 โ”‚
โ”‚ โ”† Williamson โ”† โ”† 0 โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 1642389 โ”† Zyon โ”† Zyon โ”† 161061275 โ”† โ€ฆ โ”† 38 โ”† 537 โ”† 533 โ”† 1 โ”‚
โ”‚ โ”† Pullin โ”† โ”† 0 โ”† โ”† โ”† โ”† โ”† โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
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 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 2026-10-25T01:00Z โ”† Denver Nuggets โ”† New Orleans Pelicans โ”† 141 โ”† 135 โ”‚
โ”‚ 2026-10-23T20:00Z โ”† Milwaukee Bucks โ”† Chicago Bulls โ”† 125 โ”† 132 โ”‚
โ”‚ 2026-10-21T23:00Z โ”† Orlando Magic โ”† Atlanta Hawks โ”† 125 โ”† 132 โ”‚
โ”‚ 2026-10-25T02:00Z โ”† Phoenix Suns โ”† Golden State Warriors โ”† 131 โ”† 122 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ 2026-10-25T00:00Z โ”† Chicago Bulls โ”† Washington Wizards โ”† 120 โ”† 124 โ”‚
โ”‚ 2026-10-21T23:00Z โ”† Washington Wizards โ”† Milwaukee Bucks โ”† 128 โ”† 116 โ”‚
โ”‚ 2026-10-24T23:30Z โ”† Philadelphia 76ers โ”† Milwaukee Bucks โ”† 127 โ”† 116 โ”‚
โ”‚ 2026-10-22T00:00Z โ”† New Orleans Pelicans โ”† Indiana Pacers โ”† 120 โ”† 123 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŽ‰ 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=True to any wrapper for a pandas frame instead of polars.
  • ESPN espn_nba_* wrappers also accept return_parsed=False for 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! ๐Ÿ€๐Ÿ”ฅ

๐Ÿฆ“ Officiatingโ€‹

Last Two Minute reports and referee assignments, from official.nba.com and the NBA's own feeds.

from sportsdataverse.nba import nba_officiating

print("nba_officiating:", [n for n in dir(nba_officiating) if not n.startswith("_")][:8])
safe("referee assignments", lambda: nba_officiating.nba_referee_assignments("2024-04-01"))
safe("L2M games", lambda: nba_officiating.nba_l2m_games(2024))

nba_officiating: ['Any', 'AssetFetchError', 'L2M_CALLS_SCHEMA', 'L2M_GAME_SCHEMA', 'L2M_STATS_SCHEMA', 'Literal', 'NBA_REFEREE_ASSIGN_SCHEMA', 'NBA_REFEREE_REPLAY_SCHEMA']


โœ… referee assignments โ€” 2 rows


โœ… L2M games โ€” 413 rows





shape: (413, 4)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ game_id โ”† season โ”† season_type โ”† label โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† i32 โ”† str โ”† str โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 0042300403 โ”† 2024 โ”† playoffs โ”† Celtics 106, Mavericks 99 โ”‚
โ”‚ 0042300314 โ”† 2024 โ”† playoffs โ”† Timberwolves 105, Mavericks 10โ€ฆ โ”‚
โ”‚ 0042300304 โ”† 2024 โ”† playoffs โ”† Celtics 105, Pacers 102 โ”‚
โ”‚ 0042300303 โ”† 2024 โ”† playoffs โ”† Celtics 114, Pacers 111 โ”‚
โ”‚ โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”† โ€ฆ โ”‚
โ”‚ 0022300068 โ”† 2024 โ”† regular โ”† Heat 103, Pistons 102 โ”‚
โ”‚ 0022300069 โ”† 2024 โ”† regular โ”† Raptors 97, Timberwolves 94 โ”‚
โ”‚ 0022300073 โ”† 2024 โ”† regular โ”† Mavericks 126, Spurs 119 โ”‚
โ”‚ 0022300062 โ”† 2024 โ”† regular โ”† Suns 108, Warriors 104 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜