leave_one_marker_out

leave_one_marker_out(
    result,
    clock,
    *,
    data,
    reference,
    markers=None,
    test=None,
    covariates=('age', 'sex'),
    age_col='age',
    sex_col=None,
)

Recompute a clinical clock without each marker in turn, and say what moved.

A composite clock associated with an outcome does not say which of its markers carries the association. A KDM built on nine markers can owe most of an association to one of them, and reporting it as “biological aging” then says more than the data do. This removes one marker at a time, recomputes the clock, and reports how far the score moved and, with test=, how much of the association went with the marker.

Parameters

Name Type Description Default
result The scored :class:~falconage.score.FalconResult. Its clock column is the baseline, and its obs supplies test and the covariates. required
clock str phenoage, kdm or hd, or any clock the registry computes with one of those three formulas. required
data The marker data the result was scored from, as a :class:~falconage.core.FalconData or a DataFrame indexed by sample. A result holds scores, not markers, so they are passed again. required
reference The reference population’s markers, as a FalconData or a DataFrame. For KDM and HD, the rows the scoring reference was fitted on (the frame passed to fit_kdm or fit_hd), because removing a marker means refitting on what remains. For PhenoAge, the population whose marker means a removed marker is held at; see Notes. required
markers Sequence[str] | None For KDM and HD, the panel the scoring reference was fitted on, in any order; required, because neither clock has a fixed panel. For PhenoAge the panel is fixed at Levine’s nine biomarkers, and this chooses which of them to remove (all nine by default). None
test str | None A column of result.obs. When given, each version of the clock is tested against it with :func:associate: ordinary least squares of test on the score and covariates. None
covariates Sequence[str] Passed to :func:associate, with text columns (sex) entered as one indicator per level after the first. Adjusting for age makes the tested quantity the age-independent part of the score, the same thing an acceleration residual isolates. ('age', 'sex')
sex_col str | None KDM and HD only: fit the reference separately within each level of this column (present in both data and reference) and score each sample against its own level’s fit, as :func:~falconage.models.clinical.validate_panel and BioAge do. Leave it None when the result was scored with one pooled reference. None

Returns

Name Type Description
pandas.DataFrame One row per removed marker, after a first row "(none)" for the full panel, with columns: n Samples scored by both the full and the reduced clock. r_spearman Rank correlation of the reduced clock with the full one. mean_change, mean_abs_change Mean and mean absolute of reduced - full, in the clock’s unit. Only where the scale admits a difference; NaN for HD, whose Mahalanobis distance shrinks when a dimension is removed whatever that dimension carried, so its raw change measures the panel size. n_test, beta, se, p With test=: the association from :func:associate, beta in units of test per unit of the clock. partial_r With test=: the partial correlation of test with the score given the covariates, t / sqrt(t^2 + df) from the same fit, the t test of a regression coefficient being also the test of the corresponding partial correlation (Cohen, Cohen, West and Aiken 2003, Applied Multiple Regression/Correlation Analysis for the Behavioral Sciences, 3rd ed.). Scale-free, so the full and reduced clocks, whose spreads differ, compare directly. attenuation With test=: 1 - partial_r / partial_r(full panel), the share of the full panel’s association lost without the marker. 1 means the marker carried all of it; below 0, what remains carries the association more cleanly without it. Read it only when the full panel’s association is itself clearly non-zero. Rows after the first are sorted by attenuation (largest first) with test=, and otherwise by r_spearman (smallest first), so the marker that matters most is on top either way. attrs records the clock, how a marker was removed, the values PhenoAge’s markers were held at, and the test and covariates.

Raises

Name Type Description
AnalysisError For a clock that is not one of the three clinical formulas, a missing reference, markers (KDM, HD), marker or test column, a panel too small to lose a marker (KDM needs two, HD three), or when recomputing the full panel from data and reference does not reproduce the scores in result: the decomposition would then describe some other clock.

Notes

KDM and HD are refitted. Neither has fixed weights: KDM’s come from per-marker regressions on age in the reference, HD’s from its covariance, so the clock without a marker is the clock fitted without it, by :func:~falconage.models.clinical.fit_kdm or :func:~falconage.models.clinical.fit_hd exactly as the full one was. For KDM that also re-estimates r_char, s_R and s_BA^2 on the smaller panel, as :func:~falconage.models.clinical.validate_panel does. This is leave-one-covariate-out, refitting without the variable (Lei et al. 2018, J Am Stat Assoc 113:1094-1111, doi:10.1080/01621459.2017.1307116).

PhenoAge is not refitted. Its weights are Levine’s (2018), fitted on NHANES III mortality, and refitting them on a user’s reference would produce a different clock that happens to share a name. A marker is removed instead by holding it at its mean in reference; for CRP, the mean of log(CRP), since that is the quantity its weight multiplies, which is the geometric mean of CRP. This is well defined because PhenoAge is an affine function of its linear predictor xb: the Gompertz inversion gives -log(1 - M) = exp(xb) (exp(120 gamma) - 1) / gamma, so PhenoAge = const + xb / 0.090165. Holding marker j at c_j therefore moves each person by exactly -beta_j (x_j - c_j) / 0.090165 years, whatever the other markers are. That is the marker’s additive contribution against the reference as baseline, the attribution a linear model gives each input (Strumbelj and Kononenko 2014, Knowl Inf Syst 41:647-665, doi:10.1007/s10115-013-0679-x; Lundberg and Lee 2017, NeurIPS, the linear case of SHAP). Which mean is used moves every score by the same amount, so p and attenuation do not depend on it; mean_change does, and against a reference such as NHANES III it says how much of the cohort’s departure from that population the marker accounts for. Passing this cohort’s own markers as reference holds each at the cohort mean and makes mean_change zero by construction.

What it cannot say. Refitting lets correlated markers take over from the removed one, so two markers carrying the same signal can each show little attenuation although together they carry all of it; holding a PhenoAge marker fixed does not, because the other weights stay put. A marker with high attenuation carries the association given the rest of the panel, which is a statement about the clock, not a causal one about the marker.