Skip to contents

Fit a Ridge-Regression RAPM Model from Possession Data

Fit a Ridge-Regression RAPM Model from Possession Data

Usage

nba_rapm(possessions, ...)

Arguments

possessions

A possession-level stint matrix as produced by nba_possession_lineups(), with columns off_player_1 through off_player_5, def_player_1 through def_player_5 (integer NBA Stats person IDs), and points (numeric, points scored on that possession).

...

Reserved for future keyword arguments (currently ignored).

Value

Returns a data.frame with one row per player:

col_nametypesdescription
player_idintegerNBA Stats person ID. Rows are sorted ascending by player_id.
o_rapmnumericOffensive RAPM (per-100-possession points added on offense). Positive = better offensive player.
d_rapmnumericDefensive RAPM (per-100-possession points saved on defense). Positive = better defensive player (sign is flipped so good defense is positive).
rapmnumericTotal RAPM = o_rapm + d_rapm. Positive = net positive impact.
off_possintegerNumber of possessions the player appeared on offense.
def_possintegerNumber of possessions the player appeared on defense.

Returns a 0-row frame with the same schema when input is empty or all possessions have NA lineup cells (never-raise).

Note: RAPM is expressed in per-100-possession units. A full season of possessions (~5,000-8,000) is needed for statistically meaningful estimates. Results from a single game (~150-250 possessions) are highly unstable and are provided here for pipeline illustration only.

Values are relative to average: an unpenalized intercept absorbs the mean points per possession, and the ridge penalty applies equally to the raw player indicators (no standardization), the same model as sportsdataverse-py. glmnet and sklearn scale and choose the penalty differently, so the two agree closely but not exactly. The data do not fix the overall level of the offensive or defensive block (nor, therefore, of rapm); the equal penalty sets it so that the plain average of o_rapm over players, and of d_rapm, is 0. Possession-weighted means are not fixed by the model: if playing time is correlated with player quality they can be well away from 0. On the 2025-26 NBA regular season they were +0.30 (O-RAPM) and +0.07 (D-RAPM).

Results are deterministic: the cross-validation uses fixed, construction-based folds (not random), so repeated calls on the same possessions return identical output with no need to set a seed.

Details

 poss <- nba_possession_lineups(game_id = "0022200001")
 nba_rapm(poss)

Author

Saiem Gilani