π 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.
| Function | What it gives you | Source |
|---|---|---|
espn_mbb_teams | Every D-I team (grab team_ids) | π₯ ESPN β |
espn_mbb_schedule | Games + results for a date / window | π₯ ESPN β |
espn_mbb_scoreboard | Rich scoreboard for a date (status, lines, odds) | π₯ ESPN β |
espn_mbb_standings | Conference standings, one row per team | π₯ ESPN β |
espn_mbb_rankings | AP / Coaches poll (in-season) | π₯ ESPN β |
espn_mbb_summary | Full game summary: box, plays, win prob | π₯ ESPN β |
espn_mbb_team_roster | A team's roster | π₯ ESPN β |
espn_mbb_pbp | Event-level play-by-play for a game | π₯ ESPN β |
espn_mbb_game_rosters | Who dressed + started for one game | π₯ ESPN β |
espn_mbb_player_stats | A player's season stat line | π₯ ESPN β |
fox_mbb_league_leaders | Stat leaders (scoring, rebounds, β¦) | π¦ Fox β |
fox_mbb_standings | Fox conference standings for a team | π¦ Fox β |
fox_mbb_team_roster | Fox roster for a team | π¦ Fox |
espn_mbb_team_schedule | One team's full season schedule | π₯ ESPN β |
espn_mbb_conferences | Conference / group catalog | π₯ ESPN β |
load_mbb_schedule | Whole-season schedule parquet | π¦ loader |
load_mbb_player_boxscore | Season player box scores | π¦ loader |
load_mbb_team_boxscore | Season team box scores | π¦ loader |
load_mbb_pbp | Season play-by-play parquet | π¦ loader |
most_recent_mbb_season | Current 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 β
β Dwayne Koroma β 4 β 6' 8" β 212 lbs β
β β¦ β β¦ β β¦ β β¦ β
β UroΕ‘ Paunovic β 77 β 6' 3" β 190 lbs β
β Eric Reibe β 12 β 7' 1" β 260 lbs β
β Jacob Ross β 13 β 6' 5" β 195 lbs β
β Jayden Ross β 23 β 6' 7" β 205 lbs β
β Malachi Smith β 0 β 6' 1" β 180 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 β
β Jordan Sears β UT Martin β 32 β 21.6 β
β Tucker DeVries β Drake β 34 β 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 β
β Wright State Raiders β 218.0 β 569.0 β 38.3 β
β McNeese Cowboys β 257.0 β 671.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 β
β Zach Edey β PUR β 39 β 983.0 β 67.3 β
β Chaz Lanier β UNF β 32 β 629.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, 58)
shape: (10, 6)
βββββββββββββ¬βββββββββββββββββββββ¬ββββββββββββββββββββ¬ββββββββββββββββββββ¬βββββββββββββ¬βββββββββββββ
β game_id β period_display_val β clock_display_val β text β home_score β away_score β
β --- β ue β ue β --- β --- β --- β
β i32 β --- β --- β str β i32 β i32 β
β β str β str β β β β
βββββββββββββͺβββββββββββββββββββββͺββββββββββββββββββββͺββββββββββββββββββββͺβββββββββββββͺβββββββββββββ‘
β 401573353 β 2nd Half β 0:19 β Aanen Moody made β 89 β 79 β
β β β β Three Point Jβ¦ β β β
β 401573354 β 2nd Half β 0:20 β Terren Frank made β 48 β 62 β
β β β β Jumper. β β β
β 401573355 β 2nd Half β 0:04 β Miguel Tomley β 75 β 76 β
β β β β made Free Throw. β β β
β 401573355 β 2nd Half β 0:04 β Miguel Tomley β 76 β 76 β
β β β β made Free Throw. β β β
β 401573355 β OT β 1:16 β Carson Basham β 83 β 79 β
β β β β made Free Throw. β β β
β 401573355 β OT β 0:51 β Trent McLaughlin β 83 β 82 β
β β β β made Three Poβ¦ β β β
β 401573355 β OT β 0:29 β Maleek Arington β 84 β 82 β
β β β β made Free Throβ¦ β β β
β 401573355 β OT β 0:06 β Liam Lloyd made β 84 β 83 β
β β β β Free Throw. β β β
β 401573355 β OT β 0:06 β Liam Lloyd made β 84 β 84 β
β β β β Free Throw. β β β
β 401573355 β 2OT β 0:42 β Jayden Jackson β 88 β 90 β
β β β β made 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 β β β β β
ββββββββββββββ΄ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββ΄βββββββββββββ΄βββββββββββββ΄ββββββββ
ποΈ stats.ncaa.org β the ncaa_mbb_* family (bigballR parity)β
New in 0.0.72: a 16-function port of bigballR wired to stats.ncaa.org β
schedules, rosters, box scores, play-by-play, lineups, possessions, on/off
splits, and shot locations (ncaa_mbb_team_schedule, ncaa_mbb_game_pbp,
ncaa_mbb_lineups, ncaa_mbb_possessions, ncaa_mbb_on_off, ...). The
(team, season) β stats.ncaa.org id crosswalk ships with the package, so it
works offline:
from sportsdataverse.mbb import ncaa_mbb_team_ids
ids = ncaa_mbb_team_ids()
print("team-id crosswalk:", ids.shape)
ids.filter(pl.col("season") == "2025-26").head()
team-id crosswalk: (6006, 4)
shape: (5, 4)
ββββββββββββββββββββββ¬βββββββββββββ¬βββββββββ¬ββββββββββ
β team β conference β id β season β
β --- β --- β --- β --- β
β str β str β i64 β str β
ββββββββββββββββββββββͺβββββββββββββͺβββββββββͺββββββββββ‘
β A&M-Corpus Christi β Southland β 609609 β 2025-26 β
β Abilene Christian β WAC β 609614 β 2025-26 β
β Air Force β MWC β 609502 β 2025-26 β
β Akron β MAC β 609541 β 2025-26 β
β Alabama β SEC β 609617 β 2025-26 β
ββββββββββββββββββββββ΄βββββββββββββ΄βββββββββ΄ββββββββββ
Live game fetches go through the shared proxy-bound NCAA fetch layer
(stats.ncaa.org is IP-ban-happy β configure SDV_PY_NCAA_* /
SDV_PY_PROXYBONANZA_* before fetching):
from sportsdataverse.mbb import ncaa_mbb_game_pbp, ncaa_mbb_lineups
pbp = ncaa_mbb_game_pbp(game_id) # one row per event
lineups = ncaa_mbb_lineups(game_id) # five-player stints
π 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. Passreturn_as_pandas=Trueanywhere 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! ππ₯