gp_expectancy.py → gp_expectancy.csv)Built 2026-07 for the 2026-27 projection stack: project_rosters_2026_27.py values every player at his per-82 rate; this model supplies the missing availability multiplier, ExpGPFrac2026 (expected fraction of an 82-game schedule played).
roster_toi_2010_2020.csv + roster_toi.csv, regular-season games only (game_id[4:6] == "02"; coverage verified complete: 12,590 + 6,560 distinct games).player_bio.csv birthDate, as of Oct 1 of the season year.| statistic (skaters) | r | n |
|---|---|---|
| YoY corr of GP-fraction, all player-season pairs | 0.630 | 11,697 |
| YoY corr, prior-season fraction ≥ 0.50 (regulars) | 0.352 | 8,153 |
| corr(frac_N, mean of prior 2 seasons) | 0.617 | 9,630 |
| corr(frac_N, mean of prior 3 seasons) | 0.603 | 7,826 |
Games-missed is clearly persistent, but much of the pooled 0.63 is the regular-vs-fringe axis; within established regulars the health signal itself is r ≈ 0.35 — real, but noisy enough that heavy shrinkage is warranted. Multi-season averages carry essentially the same signal as last season alone at the pooled level; the model bake-off below settles the weighting question.
Residuals of the tuned model (actual − predicted) regressed on age (2012-2025, eligible skater rows): about −0.010 fraction (≈ −0.9 GP) per year of age past 27, flattening slightly after 30. Confirmed model-free in the healthy cohort (last-2-season mean ≥ 0.85):
| age band | mean next-season fraction | n |
|---|---|---|
| 18-24 | 0.890 | 676 |
| 25-29 | 0.860 | 1,816 |
| 30-33 | 0.842 | 1,081 |
| 34-44 | 0.828 | 498 |
The gradient is real but small (~5 GP from age 22 to 36 for a healthy player).
Eligibility per target season: skater, appeared that season, ≥ 1 prior panel season, last-2 prior mean ≥ 0.25 (semi-regular gate). n = 5,179 predictions. Rookie (first) seasons are never targets. No-history players get the league mean fraction (μ₂₀₂₆ = 0.763 for this population; regulars run ~0.82-0.85).
| candidate | walk-forward MAE |
|---|---|
| league-mean constant | 0.2184 |
| career mean (equal weights) | 0.2160 |
| last-season-only | 0.1872 |
| recency-weighted, decay 0.3, no shrink | 0.1796 |
| recency-weighted, decay 0.3 + shrink k = 0.25 (winner) | 0.1780 |
| winner + age adjustment (intercept + age-27 + hinge-30) | 0.1815 |
Tuning grid: decay ∈ {0.3…1.0}, shrink k ∈ {0…3} pseudo-seasons toward the trailing-3-season league mean; the surface is flat near the optimum ((0.4, 0.25) and (0.3, 0.5) are within 0.0011). Steep decay wins: last season carries most of the health signal; older seasons add a little; heavy shrinkage is not needed once recency-weighting is in place (k = 0.25 ≈ a quarter-season of league-mean ballast, which mostly regularizes stale or one-season histories).
Age adjustment dropped. It failed walk-forward in every form tried: full spec (+0.0034 MAE), slope-only fit on regulars with no intercept (+0.0020), single hinge at 28 (+0.0041), and even when evaluated only on regulars (0.1509 → 0.1557). The Q2 gradient (~0.01/yr) is an order of magnitude smaller than residual noise (MAE 0.18), and fitting it walk-forward adds estimation error faster than it removes bias. Per the design rule — age adjustment only if it survives the walk-forward test — ExpGPFrac2026 is age-free. Bias check: eval-window mean predicted 0.770 vs actual 0.755.
Final model: pred = (Σ 0.3^lag · frac + 0.25 · μ) / (Σ 0.3^lag + 0.25), lag measured back from 2025-26, clipped to [0.02, 1].
P(fraction < 0.40 | last-2-season mean ≥ 0.85), i.e. a lost season for a previously healthy skater:
| age band | P(catastrophe) | n |
|---|---|---|
| 18-24 | 0.025 | 17/676 |
| 25-29 | 0.046 | 83/1,816 |
| 30-33 | 0.057 | 62/1,081 |
| 34-44 | 0.052 | 26/498 |
| all | 0.046 | 189/4,072 |
The tail is age-dependent — roughly 2× as likely at 30+ as under 25 — even though age adds nothing to the point prediction. If the season simulator ever samples GP rather than using the expectation, it should draw catastrophes at ~2.5% (U25) / ~5.5% (30+) on top of the continuous part; the expectation already prices the average of this tail.
| player | last-3 fractions (2023-25) | age 2026 | ExpGPFrac2026 |
|---|---|---|---|
| Brent Burns (iron man) | 1.00, 1.00, 1.00 | 42 | 0.965 |
| Alex Ovechkin | 0.96, 0.79, 1.00 | 41 | 0.923 |
| Nikita Kucherov | 0.99, 0.95, 0.93 | 33 | 0.908 |
| Jack Eichel | 0.77, 0.94, 0.91 | 30 | 0.884 (healthy since 2022-23; the 2021-22 crisis has decayed out) |
| Sidney Crosby | 1.00, 0.98, 0.83 | 39 | 0.858 |
| Patrick Kane | 0.61, 0.88, 0.82 | 38 | 0.811 |
| Mark Stone | 0.68, 0.80, 0.74 | 34 | 0.750 |
| Gabriel Landeskog | 0.00, 0.00, 0.73 | 34 | 0.554 |
| Phil Kessel | last GP 2022-23 | 39 | not in output (no 2023-25 GP) |
Mean ExpGPFrac2026 among skaters with 2025-26 fraction ≥ 0.5: 0.823 — inside the expected 0.80-0.85 band for regulars.
gp_expectancy.csv — 1,328 players (everyone with ≥ 1 GP in 2023-24 through 2025-26; 132 goalies flagged by pos): playerId, name, pos, Age2026, Frac2023/24/25, NSeasons, ExpGPFrac2026, ExpGP82.
Hook-up note for project_rosters_2026_27.py: expected value per roster slot ≈ ProjValue82 × ExpGPFrac2026 (plus replacement-level fill for the missing games, if modeled). Rookies absent from the CSV: use the league mean fraction for regulars (~0.82) rather than 0.763, since a projected roster slot conditions on being a regular.