Skip to main content

๐Ÿ€๐ŸŒ International basketball with sportsdataverse-py

This page is the executed notebook 16_international_basketball_intro.ipynb: download it to run it yourself.

The NBA is one league; the rest of the basketball world is dozens. sportsdataverse-py wraps the feeds behind the biggest of them, and this notebook tours four:

LeagueModuleProvider
EuroLeaguesportsdataverse.euroleagueEuroLeague Competition Engine
Super League Basketball (UK)sportsdataverse.basketball.geniusGenius Sports LiveStats
NBL (Australia)sportsdataverse.basketball.nbl_auNBL Rosetta
Liga Endesa (Spain)sportsdataverse.basketball.acbACB

No API keys, polars by default (return_as_pandas=True for pandas), and at the end a EuroLeague shot chart drawn on a FIBA court with sdvplot. ๐ŸŽฏ

๐Ÿงฐ The toolboxโ€‹

FunctionWhat it gives you
euroleague_seasonsSeasons of a competition (E = EuroLeague, U = EuroCup)
euroleague_gamesEvery game of a season, played or not
euroleague_standingsStandings after a round
euroleague_game_pointsShot chart of one game (coord_x / coord_y in cm)
slb_competitionsSLB competitions with their END-year season
slb_scheduleEvery match of an SLB season
slb_player_boxPlayer box score of a finished SLB game
slb_shotsShot chart of a finished SLB game
nbl_rosetta_standingsNBL regular-season ladder
nbl_rosetta_matchesMatches of an NBL season
acb_standingsLiga Endesa table after a round
acb_calendarEvery round of a season with its games

Genius LiveStats serves many leagues (wbbl_*, bal_*, nznbl_*, esake_*, lfb_*, ...) with the same shape, so the SLB recipes below work for every one of them.

๐Ÿ”Œ Setupโ€‹

pip install "sportsdataverse[sdvplot]"

The sdvplot extra brings in sdvplot and sportypy for the court drawing.

import matplotlib.pyplot as plt
import polars as pl

from sportsdataverse import euroleague as el
from sportsdataverse.basketball import acb, genius, nbl_au

pl.Config.set_tbl_rows(10)
polars.config.Config

Provider feeds are seasonal and occasionally rate-limited, so a tiny safe() helper runs each call defensively: you get the frame when the feed is up, and a one-line skip when it isn't, naming whether the answer was nothing there (NoDataError) or the fetch failed (AssetFetchError). ๐Ÿ›Ÿ

from sportsdataverse.errors import AssetFetchError, NoDataError

def safe(label, thunk):
try:
out = thunk()
print(f"โœ… {label}")
return out
except (NoDataError, AssetFetchError) as e:
print(f"\u23ed\ufe0f {label}: {type(e).__name__}: {e}")
return None

๐Ÿ‡ช๐Ÿ‡บ EuroLeagueโ€‹

Season codes are the competition letter plus the START year (E2025 is 2025-26). We take the newest season that has a played game, so the notebook works in the off-season too.

seasons = safe("EuroLeague seasons", lambda: el.euroleague_seasons("E"))
season_code, games = None, None
if seasons is not None:
for code in seasons.sort("year", descending=True)["code"].head(3):
g = safe(f"games {code}", lambda code=code: el.euroleague_games("E", code))
if g is not None and g.filter(pl.col("played") == True).height:
season_code, games = code, g
break
print("season:", season_code, "| games:", None if games is None else games.shape)
โœ… EuroLeague seasons


โœ… games E2026
season: E2026 | games: (380, 94)
played = games.filter(pl.col("played") == True).sort("utc_date") if games is not None else None
cols = ["game_code", "round", "utc_date", "local_club_name", "road_club_name"]
(played.select([c for c in cols if c in played.columns]).tail(5)
if played is not None and played.height else "no played games")
shape: (5, 5)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ game_code โ”† round โ”† utc_date โ”† local_club_name โ”† road_club_name โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† i64 โ”† str โ”† str โ”† str โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 63 โ”† 7 โ”† 2026-10-22T18:30:00Z โ”† Armani Olimpia Milan โ”† FC Barcelona โ”‚
โ”‚ 66 โ”† 7 โ”† 2026-10-22T18:45:00Z โ”† Partizan Mozzart Bet โ”† Crvena Zvezda Meridianbet โ”‚
โ”‚ โ”† โ”† โ”† Belgrade โ”† Belgโ€ฆ โ”‚
โ”‚ 68 โ”† 7 โ”† 2026-10-23T17:00:00Z โ”† Zalgiris Kaunas โ”† LDLC ASVEL Villeurbanne โ”‚
โ”‚ 70 โ”† 7 โ”† 2026-10-23T18:15:00Z โ”† Olympiacos Piraeus โ”† Fenerbahce Tarfin Istanbul โ”‚
โ”‚ 69 โ”† 7 โ”† 2026-10-23T18:45:00Z โ”† Paris Basketball โ”† Valencia Basket โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Standings after the latest round ๐Ÿ†โ€‹

euroleague_standings takes the round as a string.

last_round = str(played["round"].max()) if played is not None and played.height else None
stand = safe(f"standings round {last_round}",
lambda: el.euroleague_standings("E", season_code, last_round)) if last_round else None
cols = ["position", "club_name", "games_played", "games_won", "games_lost", "points_difference"]
(stand.select([c for c in cols if c in stand.columns]).head(10)
if stand is not None and stand.height else "standings unavailable")
โœ… standings round 7





shape: (10, 6)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ position โ”† club_name โ”† games_played โ”† games_won โ”† games_lost โ”† points_difference โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† str โ”† i64 โ”† i64 โ”† i64 โ”† str โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1 โ”† Panathinaikos AKTOR โ”† 7 โ”† 6 โ”† 1 โ”† +96 โ”‚
โ”‚ โ”† Athens โ”† โ”† โ”† โ”† โ”‚
โ”‚ 2 โ”† Fenerbahce Tarfin โ”† 7 โ”† 6 โ”† 1 โ”† +57 โ”‚
โ”‚ โ”† Istanbul โ”† โ”† โ”† โ”† โ”‚
โ”‚ 3 โ”† Valencia Basket โ”† 7 โ”† 5 โ”† 2 โ”† +57 โ”‚
โ”‚ 4 โ”† Olympiacos Piraeus โ”† 7 โ”† 5 โ”† 2 โ”† +37 โ”‚
โ”‚ 5 โ”† Hapoel IBI Tel Aviv โ”† 7 โ”† 5 โ”† 2 โ”† +29 โ”‚
โ”‚ 6 โ”† Dubai Basketball โ”† 7 โ”† 5 โ”† 2 โ”† +13 โ”‚
โ”‚ 7 โ”† Real Madrid โ”† 7 โ”† 4 โ”† 3 โ”† +37 โ”‚
โ”‚ 8 โ”† Crvena Zvezda Meridianbet โ”† 7 โ”† 4 โ”† 3 โ”† +28 โ”‚
โ”‚ โ”† Belgโ€ฆ โ”† โ”† โ”† โ”† โ”‚
โ”‚ 9 โ”† FC Barcelona โ”† 7 โ”† 4 โ”† 3 โ”† +17 โ”‚
โ”‚ 10 โ”† Virtus Bologna โ”† 7 โ”† 4 โ”† 3 โ”† -2 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

A shot chart on a FIBA court ๐ŸŽฏโ€‹

euroleague_game_points returns one row per field-goal attempt and made free throw, in centimeters with the hoop at the origin. sdvplot.court_coords(..., provider="euroleague") converts that frame to meters on the FIBA court that sdvplot.surface("fiba") draws (free throws, encoded -1, -1, become null). Both teams shoot at one basket, so we draw the defensive half.

import sdvplot

gcode = str(played["game_code"][-1]) if played is not None and played.height else None
pts = safe(f"shots game {gcode}", lambda: el.euroleague_game_points(gcode, season_code)) if gcode else None
if pts is not None and pts.height:
shots = sdvplot.court_coords(pts, x="coord_x", y="coord_y", provider="euroleague")
shots = shots.filter(pl.col("court_x").is_not_null())
made = shots.filter(pl.col("id_action").is_in(["2FGM", "3FGM"]))
missed = shots.filter(pl.col("id_action").is_in(["2FGA", "3FGA"]))
ax = sdvplot.surface("fiba", display_range="defense")
ax.scatter(missed["court_x"], missed["court_y"], marker="x", s=30, c="#888888", zorder=20, label="missed")
ax.scatter(made["court_x"], made["court_y"], s=30, c="#d62728", zorder=21, label="made")
ax.legend(loc="upper right")
ax.set_title(f"EuroLeague {season_code} game {gcode}: {shots.height} field-goal attempts")
plt.show()
else:
print("no shot data for this game")
โœ… shots game 69

png

๐Ÿ‡ฌ๐Ÿ‡ง Super League Basketball (Genius Sports)โ€‹

slb_competitions lists the SLB competitions Genius hosts; slb_schedule defaults to the current one.

comps = safe("SLB competitions", genius.slb_competitions)
comps.select([c for c in ["comp_id", "label", "season"] if c in comps.columns]).head() if comps is not None else None
โœ… SLB competitions





shape: (5, 3)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ comp_id โ”† label โ”† season โ”‚
โ”‚ --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† str โ”† i64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 49597 โ”† Championship 26-27 โ”† 2027 โ”‚
โ”‚ 39625 โ”† Championship 2024-25 โ”† 2025 โ”‚
โ”‚ 41897 โ”† Championship 2025-26 โ”† 2026 โ”‚
โ”‚ 49599 โ”† Cup 26-27 โ”† 2027 โ”‚
โ”‚ 39732 โ”† Cup 2024-25 โ”† 2025 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
sched = safe("SLB schedule", genius.slb_schedule)
done = sched.filter(pl.col("status") == "COMPLETE") if sched is not None else None
cols = ["match_id", "date_time", "home_team_name", "home_score", "away_team_name", "away_score"]
(done.select([c for c in cols if c in done.columns]).tail(5)
if done is not None and done.height else "no completed SLB games yet")
โœ… SLB schedule





shape: (5, 6)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ match_id โ”† date_time โ”† home_team_name โ”† home_score โ”† away_team_name โ”† away_score โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† str โ”† str โ”† i64 โ”† str โ”† i64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 2882466 โ”† Oct 23, 2026, โ”† Leicester Riders โ”† 87 โ”† Manchester โ”† 89 โ”‚
โ”‚ โ”† 7:30 PM โ”† โ”† โ”† Basketball โ”† โ”‚
โ”‚ 2882469 โ”† Oct 23, 2026, โ”† Newcastle Eagles โ”† 82 โ”† Surrey 89ers โ”† 55 โ”‚
โ”‚ โ”† 7:30 PM โ”† โ”† โ”† โ”† โ”‚
โ”‚ 2882472 โ”† Oct 24, 2026, โ”† London Lions โ”† 82 โ”† Bristol Flyers โ”† 76 โ”‚
โ”‚ โ”† 6:00 PM โ”† โ”† โ”† โ”† โ”‚
โ”‚ 2882446 โ”† Oct 25, 2026, โ”† Liverpool Basketball โ”† 77 โ”† Newcastle Eagles โ”† 74 โ”‚
โ”‚ โ”† 4:00 PM โ”† โ”† โ”† โ”† โ”‚
โ”‚ 2882449 โ”† Oct 25, 2026, โ”† Cheshire Phoenix โ”† 91 โ”† Surrey 89ers โ”† 107 โ”‚
โ”‚ โ”† 5:30 PM โ”† โ”† โ”† โ”† โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

One game: box score and shots ๐Ÿ“‹โ€‹

Take the latest completed match_id, then the player box score (slb_player_box) and the shot chart (slb_shots, x/y as a percent of the full court).

mid = int(done["match_id"][-1]) if done is not None and done.height else None
box = safe(f"SLB box {mid}", lambda: genius.slb_player_box(mid)) if mid else None
cols = ["name", "minutes", "points", "rebounds_total", "assists", "field_goals_made", "field_goals_attempted"]
(box.select([c for c in cols if c in box.columns]).sort("points", descending=True).head(8)
if box is not None and box.height and "points" in box.columns else "box score unavailable")
โœ… SLB box 2882449





shape: (8, 7)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ name โ”† minutes โ”† points โ”† rebounds_total โ”† assists โ”† field_goals_mad โ”† field_goals_att โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† e โ”† empted โ”‚
โ”‚ str โ”† str โ”† i64 โ”† i64 โ”† i64 โ”† --- โ”† --- โ”‚
โ”‚ โ”† โ”† โ”† โ”† โ”† i64 โ”† i64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ A. Patterson โ”† 18:55 โ”† 20 โ”† 3 โ”† 1 โ”† 5 โ”† 10 โ”‚
โ”‚ E. Reynolds Ii โ”† 29:22 โ”† 18 โ”† 3 โ”† 5 โ”† 5 โ”† 13 โ”‚
โ”‚ J. Graham โ”† 26:37 โ”† 17 โ”† 4 โ”† 3 โ”† 7 โ”† 11 โ”‚
โ”‚ C. Mcgill โ”† 31:13 โ”† 16 โ”† 3 โ”† 2 โ”† 6 โ”† 9 โ”‚
โ”‚ J. Searles โ”† 25:48 โ”† 15 โ”† 4 โ”† 4 โ”† 5 โ”† 9 โ”‚
โ”‚ J. Hinds โ”† 23:10 โ”† 14 โ”† 2 โ”† 0 โ”† 6 โ”† 11 โ”‚
โ”‚ R. Cisarik โ”† 30:33 โ”† 13 โ”† 4 โ”† 2 โ”† 5 โ”† 7 โ”‚
โ”‚ R. Stewart โ”† 11:45 โ”† 11 โ”† 2 โ”† 0 โ”† 4 โ”† 5 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
sh = safe(f"SLB shots {mid}", lambda: genius.slb_shots(mid)) if mid else None
(sh.group_by("action_type", "result").len().sort("action_type")
if sh is not None and sh.height else "no shots for this game")
โœ… SLB shots 2882449





shape: (4, 3)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”
โ”‚ action_type โ”† result โ”† len โ”‚
โ”‚ --- โ”† --- โ”† --- โ”‚
โ”‚ str โ”† i64 โ”† u32 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ก
โ”‚ 2pt โ”† 0 โ”† 27 โ”‚
โ”‚ 2pt โ”† 1 โ”† 43 โ”‚
โ”‚ 3pt โ”† 0 โ”† 42 โ”‚
โ”‚ 3pt โ”† 1 โ”† 26 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ‡ฆ๐Ÿ‡บ NBL (Australia)โ€‹

The NBL's Rosetta API takes the season as its END year. nbl_rosetta_standings gives the ladder; nbl_rosetta_matches the match list.

nbl_season = 2026
ladder = safe(f"NBL ladder {nbl_season}", lambda: nbl_au.nbl_rosetta_standings(nbl_season))
cols = ["position", "team_name", "won", "lost", "win_percentage", "points_for", "points_against", "streak"]
(ladder.select([c for c in cols if c in ladder.columns]).sort("position")
if ladder is not None and ladder.height else "ladder unavailable")
โœ… NBL ladder 2026





shape: (10, 8)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ position โ”† team_name โ”† won โ”† lost โ”† win_percentage โ”† points_for โ”† points_against โ”† streak โ”‚
โ”‚ --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† str โ”† i64 โ”† i64 โ”† f64 โ”† i64 โ”† i64 โ”† i64 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1 โ”† Sydney Kings โ”† 24 โ”† 9 โ”† 0.7273 โ”† 3276 โ”† 2879 โ”† 11 โ”‚
โ”‚ 2 โ”† Adelaide 36ers โ”† 23 โ”† 10 โ”† 0.697 โ”† 3042 โ”† 2890 โ”† 0 โ”‚
โ”‚ 3 โ”† South East โ”† 22 โ”† 11 โ”† 0.6667 โ”† 3324 โ”† 3061 โ”† 0 โ”‚
โ”‚ โ”† Melbourne โ”† โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ โ”† Phoenix โ”† โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 4 โ”† Perth Wildcats โ”† 21 โ”† 12 โ”† 0.6364 โ”† 2996 โ”† 2840 โ”† 1 โ”‚
โ”‚ 5 โ”† Melbourne United โ”† 20 โ”† 13 โ”† 0.6061 โ”† 3041 โ”† 2905 โ”† 1 โ”‚
โ”‚ 6 โ”† Tasmania โ”† 14 โ”† 19 โ”† 0.4242 โ”† 2873 โ”† 2884 โ”† 0 โ”‚
โ”‚ โ”† JackJumpers โ”† โ”† โ”† โ”† โ”† โ”† โ”‚
โ”‚ 7 โ”† NZ Breakers โ”† 13 โ”† 20 โ”† 0.3939 โ”† 3022 โ”† 3058 โ”† 1 โ”‚
โ”‚ 8 โ”† Illawarra Hawks โ”† 13 โ”† 20 โ”† 0.3939 โ”† 3074 โ”† 3205 โ”† 2 โ”‚
โ”‚ 9 โ”† Cairns Taipans โ”† 9 โ”† 24 โ”† 0.2727 โ”† 2754 โ”† 3194 โ”† 0 โ”‚
โ”‚ 10 โ”† Brisbane Bullets โ”† 6 โ”† 27 โ”† 0.1818 โ”† 2710 โ”† 3196 โ”† 0 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
matches = safe(f"NBL matches {nbl_season}", lambda: nbl_au.nbl_rosetta_matches(nbl_season))
(matches.group_by("match_status").len() if matches is not None and "match_status" in matches.columns
else "matches unavailable")
โœ… NBL matches 2026





shape: (2, 2)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”
โ”‚ match_status โ”† len โ”‚
โ”‚ --- โ”† --- โ”‚
โ”‚ str โ”† u32 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ก
โ”‚ draft โ”† 3 โ”‚
โ”‚ complete โ”† 206 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ‡ช๐Ÿ‡ธ Liga Endesa (ACB)โ€‹

acb_standings defaults to the table after the current round; acb_calendar lists every round of the season in one call. A bar chart of the table makes the early-season race easy to read.

table = safe("ACB standings", acb.acb_standings)
if table is not None and table.height:
t = table.sort("win_pct", "plus_minus")
fig, ax = plt.subplots(figsize=(7, 6))
ax.barh(t["team"], t["win_pct"], color="#1f4e79")
ax.set_xlabel("win %")
end = int(t["season"][0]) # END year
ax.set_title(f"Liga Endesa {end - 1}-{str(end)[2:]} standings")
plt.tight_layout()
plt.show()
else:
print("ACB standings unavailable")
โœ… ACB standings

png

cal = safe("ACB calendar", acb.acb_calendar)
(cal.group_by("round_number").agg(games=pl.len(), played=pl.col("home_score").is_not_null().sum())
.sort("round_number").head(8) if cal is not None and cal.height else "calendar unavailable")
โœ… ACB calendar





shape: (8, 3)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ round_number โ”† games โ”† played โ”‚
โ”‚ --- โ”† --- โ”† --- โ”‚
โ”‚ i64 โ”† u32 โ”† u32 โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1 โ”† 9 โ”† 9 โ”‚
โ”‚ 2 โ”† 9 โ”† 9 โ”‚
โ”‚ 3 โ”† 9 โ”† 9 โ”‚
โ”‚ 4 โ”† 9 โ”† 9 โ”‚
โ”‚ 5 โ”† 9 โ”† 9 โ”‚
โ”‚ 6 โ”† 9 โ”† 9 โ”‚
โ”‚ 7 โ”† 9 โ”† 9 โ”‚
โ”‚ 8 โ”† 9 โ”† 9 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŽ‰ Where to nextโ€‹

  • Every Genius league (wbbl_, bal_, nznbl_, esake_, lfb_, lnbm_, lub_) has the same _schedule / _player_box / _pbp / _shots set as SLB.
  • More homes under sportsdataverse.basketball: fiba_hub, atrium, feb, scb, lba, vtb, kbl, bleague, cebl, aba and others; each has a reference page in the sidebar.
  • Shot charts: sdvplot's court_coords also converts NBA and WNBA stats-API shots (provider="nba").
  • NBA, WNBA and college basketball have their own notebooks (04, 08, 06, 05).