13 — Player Value / VORP for the NSBA4 14-team Draft
13 — Player Value / VORP for the NSBA4 14-team Draft
Date: 2026-05-30
Script: scripts/player_value.py (reproducible; reads the game-only spine + combine_ability)
Outputs: outputs/player_value_table.csv (181 players with game history, 34 cols)
Env: /home/david/code/nsba/.venv/bin/python
Converts the game spine (player_season_master.csv, team_season.csv) into a draftable
value board: projected PPTF (reliability-shrunk), replacement level for a 14-team draft,
and VORP overall + per category. A ridge plus-minus (RAPM) was attempted as a
diagnostic only and is reported as under-powered (see §5).
Scoring world = GAMES (standard Science Bowl, TU +4 / neg −4 / bonus +10). The combine is a pyramidal, gameable tryout and is attached here only as a prior/seed (
theta_overall,debiked_overallcolumns) — never mixed into the value stats. Per prior D7, game data is trusted over combine.
Method
1. Stabilization point for PPTF (the shrinkage constant)
The task asks for "reliability shrinkage from the stabilization work." No standalone
stabilization file existed, so it is computed here via variance components on
points-per-faced-tossup, which is exactly the optimal regression weight (A7 §5):
shrink = n / (n + n_stabilize).
Per faced tossup the outcome y ∈ {+4 correct, −4 neg, 0 else}. The per-event variance is
ev_var = 16(p_corr+p_neg) − (4p_corr − 4p_neg)²; a player's PPTF sampling variance is
ev_var / tossups_faced. The stabilization padding is
n_stabilize = pooled ev_var / between-player variance (the n where reliability = 0.5).
| season | grand PPTF | between-var | ev_var | n_stab (rel 0.5) | n_stab (rel 0.7) |
|---|---|---|---|---|---|
| nsba1_2022 | 0.339 | 0.065 | 1.89 | 29 | 68 |
| nsba2_2023 | 0.371 | 0.063 | 1.90 | 30 | 71 |
| nsba3_2025 | 0.282 | 0.027 | 1.85 | 69 | 161 |
| POOLED | 0.332 | 0.054 | 1.88 | 35 | 82 |
PPTF stabilizes at ≈ 35 tossups faced (reliability 0.5), ≈ 82 for reliability 0.7 —
roughly 1.5–4 games. The pooled n_stabilize = 35 is the padding constant. nsba3's
much higher figure (69) is an artifact: its TUH denominator is estimated (games × season
mean, paired layout records no real heard-counts), which compresses between-player variance
and inflates the apparent stabilization point. The two real-TUH seasons agree tightly at
29–30.
2. Projected PPTF (Marcel-lite + empirical-Bayes shrinkage)
One projection per canonical player (seasons collapsed). Prior seasons are recency- weighted 5/4/3 (nsba3 newest → 5). The recency-weighted observed PPTF is shrunk toward the league grand mean with the stabilization padding:
proj_pptf = (eff_n · obs_pptf + 35 · grand_pptf) / (eff_n + 35)
reliability = eff_n / (eff_n + 35) # how much we trust the player's own number
eff_n is the player's recency-weighted total tossups faced. Per-category projections use
the same shrinkage toward that category's grand mean (denominator = that subject's
tossups), so rare-category rates regress harder — this is the "regress to the category mean"
fallback the task allowed, applied uniformly.
3. Replacement level (14 teams × 5 slots)
Per A6 §3 and decision D1, replacement level is defined by the draft itself: rank all players with game history by projected PPTF; the 70th pick (14 × 5) is the last drafted, so replacement = its projected PPTF.
replacement PPTF = 0.298 (grand mean = 0.332, so replacement ≈ 90% of average)
expected tossups faced / season = 150 (median seasonal TUF — VORP unit = points/season)
4. VORP
VORP = (proj_pptf − replacement_rate) × expected_TUF. Per-category VORP uses a
category-specific replacement (rank-70 of that category's projection) and expected_TUF / 6
(six categories cycle per match, D1), so a CS specialist is valued against the CS waiver
pool, not the overall one (supports the scarce-specialist VONA logic, D8).
Results
Top of the value board (full table → outputs/player_value_table.csv)
| rank | player | team (last) | season | G | proj PPTF | reliab. | VORP |
|---|---|---|---|---|---|---|---|
| 1 | arolakiv | devansh | nsba1 | 10 | 1.088 | 0.86 | 118.5 |
| 2 | yufei | Vedang | nsba2 | 7 | 1.028 | 0.83 | 109.5 |
| 3 | sanj | Coby | nsba2 | 8 | 0.923 | 0.84 | 93.7 |
| 4 | thedoge | Okeh City | nsba2 | 7 | 0.898 | 0.82 | 90.0 |
| 5 | JoshuaW | Mikey Mouse | nsba2 | 9 | 0.850 | 0.86 | 82.8 |
| 6 | VulcanForge | sheep b | nsba1 | 11 | 0.825 | 0.87 | 79.0 |
| 7 | ne | doge disciples | nsba1 | 10 | 0.811 | 0.86 | 77.0 |
| 8 | dan.k.memes | Yared | nsba2 | 10 | 0.775 | 0.87 | 71.6 |
| 9 | AdiyaSivahuma | aastha | nsba1 | 11 | 0.718 | 0.87 | 63.0 |
| 10 | Anurag Sodhi | Ferrum | nsba2 | 21 | 0.706 | 0.92 | 61.2 |
Projections track raw PPTF with sensible shrinkage (e.g. arolakiv's raw 1.21 → 1.09; the
21-game Anurag Sodhi keeps reliability 0.92, almost unshrunk). Only 3 of the top-25 carry
an nsba4 combine row (Akhil Batchu #15, Rohan G #17, Kian Dhawan #23) — most elite
historical players have not (yet) re-registered, which is the central draft-pool caveat below.
[corrected 2026-05-30: was "5"; the artifact outputs/player_value_table.csv top-25 has
has_nsba4_combine.sum()==3. See finding 15b / red-team M3.]
Per-category replacement & best specialists
| category | replacement PPTF | top-3 by category VORP |
|---|---|---|
| Bio | +0.206 | Arjun D (43.7), Kian Dhawan (36.3), AdiyaSivahuma (34.0) |
| Chem | +0.189 | Anurag S (42.3), ne (42.1), yufei (38.0) |
| CS | +0.133 | thedoge (36.4), dan.k.memes (30.4), Lockheed Martin (26.3) |
| ESS | +0.201 | cymbidium (33.7), reverse (29.6), Peter B (29.3) |
| Math | +0.210 | arolakiv (41.7), minitarrasque (36.5), VulcanForge (35.9) |
| Phys | +0.185 | sanj (38.8), Praneel Avula (31.9), thedoge (29.2) |
CS has the lowest replacement floor (+0.133 vs +0.19–0.21 elsewhere) — consistent with the scarce-specialist thesis (D8): the CS waiver pool is the weakest, so a real CS answerer clears replacement by more. CS is also the "unbikable" cleaner signal (D7). thedoge appears in the top-3 of both CS and Physics → a genuine multi-category asset.
Replacement is close to average (a real, expected caveat)
replacement (0.298) ≈ 0.90 × grand mean (0.332). With 181 players in the pool and only 70
draft slots, the talent drop-off from average to the last pick is shallow, so VORP is
compressed and discriminates best at the top. A6 §3(4) predicted exactly this for a thin
pool; it means VORP rank-orders the elite cleanly but flattens in the mid-rounds, where
category scarcity (per-category VORP) and availability flags should drive picks instead.
5. RAPM diagnostic — attempted, under-powered, do not use for ranking
Lineup recoverability gates RAPM. Per the data dictionary, per-player tossups-heard (TUH) is on the sheet for all nsba1 games + ~12 nsba2 games; nsba3's paired layout records none, and no season records per-tossup seating (subs are allowed). So substitution-aware RAPM is not recoverable — only a game-level ridge plus-minus is possible.
I built one (one row per team-game: y = team toss-point margin per tossup; design = TUH-
share of each on-floor player +1 own / −1 opponent; ridge λ = 40):
- 78 games, 114 players, 156 possession-rows. More players than rows → the system is
rank-deficient and dominated by the ridge prior.
- corr(RAPM, observed PPTF) = 0.19 — RAPM recovers almost nothing beyond noise.
This confirms prior A6 §4(4): RAPM is data-hungry, quiz bowl has only one buzzer per
tossup (weak lineup-interaction structure), and individual buzz attribution already captures
the signal. RAPM is a diagnostic null here, not a rating. The rapm column is shipped in
the table only as a cross-check; box-score PPTF/VORP is the rating. RAPM would only become
viable with full per-tossup seating across many more games.
Output schema (outputs/player_value_table.csv)
value_rank, canonical_id, canonical_name, team_last, n_seasons, last_season, games_total,
total_tuf, obs_pptf, proj_pptf, reliability, repl_rate, vorp,
vorp_{b,ch,cs,ess,m,p}, proj_pptf_{b,ch,cs,ess,m,p}, rapm, neg_rate,
theta_overall, debiked_overall, has_nsba4_combine,
likely_returning, availability_flag, tanking_risk, grade_or_age.
proj_pptfis reliability-shrunk (toward grand mean, padding 35 TUH);reliability= how much the player's own data drove it (1 = unshrunk).vorpis in points per season above replacement (× 150 expected TUF).theta_overall/debiked_overallare the combine seed (latest combine season), a prior — not part of value.has_nsba4_combineflags who is in the actual draft pool.
Limitations / caveats
- The board is historical talent, not the nsba4 pool. Only 3 of the top-25 have an
nsba4 combine row (Akhil Batchu, Rohan G, Kian Dhawan — corrected from "5", red-team M3;
this strengthens the caveat, the live pool is thinner than stated). Most of this value is
locked in players who may not return. The table
is the value of every player who ever played; intersect with
has_nsba4_combine/ draft eligibility before drafting. For unregistered-but-returning players the combinethetais the only nsba4-era signal. - nsba3 PPTF is a games×rate proxy (no real TUH), which both noises its projections and inflates its stabilization estimate. Trust nsba1/nsba2 PPTF more; treat nsba3 ranks as directional.
- Compressed VORP / thin pool. Replacement ≈ 0.9× average, so VORP separates the elite well but flattens mid-board — lean on per-category VORP + scarcity (esp. CS) and availability there, not overall VORP.
- Cross-season comparability is assumed. PPTF is pooled across three seasons with a single grand mean and one padding constant; difficulty/era drift is not adjusted. The recency 5/4/3 weighting partially mitigates this.
- No aging curve applied. Projections regress to the population mean, which
under-projects rising young players (A7 §5.4).
grade_or_ageis carried but sparse (only ~17 player-seasons have any qualitative flag), so a development adjustment was not built here — a known gap for the youngest players. - Per-category denominators are approximate where a player never scored in a subject (subject TUF backed off to TUF/6). Per-category VORP for zero-data subjects is therefore a regression-to-category-mean placeholder, not observed depth.
- RAPM is null (§5) — reported only to close the diagnostic question; not a rating.
- Small n throughout (3 seasons, 181 players, median 7 games). Read
vorpwithreliability; a high VORP at reliability < 0.6 (e.g. 1-game Kaden W) is a small-sample flag, not a confident projection.
Reproduce
/home/david/code/nsba/.venv/bin/python scripts/player_value.py
Writes outputs/player_value_table.csv and prints the stabilization, replacement, top-25,
per-category, and RAPM-diagnostic tables above.