NSBA Draft Analyticsembargoed · 2026-06-06

13_player_value.md

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_overall columns) — 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.


Limitations / caveats

  1. 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 combine theta is the only nsba4-era signal.
  2. 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.
  3. 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.
  4. 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.
  5. No aging curve applied. Projections regress to the population mean, which under-projects rising young players (A7 §5.4). grade_or_age is 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.
  6. 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.
  7. RAPM is null (§5) — reported only to close the diagnostic question; not a rating.
  8. Small n throughout (3 seasons, 181 players, median 7 games). Read vorp with reliability; 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.


NSBA Draft Analytics · embargoed until after the SSB draft · ← hub