Running Net Points
A possession-by-possession accounting of a player's net point impact, independent of who else was on the floor.
Credit: Justin Jacobs (Squared2020)
Why it exists
Traditional box-score stats are additive in a way that obscures more than it reveals. A player who scores 22 points looks productive whether those points came against a scrambled defense off transition turnovers or in the fourth quarter against a set half-court scheme. Points don't know the difference. Assists don't know whether the pass found a wide-open corner three or a contested mid-range pull-up. The box score tells you what happened in aggregate; it does not tell you which player made possessions better or worse.
Plus-minus tries to solve this at the lineup level — if your five outscores theirs, the margin gets credited to everyone on your side. But that's exactly the problem. A player who spends 30 minutes alongside four excellent teammates can post a gleaming plus-minus number while doing very little himself. A player dragging a bad bench unit will look terrible by plus-minus even if he's the only thing keeping the margin close. The stat punishes or rewards you for your teammates.
Running Net Points starts from a different premise: account for each player's individual involvement, possession by possession, without contaminating it with whoever else happens to be on the floor. Every time the ball is in play, RNP asks two questions — did this player contribute to or away from their team's offensive output on this possession, and were they involved in the defensive outcome? The answers accumulate as a running ledger, which is the "Running" part of the name. By the end of a game you have a single number representing that player's net point contribution across every possession they participated in.
This is not a perfect measure of value — no single number is. But it is a more honest attempt to isolate individual impact than anything the box score offers on its own.
Production methodology
Numbers come from the Python ETL (etl/src/nba_rnp_etl/stats/rnp.py): a running box-score per game, and after each possession points produced and points allowed recomputed for every player on the floor via Justin Jacobs’ port of Dean Oliver’s formulas (not an even five-way split).
M1 vs production. M1 used equal-attribution (points split evenly among the five offensive players and five defenders) to validate possession plumbing; it does not match historical RNP leaderboards and is documented only for transparency.
Jacobs / Oliver runs end to end today; formulas are pinned to the legacy reference in unit tests. Remaining hybrid parity (Gate 2 in the M2 notes) is about how per-player-scoped team and opponent totals accumulate at possession boundaries — not which formula family the app runs.
Specs in this repository (open in GitHub):
- RNP parity notes — M1 (historical equal split; superseded for formula)
- M2 RNP parity notes — current production and remaining hybrid-parity scope
- M2 parity design — accumulator follow-up
Dean Oliver attribution (what you see in “Produced” and “Allowed”)
Game views show cumulative produced and allowed from the same ETL snapshot each possession. Produced is Oliver’s individual points produced (FG, assist, FT, and OREB parts, with Jacobs’ running qAST). Allowed uses Oliver’s defensive rating and stops on those running totals (steals, blocks, boards, misses, fouls, opponent efficiency). Δ RNP is the change in (produced − allowed) since the previous ledger row.
Full derivation (including Jacobs’ 0.2 defensive share): Running Net Points — the math.
Formula
RNP_t = PProd_t − PAllowed_t
After each possession the accumulator updates team and player boxes; PProd and PAllowed are recomputed from those running totals for everyone on court. Both are cumulative in the feed; charts plot how their difference moves each possession.
Offense follows Oliver’s points-produced split (scoring and shot type, assists weighted to team shot context, free throws, OREB value). Jacobs’ running qAST differs slightly from Oliver’s season split — the math page.
PAllowed uses the same DRtg / stops construction on that feed. UI labels (involvement, shot type) explain the play; the numbers come from code, not hand-picked primary defenders on every make.
Worked example
The simplest way to see how this works is to walk through a three-possession sequence for a single player, starting with their RNP at zero (say, the first three possessions of their night).
Possession 1: Pull-up three from the top of the key, made. The player gets full offensive credit for the three points because this was an unassisted shot. Defensive possession: the opponent runs a pick-and-roll, the player is the ball handler's primary defender, and the shooter converts a layup over a weak contest. A partial negative defensive adjustment is applied. Net for this possession: +2.4.
Possession 2: Offensive possession, the player sets a screen and cuts — no direct involvement in the shot attempt. Defensive possession: weak-side help rotation, the opponent drives baseline and the player takes a charge. Charge credit is partial because the defensive assignment was shared with a help rotator. Net for this possession: +0.4. Running total: +2.8. (Note: a charge nets positive defense despite the points yielded, because the charge earns possession credit back.)
Possession 3: Assist to a catch-and-shoot corner three, made. Weighted assist credit flows to the player, scaled to the three-point value of the shot. No defensive involvement — on this possession the ball moved to the opposite side and the player was off the ball with no rotation responsibility. Net for this possession: +1.1. Running total: +3.9 after three possessions.
The step-through below shows every possession from a 2019-20 game, so you can watch the ledger accumulate in real time.
LeBron drives, kicks to Kuzma for 3 (miss)
How to read it
Single-game RNP values tend to cluster around a range most casual fans wouldn't expect — even a good night often lands somewhere between 5 and 12, not the 40-point explosion the raw box score implies. That's because RNP is a net measure, not a counting stat: defensive lapses and missed possessions drag the number down even when the offensive output is high.
| RNP value | What it typically means |
|---|---|
| Less than 0 | Net negative on the night; the team was worse with this player involved than without |
| 0 – 5 | League-average range for a starter; a decent but unremarkable game |
| 5 – 10 | Above average; the player made more possessions better than worse |
| 10 – 15 | Strong performance; among the better individual games on a given night |
| More than 15 | Elite; usually reserved for true go-to performances or dominant defensive efforts |
A typical starter on a middle-of-the-road team might spend most of a season averaging somewhere in the 2-6 range. Cracking double digits for a single game is noteworthy. Averaging 8+ across a full season would put a player in genuinely elite territory. These thresholds are not fixed — opponent quality, pace, and lineup context all shift the baseline — but they give a working frame for interpretation.
Limitations
Same themes as the project README (web and ETL): feed quality, defensive modeling, usage, no opponent adjustment inside the stat, and single-game noise.
Defensive involvement is identified through heuristics, not camera tracking or optical player-location data. The system can infer primary defenders from rotation tagging, but it cannot see where a player's feet were at shot release or precisely how much their body presence affected an attempt. This is a real limitation: the defensive component of RNP is directionally useful but less reliable than the offensive component, where the play-by-play data is richer and more consistent.
The stat rewards usage to a degree. Players who touch the ball more get more opportunities to accumulate both positive and negative RNP. That's not the same as saying high-usage players will always have high RNP — a player who turns the ball over constantly will accrue negative offensive contributions at a brutal rate — but it does mean a sixth man playing 18 minutes is not directly comparable to a starter playing 36, even on a per-minute basis, without accounting for the difference in touch volume.
RNP does not currently adjust for opponent quality. A player posting 12 RNP against an elite defensive opponent deserves more credit than the same number against one of the league's worst defenses. When evaluating RNP across a season, it helps to hold schedule context in mind rather than treating the number as fully context-neutral.
On the web app, league percentile and rank compare season RNP totals to other players in the same loaded season slice (minimum games rule). That is league-relative placement, not opponent adjustment — see League context for RNP.
Finally, single-game RNP is noisier than season aggregates. Any one game contains too few possessions, too much variance in matchups, and too much dependency on officiating and feed-tagging to be treated as a clean verdict on a player's quality. The number earns its credibility over the full season arc.