Can We See John Stockton’s Heart Rate in the Box Score?
3-Post NBA Durability Series — Status 2026-10-11 (v3)
The Premise
John Stockton and Boris Diaw reportedly share a resting heart rate of ~35 BPM — well below the elite-athlete norm of ~50 BPM. The question: does that physiology leave a detectable trace in box-score data as statistical resilience — fewer missed games, steadier late-game performance, more durable decision-making under fatigue? Stockton’s durability is currently circulating on social media, providing a natural hook.
Post Structure
| Post | Subject | Data Window |
|---|---|---|
| 1 | John Stockton + 14 PG comps | E1/E3 full career 1984–2003; E2/E4/E5 1996–2003 only |
| 2 | Boris Diaw + 5 PF comps (Nowitzki, Boozer, Jamison, Marion, Lewis) | 2004–2016, full BRef |
| 3 | All qualifying BRef-era guards + forwards + centers | 1980–2010 debuts |
Stockton ships first to ride the current conversation; Diaw follows immediately as independent methodological confirmation — different position, different era, full BRef coverage, no age-regime caveat. Post 3 addresses the cherry-picking objection: placing both players in the full distribution of all BRef-era players who meet the career-length threshold.
Estimands
| Metric | Data Tier | Status | |
|---|---|---|---|
| E1 | Games missed per 82 — career durability | Game logs | Done — both cohorts |
| E3 | Shot selection: 3PA/FGA, FTA/FGA on B2B vs rest | Game logs | Done — both cohorts |
| E2 | 4Q FT% degradation vs Q1–Q3 | Quarter box scores | Done (PG) — Stockton +4.1 pp, only improver; PF pending |
| E4 | 4Q AST/TOV ratio vs full-game | Quarter box scores | Degenerate at quarter resolution (52% zero-TOV rows); dropped |
| E5 | 4Q TS% vs Q1–Q3 | Quarter box scores | Degenerate (21% coverage per quarter); dropped |
| E6 | Full Q1→Q2→Q3→Q4 scoring profile | Quarter box scores | Done — both cohorts |
E1 and E3 are primary. E6 (profile plot) generated from existing quarter splits; committed to both posts.
Inference Approach
E1’s denominator corrects for lockout-shortened seasons: 1999→50 games, 2012→66, 2020→65, 2021→72.
n_treated = 1 makes bootstrap CIs invalid. For E1/E3: resample the treatment label across all N cohort players 5,000 times; report where the observed gap falls in the null distribution. For E2/E4/E5: mixed-effects model metric ~ quarter * is_treated + rest_days + (1|player_id) via statsmodels.MixedLM; permutation resamples is_treated at the player level (not game level) to preserve within-player correlation.
Current Status
Infrastructure: complete. 141 tests passing across four scripts:
| Script | Purpose |
|---|---|
fetch_bref_data.py |
BRef game log scraper; HTML-cached per (player, season); 4 sec/request |
analyze_durability.py |
E1 + E3; OLS + permutation; emits {cohort}_comparators.csv |
fetch_quarter_splits.py |
Quarter box score scraper; cached by game ID; 3.5s/request + jitter |
analyze_quarter_splits.py |
E2/E4/E5; mixed-effects model; player-level permutation |
Data fetch: complete for PG and PF cohorts; population fetch running locally.
| Status | Players |
|---|---|
| Full PG game logs + quarter splits | Stockton, Payton, Jackson, Harper |
| Full PG game logs only | Thomas, Hardaway, K. Johnson, Porter, Bogues, Strickland, Kidd, Nash, Blaylock, Anderson, Douglas |
| PF game logs + quarter splits (all 6) | Diaw, Nowitzki, Boozer, Jamison, Marion, Lewis |
| Population (local fetch) | ~109 players fetched, running — ETA ~Oct 14 |
Post drafts:
| Post | E1/E3 | E2/E4/E5 | E6 | Status |
|---|---|---|---|---|
| Stockton | Written | E2 written; E4/E5 dropped | Written | Published (draft removed; freeze committed) |
| Diaw | Written | E2 written (null); E4/E5 dropped | Written | Published (draft removed; freeze committed) |
| Population | — | — | — | Outline only — blocked on population fetch |
Caveats (mandatory in every post)
- Selection-on-outcome bias — >=600-game threshold conditions on the dependent variable; E1 estimates durability among players who completed long careers, not all players.
- B2B state selection — playing a B2B selects healthier player-states; within-player pairing removes stable effects but not selection.
- Blowout/garbage time — starters sit in non-competitive games; 4Q minutes are not random. Compresses treatment effect toward zero.
- Intentional fouling — close-game FTA inflation in Q4; FT% estimates partly reflect pressure context, not just physical state.
- Timeout-adjusted rest — unmeasured confounder; coaches use Q4 timeouts strategically.
- Age-regime — Stockton’s quarter data covers ages 34–40 (career decline). A null within-game result does not contradict a prime-years resilience claim.
- Pre-1996 quarter gap — BRef play-by-play starts 1996-97; only Stockton ages 34–40 are covered. Justin Jacobs (Squared Statistics) has video-reconstructed pre-1996 quarter logs that would extend the window to his prime (ages 22–33). Outreach drafted, not sent.
- No causality — frame consistently as “consistent with” and “does not contradict.”
What’s Left
| Step | Blocker | Est. |
|---|---|---|
| Population fetch (local) | Running — ETA ~Oct 14 | — |
| Population analysis + post | Population fetch complete | 1 day |
| Jacobs outreach (#22) — manual browser form | Human action required | 20 min |
NOTE: Causal language forbidden. Null results are valid findings — state them plainly. Sourcing asymmetry: Diaw’s 35 BPM comes from documented post-retirement interviews; the Stockton figure is less sourced — acknowledge this explicitly in the Stockton post.
Changelog
v3 — 2026-10-11
- E6 plots added —
plot_e6_quarter_profile()added toanalyze_quarter_splits.py; generates Q1→Q4 FT% line chart highlighting treated player. PG: Stockton is the only player with a monotonic Q1→Q4 rise (76.9% → 80.6% → 83.2% → 84.7%). PF: Diaw shows slight Q4 decline (67.7%), Marion is the Q4 outlier (82.2%). - E6 figures generated for both cohorts (
data/figures/pg/e6_quarter_profile.png,data/figures/pf/e6_quarter_profile.png); figures committed. - E6 section added to both posts — Stockton post: updated from “pending” placeholder to real figure with caption; conclusion updated with monotonic-increase note. Diaw post: new E6 section inserted after E5.
- Diaw E2/E4/E5 complete — PF quarter splits were already fetched (Oct 10); analysis complete; post draft finished.
- Posts 1 + 2 published —
draft: trueremoved from Stockton and Diaw posts;jupyter: blogadded to frontmatter;_freeze/outputs generated locally and committed; CI will serve frozen outputs without re-executing Python. - CI fixed — kernel registration (
python -m ipykernel install --user --name blog) added topublish.yml; last green run 2026-10-09. - Population fetch running locally — PID 39001; ~109 population players fetched so far; ETA ~Oct 14.
- 141 tests passing (was 131 after Oct 10 session).
v2 — 2026-10-10 (updated same day)
- BRef ID fixes: Bogues (
boguemu01), Harper (harpede01), Strickland (stricro02) — PG cohort grew from 12 → 15 players - E1 + E3 analysis complete for both PG (15 players) and PF (6 players)
- Stockton post drafted with E1/E3 results; E2/E4/E5 sections stubbed pending figures
- Diaw post drafted with E1/E3 results; E2/E4/E5 sections stubbed
- Quarter splits fetched for Payton, Jackson, Harper, Stockton (5,585 rows)
- E2/E4/E5 analysis run for PG cohort; E2 interpretable (Stockton Q4 FT% +4.1 pp, only improver, p=0.494 at floor); E4/E5 dropped — degenerate at per-quarter resolution (52% zero-TOV rows; 21% TS% coverage)
- Stockton post now complete draft (E1/E3/E2 written; E4/E5 explained as methodologically degenerate)
- Population fetch scope expanded to include centers; running on Apollo (232/1,995 players, 99,883 rows)
- Population cohort size: 1,995 players (guards + forwards + centers, debuts 1980–2010)
- Fixed 5 bugs in
fetch_quarter_splits.py: Age column KeyError, rate limit (0.35s→3.5s+jitter), 429 backoff, game_id Tm→Team column, season range (1996→1997) - Fixed parallel
--playerrace condition infetch_bref_data.pywithfcntl.flock()
v1 — 2026-10-09
- Initial status document; infrastructure complete (131 tests); data fetch incomplete