Skip to main content

NBA — additional Python functions — Analytics: nba_l2m–nba_player

nba_l2m​

nba_l2m(game_id: 'str | int', *, raw: 'bool' = False, return_as_pandas: 'bool' = False, proxy: 'dict | None' = None) -> 'dict[str, Any]'

Fetch and parse an NBA Last Two Minute report from official.nba.com.

Retrieves the L2M report for a given game and returns parsed tables of calls, game metadata, and error statistics. The game_id is zero-padded to 10 digits (e.g., 42500405 becomes "0042500405"). A report is published for any game that is within 3 points (5 points before the 2017-18 season) at any point during the last two minutes of the fourth quarter or overtime -- not only playoff games.

Parameters

ParameterTypeDefaultDescription
game_idstr | intNBA game ID (can be int or str). Automatically zero-padded to 10 digits.
rawboolFalseIf True, return the raw JSON payload (dict) instead of parsed DataFrames.
return_as_pandasboolFalseIf True, return pandas DataFrames instead of polars.
proxydict | NoneNoneOptional proxy dict passed through to the HTTP layer.

Returns

If raw=True, the raw JSON dict. Otherwise, a dict with keys "calls", "game", "stats" mapping to DataFrames as documented in parse_nba_l2m.

col_nametypedescription
calls.game_idString10-digit NBA game id (zero-padded), the join key back to the L2M game and stats tables.
calls.periodInt64Period number extracted from period_name's digits: 4 for the fourth quarter, 5+ for overtime periods (Q5 = OT1 through Q8 = OT4).
calls.period_nameStringRaw NBA.com period label for the graded play, e.g. Q4 for the fourth quarter or Q5 for the first overtime.
calls.pc_timeStringRaw game clock string from the L2M report before normalization, in MM:SS or MM:SS.t tenths-of-a-second format.
calls.seconds_remainingFloat64Seconds remaining in the period at the graded play, parsed out of pc_time.
calls.call_typeStringRaw "Call: Type" label from the report, e.g. "Foul: Shooting" or "Turnover: 24 Second Violation"; whitespace (including doubled spaces) is NOT cleaned here -- the cleaned, upper-cased parts are call and type.
calls.callStringUpper-cased category before the colon in call_type, e.g. FOUL, TURNOVER, or STOPPAGE.
calls.typeStringUpper-cased detail after the colon in call_type, e.g. SHOOTING or 24 SECOND VIOLATION.
calls.committingStringPlayer, team nickname, or coach responsible for the graded action.
calls.disadvantagedStringPlayer or team nickname disadvantaged by the graded action, blank when not applicable.
calls.decisionStringNormalized grading decision: CC (correct call), CNC (correct non-call), IC (incorrect call), or INC (incorrect non-call); null when the report marks the play undetectable at game speed.
calls.decision_rawStringRaw grading code from the report before normalization, including the rare NCC/NCI aliases folded into decision.
calls.commentStringGrader's free-text explanation of the ruling, sometimes naming a player as "Last (TEAM)".
calls.difficultyStringGrader's difficulty rating for the play, e.g. Observable, Difficult, Blatant/Hard, or Undetectable.
calls.video_event_idStringL2M video event id from the report (source field VideolLink, sic); not a stats.nba.com play-by-play EVENTNUM, so it cannot be joined to pbp data.
calls.pos_idInt64Possession id assigned by the report, rising monotonically across the game; rows sharing pos_start/pos_end belong to the same possession.
calls.pos_startStringGame clock at the start of the possession containing this graded play.
calls.pos_endStringGame clock at the end of the possession containing this graded play.
calls.pos_team_idInt6410-digit NBA team id of the team in possession during the graded play.
game.game_idString10-digit NBA game id (zero-padded), the join key to the calls and stats tables.
game.game_dateDateGame date parsed from the report's local tip-off timestamp (no timezone offset is applied).
game.season_typeStringSeason type inferred from the third digit of game_id: preseason, regular, all-star, playoffs, play-in, or nba-cup-final.
game.home_team_idInt6410-digit NBA team id (1610612xxx) of the home team.
game.away_team_idInt6410-digit NBA team id (1610612xxx) of the away team.
game.home_team_abbrStringThree-letter abbreviation of the home team.
game.away_team_abbrStringThree-letter abbreviation of the away team.
game.home_team_nameStringHome team nickname as published in the report, e.g. Thunder rather than Oklahoma City Thunder.
game.away_team_nameStringAway team nickname as published in the report.
game.home_scoreInt64Home team's final score for the game.
game.away_scoreInt64Away team's final score for the game.
game.l2m_commentsStringReport-level note from the league, e.g. a technical-issue disclaimer about missing video; null for almost every game.
stats.game_idString10-digit NBA game id (zero-padded), the join key to the calls and game tables.
stats.stat_nameStringName of the report-level error-rate statistic: Calls, Errors in Favor, or Possessions in Favor.
stats.homeInt64Value of stat_name attributed to the home team.
stats.awayInt64Value of stat_name attributed to the away team.

Raises

ExceptionDescription
ValueErrorgame_id is not one non-negative integer id -- a bool, a negative or fractional number, a string that is not all digits, or more than 10 digits (checked before any request).
NoDataErrorThe game has no L2M report (a 404, or S3's AccessDenied 403). Only games within 3 points (5 before 2017-18) at some point in the last two minutes of the fourth quarter or overtime get one, so most games have none; a game that just ended may not be graded yet.
AssetFetchErrorThe fetch failed (a transport error, a rate limit, an Akamai WAF block, or a 5xx that outlived the retry budget), or the 200 body is not a JSON object whose game is exactly one row, or its l2m/stats table is not a list of records (empty is fine) -- an error envelope or a changed schema, checked with raw=True too.

Example

from sportsdataverse.nba.nba_officiating import nba_l2m
result = nba_l2m("0042500405")
calls = result["calls"]
print(f"Game had {calls.height} tracked plays in the L2M window")

# Parse as pandas instead

result = nba_l2m("0042500405", return_as_pandas=True)
calls_pd = result["calls"]

# Access raw JSON

payload = nba_l2m("0042500405", raw=True)
print(payload["game"])

nba_l2m_games​

nba_l2m_games(season: 'int | str', *, return_as_pandas: 'bool' = False, proxy: 'dict | None' = None) -> 'Union[pl.DataFrame, pd.DataFrame]'

Fetch the list of games with Last Two Minute reports for an NBA season.

Retrieves and parses the L2M season index page from official.nba.com, returning a table of all games for which L2M reports exist. JSON reports exist only from 2019-01-01 onward; earlier seasons' index pages list PDFs, which this function ignores. A release loader for historical (PDF-era) reports is planned but does not exist yet.

Parameters

ParameterTypeDefaultDescription
seasonint | strThe NBA season (end year) as a 4-digit int or string, e.g. 2026 for the 2025-26 season.
return_as_pandasboolFalseIf True, return a pandas DataFrame instead of polars.
proxydict | NoneNoneOptional proxy dict passed through to the HTTP layer.

Returns

A polars.DataFrame (or pandas.DataFrame when return_as_pandas=True) with schema {"game_id": Utf8, "season": Int32, "season_type": Utf8, "label": Utf8}, one row per unique game ID in page order.

col_nametypedescription
game_idString10-digit NBA game id (zero-padded), parsed from the season listing page's report link.
seasonInt64NBA season end year passed to the function (e.g. 2026 for the 2025-26 season), stamped onto every row.
season_typeStringSeason type inferred from the third digit of game_id: preseason, regular, all-star, playoffs, play-in, or nba-cup-final.
labelStringMatchup label text scraped from the listing page link, e.g. "Thunder 125, Rockets 124 (2OT)".

Raises

ExceptionDescription
ValueErrorseason is not a 4-digit year (an int or an all-digit string), checked before any request.
NoDataErrorOfficial.nba.com has no listing page for that season (a 404, or S3's AccessDenied 403) -- e.g. a future season, or one from before the L2M program began.
AssetFetchErrorThe fetch failed (network error, rate limit, or Akamai WAF block), or a 200 response that is missing the expected "Last Two Minute" page marker (an Akamai interstitial, a blank body, or a redesigned page), or that links reports in a shape the parser does not read (a redesigned page) -- checked here, not in parse_nba_l2m_games, so the parser itself never raises.

Example

from sportsdataverse.nba.nba_officiating import nba_l2m_games
df = nba_l2m_games(2026)
print(f"Season had {df.height} games with L2M reports")

# Get playoff games only

df = nba_l2m_games(2026)
playoffs = df.filter(df["season_type"] == "playoffs")

# Convert to pandas

df = nba_l2m_games(2026, return_as_pandas=True)

nba_lineup_count_rows​

nba_lineup_count_rows(possessions: 'pl.DataFrame', schedule: 'pl.DataFrame', *, pbp: 'pl.DataFrame | None' = None) -> 'pl.DataFrame'

Map nba_stats_possessions rows onto the lineup-seasons count frame.

Each possession's counts go to the side that had the ball, its dreb (the defense's defensive rebounds) to the other side, on the possession's lineup. A possession whose offense is neither team of its schedule game is dropped, as in the game impact box.

Parameters

ParameterTypeDefaultDescription
possessionsDataFrameload_nba_stats_possessions / load_wnba_stats_possessions rows (fg2a, fg2m, fg3a, fg3m, fta, oreb, dreb, tov; with pbp, also start_order_index).
scheduleDataFrameThe games to keep (game_id, home_team_id, away_team_id).
pbpDataFrame | NoneNoneload_nba_stats_pbp / load_wnba_stats_pbp rows (game_id, order_index, team_id, is_field_goal, is_made_shot, shot_value, shot_distance). When given, the rows also carry the shot-zone counts (rima, rimm, mida, midm): each two the offense took in the possession, at the rim when it was under 4 feet, mid-range otherwise.

Returns

One row per possession in sportsdataverse._common.lineup_seasons.lineup_count_schema (with the shot zones when pbp is given) with Int64 player ids.

col_nametypedescription
seasonInt64Season, named by the year it ends.
game_idStringstats.nba.com game id.
home_team_idInt64stats.nba.com team id of the home side.
away_team_idInt64stats.nba.com team id of the away side.
home_1Int64stats.nba.com id of home player 1 on the floor when the possession starts.
home_2Int64stats.nba.com id of home player 2 on the floor when the possession starts.
home_3Int64stats.nba.com id of home player 3 on the floor when the possession starts.
home_4Int64stats.nba.com id of home player 4 on the floor when the possession starts.
home_5Int64stats.nba.com id of home player 5 on the floor when the possession starts.
away_1Int64stats.nba.com id of away player 1 on the floor when the possession starts.
away_2Int64stats.nba.com id of away player 2 on the floor when the possession starts.
away_3Int64stats.nba.com id of away player 3 on the floor when the possession starts.
away_4Int64stats.nba.com id of away player 4 on the floor when the possession starts.
away_5Int64stats.nba.com id of away player 5 on the floor when the possession starts.
home_fgaInt64The home side's field-goal attempts in the possession.
home_fgmInt64The home side's field goals made in the possession.
home_fg3aInt64The home side's three-point attempts in the possession.
home_fg3mInt64The home side's threes made in the possession.
home_ftaInt64The home side's free-throw attempts in the possession.
home_orebInt64The home side's offensive rebounds in the possession.
home_drebInt64The home side's defensive rebounds in the possession.
home_tovInt64The home side's turnovers in the possession.
away_fgaInt64The away side's field-goal attempts in the possession.
away_fgmInt64The away side's field goals made in the possession.
away_fg3aInt64The away side's three-point attempts in the possession.
away_fg3mInt64The away side's threes made in the possession.
away_ftaInt64The away side's free-throw attempts in the possession.
away_orebInt64The away side's offensive rebounds in the possession.
away_drebInt64The away side's defensive rebounds in the possession.
away_tovInt64The away side's turnovers in the possession.

Raises

ExceptionDescription
ValueErrorIf a count column holds a null in a possession of a schedule game.

Example

from sportsdataverse.nba import nba_loaders as L
from sportsdataverse.nba.nba_lineup_seasons import nba_lineup_count_rows
rows = nba_lineup_count_rows(L.load_nba_stats_possessions(2024), L.load_nba_stats_schedules(2024))

nba_lineup_seasons​

nba_lineup_seasons(seasons: 'int | list[int]', *, league_id: 'str' = '00', season_type: 'str' = 'regular-season', min_poss: 'int | None' = None, return_as_pandas: 'bool' = False) -> 'pl.DataFrame | pd.DataFrame'

Lineup seasons: 2-, 3- and 5-man units with possessions, minutes, ratings and four factors.

The definitions are in sportsdataverse._common.lineup_seasons.

Parameters

ParameterTypeDefaultDescription
seasonsint | list[int]A season or list of seasons, named by the year it ends (2026 is 2025-26). The WNBA season is its calendar year.
league_idstr'00'"00" NBA or "10" WNBA.
season_typestr'regular-season'"regular-season" (default), "preseason", "playoffs" or "play-in", matched to the schedule's season_type.
min_possint | NoneNoneThe rank gate, offensive plus defensive possessions. Defaults to 400 (NBA) or 200 (WNBA).
return_as_pandasboolFalseReturn a pandas frame instead of polars.

Returns

One row per (season, team_id, n_players, lineup_id): n_players (2, 3 or 5), lineup_id (the player ids sorted and joined with -), player_1..5, minutes, poss / opp_poss, pts / opp_pts, plus_minus, off_rtg / def_rtg / net_rtg (per 100 possessions), the four factors for (efg_pct, tov_pct, orb_pct, ft_rate) and against (opp_*) as fractions, and for every rate <m>_rank / <m>_pct (within the season and unit size, among units with at least min_poss) and <m>_n. Ids are the stats.nba.com ids (Int64). Empty input returns that schema with zero rows.

col_nametypedescription
seasonInt64Season, named by the year it ends (the WNBA season is its calendar year).
team_idInt64stats.nba.com team id.
n_playersInt64Unit size: 2, 3 or 5 players.
lineup_idStringThe unit's stats.nba.com player ids, sorted numerically and joined with '-'.
player_1Int64Player 1 of the unit, in lineup_id order; null past n_players.
player_2Int64Player 2 of the unit, in lineup_id order; null past n_players.
player_3Int64Player 3 of the unit, in lineup_id order; null past n_players.
player_4Int64Player 4 of the unit, in lineup_id order; null past n_players.
player_5Int64Player 5 of the unit, in lineup_id order; null past n_players.
minutesFloat64Game-clock minutes the unit shared the floor over the season.
possInt64Offensive possessions with the unit on the floor (a possession counts for the five on the floor when it starts; a 2- or 3-man unit sums the fives that contain it).
opp_possInt64Defensive possessions with the unit on the floor, counted as poss is.
ptsInt64The team's points with the unit on the floor, each scoring action credited to the lineup at that action (a free throw to the lineup at the last foul).
opp_ptsInt64The opponent's points with the unit on the floor, credited as pts is.
plus_minusInt64The unit's +/- over the season: pts - opp_pts.
off_rtgFloat64Offensive rating: pts per 100 poss. Null with a zero denominator. On 2024-25 units it runs about 5 above stats.nba.com's lineup rating, which credits a possession to every lineup on the floor during it (its counts run ~4% higher).
def_rtgFloat64Defensive rating: opp_pts per 100 opp_poss. Null with a zero denominator. On 2024-25 units it runs about 5 above stats.nba.com's lineup rating, which credits a possession to every lineup on the floor during it (its counts run ~4% higher).
net_rtgFloat64Net rating: off_rtg - def_rtg; null when either is null.
efg_pctFloat64Effective field-goal percentage, (FGM + 0.5 * 3PM) / FGA, as a fraction. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
tov_pctFloat64Turnovers per offensive possession, as a fraction. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
orb_pctFloat64Offensive-rebound rate: OREB / (OREB + the opponent's DREB), as a fraction. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
ft_rateFloat64Free-throw rate: FTA / FGA. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
opp_efg_pctFloat64The opponent's effective field-goal percentage with the unit on the floor. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
opp_tov_pctFloat64The opponent's turnovers per defensive possession. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
opp_orb_pctFloat64The opponent's offensive-rebound rate: its OREB / (its OREB + the unit's DREB). Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
opp_ft_rateFloat64The opponent's free-throw rate: FTA / FGA. Null with a zero denominator. Each possession's shots, free throws, rebounds and turnovers count for the lineup that started it, even after a substitution inside it.
off_rtg_rankFloat64Rank of off_rtg within the season and unit size, league-wide (the highest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose off_rtg_n is at least 1,300 offensive possessions (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
def_rtg_rankFloat64Rank of def_rtg within the season and unit size, league-wide (the lowest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose def_rtg_n is at least 2,000 defensive possessions (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
net_rtg_rankFloat64Rank of net_rtg within the season and unit size, league-wide (the highest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose net_rtg_n is at least 2,000 possessions (offense plus defense) (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
efg_pct_rankFloat64Rank of efg_pct within the season and unit size, league-wide (the highest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose efg_pct_n is at least 1,200 field-goal attempts (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
tov_pct_rankFloat64Rank of tov_pct within the season and unit size, league-wide (the lowest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose tov_pct_n is at least 1,400 offensive possessions (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
orb_pct_rankFloat64Rank of orb_pct within the season and unit size, league-wide (the highest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose orb_pct_n is at least 410 offensive-rebound chances (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
ft_rate_rankFloat64Rank of ft_rate within the season and unit size, league-wide (the highest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose ft_rate_n is at least 1,300 field-goal attempts (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
opp_efg_pct_rankFloat64Rank of opp_efg_pct within the season and unit size, league-wide (the lowest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose opp_efg_pct_n is at least 1,900 opponent field-goal attempts (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
opp_tov_pct_rankFloat64Rank of opp_tov_pct within the season and unit size, league-wide (the highest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose opp_tov_pct_n is at least 960 defensive possessions (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
opp_orb_pct_rankFloat64Rank of opp_orb_pct within the season and unit size, league-wide (the lowest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose opp_orb_pct_n is at least 1,200 opponent offensive-rebound chances (the sample at which the rate is half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
opp_ft_rate_rankFloat64Rank of opp_ft_rate within the season and unit size, league-wide (the lowest value ranks 1; ties average), among units with at least min_poss possessions (poss + opp_poss; default 400 with league_id '00' (NBA), 200 with league_id '10' (WNBA)), whose opp_ft_rate_n is at least 1,500 opponent field-goal attempts (the sample at which the rate was, on the fit seasons, half signal, rate_gates); null for a non-qualifier, a sample under that gate or a null rate, and for every unit of the group when fewer than ten qualifying units have a finite value of this rate or they all share one value.
off_rtg_pctFloat64Percentile of off_rtg among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
def_rtg_pctFloat64Percentile of def_rtg among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
net_rtg_pctFloat64Percentile of net_rtg among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
efg_pct_pctFloat64Percentile of efg_pct among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
tov_pct_pctFloat64Percentile of tov_pct among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
orb_pct_pctFloat64Percentile of orb_pct among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
ft_rate_pctFloat64Percentile of ft_rate among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
opp_efg_pct_pctFloat64Percentile of opp_efg_pct among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
opp_tov_pct_pctFloat64Percentile of opp_tov_pct among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
opp_orb_pct_pctFloat64Percentile of opp_orb_pct among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
opp_ft_rate_pctFloat64Percentile of opp_ft_rate among the same qualified units: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null when the rank is null.
off_rtg_nInt64Sample size of off_rtg: poss.
def_rtg_nInt64Sample size of def_rtg: opp_poss.
net_rtg_nInt64Sample size of net_rtg: poss + opp_poss.
efg_pct_nInt64Sample size of efg_pct: the unit's field-goal attempts.
tov_pct_nInt64Sample size of tov_pct: poss.
orb_pct_nInt64Sample size of orb_pct: the unit's offensive-rebound chances.
ft_rate_nInt64Sample size of ft_rate: the unit's field-goal attempts.
opp_efg_pct_nInt64Sample size of opp_efg_pct: the opponent's field-goal attempts.
opp_tov_pct_nInt64Sample size of opp_tov_pct: opp_poss.
opp_orb_pct_nInt64Sample size of opp_orb_pct: the opponent's offensive-rebound chances.
opp_ft_rate_nInt64Sample size of opp_ft_rate: the opponent's field-goal attempts.

Raises

ExceptionDescription
ValueErrorIf league_id is not "00" or "10", season_type is not one of the four above, or the possessions carry a null shot, free-throw, rebound or turnover count.

Example

from sportsdataverse.nba.nba_lineup_seasons import nba_lineup_seasons
units = nba_lineup_seasons(2025)
units.filter(n_players=5).sort("net_rtg_rank").head()

nba_neutral_site_games​

nba_neutral_site_games(seasons: 'list[int] | None' = None, *, league_id: 'str' = '00', return_as_pandas: 'bool' = False) -> 'pl.DataFrame | pd.DataFrame'

The curated NBA / WNBA neutral-site games: played abroad, or NBA Cup knockout games in Las Vegas.

The 2020 bubbles are a date rule (BUBBLES), not rows. The team ratings, the season sim, the rated priors and game flow drop the home-court term for every game in either.

Parameters

ParameterTypeDefaultDescription
seasonslist[int] | NoneNoneSeasons to keep, named by the year they end (2026 is 2025-26; the WNBA season is its calendar year). None keeps all.
league_idstr'00'"00" NBA or "10" WNBA.
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

One row per game: league, season, game_id (the stats.nba.com id, Utf8), game_date, season_type (regular-season, or nba-cup for a Cup final), home_team / away_team (stats abbreviations, the designated home team first), arena, city, country, reason (international or cup_knockout) and source_url.

col_nametypedescription
leagueStringLeague the game belongs to: nba or wnba.
seasonInt64Season, named by the year it ends (the WNBA season is its calendar year).
game_idStringstats.nba.com game id (Utf8).
game_dateDateGame date (US Eastern), as in the stats schedule release.
season_typeStringSchedule season type: regular-season, or nba-cup for an NBA Cup final (which does not count in the standings).
home_teamStringDesignated home team (stats.nba.com abbreviation).
away_teamStringAway team (stats.nba.com abbreviation).
arenaStringArena the game was played in.
cityStringCity of the arena.
countryStringCountry of the arena.
reasonStringWhy the game is neutral: international (played abroad) or cup_knockout (an NBA Cup knockout game in Las Vegas).
source_urlStringThe source that documents the game.

Raises

ExceptionDescription
ValueErrorIf league_id is not "00" or "10".

Example

from sportsdataverse.nba import nba_neutral_site_games
games = nba_neutral_site_games([2026])

# WNBA

nba_neutral_site_games(league_id="10")

nba_official_seasons​

nba_official_seasons(seasons: 'int | list[int]', *, season_type: 'int | None' = 2, return_as_pandas: 'bool' = False) -> 'pl.DataFrame | pd.DataFrame'

NBA official season profiles: games, fouls and FTA per game, pace and home splits.

One row per official and season over the completed games of season_type the official worked. fouls_per_game and fta_per_game are both teams' totals from ESPN's team box; pace is possessions (FGA - ORB + TOV + 0.44 FTA, averaged over the two teams) per 48 minutes, over the games' minutes including overtime; home_win_pct and home_minus_away_fouls (home fouls less away fouls, per game) leave out neutral-site games. Every rate has <m>_n, the games it is computed over. The release keys officials by names only, so official_id is null.

Parameters

ParameterTypeDefaultDescription
seasonsint | list[int]A season or list of seasons, named by the year it ends (2025 is 2024-25).
season_typeint | None2ESPN's season type: 2 regular season (default), 3 postseason; None keeps every type.
return_as_pandasboolFalseReturn a pandas frame instead of polars.

Returns

One row per (season, official): season, official_id (Int64), official_name, games, fouls_per_game, fta_per_game, pace, home_win_pct and home_minus_away_fouls, each rate with its _n. A season without an officials release returns that schema with zero rows.

col_nametypedescription
seasonInt64Season, named by the year it ends (the WNBA season is its calendar year).
official_idInt64ESPN official id where the officials release carries one (WNBA, WBB); null where it has names only (NBA, MBB).
official_nameStringOfficial's name, whitespace-normalised; the key when official_id is null.
gamesInt64Completed games of the season type the officials table lists the official on.
fouls_per_gameFloat64Both teams' fouls per game (ESPN team box fouls), over fouls_per_game_n games.
fouls_per_game_nInt64Games with a two-team box behind fouls_per_game.
fta_per_gameFloat64Both teams' free-throw attempts per game, over fta_per_game_n games.
fta_per_game_nInt64Games with a two-team box behind fta_per_game.
paceFloat64Possessions (FGA - ORB + TOV + 0.44 FTA, averaged over the two teams) per regulation length (48 minutes NBA, 40 otherwise), over the games' minutes including overtime.
pace_nInt64Games with a box possession estimate and a known last period behind pace.
home_win_pctFloat64Share of the official's non-neutral-site games with a recorded winner that the home team won.
home_win_pct_nInt64Non-neutral-site games with a recorded home winner behind home_win_pct.
home_minus_away_foulsFloat64Home team's fouls minus the away team's, per non-neutral-site game.
home_minus_away_fouls_nInt64Non-neutral-site games with a two-team box behind home_minus_away_fouls.

Example

from sportsdataverse.nba.nba_official_seasons import nba_official_seasons
refs = nba_official_seasons(2025)
refs.sort("fouls_per_game", descending=True).select("official_name", "games", "fouls_per_game").head()

nba_play_context​

nba_play_context(game_id: 'str', league_id: 'str' = '00', *, transition_seconds: 'float' = 6.0, transition_variant: 'str' = 'hoop_math', return_as_pandas: 'bool' = False) -> 'Union[pl.DataFrame, pd.DataFrame]'

Fetch one game and return its possessions with the full CTG play-context surface.

Single live call to nba_stats_playbyplayv3, then add_play_context. Works for NBA (league_id="00"), WNBA and the G-League — stats.wnba.com ships the same play-by-play shapes, and every threshold here is league-agnostic.

Parameters

ParameterTypeDefaultDescription
game_idstrTen-character game identifier (e.g. "0022200001").
league_idstr'00'League identifier ("00" NBA, "10" WNBA, "20" G-League).
transition_secondsfloat6.0Transition initial-play cutoff (default 6.0).
transition_variantstr'hoop_math'See add_transition.
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

Possession frame with the play-context columns. Empty/malformed payloads return a zero-row frame — never raises.

No returns table is published for this function: no capture: it reads stats.nba.com, which answers HTTP 403 to the datacenter IP the docs are built on; the function works from a residential IP.

Example

from sportsdataverse.nba.nba_play_context import nba_play_context
poss = nba_play_context("0022200001")
print(poss["possession_start_type_ctg"].value_counts())

# Transition rate for the game

import polars as pl
clean = poss.filter(
(pl.col("is_garbage_time_ctg") == False) & (pl.col("is_heave_possession") == False)
)
print(clean["is_transition_ctg"].mean())

nba_play_style_features​

nba_play_style_features(seasons: 'int | list[int]', *, side: 'str' = 'offense', entity: 'str' = 'player', return_as_pandas: 'bool' = False) -> 'pl.DataFrame | pd.DataFrame'

The play-style clustering features, one row per qualifying player- or team-season.

Parameters

ParameterTypeDefaultDescription
seasonsint | list[int]A season or list of seasons, named by the year it ends (2026 is 2025-26). Seasons before NBA_PLAY_STYLE_FIRST_SEASON are skipped.
sidestr'offense'"offense" or "defense".
entitystr'player'"player" or "team".
return_as_pandasboolFalseReturn a pandas frame instead of polars.

Returns

The ids, season (and min for players) and the raw (unstandardized) features of NBA_PLAY_STYLE_FEATURES for (side, entity) (every candidate feature for the team defense). A season no release covers yet is skipped; with none left the frame has those columns and zero rows. The returns table below is the default side="offense", entity="player" frame.

col_nametypedescription
player_idInt64stats.nba.com player id.
seasonInt64Season, named by the year it ends (2026 is 2025-26).
player_nameStringPlayer's display name on stats.nba.com.
team_idInt64stats.nba.com team id (the league-dashboard team; a traded player's last team).
minFloat64Regular-season minutes; the qualifier is a quarter of the season's most minutes.
pt_isolationFloat64Share of the player's Synergy possessions that were isolations (stats.nba.com play-type frequency).
pt_prballhandlerFloat64Share of Synergy possessions as the pick-and-roll ball handler.
pt_prrollmanFloat64Share of Synergy possessions as the pick-and-roll roll man.
pt_postupFloat64Share of Synergy possessions that were post-ups.
pt_spotupFloat64Share of Synergy possessions that were spot-ups.
pt_handoffFloat64Share of Synergy possessions out of handoffs.
pt_offscreenFloat64Share of Synergy possessions coming off screens.
pt_cutFloat64Share of Synergy possessions on cuts.
pt_transitionFloat64Share of Synergy possessions in transition.
pt_offreboundFloat64Share of Synergy possessions on putbacks after an offensive rebound.
fga3_shareFloat64Share of field-goal attempts that were threes.
uast_shareFloat64Share of made field goals that were unassisted.
paint_pts_shareFloat64Share of points scored in the paint.
mid_pts_shareFloat64Share of points from mid-range twos.
drives_p36Float64Drives per 36 minutes (tracking).
touches_p36Float64Touches per 36 minutes (tracking).
sec_per_touchFloat64Average seconds per touch (tracking).
drib_per_touchFloat64Average dribbles per touch (tracking).
catch_shoot_shareFloat64Catch-and-shoot attempts as a share of field-goal attempts.
pull_up_shareFloat64Pull-up attempts as a share of field-goal attempts.
post_touches_p36Float64Post touches per 36 minutes.
paint_touches_p36Float64Paint touches per 36 minutes.
potential_ast_p36Float64Potential assists per 36 minutes.

Raises

ExceptionDescription
ValueErrorIf side or entity is not one of the values above.

Example

from sportsdataverse.nba.nba_play_style import nba_play_style_features
feats = nba_play_style_features(2025, side="defense", entity="team")

nba_player_ages​

nba_player_ages(season: 'str', *, league_id: 'str' = '00', fetch: 'Optional[Callable[..., pl.DataFrame]]' = None) -> 'pl.DataFrame'

Per-player age for a season (bulk), for the DARKO aging curve.

Parameters

ParameterTypeDefaultDescription
seasonstrNBA season, e.g. "2023-24".
league_idstr'00'LeagueID ("00" NBA).
fetchOptional[Callable[..., DataFrame]]NoneInjectable nba_stats_leaguedashplayerbiostats replacement for offline tests.

Returns

Frame player_id:Int64, age:Float64.

No returns table is published for this function: no capture: it reads stats.nba.com, which answers HTTP 403 to the datacenter IP the docs are built on; the function works from a residential IP.

Example

from sportsdataverse.nba import nba_player_ages
ages = nba_player_ages("2023-24")
print(ages.head())

nba_player_factor_rapm​

nba_player_factor_rapm(seasons: 'int | list[int]', *, league_id: 'str' = '00', season_type: 'str' = 'regular-season', min_poss: 'int | None' = None, return_as_pandas: 'bool' = False) -> 'pl.DataFrame | pd.DataFrame'

Player RAPM split into Dean Oliver's four factors, offense and defense, per player-season.

The definitions are in sportsdataverse._common.factor_rapm: o_efg_rapm + o_tov_rapm + o_orb_rapm + o_ftr_rapm + o_other_rapm is o_rapm and the five d_* components add up to d_rapm, each season fit on its own.

Parameters

ParameterTypeDefaultDescription
seasonsint | list[int]A season or list of seasons, named by the year it ends (2026 is 2025-26). The WNBA season is its calendar year.
league_idstr'00'"00" NBA or "10" WNBA.
season_typestr'regular-season'"regular-season" (default), "preseason", "playoffs" or "play-in", matched to the schedule's season_type.
min_possint | NoneNoneThe rank gate, offensive plus defensive possessions. Defaults to 400 (NBA) or 200 (WNBA).
return_as_pandasboolFalseReturn a pandas frame instead of polars.

Returns

One row per (season, player_id): off_poss / def_poss, rapm / o_rapm / d_rapm, the ten components (the four factors and the remainder, each way; per 100 possessions, higher is better), <m>_rank / <m>_pct within the season among players with at least min_poss possessions, <m>_se (clustered by game), <m>_n, and the pts_per_fga and ridge_alpha of the fit. Ids are the stats.nba.com ids (Int64). Empty input returns that schema with zero rows.

col_nametypedescription
seasonInt64Season, named by the year it ends (the WNBA season is its calendar year).
player_idInt64stats.nba.com player id.
off_possInt64Offensive possessions the player was on the floor for (with all ten lineup slots and counts known).
def_possInt64Defensive possessions the player was on the floor for (with all ten lineup slots and counts known).
rapmFloat64Plain RAPM per 100 possessions: o_rapm + d_rapm.
o_rapmFloat64Offensive plain RAPM per 100 possessions (nba_rapm's single-season ridge at the fixed NBA penalty, see ridge_alpha, shrunk to zero); equals o_efg_rapm + o_tov_rapm + o_orb_rapm + o_ftr_rapm + o_other_rapm. Fit on the possessions release (2026-08-13). At the same penalty it tracks nba_player_impact's (fit 2026-10-12 on a re-compile with the same possessions and some different lineups): 2025-26 corr 0.997, max 1.31. nba_player_impact releases published before November 2026 were fit at a per-season penalty, so they differ in scale until re-published.
d_rapmFloat64Defensive plain RAPM per 100 possessions, sign-flipped so higher is better; equals d_efg_rapm + d_tov_rapm + d_drb_rapm + d_ftr_rapm + d_other_rapm. Fit at the fixed NBA penalty (see ridge_alpha) on the possessions release (2026-08-13). At the same penalty it tracks nba_player_impact's (fit 2026-10-12 on a re-compile with the same possessions and some different lineups): 2025-26 corr 0.997, max 1.31. nba_player_impact releases published before November 2026 were fit at a per-season penalty, so they differ in scale until re-published.
o_efg_rapmFloat64Offensive shooting (eFG) component of o_rapm: the ridge fit to field-goal points less pts_per_fga per attempt, per 100 offensive possessions.
o_tov_rapmFloat64Offensive turnover component of o_rapm: the ridge fit to -pts_per_fga per turnover, per 100 offensive possessions (positive = fewer turnovers).
o_orb_rapmFloat64Offensive rebounding component of o_rapm: the ridge fit to +pts_per_fga per offensive rebound, per 100 offensive possessions.
o_ftr_rapmFloat64Offensive free-throw component of o_rapm, on possessions with a free-throw attempt: the ridge fit to free-throw points less pts_per_fga per chance that ended in neither a shot nor a turnover (FT rate and FT%), per 100 offensive possessions.
o_other_rapmFloat64Offensive remainder of o_rapm (bookkeeping, not a skill): the ridge fit to what the four factors leave of points - pts_per_fga, i.e. -pts_per_fga per chance with no shot, turnover or free throw (a period running out) and +pts_per_fga per shot or turnover beyond the recorded chances (an unrecorded offensive rebound); non-zero on about 1% of possessions.
d_efg_rapmFloat64Defensive shooting (opponent eFG) component of d_rapm, per 100 possessions, sign-flipped so higher is better: positive = the opponent shoots worse.
d_tov_rapmFloat64Defensive turnover component of d_rapm, per 100 possessions, sign-flipped so higher is better: positive = forces more turnovers.
d_drb_rapmFloat64Defensive rebounding component of d_rapm, per 100 possessions, sign-flipped so higher is better: positive = fewer opponent offensive rebounds.
d_ftr_rapmFloat64Defensive free-throw component of d_rapm, per 100 possessions, sign-flipped so higher is better: positive = fewer opponent free-throw points.
d_other_rapmFloat64Defensive remainder of d_rapm (bookkeeping, not a skill), per 100 possessions, sign-flipped so higher is better: the opponent's o_other part.
rapm_rankFloat64Rank of rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) rapm repeats at Spearman 0.322, so single-season ranks are noisy.
rapm_pctFloat64Percentile of rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) rapm repeats at Spearman 0.322, so single-season ranks are noisy.
o_rapm_rankFloat64Rank of o_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_rapm repeats at Spearman 0.292, so single-season ranks are noisy.
o_rapm_pctFloat64Percentile of o_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_rapm repeats at Spearman 0.292, so single-season ranks are noisy.
d_rapm_rankFloat64Rank of d_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_rapm repeats at Spearman 0.361, so single-season ranks are noisy.
d_rapm_pctFloat64Percentile of d_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_rapm repeats at Spearman 0.361, so single-season ranks are noisy.
o_efg_rapm_rankFloat64Rank of o_efg_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_efg_rapm repeats at Spearman 0.308, so single-season ranks are noisy.
o_efg_rapm_pctFloat64Percentile of o_efg_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_efg_rapm repeats at Spearman 0.308, so single-season ranks are noisy.
o_tov_rapm_rankFloat64Rank of o_tov_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_tov_rapm repeats at Spearman 0.334, so single-season ranks are noisy.
o_tov_rapm_pctFloat64Percentile of o_tov_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_tov_rapm repeats at Spearman 0.334, so single-season ranks are noisy.
o_orb_rapm_rankFloat64Rank of o_orb_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_orb_rapm repeats at Spearman 0.423, so single-season ranks are noisy.
o_orb_rapm_pctFloat64Percentile of o_orb_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_orb_rapm repeats at Spearman 0.423, so single-season ranks are noisy.
o_ftr_rapm_rankFloat64Rank of o_ftr_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_ftr_rapm repeats at Spearman 0.366, so single-season ranks are noisy.
o_ftr_rapm_pctFloat64Percentile of o_ftr_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_ftr_rapm repeats at Spearman 0.366, so single-season ranks are noisy.
o_other_rapm_rankFloat64Rank of o_other_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_other_rapm repeats at Spearman 0.259, so single-season ranks are noisy.
o_other_rapm_pctFloat64Percentile of o_other_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) o_other_rapm repeats at Spearman 0.259, so single-season ranks are noisy.
d_efg_rapm_rankFloat64Rank of d_efg_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_efg_rapm repeats at Spearman 0.247, so single-season ranks are noisy.
d_efg_rapm_pctFloat64Percentile of d_efg_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_efg_rapm repeats at Spearman 0.247, so single-season ranks are noisy.
d_tov_rapm_rankFloat64Rank of d_tov_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_tov_rapm repeats at Spearman 0.387, so single-season ranks are noisy.
d_tov_rapm_pctFloat64Percentile of d_tov_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_tov_rapm repeats at Spearman 0.387, so single-season ranks are noisy.
d_drb_rapm_rankFloat64Rank of d_drb_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_drb_rapm repeats at Spearman 0.229, so single-season ranks are noisy.
d_drb_rapm_pctFloat64Percentile of d_drb_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_drb_rapm repeats at Spearman 0.229, so single-season ranks are noisy.
d_ftr_rapm_rankFloat64Rank of d_ftr_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_ftr_rapm repeats at Spearman 0.341, so single-season ranks are noisy.
d_ftr_rapm_pctFloat64Percentile of d_ftr_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_ftr_rapm repeats at Spearman 0.341, so single-season ranks are noisy.
d_other_rapm_rankFloat64Rank of d_other_rapm within the season, league-wide (1 = best, highest first, ties averaged), among players with at least min_poss possessions (off_poss + def_poss, default 400 (NBA) or 200 (WNBA)); null below 10 qualifiers. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_other_rapm repeats at Spearman 0.376, so single-season ranks are noisy.
d_other_rapm_pctFloat64Percentile of d_other_rapm among the same qualified players: 100 * (n + 1 - rank) / (n + 1), n the number ranked; null where the rank is. Year over year (2024-25 to 2025-26, 293 players over 2,000 possessions) d_other_rapm repeats at Spearman 0.376, so single-season ranks are noisy.
rapm_seFloat64Standard error of rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
o_rapm_seFloat64Standard error of o_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
d_rapm_seFloat64Standard error of d_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
o_efg_rapm_seFloat64Standard error of o_efg_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
o_tov_rapm_seFloat64Standard error of o_tov_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
o_orb_rapm_seFloat64Standard error of o_orb_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
o_ftr_rapm_seFloat64Standard error of o_ftr_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
o_other_rapm_seFloat64Standard error of o_other_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
d_efg_rapm_seFloat64Standard error of d_efg_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
d_tov_rapm_seFloat64Standard error of d_tov_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
d_drb_rapm_seFloat64Standard error of d_drb_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
d_ftr_rapm_seFloat64Standard error of d_ftr_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
d_other_rapm_seFloat64Standard error of d_other_rapm, clustered by game (the ridge sandwich): sampling noise around the shrunk estimate, not the shrinkage bias.
rapm_nInt64Sample size of rapm: off_poss + def_poss.
o_rapm_nInt64Sample size of o_rapm: off_poss.
d_rapm_nInt64Sample size of d_rapm: def_poss.
o_efg_rapm_nInt64Sample size of o_efg_rapm: off_poss.
o_tov_rapm_nInt64Sample size of o_tov_rapm: off_poss.
o_orb_rapm_nInt64Sample size of o_orb_rapm: off_poss.
o_ftr_rapm_nInt64Sample size of o_ftr_rapm: off_poss.
o_other_rapm_nInt64Sample size of o_other_rapm: off_poss.
d_efg_rapm_nInt64Sample size of d_efg_rapm: def_poss.
d_tov_rapm_nInt64Sample size of d_tov_rapm: def_poss.
d_drb_rapm_nInt64Sample size of d_drb_rapm: def_poss.
d_ftr_rapm_nInt64Sample size of d_ftr_rapm: def_poss.
d_other_rapm_nInt64Sample size of d_other_rapm: def_poss.
pts_per_fgaFloat64The season's points per field-goal attempt, the value of one shot used in the split (pts_per_fga per turnover, offensive rebound and free-throw chance).
ridge_alphaFloat64The ridge penalty of the fit, shared by every component so they add up: plain RAPM's fixed league value (NBA 2,683, WNBA 1,931; sportsdataverse.nba.nba_rapm.RAPM_PENALTY), the same in every season. Rows fit before November 2026 carry RidgeCV's per-season leave-one-out pick instead, on a scale that changed with it.

Raises

ExceptionDescription
ValueErrorIf league_id is not "00" or "10" or season_type is not one of the four above.

Example

import polars as pl
from sportsdataverse.nba.nba_player_factor_rapm import nba_player_factor_rapm
split = nba_player_factor_rapm(2025)
split.select("player_id", "rapm", "o_efg_rapm", "o_tov_rapm", "d_drb_rapm").head()

# The six-factor view (free throws folded into shooting)

six = split.select(
"season", "player_id",
o_shooting=pl.col("o_efg_rapm") + pl.col("o_ftr_rapm"),
o_tov=pl.col("o_tov_rapm"), o_reb=pl.col("o_orb_rapm"),
d_shooting=pl.col("d_efg_rapm") + pl.col("d_ftr_rapm"),
d_tov=pl.col("d_tov_rapm"), d_reb=pl.col("d_drb_rapm"),
)

nba_player_identity​

nba_player_identity(player_logs: 'pl.DataFrame') -> 'pl.DataFrame'

Human-readable identity for every player in a season's box logs.

Model outputs key on player_id alone, which makes them unusable without a second lookup -- a leaderboard reads 1628983 instead of Shai Gilgeous-Alexander. This derives the display columns from the season's own game logs, so they are season-accurate: a player's team is what he actually played for that year, not his current one (which is what a player directory would give and would silently mislabel every historical season).

A traded player has rows for several teams. team_* is his primary team by minutes -- the one a reader means when they say "his team that season" -- and teams lists every abbreviation he appeared for, in descending minutes, so a trade is visible rather than silently collapsed.

Parameters

ParameterTypeDefaultDescription
player_logsDataFramePer-player-per-game rows from leaguegamelog (the player_or_team="P" variant), carrying player_id, player_name, team_id, team_abbreviation, team_name and min.

Returns

One row per player_id with PLAYER_IDENTITY_SCHEMA. An empty input -- or one missing any required column, min included -- gives the zero-row frame with that schema, so callers can join unconditionally. min is required rather than optional: without it every team totals zero minutes and "primary team" quietly degrades to whichever team_id sorts first, which looks like an answer but is not one. A malformed frame yields the typed zero-row frame instead of raising, so a caller can join unconditionally.

col_nametypedescription
player_idInt64Unique player identifier.
player_nameStringPlayer name.
team_idInt64Unique team identifier.
team_abbreviationStringShort team abbreviation (e.g. 'LAS').
team_nameStringFull team display name (e.g. 'Las Vegas Aces').
teamsStringNested list of member-team membership spans.

Example

import polars as pl
from sportsdataverse.nba import nba_player_identity

logs = pl.DataFrame({
"player_id": [1628983],
"player_name": ["Shai Gilgeous-Alexander"],
"team_id": [1610612760],
"team_abbreviation": ["OKC"],
"team_name": ["Oklahoma City Thunder"],
"min": [34.0],
})
ratings = pl.DataFrame({"player_id": [1628983], "war": [21.9]})
named = ratings.join(nba_player_identity(logs), on="player_id", how="left")
print(named.select("player_name", "team_name", "war"))