A possession-level impact model that starts each player from a priced-out account of his skills, then lets the lineups move him — and three exact decompositions of the result.
Part 1 · The Fit
Raw plus-minus is misleading because a player’s number is tangled up with the four teammates and five opponents on the floor with him. RAPM untangles it by solving for everyone at once.
The unit is a stint — a stretch where the same ten players are on the floor. Each stint becomes two rows, one per direction of play:
So every ten-man combination that ever shared the floor becomes evidence, and the solve asks: what offensive and defensive coefficient for each player best explains the points scored across all of them?
With thousands of players in tangled combinations, an unpenalised fit overfits wildly — a player with a handful of good minutes lands at +40. Ridge adds a penalty on large coefficients, and the thinner the sample behind an estimate, the harder it gets pulled in.
Two things are deliberately left unpenalised: the intercept and home court. Shrinking the intercept would bias every rating, and home court is a real effect rather than noise. Defence carries the heavier penalty of the two player blocks — fewer attributable events per possession means lower signal-to-noise.
Lambda is fixed at 5000 offence / 6000 defence rather than cross-validated per window. That is a choice, and the reason is comparability: measured across 30 seasons, per-window CV picked anywhere from 1500 to 3500 with an RMSE curve flat to 0.003% across that whole range. It was choosing between indistinguishable options while breaking cross-season comparability — a +5 in a season that picked 1500 is shrunk less than a +5 in a season that picked 3500, which quietly distorts every all-time list.
The defensive lambda was picked for differentiation, not reliability. Pushing it to 25,000 raises split-half correlation from 0.32 to 0.51 — but collapses the spread from 0.93 to 0.21. That is shrinkage buying a reliability number while destroying the information the number was supposed to describe.
Fits run on backward-looking windows — one year and three years — labelled by the season they end on. A three-year window ending 2025-26 is built from 2023-24 through 2025-26. Windows ending in a completed season are final and never rewritten, so an in-season refresh touches only the current window.
Part 2 · The Prior
A plain ridge model shrinks every player toward zero — it assumes you are league-average until the possessions prove otherwise. Simul RAPM shrinks toward a priced-out account of your skillsinstead. That is the “Simul” part.
b₀ is built in three stages, and it is in points per 100 possessions, matching the units of the coefficients it sits next to. A z-score in that slot would look fine, run fine, and shrink every player toward a number that means nothing.
Four sources (role stats, career box score, tracking, shot zones) collapse to one row per player-season with roughly fifty talent columns, split into offensive and defensive groups: catch-and-shoot three production and accuracy, pull-up three production and accuracy, points per rim attempt, rim protection, turnover avoidance, and so on.
The zone data gives a decomposition worth naming, because it separates two things that are usually smeared together:
Skills stabilise at wildly different rates, so every column is shrunk separately against its own sample size:
The target is not the league mean. Each stat is regressed on the role scores and the fitted value becomes the target, so a Rollman’s three-point prior regresses toward what Rollmen shoot, not toward what the league shoots. This is where the role system feeds the RAPM model.
Standardised talent then has to become points. The skills are priced by fitting directly on stint rows:
Two details that took real work to get right. Signs are constrained.Columns are oriented so higher is better, so the sign is known before fitting. Left free, the regression produces things like blocks-over-expectation at −0.10 — which is not a finding about blocks, it is a residual correction in a collinear design being read as one. Constrained columns can go to zero, the honest “this adds nothing here”; they cannot go negative.
And lambda is chosen by coefficient stability, not prediction. Stint outcomes are so noisy that talent explains almost none of their variance, so CV error is flat across the whole grid and selecting on it leaves the fit essentially unpenalised — precisely where correlated columns split into large cancelling pairs that move every time you refit. Instead the games are cut into two disjoint halves, g is fit on each, and the two coefficient vectors are correlated. The lambda that makes the prices reproducible is the one that has stopped fitting noise.
Because the prior is stored alongside the result, every player carries the gap between them. Delta is what the lineups saw that the skill accounting could not — the closest thing this system has to a measurement of gravity, screening, off-ball defence and everything else that never enters a box score.
Part 3 · Cleaning
Every free throw attempt has its actual outcome replaced by the shooter’s regressed season rate, (makes + k×league) / (attempts + k)with k = 50, so a hot or cold night at the line does not land in anyone’s rating. This was the single largest measured gain in the whole project: split-half reliability 0.408 → 0.451. Technicals are dropped, since the shooter is often a designated one rather than the player who was fouled.
Blowout minutes are mostly bench players going through the motions. Possessions in the fourth quarter or later, inside 5:00, at a margin of 20 or more are dropped — whole stints, not truncated portions, because a stint straddling the 5:00 mark is usually entirely garbage or entirely not, so proration buys very little and adds an approximation.
One implementation detail that matters more than it sounds: the margin is computed from cumulative stint points rather than from a score column, because pre-2017 files coerce missing scores to zero for 21–75% of rows. Gating on the raw column would misfire on every early season.
Honest caveat: this is only about 0.9% of possessions at the median. It is kept because it is defensible, not because it moves anything.
A half-life decay exists as an option, weighting each stint by 0.5(age / half-life)and rescaling so the total weight is preserved. It defaults to off. Measured on three-year windows it produced a 0.021% improvement with individual windows disagreeing on the direction — which is noise, not an effect. It is documented here rather than quietly shipped.
Part 4 · The Breakdowns
A single number tells you a player helped; it does not tell you how. The breakdowns split O_RAPM and D_RAPM into components — and they do it exactly, which is unusual enough to be worth explaining.
This is the property every failed attempt lacked, and it is the rule the breakdowns are held to: a component has to be a share of the same target, not a separate fit.
Every target here is a team-level property of the stint— “points this lineup scored at the rim”. No player is ever named in a target, which is what keeps this out of the failure mode where a coefficient reads presence instead of effect. There are no tunable parameters: no charge rate, no assist share, no rebound price. The only inputs are the play-by-play and the league points-per-possession computed from it.
| Channel | What it measures |
|---|---|
| Rim | Effect on how much the team scores at the rim while he is on the floor. |
| Mid | Same, midrange. |
| 3PT | Same, from three. |
| FT | Same, at the line. |
| TOV | Turnovers avoided — possessions that ended without a shot. |
| Other | Points the parse could not tag. Runs 4.6–4.9%, mostly and-1s. A large value here is a parse gap, not a finding. |
Read the labels literally.“Rim” sounds like rim finishing and is not — it is the team’s rim scoring shift. A high-gravity player raises team three-point scoring on shots he never touches, and a great finisher whose gravity pulls the offence to the arc can come out slightly negative at the rim despite finishing at 80%.
On offence a row reads as a profile more than a skill list. Giannis at rim +6.9 / mid −3.2 is one fact — he moves attempts to the rim — not two findings.
The defensive split is the stronger half, and it is the reason this shipped. Rim protectors sort themselves out with no supervision at all: Holmgren, Wembanyama, Hartenstein and Queta at the top of D_rim, perimeter guards at the bottom. Defence has been the weak half of this project throughout — split-half 0.38, prior R² 0.23 — and a rim-versus-perimeter split falls out of it for free.
The same exactness argument, applied to a different split of the possessions. Two passes: an ordinary fit used only to rank lineups, then a decomposition of that same fit by the rank. Four two-way splits, each partitioning the possessions, each summing back to its parent:
| Split | Cut by |
|---|---|
| O vs good / bad D | Opposing five’s mean defensive rating, at the possession-weighted median. |
| D vs good / bad O | Opposing five’s mean offensive rating. |
| O with strong / weak cast | Second-best offensive rating among his own five. |
| D with strong / weak cast | Second-best defensive rating among his own five. |
Why second-best and not the mean of his teammates. The tag has to be a property of the lineup, never of the player being evaluated. “Mean of his other four” is a different number depending on whose coefficient you are reading — the per-player-target trap that broke every earlier attempt at this. Second-best is one number per lineup, and it happens to mean something: for a star, normally the best rating on the floor, it is the quality of his best help.
Two derived columns come out of this. O Gap is offence against good defences minus offence against bad ones — how much of the offence holds up when the defence is real. Self-Made is offence with a weak cast minus offence with a strong one — how much of it survives without help.
Two Honest Caveats on This One
The first-pass rating of a player feeds the tag on his own rows through the supporting-cast split. Second-best removes him whenever he is the best rating in the lineup, which for stars is most of the time — but not always. The opponent splits have no such issue, since that is a different team entirely.
And compare within similar roles. A bench player’s “strong cast” rows are ones where a star was on the floor; a star’s are ones where his second option was. Same column name, different question.
Isolating playmaking and creation was attempted twelve different ways and failed every time, for one underlying reason worth stating: a target that is nonzero only when a player is on the floor encodes his presence, not his effect.“Points he assisted” comes back as assist volume — Giannis at +10.8 on assisted threes is how many he assists, not what it is worth. Those columns exist in the research code and are deliberately not published.
Turnovers and offensive rebounds are also not fully separable in the channel split. The target is points per possession; turnovers change the denominator and offensive rebounds add attempts inside a possession whose points are already counted elsewhere. Isolating them properly needs a model that does not sum exactly — which is a trade this deliberately does not make.
Part 5 · How Good Is It
Fit the odd-numbered games and the even-numbered games separately, then correlate the two sets of ratings. Same players, same season, different possessions — a direct read on measurement precision.
| Variant | Corr | Offence | Defence | Spread |
|---|---|---|---|---|
| Baseline, λ=2000 | 0.408 | 0.435 | 0.341 | 1.83 |
| + FT adjustment, λ=3000 | 0.451 | 0.468 | 0.380 | 1.41 |
| λ=6000 | 0.538 | 0.534 | 0.454 | 0.90 |
| λ=12000 | 0.607 | 0.591 | 0.517 | 0.55 |
The last two rows are the shrinkage trap, not improvement. Reliability is trivially gamed: push the penalty high enough and both halves collapse toward zero and correlate at 0.99 while carrying no information. Spread falls from 1.41 to 0.55 across those rows. Always read the correlation next to the spread, and only compare variants at the same lambda.
Two structural facts fall out. Defence sits about 0.09 below offence and no lambda fixes it — there are simply fewer attributable events per possession. And reliability has declined over time, from 0.45–0.53 in the late 90s to 0.30–0.41 recently, because players per team went from 335 to 430 and each one gets fewer possessions.
| Prior target | Offence R² | Defence R² |
|---|---|---|
| Same-season 3yr RAPM | 0.53 | 0.23 |
| Luck-adjusted on/off | 0.40 | 0.10 |
| Next-season 3yr RAPM | 0.63 | 0.26 |
Read these against shrinking to zero, which is R² = 0 by construction. Even the weakest of them beats assuming everyone is average.
Ratings from the window ending in one season are used to predict lineup and team results in the next. Correlation with team net rating rises from 0.778 without the prior to 0.790 with it — but the slope falls from 0.78 to 0.68, which means the ratings are over-dispersed. That is a scale problem with a scale fix, not an information problem. Worth knowing when reading an extreme value: the ordering is better than the magnitude.
As with everything here, this is a model and not the truth. RAPM cannot see why a player is good, only that lineups featuring him tend to win the possession battle. The breakdowns narrow that gap — they say which channel the value arrived through, and against whom — but they still describe an effect, not a cause.