Skip to main content
Version: 0.0.70

πŸ€ Men's college basketball with sportsdataverse-py

Welcome to Selection-Sunday-grade hoops data! πŸŽ‰ In a handful of lines of Python you're about to pull NCAA Division I men's basketball β€” full schedules, play-by-play, standings, rosters, statistical leaders and multi-season parquet archives β€” and get it all back as tidy polars DataFrames ready to model.

sportsdataverse.mbb leads with two premium sources:

  • πŸŸ₯ ESPN (espn_mbb_*) β€” the site + core APIs behind ESPN.com: live scoreboards, schedules, standings, rankings, box scores, win probability and play-by-play.
  • 🦊 FoxSports (fox_mbb_*) β€” FoxSports' league-leader, standings, roster, boxscore and odds feeds.

Plus πŸ“¦ release loaders (load_mbb_*) that hand you whole seasons of play-by-play, box scores, shots and schedules from the data repo in one call.

R user? The men's-basketball companion is hoopR (NBA + NCAA). Let's tip off! πŸ€

🧰 The toolbox​

Every accessor returns a tidy polars DataFrame by default β€” pass return_as_pandas=True for pandas. The richest live surfaces are ESPN and Fox; the load_* loaders read pre-built parquet from the data release (rock-solid, no live API). Click any name for the full reference.

FunctionWhat it gives youSource
espn_mbb_teamsEvery D-I team (grab team_ids)πŸŸ₯ ESPN ⭐
espn_mbb_scheduleGames + results for a date / windowπŸŸ₯ ESPN ⭐
espn_mbb_scoreboardRich scoreboard for a date (status, lines, odds)πŸŸ₯ ESPN ⭐
espn_mbb_standingsConference standings, one row per teamπŸŸ₯ ESPN ⭐
espn_mbb_rankingsAP / Coaches poll (in-season)πŸŸ₯ ESPN ⭐
espn_mbb_summaryFull game summary: box, plays, win probπŸŸ₯ ESPN ⭐
espn_mbb_team_rosterA team's rosterπŸŸ₯ ESPN ⭐
espn_mbb_pbpEvent-level play-by-play for a gameπŸŸ₯ ESPN ⭐
espn_mbb_game_rostersWho dressed + started for one gameπŸŸ₯ ESPN ⭐
espn_mbb_player_statsA player's season stat lineπŸŸ₯ ESPN ⭐
fox_mbb_league_leadersStat leaders (scoring, rebounds, …)🦊 Fox ⭐
fox_mbb_standingsFox conference standings for a team🦊 Fox ⭐
fox_mbb_team_rosterFox roster for a team🦊 Fox
espn_mbb_team_scheduleOne team's full season scheduleπŸŸ₯ ESPN ⭐
espn_mbb_conferencesConference / group catalogπŸŸ₯ ESPN ⭐
load_mbb_scheduleWhole-season schedule parquetπŸ“¦ loader
load_mbb_player_boxscoreSeason player box scoresπŸ“¦ loader
load_mbb_team_boxscoreSeason team box scoresπŸ“¦ loader
load_mbb_pbpSeason play-by-play parquetπŸ“¦ loader
most_recent_mbb_seasonCurrent season-year helperπŸ› οΈ helper

⭐ = premium live source.

πŸ”Œ Setup​

pip install sportsdataverse

No API key needed β€” ESPN, Fox and the parquet loaders are all open. 😊

import polars as pl
import sportsdataverse as sdv

pl.Config.set_tbl_rows(10)
print("most recent MBB season:", sdv.mbb.most_recent_mbb_season())
most recent MBB season: 2026

ESPN's live endpoints (scoreboard, rankings, standings, a single game's play-by-play) are seasonal β€” in the offseason a poll or scoreboard can come back empty. So we use a tiny safe() helper: you get the frame when the feed is up, and a friendly one-liner when it isn't β€” never a scary traceback. πŸ›Ÿ The load_* parquet loaders are stable year-round, so we call those directly.

def safe(label, thunk):
"""Run a live call defensively; return its result or None with a note."""
try:
out = thunk()
ok = out is not None and (not hasattr(out, "height") or out.height)
print(f"{'βœ…' if ok else 'ℹ️ '} {label}{'' if ok else ' β€” no rows right now'}")
return out
except Exception as e: # noqa: BLE001 -- demo resilience
print(f"⏭️ {label}: unavailable right now ({type(e).__name__})")
return None

🏟️ Every team in Division I​

Start with espn_mbb_teams β€” one row per program, with the team_id you'll pass into roster, schedule and summary calls. This is a plain catalog fetch, so it's reliable year-round.

teams = sdv.mbb.espn_mbb_teams()
print("teams:", teams.shape)
teams.select(["team_id", "team_location", "team_name", "team_abbreviation", "team_is_active"]).head()
teams: (362, 14)





shape: (5, 5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_id ┆ team_location ┆ team_name ┆ team_abbreviation ┆ team_is_active β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str ┆ bool β”‚
β•žβ•β•β•β•β•β•β•β•β•β•ͺ═══════════════════β•ͺ══════════════β•ͺ═══════════════════β•ͺ════════════════║
β”‚ 2000 ┆ Abilene Christian ┆ Wildcats ┆ ACU ┆ true β”‚
β”‚ 2005 ┆ Air Force ┆ Falcons ┆ AF ┆ true β”‚
β”‚ 2006 ┆ Akron ┆ Zips ┆ AKR ┆ true β”‚
β”‚ 2010 ┆ Alabama A&M ┆ Bulldogs ┆ AAMU ┆ true β”‚
β”‚ 333 ┆ Alabama ┆ Crimson Tide ┆ ALA ┆ true β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“… Schedule & scores for a date window​

espn_mbb_schedule takes a single dates=YYYYMMDD or a 'YYYYMMDD-YYYYMMDD' window and returns one row per game with final scores. Here's championship day of the 2024 tournament.

sched = safe(
"schedule 2024-04-08",
lambda: sdv.mbb.espn_mbb_schedule(dates=20240408),
)
(sched.select(["id", "home_display_name", "away_display_name", "home_score", "away_score"]).head()
if sched is not None and sched.height else "schedule unavailable")
βœ… schedule 2024-04-08





shape: (1, 5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ id ┆ home_display_name ┆ away_display_name ┆ home_score ┆ away_score β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════β•ͺ═════════════════════β•ͺ════════════β•ͺ════════════║
β”‚ 401638645 ┆ UConn Huskies ┆ Purdue Boilermakers ┆ 75 ┆ 60 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Š The rich scoreboard​

espn_mbb_scoreboard is the deluxe version: for a given date it returns status, broadcast, betting lines and team line scores β€” 50 columns wide. Defaults to polars; we peek at a tidy slice.

sb = safe(
"scoreboard 2024-04-08",
lambda: sdv.mbb.espn_mbb_scoreboard(dates=20240408, return_as_pandas=False),
)
if sb is not None and getattr(sb, "height", 0):
keep = ["game_id", "short_name", "status_type_description",
"home_team_short_display_name", "away_team_short_display_name"]
out = sb.select([c for c in keep if c in sb.columns]).head()
else:
out = "scoreboard empty right now (offseason)"
out
βœ… scoreboard 2024-04-08





shape: (1, 3)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ game_id ┆ short_name ┆ status_type_description β”‚
β”‚ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ═════════════β•ͺ═════════════════════════║
β”‚ 401638645 ┆ PUR VS CONN ┆ Final β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ† Conference standings​

espn_mbb_standings returns one row per team for a season with wins, losses, win pct, point differential and conference grouping. Great for a quick power look across the league.

standings = safe(
"standings 2024",
lambda: sdv.mbb.espn_mbb_standings(season=2024, return_as_pandas=False),
)
if standings is not None and getattr(standings, "height", 0):
keep = ["team_display_name", "group_name", "wins", "losses",
"win_percent", "point_differential"]
out = (standings.select([c for c in keep if c in standings.columns])
.sort("win_percent", descending=True).head(10))
else:
out = "standings unavailable"
out
βœ… standings 2024





shape: (10, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_display_name ┆ group_name ┆ wins ┆ losses ┆ win_percent ┆ point_differential β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════════β•ͺ══════β•ͺ════════β•ͺ═════════════β•ͺ════════════════════║
β”‚ McNeese Cowboys ┆ Southland Conference ┆ 17.0 ┆ 1.0 ┆ 0.9444444 ┆ 308.0 β”‚
β”‚ Vermont Catamounts ┆ America East ┆ 15.0 ┆ 1.0 ┆ 0.9375 ┆ 179.0 β”‚
β”‚ ┆ Conference ┆ ┆ ┆ ┆ β”‚
β”‚ Saint Mary's Gaels ┆ West Coast Conference ┆ 15.0 ┆ 1.0 ┆ 0.9375 ┆ 312.0 β”‚
β”‚ UConn Huskies ┆ Big East Conference ┆ 18.0 ┆ 2.0 ┆ 0.9 ┆ 277.0 β”‚
β”‚ South Florida Bulls ┆ American Conference ┆ 16.0 ┆ 2.0 ┆ 0.8888889 ┆ 124.0 β”‚
β”‚ Colgate Raiders ┆ Patriot League ┆ 16.0 ┆ 2.0 ┆ 0.8888889 ┆ 217.0 β”‚
β”‚ App State ┆ Sun Belt Conference ┆ 16.0 ┆ 2.0 ┆ 0.8888889 ┆ 198.0 β”‚
β”‚ Mountaineers ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ Gonzaga Bulldogs ┆ West Coast Conference ┆ 14.0 ┆ 2.0 ┆ 0.875 ┆ 305.0 β”‚
β”‚ Princeton Tigers ┆ Ivy League ┆ 12.0 ┆ 2.0 ┆ 0.857143 ┆ 136.0 β”‚
β”‚ North Carolina Tar ┆ Atlantic Coast ┆ 17.0 ┆ 3.0 ┆ 0.85 ┆ 210.0 β”‚
β”‚ Heels ┆ Conference ┆ ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🍳 Cookbook: common MBB tasks​

Now the fun part β€” real tasks you'll reach for constantly, each built on a premium ESPN or Fox wrapper. Every recipe is guarded so a transient or offseason hiccup prints a note instead of breaking the page.

Recipe 1 β€” National scoring leaders πŸ₯‡ (FoxSports)​

fox_mbb_league_leaders serves the leaderboard direct from FoxSports β€” pick a category (scoring, rebounds, assists, …) and who (player or team). No IDs needed.

leaders = safe(
"fox scoring leaders",
lambda: sdv.mbb.fox_mbb_league_leaders(category="scoring", who="player"),
)
if leaders is not None and getattr(leaders, "height", 0):
keep = ["players", "gp", "mpg", "ppg", "pts"]
out = leaders.select([c for c in keep if c in leaders.columns]).head(10)
else:
out = "Fox leaders unavailable right now"
out
βœ… fox scoring leaders





shape: (10, 5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”
β”‚ players ┆ gp ┆ mpg ┆ ppg ┆ pts β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•ͺ═════β•ͺ══════β•ͺ══════β•ͺ══════║
β”‚ 1 ┆ 37 ┆ null ┆ null ┆ null β”‚
β”‚ 2 ┆ 37 ┆ null ┆ null ┆ null β”‚
β”‚ 3 ┆ 37 ┆ null ┆ null ┆ null β”‚
β”‚ 4 ┆ 36 ┆ null ┆ null ┆ null β”‚
β”‚ 5 ┆ 36 ┆ null ┆ null ┆ null β”‚
β”‚ 6 ┆ 35 ┆ null ┆ null ┆ null β”‚
β”‚ 7 ┆ 35 ┆ null ┆ null ┆ null β”‚
β”‚ 8 ┆ 35 ┆ null ┆ null ┆ null β”‚
β”‚ 9 ┆ 35 ┆ null ┆ null ┆ null β”‚
β”‚ 10 ┆ 35 ┆ null ┆ null ┆ null β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜

Recipe 2 β€” Look up a team's roster πŸ‘₯ (ESPN)​

Grab a team_id from espn_mbb_teams, then espn_mbb_team_roster returns the current roster. Here we resolve UConn (the 2024 champs) by abbreviation so the recipe is self-contained.

row = teams.filter(pl.col("team_abbreviation") == "CONN")
tid = int(row["team_id"][0]) if row.height else 41 # 41 = UConn fallback
roster = safe(
f"roster team_id={tid}",
lambda: sdv.mbb.espn_mbb_team_roster(team_id=tid, return_as_pandas=False),
)
if roster is not None and getattr(roster, "height", 0):
keep = ["full_name", "jersey", "display_height", "display_weight"]
out = roster.select([c for c in keep if c in roster.columns]).head(12)
else:
out = "roster unavailable right now"
out
βœ… roster team_id=41





shape: (12, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ full_name ┆ jersey ┆ display_height ┆ display_weight β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ════════β•ͺ════════════════β•ͺ════════════════║
β”‚ Solo Ball ┆ 1 ┆ 6' 4" ┆ 200 lbs β”‚
β”‚ Silas Demary Jr. ┆ 2 ┆ 6' 4" ┆ 195 lbs β”‚
β”‚ Rrezon Elezaj ┆ 10 ┆ 7' 1" ┆ 225 lbs β”‚
β”‚ Jacob Furphy ┆ 7 ┆ 6' 6" ┆ 205 lbs β”‚
β”‚ Alex Karaban ┆ 11 ┆ 6' 8" ┆ 230 lbs β”‚
β”‚ … ┆ … ┆ … ┆ … β”‚
β”‚ Braylon Mullins ┆ 24 ┆ 6' 6" ┆ 196 lbs β”‚
β”‚ UroΕ‘ Paunovic ┆ 77 ┆ 6' 3" ┆ 190 lbs β”‚
β”‚ Tarris Reed Jr. ┆ 5 ┆ 6' 11" ┆ 265 lbs β”‚
β”‚ Eric Reibe ┆ 12 ┆ 7' 1" ┆ 260 lbs β”‚
β”‚ Jacob Ross ┆ 13 ┆ 6' 5" ┆ 195 lbs β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 3 β€” Season scoring leaderboard from parquet πŸ“¦β€‹

The load_* loaders pull whole seasons from the data release β€” perfect for analysis that shouldn't depend on a live endpoint. load_mbb_player_boxscore gives every player-game; we aggregate to a per-player points-per-game board.

pbox = sdv.mbb.load_mbb_player_boxscore(seasons=[2024])
print("player box rows:", pbox.shape)
(pbox
.filter(pl.col("points").is_not_null())
.group_by(["athlete_display_name", "team_short_display_name"])
.agg(
pl.len().alias("g"),
pl.col("points").cast(pl.Float64, strict=False).mean().round(1).alias("ppg"),
)
.filter(pl.col("g") >= 20)
.sort("ppg", descending=True)
.head(10))
player box rows: (198586, 55)





shape: (10, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”
β”‚ athlete_display_name ┆ team_short_display_name ┆ g ┆ ppg β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ u32 ┆ f64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═════════════════════════β•ͺ═════β•ͺ══════║
β”‚ Zach Edey ┆ Purdue ┆ 39 ┆ 25.2 β”‚
β”‚ Tommy Bruner ┆ Denver ┆ 34 ┆ 24.0 β”‚
β”‚ Terrence Shannon Jr. ┆ Illinois ┆ 32 ┆ 23.0 β”‚
β”‚ Tyler Thomas ┆ Hofstra ┆ 33 ┆ 22.5 β”‚
β”‚ Xavier Johnson ┆ S Illinois ┆ 32 ┆ 22.2 β”‚
β”‚ David Jones ┆ Memphis ┆ 32 ┆ 21.8 β”‚
β”‚ Dalton Knecht ┆ Tennessee ┆ 36 ┆ 21.7 β”‚
β”‚ Tyson Acuff ┆ E Michigan ┆ 27 ┆ 21.7 β”‚
β”‚ Tucker DeVries ┆ Drake ┆ 34 ┆ 21.6 β”‚
β”‚ Jordan Sears ┆ UT Martin ┆ 32 ┆ 21.6 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜

Recipe 4 β€” Play-by-play slice for one game 🎬 (ESPN)​

espn_mbb_pbp returns a dict; its plays list is event-level. We frame it and pull just the scoring plays of the 2024 national championship (UConn vs. Purdue, game_id=401638636).

pbp = safe("pbp 401638636", lambda: sdv.mbb.espn_mbb_pbp(game_id=401638636))
if isinstance(pbp, dict) and pbp.get("plays"):
plays = pl.DataFrame(pbp["plays"], infer_schema_length=None)
keep = ["period.number", "clock.displayValue", "text", "scoringPlay",
"homeScore", "awayScore"]
out = (plays.select([c for c in keep if c in plays.columns])
.filter(pl.col("scoringPlay") == True) # noqa: E712
.head(10))
else:
out = "play-by-play unavailable right now"
out
βœ… pbp 401638636





shape: (10, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ period.number ┆ clock.displayValue ┆ text ┆ scoringPlay ┆ homeScore ┆ awayScore β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ i64 ┆ str ┆ str ┆ bool ┆ i64 ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ════════════════════β•ͺ═══════════════════════β•ͺ═════════════β•ͺ═══════════β•ͺ═══════════║
β”‚ 1 ┆ 19:27 ┆ Ryan Kalkbrenner made ┆ true ┆ 0 ┆ 2 β”‚
β”‚ ┆ ┆ Layup. A… ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 18:24 ┆ Dalton Knecht made ┆ true ┆ 2 ┆ 2 β”‚
β”‚ ┆ ┆ Jumper. ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 17:04 ┆ Mason Miller made ┆ true ┆ 2 ┆ 5 β”‚
β”‚ ┆ ┆ Three Point … ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 16:48 ┆ Dalton Knecht made ┆ true ┆ 3 ┆ 5 β”‚
β”‚ ┆ ┆ Free Throw. ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 16:34 ┆ Baylor Scheierman ┆ true ┆ 3 ┆ 7 β”‚
β”‚ ┆ ┆ made Jumper. ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 15:34 ┆ Josiah-Jordan James ┆ true ┆ 6 ┆ 7 β”‚
β”‚ ┆ ┆ made Three… ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 14:45 ┆ Josiah-Jordan James ┆ true ┆ 9 ┆ 7 β”‚
β”‚ ┆ ┆ made Three… ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 14:19 ┆ Baylor Scheierman ┆ true ┆ 9 ┆ 9 β”‚
β”‚ ┆ ┆ made Jumper. ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 14:07 ┆ Jordan Gainey made ┆ true ┆ 11 ┆ 9 β”‚
β”‚ ┆ ┆ Jumper. Ass… ┆ ┆ ┆ β”‚
β”‚ 1 ┆ 13:39 ┆ Baylor Scheierman ┆ true ┆ 11 ┆ 12 β”‚
β”‚ ┆ ┆ made Three P… ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 5 β€” Best net scoring margin πŸ“Š (parquet)​

load_mbb_team_boxscore gives one row per team-game with the opponent's score attached, so a single group-by ranks every program by points scored minus points allowed β€” the cleanest one-number power proxy. Pure parquet, no live endpoint.

tbox = sdv.mbb.load_mbb_team_boxscore(seasons=[2024])
print("team box rows:", tbox.shape)
(tbox
.group_by("team_display_name")
.agg(
pl.len().alias("g"),
pl.col("team_score").cast(pl.Float64, strict=False).mean().round(1).alias("ppg"),
pl.col("opponent_team_score").cast(pl.Float64, strict=False).mean().round(1).alias("opp_ppg"),
)
.with_columns((pl.col("ppg") - pl.col("opp_ppg")).round(1).alias("net_margin"))
.filter(pl.col("g") >= 25)
.sort("net_margin", descending=True)
.head(10))
team box rows: (12480, 57)





shape: (10, 5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_display_name ┆ g ┆ ppg ┆ opp_ppg ┆ net_margin β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ u32 ┆ f64 ┆ f64 ┆ f64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═════β•ͺ══════β•ͺ═════════β•ͺ════════════║
β”‚ UConn Huskies ┆ 40 ┆ 81.4 ┆ 63.4 ┆ 18.0 β”‚
β”‚ McNeese Cowboys ┆ 34 ┆ 80.0 ┆ 62.2 ┆ 17.8 β”‚
β”‚ Houston Cougars ┆ 37 ┆ 73.5 ┆ 57.6 ┆ 15.9 β”‚
β”‚ Gonzaga Bulldogs ┆ 35 ┆ 84.5 ┆ 69.1 ┆ 15.4 β”‚
β”‚ Arizona Wildcats ┆ 36 ┆ 87.1 ┆ 72.1 ┆ 15.0 β”‚
β”‚ Auburn Tigers ┆ 35 ┆ 83.1 ┆ 68.3 ┆ 14.8 β”‚
β”‚ Saint Mary's Gaels ┆ 34 ┆ 74.0 ┆ 59.2 ┆ 14.8 β”‚
β”‚ Iowa State Cyclones ┆ 37 ┆ 75.3 ┆ 61.5 ┆ 13.8 β”‚
β”‚ James Madison Dukes ┆ 36 ┆ 83.2 ┆ 69.6 ┆ 13.6 β”‚
β”‚ Purdue Boilermakers ┆ 39 ┆ 82.3 ┆ 69.0 ┆ 13.3 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 6 β€” Best 3-point shooting teams 🎯 (parquet)​

Same team-box parquet, different question: sum makes and attempts across the season, then divide. A min attempts filter keeps small-sample flukes off the board so the leaders are real volume shooters.

(tbox
.group_by("team_display_name")
.agg(
pl.col("three_point_field_goals_made")
.cast(pl.Float64, strict=False).sum().alias("tpm"),
pl.col("three_point_field_goals_attempted")
.cast(pl.Float64, strict=False).sum().alias("tpa"),
)
.with_columns((pl.col("tpm") / pl.col("tpa") * 100).round(1).alias("three_pct"))
.filter(pl.col("tpa") >= 500)
.sort("three_pct", descending=True)
.select(["team_display_name", "tpm", "tpa", "three_pct"])
.head(10))
shape: (10, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_display_name ┆ tpm ┆ tpa ┆ three_pct β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ f64 ┆ f64 ┆ f64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════β•ͺ═══════β•ͺ═══════════║
β”‚ Kentucky Wildcats ┆ 327.0 ┆ 800.0 ┆ 40.9 β”‚
β”‚ Purdue Boilermakers ┆ 318.0 ┆ 788.0 ┆ 40.4 β”‚
β”‚ Dayton Flyers ┆ 310.0 ┆ 777.0 ┆ 39.9 β”‚
β”‚ UNC Greensboro Spartans ┆ 322.0 ┆ 812.0 ┆ 39.7 β”‚
β”‚ Samford Bulldogs ┆ 351.0 ┆ 889.0 ┆ 39.5 β”‚
β”‚ Colorado Buffaloes ┆ 254.0 ┆ 649.0 ┆ 39.1 β”‚
β”‚ Northwestern Wildcats ┆ 278.0 ┆ 713.0 ┆ 39.0 β”‚
β”‚ Baylor Bears ┆ 301.0 ┆ 773.0 ┆ 38.9 β”‚
β”‚ McNeese Cowboys ┆ 257.0 ┆ 671.0 ┆ 38.3 β”‚
β”‚ Wright State Raiders ┆ 218.0 ┆ 569.0 ┆ 38.3 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 7 β€” Most efficient scorers ⚑ (true shooting %)​

Points-per-game rewards volume; true shooting % rewards efficiency β€” it folds threes and free throws into one rate via TS% = PTS / (2 Β· (FGA + 0.44Β·FTA)). We compute it straight from load_mbb_player_boxscore, keeping only high-usage scorers.

pbox = sdv.mbb.load_mbb_player_boxscore(seasons=[2024])
(pbox
.filter(pl.col("points").is_not_null())
.group_by(["athlete_display_name", "team_abbreviation"])
.agg(
pl.len().alias("g"),
pl.col("points").cast(pl.Float64, strict=False).sum().alias("pts"),
pl.col("field_goals_attempted").cast(pl.Float64, strict=False).sum().alias("fga"),
pl.col("free_throws_attempted").cast(pl.Float64, strict=False).sum().alias("fta"),
)
.with_columns(
(pl.col("pts") / (2 * (pl.col("fga") + 0.44 * pl.col("fta"))) * 100)
.round(1).alias("ts_pct"))
.filter((pl.col("g") >= 25) & (pl.col("pts") >= 400))
.sort("ts_pct", descending=True)
.select(["athlete_display_name", "team_abbreviation", "g", "pts", "ts_pct"])
.head(10))
shape: (10, 5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ athlete_display_name ┆ team_abbreviation ┆ g ┆ pts ┆ ts_pct β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ u32 ┆ f64 ┆ f64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════β•ͺ═════β•ͺ═══════β•ͺ════════║
β”‚ Jayson Kent ┆ INST ┆ 38 ┆ 514.0 ┆ 73.3 β”‚
β”‚ Aubin Gateretse ┆ STET ┆ 35 ┆ 407.0 ┆ 71.2 β”‚
β”‚ Lynn Kidd ┆ VT ┆ 33 ┆ 434.0 ┆ 70.7 β”‚
β”‚ Reed Sheppard ┆ UK ┆ 33 ┆ 411.0 ┆ 70.5 β”‚
β”‚ Vladislav Goldin ┆ FAU ┆ 34 ┆ 534.0 ┆ 69.1 β”‚
β”‚ Ryan Kalkbrenner ┆ CREI ┆ 35 ┆ 604.0 ┆ 68.9 β”‚
β”‚ Cedric Coward ┆ EWU ┆ 32 ┆ 494.0 ┆ 68.3 β”‚
β”‚ Jaylin Williams ┆ AUB ┆ 34 ┆ 422.0 ┆ 68.2 β”‚
β”‚ Chaz Lanier ┆ UNF ┆ 32 ┆ 629.0 ┆ 67.3 β”‚
β”‚ Zach Edey ┆ PUR ┆ 39 ┆ 983.0 ┆ 67.3 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 8 β€” One conference's power board 🏟️ (ESPN, join)​

espn_mbb_conferences is the group catalog; espn_mbb_standings carries a group_name per team. Filter standings to a single league β€” here the Big 12 β€” to get a clean intra-conference pecking order.

confs = safe("conferences", lambda: sdv.mbb.espn_mbb_conferences())
if confs is not None and getattr(confs, "height", 0):
print("some conferences:",
confs.filter(pl.col("is_conference"))["name"].to_list()[:8])
st = safe("standings 2024", lambda: sdv.mbb.espn_mbb_standings(season=2024))
if st is not None and getattr(st, "height", 0) and "group_name" in st.columns:
keep = ["team_display_name", "wins", "losses", "win_percent", "point_differential"]
out = (st.filter(pl.col("group_name").str.contains("Big 12"))
.select([c for c in keep if c in st.columns])
.sort("win_percent", descending=True)
.head(12))
out = out if out.height else st.select(
[c for c in keep if c in st.columns]).sort(
"win_percent", descending=True).head(12)
else:
out = "standings unavailable right now"
out
βœ… conferences
some conferences: ['NCAA Division I', 'Non-NCAA Division I']
βœ… standings 2024





shape: (12, 5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_display_name ┆ wins ┆ losses ┆ win_percent ┆ point_differential β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ══════β•ͺ════════β•ͺ═════════════β•ͺ════════════════════║
β”‚ Houston Cougars ┆ 15.0 ┆ 3.0 ┆ 0.8333333 ┆ 191.0 β”‚
β”‚ Iowa State Cyclones ┆ 13.0 ┆ 5.0 ┆ 0.7222222 ┆ 71.0 β”‚
β”‚ Baylor Bears ┆ 11.0 ┆ 7.0 ┆ 0.6111111 ┆ 52.0 β”‚
β”‚ Texas Tech Red Raiders ┆ 11.0 ┆ 7.0 ┆ 0.6111111 ┆ 39.0 β”‚
β”‚ BYU Cougars ┆ 10.0 ┆ 8.0 ┆ 0.5555556 ┆ 19.0 β”‚
β”‚ … ┆ … ┆ … ┆ … ┆ … β”‚
β”‚ TCU Horned Frogs ┆ 9.0 ┆ 9.0 ┆ 0.5 ┆ 21.0 β”‚
β”‚ Oklahoma Sooners ┆ 8.0 ┆ 10.0 ┆ 0.444444 ┆ -33.0 β”‚
β”‚ Kansas State Wildcats ┆ 8.0 ┆ 10.0 ┆ 0.444444 ┆ -23.0 β”‚
β”‚ Cincinnati Bearcats ┆ 7.0 ┆ 11.0 ┆ 0.3888889 ┆ 5.0 β”‚
β”‚ UCF Knights ┆ 7.0 ┆ 11.0 ┆ 0.3888889 ┆ -44.0 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 9 β€” A team's full season schedule πŸ—“οΈ (ESPN)​

espn_mbb_team_schedule returns every game on one team's slate for a season β€” matchup name, week and season type β€” perfect for building an opponent list. We use UConn's 2024 championship run.

tid_sched = int(row["team_id"][0]) if row.height else 41 # UConn fallback
tsched = safe(
f"team schedule {tid_sched}",
lambda: sdv.mbb.espn_mbb_team_schedule(team_id=tid_sched, season=2024),
)
if tsched is not None and getattr(tsched, "height", 0):
keep = ["id", "short_name", "season_type_name", "week_text"]
out = tsched.select([c for c in keep if c in tsched.columns]).head(12)
else:
out = "team schedule unavailable right now"
out
βœ… team schedule 41





shape: (12, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ id ┆ short_name ┆ season_type_name ┆ week_text β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ══════════════β•ͺ══════════════════β•ͺ═══════════║
β”‚ 401584359 ┆ NAU @ CONN ┆ Regular Season ┆ Week 1 β”‚
β”‚ 401589296 ┆ STO @ CONN ┆ Regular Season ┆ Week 1 β”‚
β”‚ 401591369 ┆ MVSU @ CONN ┆ Regular Season ┆ Week 2 β”‚
β”‚ 401591374 ┆ CONN VS IU ┆ Regular Season ┆ Week 2 β”‚
β”‚ 401601491 ┆ CONN VS TEX ┆ Regular Season ┆ Week 3 β”‚
β”‚ … ┆ … ┆ … ┆ … β”‚
β”‚ 401574563 ┆ CONN @ KU ┆ Regular Season ┆ Week 4 β”‚
β”‚ 401580309 ┆ UNC VS CONN ┆ Regular Season ┆ Week 5 β”‚
β”‚ 401591372 ┆ UAPB @ CONN ┆ Regular Season ┆ Week 5 β”‚
β”‚ 401591373 ┆ CONN VS GONZ ┆ Regular Season ┆ Week 6 β”‚
β”‚ 401599441 ┆ CONN @ HALL ┆ Regular Season ┆ Week 7 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 10 β€” Top rebounding teams 🧲 (FoxSports)​

fox_mbb_league_leaders isn't just a player board β€” flip who="team" and pick category="rebounds" to rank programs on the glass straight from FoxSports. No IDs needed.

team_reb = safe(
"fox team rebounds",
lambda: sdv.mbb.fox_mbb_league_leaders(category="rebounds", who="team"),
)
if team_reb is not None and getattr(team_reb, "height", 0):
keep = ["teams", "gp", "w", "l", "ppg", "ppg_diff"]
out = team_reb.select([c for c in keep if c in team_reb.columns]).head(10)
else:
out = "Fox team leaders unavailable right now"
out
βœ… fox team rebounds





shape: (10, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ teams ┆ gp ┆ w ┆ l ┆ ppg ┆ ppg_diff β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•ͺ═════β•ͺ═════β•ͺ═════β•ͺ══════β•ͺ══════════║
β”‚ 1 ┆ 37 ┆ 21 ┆ 16 ┆ null ┆ null β”‚
β”‚ 2 ┆ 35 ┆ 21 ┆ 15 ┆ null ┆ null β”‚
β”‚ 3 ┆ 35 ┆ 18 ┆ 17 ┆ null ┆ null β”‚
β”‚ 4 ┆ 35 ┆ 23 ┆ 13 ┆ null ┆ null β”‚
β”‚ 5 ┆ 35 ┆ 15 ┆ 20 ┆ null ┆ null β”‚
β”‚ 6 ┆ 35 ┆ 19 ┆ 18 ┆ null ┆ null β”‚
β”‚ 7 ┆ 35 ┆ 30 ┆ 9 ┆ null ┆ null β”‚
β”‚ 8 ┆ 35 ┆ 19 ┆ 16 ┆ null ┆ null β”‚
β”‚ 9 ┆ 34 ┆ 29 ┆ 6 ┆ null ┆ null β”‚
β”‚ 10 ┆ 34 ┆ 36 ┆ 3 ┆ null ┆ null β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 11 β€” Crunch-time buckets πŸ”₯ (parquet PBP)​

load_mbb_pbp is the whole season's play-by-play in one parquet β€” no live game needed. We slice it to scoring plays in the final minute of the second half: every late-game dagger across the year.

season_pbp = sdv.mbb.load_mbb_pbp(seasons=[2024])
print("season pbp rows:", season_pbp.shape)
(season_pbp
.filter(
(pl.col("scoring_play") == True) # noqa: E712
& (pl.col("period_number") >= 2)
& (pl.col("end_period_seconds_remaining").cast(pl.Float64, strict=False) <= 60)
)
.select(["game_id", "period_display_value", "clock_display_value",
"text", "home_score", "away_score"])
.head(10))
season pbp rows: (2004997, 56)





shape: (10, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ game_id ┆ period_display_val ┆ clock_display_val ┆ text ┆ home_score ┆ away_score β”‚
β”‚ --- ┆ ue ┆ ue ┆ --- ┆ --- ┆ --- β”‚
β”‚ i32 ┆ --- ┆ --- ┆ str ┆ i32 ┆ i32 β”‚
β”‚ ┆ str ┆ str ┆ ┆ ┆ β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ════════════════════β•ͺ═══════════════════β•ͺ═══════════════════β•ͺ════════════β•ͺ════════════║
β”‚ 401638645 ┆ 2nd Half ┆ 1:22 ┆ Zach Edey made ┆ 73 ┆ 60 β”‚
β”‚ ┆ ┆ ┆ Dunk. Assisted … ┆ ┆ β”‚
β”‚ 401638645 ┆ 2nd Half ┆ 0:45 ┆ Donovan Clingan ┆ 75 ┆ 60 β”‚
β”‚ ┆ ┆ ┆ made Layup. As… ┆ ┆ β”‚
β”‚ 401638643 ┆ 2nd Half ┆ 1:27 ┆ Jayden Taylor ┆ 63 ┆ 47 β”‚
β”‚ ┆ ┆ ┆ made Layup. ┆ ┆ β”‚
β”‚ 401638643 ┆ 2nd Half ┆ 0:45 ┆ Jayden Taylor ┆ 63 ┆ 50 β”‚
β”‚ ┆ ┆ ┆ made Three Point… ┆ ┆ β”‚
β”‚ 401638644 ┆ 2nd Half ┆ 1:07 ┆ Tristen Newton ┆ 83 ┆ 68 β”‚
β”‚ ┆ ┆ ┆ made Three Poin… ┆ ┆ β”‚
β”‚ 401638644 ┆ 2nd Half ┆ 0:55 ┆ Grant Nelson made ┆ 83 ┆ 70 β”‚
β”‚ ┆ ┆ ┆ Layup. ┆ ┆ β”‚
β”‚ 401638644 ┆ 2nd Half ┆ 0:33 ┆ Cam Spencer made ┆ 86 ┆ 70 β”‚
β”‚ ┆ ┆ ┆ Three Point J… ┆ ┆ β”‚
β”‚ 401638644 ┆ 2nd Half ┆ 0:20 ┆ Grant Nelson made ┆ 86 ┆ 72 β”‚
β”‚ ┆ ┆ ┆ Two Point Ti… ┆ ┆ β”‚
β”‚ 401641124 ┆ 2nd Half ┆ 1:07 ┆ Al-Amir Dawes ┆ 77 ┆ 77 β”‚
β”‚ ┆ ┆ ┆ made Three Point… ┆ ┆ β”‚
β”‚ 401641124 ┆ 2nd Half ┆ 0:20 ┆ Dre Davis made ┆ 79 ┆ 77 β”‚
β”‚ ┆ ┆ ┆ Layup. ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 12 β€” Double-double leaders 🐼 (pandas interop)​

Prefer pandas? Pass return_as_pandas=True to any loader and stay in your comfort zone. Here we count games where a player hit double digits in at least two of points / rebounds / assists β€” the classic double-double β€” entirely in pandas.

import pandas as pd

pbox_pd = sdv.mbb.load_mbb_player_boxscore(seasons=[2024], return_as_pandas=True)
for col in ["points", "rebounds", "assists"]:
pbox_pd[col] = pd.to_numeric(pbox_pd[col], errors="coerce")
pbox_pd["is_dd"] = (pbox_pd[["points", "rebounds", "assists"]] >= 10).sum(axis=1) >= 2
(pbox_pd[pbox_pd["is_dd"]]
.groupby(["athlete_display_name", "team_abbreviation"])
.size()
.reset_index(name="double_doubles")
.sort_values("double_doubles", ascending=False)
.head(10)
.reset_index(drop=True))
athlete_display_name team_abbreviation double_doubles
0 Enrique Freeman AKR 31
1 Zach Edey PUR 30
2 Vonterius Woolbright WCU 27
3 DJ Burns YSU 22
4 Oumar Ballo ARIZ 20
5 Armando Bacot UNC 19
6 Fardaws Aimaq CAL 19
7 Yaxel Lendeborg UAB 19
8 Saint Thomas UNCO 19
9 Riley Minix MORE 19

🧾 One call, the whole game: espn_mbb_summary​

espn_mbb_summary is the Swiss army knife β€” a single event_id returns a dict with team & player box scores, play-by-play, win probability, leaders, officials and more. Let's grab the team box score from that 2024 title game.

summ = safe("summary 401638636", lambda: sdv.mbb.espn_mbb_summary(event_id=401638636))
if isinstance(summ, dict) and summ.get("boxscore_team") is not None:
tb = summ["boxscore_team"]
tb = tb if isinstance(tb, pl.DataFrame) else pl.DataFrame(tb)
print("box score sections available:", [k for k in summ.keys()][:8])
out = tb.head()
else:
out = "summary unavailable right now"
out
βœ… summary 401638636
box score sections available: ['boxscore_player', 'boxscore_team', 'plays', 'winprobability', 'leaders', 'game_info', 'officials', 'header']





shape: (5, 9)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_id ┆ team_abbre ┆ team_disp ┆ home_away ┆ … ┆ stat_name ┆ stat_labe ┆ stat_disp ┆ stat_valu β”‚
β”‚ --- ┆ viation ┆ lay_name ┆ --- ┆ ┆ --- ┆ l ┆ lay_value ┆ e β”‚
β”‚ str ┆ --- ┆ --- ┆ str ┆ ┆ str ┆ --- ┆ --- ┆ --- β”‚
β”‚ ┆ str ┆ str ┆ ┆ ┆ ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•ͺ════════════β•ͺ═══════════β•ͺ═══════════β•ͺ═══β•ͺ═══════════β•ͺ═══════════β•ͺ═══════════β•ͺ═══════════║
β”‚ 156 ┆ CREI ┆ Creighton ┆ away ┆ … ┆ fieldGoal ┆ FG ┆ 26-58 ┆ null β”‚
β”‚ ┆ ┆ Bluejays ┆ ┆ ┆ sMade-fie ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ ldGoalsAt ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ tem… ┆ ┆ ┆ β”‚
β”‚ 156 ┆ CREI ┆ Creighton ┆ away ┆ … ┆ fieldGoal ┆ Field ┆ 45 ┆ null β”‚
β”‚ ┆ ┆ Bluejays ┆ ┆ ┆ Pct ┆ Goal % ┆ ┆ β”‚
β”‚ 156 ┆ CREI ┆ Creighton ┆ away ┆ … ┆ threePoin ┆ 3PT ┆ 11-23 ┆ null β”‚
β”‚ ┆ ┆ Bluejays ┆ ┆ ┆ tFieldGoa ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ lsMade-th ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ ree… ┆ ┆ ┆ β”‚
β”‚ 156 ┆ CREI ┆ Creighton ┆ away ┆ … ┆ threePoin ┆ Three ┆ 48 ┆ null β”‚
β”‚ ┆ ┆ Bluejays ┆ ┆ ┆ tFieldGoa ┆ Point % ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ lPct ┆ ┆ ┆ β”‚
β”‚ 156 ┆ CREI ┆ Creighton ┆ away ┆ … ┆ freeThrow ┆ FT ┆ 12-13 ┆ null β”‚
β”‚ ┆ ┆ Bluejays ┆ ┆ ┆ sMade-fre ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ eThrowsAt ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ tem… ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ™Œ Who suited up: game rosters​

espn_mbb_game_rosters returns one row per dressed player for a game, flagging starters β€” handy for joining onto play-by-play or box scores.

gr = safe("game rosters 401638636", lambda: sdv.mbb.espn_mbb_game_rosters(game_id=401638636))
if gr is not None and getattr(gr, "height", 0):
keep = ["athlete_display_name", "team_abbreviation", "starter"]
out = gr.select([c for c in keep if c in gr.columns]).head(10)
else:
out = "game rosters unavailable right now"
out
βœ… game rosters 401638636





shape: (10, 3)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ athlete_display_name ┆ team_abbreviation ┆ starter β”‚
β”‚ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ bool β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════β•ͺ═════════║
β”‚ Jonas Aidoo ┆ TENN ┆ true β”‚
β”‚ Dalton Knecht ┆ TENN ┆ true β”‚
β”‚ Zakai Zeigler ┆ TENN ┆ true β”‚
β”‚ Jahmai Mashack ┆ TENN ┆ true β”‚
β”‚ Josiah-Jordan James ┆ TENN ┆ true β”‚
β”‚ Tobe Awaka ┆ TENN ┆ false β”‚
β”‚ J.P. Estrella ┆ TENN ┆ false β”‚
β”‚ Freddie Dilione V ┆ TENN ┆ false β”‚
β”‚ Cameron Carr ┆ TENN ┆ false β”‚
β”‚ Jordan Gainey ┆ TENN ┆ false β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ A multi-season pipeline: highest-scoring tournament games​

The schedule loader is stable, so here's a pure-polars analysis with no live dependency. We load the 2024 season schedule and rank games by combined points β€” March Madness shootouts float right to the top.

schedule_2024 = sdv.mbb.load_mbb_schedule(seasons=[2024])
print("season schedule rows:", schedule_2024.shape)
(schedule_2024
.with_columns(
(pl.col("home_score").cast(pl.Int64, strict=False)
+ pl.col("away_score").cast(pl.Int64, strict=False)).alias("total"))
.filter(pl.col("total").is_not_null())
.sort("total", descending=True)
.select(["game_date", "home_display_name", "away_display_name",
"home_score", "away_score", "total"])
.head(10))
season schedule rows: (6249, 84)





shape: (10, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ game_date ┆ home_display_name ┆ away_display_name ┆ home_score ┆ away_score ┆ total β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ date ┆ str ┆ str ┆ i32 ┆ i32 ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════════════β•ͺ═══════════════════════β•ͺ════════════β•ͺ════════════β•ͺ═══════║
β”‚ 2024-01-03 ┆ George Washington ┆ Fordham Rams ┆ 113 ┆ 119 ┆ 232 β”‚
β”‚ ┆ Revolutionar… ┆ ┆ ┆ ┆ β”‚
β”‚ 2024-01-13 ┆ Samford Bulldogs ┆ VMI Keydets ┆ 134 ┆ 96 ┆ 230 β”‚
β”‚ 2023-12-14 ┆ Tulane Green Wave ┆ Furman Paladins ┆ 117 ┆ 110 ┆ 227 β”‚
β”‚ 2024-01-25 ┆ Denver Pioneers ┆ South Dakota Coyotes ┆ 111 ┆ 110 ┆ 221 β”‚
β”‚ 2023-11-09 ┆ Kent State Golden Flashes ┆ James Madison Dukes ┆ 108 ┆ 113 ┆ 221 β”‚
β”‚ 2023-11-08 ┆ Bryant Bulldogs ┆ Fisher College Eagles ┆ 140 ┆ 79 ┆ 219 β”‚
β”‚ 2024-02-03 ┆ UAlbany Great Danes ┆ UMBC Retrievers ┆ 102 ┆ 114 ┆ 216 β”‚
β”‚ 2024-01-21 ┆ UTSA Roadrunners ┆ Florida Atlantic Owls ┆ 103 ┆ 112 ┆ 215 β”‚
β”‚ 2024-03-02 ┆ Kentucky Wildcats ┆ Arkansas Razorbacks ┆ 111 ┆ 102 ┆ 213 β”‚
β”‚ 2024-02-10 ┆ Appalachian State ┆ Toledo Rockets ┆ 109 ┆ 104 ┆ 213 β”‚
β”‚ ┆ Mountaineers ┆ ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

πŸŽ‰ Where to next​

  • πŸŸ₯ ESPN wrappers (espn_mbb_*) cover the live site + core APIs β€” scoreboards, standings, rankings, summaries, play-by-play and more. See the additional and site reference pages.
  • 🦊 FoxSports wrappers (fox_mbb_*) β€” leaders, standings, rosters, boxscores and odds in additional.
  • πŸ“¦ Loaders (load_mbb_*) read whole seasons of parquet β€” see loaders. Pass return_as_pandas=True anywhere for pandas instead of polars.
  • πŸ€ R user? The same surface lives in hoopR (NBA + NCAA men's basketball).
  • 🚺 Women's hoops? Check out the WBB module and its companion wehoop.

Now go bracket something! πŸ€πŸ”₯