Figure 1: Polygenic effects change with age¶
Runs locally against the authors' results (CODE). With RUN = False the cells show the current local figures; set RUN = True to regenerate each panel from the script named. No participant data are stored in this notebook; keep outputs cleared before committing (python ../tools/check_no_ids.py).
Panel a Framework schematic¶
Upstream: Illustrator artwork (no script). Corrected curve shapes for panels 2 and 4: src/triplet_figures_export_2026-09-30/fig1/schematic_fix/make_fig1a_schematic_curves.py
Panel b Genetics x time schematic and T1D per-SD hazard ratio¶
Script: src/reparam_fig3/make_fig1_schematic_genetics_x_time_triplet.py
Upstream: T1D curve: src/paper_keystone/Fig1_ph_violation/recal_scale_to_empirical_triplet.py
Script header (inputs, recipe, outputs)
TRIPLET PORT (generated by reparam_fig3/_make_fig12_triplet.py; inputs + output names only).
FIGURE 1 (schematic candidate): genetic effect INTERACTS WITH TIME.
Three panels, concept -> mechanism -> evidence, to lead the paper with the IDEA (not the diagnostic curves):
A. THE IDEA (cartoon) — high- vs low-PRS cumulative risk: high-PRS onset is FRONT-LOADED (earlier),
the relative gap CONVERGES with age. Illustrative curves (labelled schematic).
B. THE MECHANISM (cartoon) — the genetic contribution to loading over age: OLD = flat (constant effect,
proportional hazards); NEW = gamma_level + t*gamma_slope, a TILT = genetics x time.
C. THE EVIDENCE (real data)— CAD per-SD hazard ratio over age: empirical declines, delta0 (time-varying) tracks
it, no-slope is flat/wrong. Real numbers from fig1_empirical_and_fits_values.csv.
Model: lambda_ik(t) = r_k(t) + G_i*gamma_level_k + t*G_i*gamma_slope_k + delta_ik(t) (the t*G*gamma_slope term).
INPUT claudefile/paper_keystone/Fig1_ph_violation/fig1_empirical_and_fits_values.csv (panel C, real)
OUTPUT fig1_schematic_genetics_x_time.{png,pdf} (+prov). new_env_pyro2. Panels A/B are schematic (labelled).
RUN /opt/miniconda3/envs/new_env_pyro2/bin/python claudefile/reparam_fig3/make_fig1_schematic_genetics_x_time.py
Panel c Q5/Q1 event-rate ratio by age (and per-SD companion)¶
Script: src/paper_keystone/Fig1_ph_violation/recal_scale_to_empirical_triplet.py
Upstream: held-out predictions: src/triplet_loo_calibration.py; backbone: src/train_slope_3way.py
Script header (inputs, recipe, outputs)
TRIPLET PORT (generated by reparam_fig3/_make_fig12_triplet.py; inputs + output names only).
Make the Fig1 model panels match empirical SCALE by recalibrating the delta0 model, and MEASURE
whether AUC is preserved. ALL quantities use PER-YEAR at-risk masking (the hazard definition),
matching the calibration (fig_calibration_prs_quintile: at-risk person-years E[:,d] >= t).
QUANTITY (explicit). Per 5-yr age window [ts,te), for person i at risk, over at-risk YEARS
A_i = {t in [ts,te): E[i,d] >= t}:
obs annual hazard h_i = mean_{t in A_i} Y[i,d,t]
pred annual hazard hhat_i = mean_{t in A_i} pi[i,d,t]
* Q5/Q1(age) = (mean annual hazard in top PRS quintile) / (bottom quintile), pooled over person-years.
* per-SD HR(age) = exp(beta / (pbar_obs (1-pbar_obs))) from an LPM of h_i (or hhat_i) on standardised
PRS z. This is a per-SD ODDS/HAZARD ratio via LPM+delta-method (NOT a Cox HR); rare-event approx.
COMMON denominator pbar_obs for every curve.
The 5-yr window is only an averaging window; per-year masking is what makes h_i a hazard.
RECIPE (FORMULATION)
model : delta0 slope LOO (pi_slope_delta0_loo_like_400000.pt) -- deployed, predict-at-event.
basis : (2026-10-01) the k fit uses the SAME basis as the displayed estimator: person-year cells for Q5/Q1,
per-person at-risk hazards for per-SD. A cell-only fit (the Jul 28+ code) left T1D per-SD at 1.68 vs 3.29;
the per-person fit reproduces the published Fig 1b curve (2.89 vs 2.93 at 45). See diag_t1d_persd_clip.py.
fix : logit recalibration logit(pi') = logit(pi) + k0(a) + k1(a) z [+ k2(a) z^2 for T1D only].
k's fit per age to log(obs/pred) over 20 PRS-percentile bins (PER-YEAR AT-RISK hazards), applied
per-age directly (UNSMOOTHED — tighter empirical match). z is clipped to the fitted range ONLY where a
z^2 term exists (T1D, DEG_BY=2); linear diseases (CAD/AF, DEG_BY=1) are unclipped.
delta untouched. DEG_BY={'T1D':2,'CAD':1,'AF':1}.
check : static-1yr-at-enrollment AUC is done separately in recal_static1yr_auc_check.py (the paper metric).
Here we also report a pooled per-year-at-risk AUC as a rough monotonicity sanity check.
Panel d gamma_1 vs gamma_0 per score x signature¶
Script: src/reparam_fig3/make_fig1_gamma_atten_accel_triplet.py
Upstream: reads the 40 triplet training checkpoints (src/train_slope_3way.py)
Script header (inputs, recipe, outputs)
TRIPLET PORT (generated by reparam_fig3/_make_fig12_triplet.py; inputs + output names only).
FIGURE 1c: gamma_level (constant genetic effect) vs gamma_slope (age-trend) for COGNATE PRS x signature pairs.
The genetics-by-time claim, made concrete per pair: each polygenic score's effect on its OWN disease's
signature has a constant part (gamma_level, gray) and an age-trend (gamma_slope). slope<0 = the genetic effect
ATTENUATES with age (front-loaded, PH-violating, blue); slope>0 = ACCELERATES (red). This is the mechanism
panel behind the Fig-1 per-SD HR decline.
DEDUP: gamma is a property of (PRS p, signature k) -- one row per unique (PRS x Sig) pair, labelled by that
signature's DOMINANT disease (argmax psi). We do NOT repeat the same (p,k) once per disease that occupies the
signature (that duplication -- AF-Sig0 under ASCVD/AtrialFib/CKD/HF, CAD-Sig5 many times -- is what bloated the
earlier plot). Only interpretable COGNATE pairs are shown (a PRS acting on its own disease's signature).
SOURCE: gamma_level, gamma_slope averaged across the 40 training-batch checkpoints of the deployed slope model
(slope_model_nokappa_v3_delta0). gamma_slope is batch-unstable (mean pairwise corr ~0.35), so we plot the
cross-batch mean and draw +/-1 SE (=SD/sqrt(40)) error bars on the slope so the reader sees which age-trends are robust
(AF/T1D large & stable; the coronary cluster ~0 = PH holds). These are the training-batch-averaged gammas
(descriptive), NOT the per-fold LOO-refit gammas that produced the deployed pi.
INPUT slope_model_nokappa_v3_delta0/slope_model_batch_*.pt (gamma_level,gamma_slope,psi per batch);
data_for_running/model_essentials.pt (disease_names); prs_names.csv (PRS labels).
OUTPUT fig1_gamma_atten_accel.{png,pdf} + fig1_gamma_atten_accel_values.csv (+prov). new_env_pyro2.
RUN /opt/miniconda3/envs/new_env_pyro2/bin/python claudefile/reparam_fig3/make_fig1_gamma_atten_accel.py