Choosing a clock
By the question, by the scale, and by whether the coefficients transfer at all
There is no best clock. There is a clock that answers the question you asked, and a much larger number that answer a different one in the same units.

The generations are not successive improvements on one measurement. Each was trained against a different target, so each returns a different quantity: a predicted age whose signal is the residual, a mortality composite rescaled into year-like units, a rate, a pair of scores with no shared origin, and a division count. A later generation is not a better answer to the earlier question, and the first generation is still the right choice when the question really is how old a sample looks.
Choosing by research question
| Question | Ready to score | Needs a licensed file | No coefficients traced | Scale |
|---|---|---|---|---|
| How old does this sample look? | altumage, cage, corticalclock, hannum, horvath2013, intrinclock, lin, meer, and 11 more |
pchannum, pchorvath2013, pcskinandblood, retroclock, systemsageageprediction |
abec, cabec, ctsliver, eabec, encen100, encen40, ensembleagehumanmouse, gliasin, and 20 more |
age_years, relative_score |
| How old is this newborn, gestationally? | bohlin, knight, leecontrol, leerefinedrobust, leerobust, mayne |
— | epicga |
gestational_weeks |
| How fast is this person aging? | dunedinpoam38 |
dunedinpace |
ensembleagestatic, ensembleagestatictop |
age_years, pace_ratio |
| Which organ system is aging fastest? | — | systemsage, systemsageblood, systemsagebrain, systemsageheart, systemsagehormone, systemsageimmune, systemsageinflammation, systemsagekidney, and 4 more |
dnamfitage |
age_years |
| Who is at risk of dying sooner, or is frailer? | dnamfili, dnamic, dnamphenoage, hd, hrsinchphenoage, kdm, linage2, metabohealth, and 2 more |
cpgptgrimage3, cpgptpcgrimage3, dnamemrage, grimage, grimage2, omicmage, pcgrimage, pcphenoage |
fiage, mammalianlifespan, senmortalityage |
age_years, mortality_log_hazard, relative_score |
| Is damage separable from adaptation? | yingadaptage, yingdamage |
— | — | age_years_relative |
| How much has this tissue divided? | cellpopage, epicmithyper, epicmithypo, epitoc1, epitoc2, epitoc3, hypoclock, replitali, and 2 more |
— | replitalinorm, sencultureage |
divisions, relative_score |
| What is the blood’s cell composition? | deconvolutebloodepicbcell, deconvolutebloodepiccd4tcell, deconvolutebloodepiccd8tcell, deconvolutebloodepicmonocyte, deconvolutebloodepicneutrophil, deconvolutebloodepicnkcell |
twelvecelldeconvolutebloodepicbas, twelvecelldeconvolutebloodepicbmem, twelvecelldeconvolutebloodepicbnv, twelvecelldeconvolutebloodepiccd4mem, twelvecelldeconvolutebloodepiccd4nv, twelvecelldeconvolutebloodepiccd8mem, twelvecelldeconvolutebloodepiccd8nv, twelvecelldeconvolutebloodepiceos, and 4 more |
— | proportion |
| How long are the telomeres? | dnamtl |
pcdnamtl |
— | telomere_kb |
| What is this person exposed to, or how do they live? | dnamstress, mccartneyalcohol, mccartneybmi, mccartneybodyfat, mccartneyeducation, mccartneyhdlcholesterol, mccartneyldlcholesterol, mccartneysmoking, and 3 more |
grimage2packyrs, pcgrimagepackyrs |
dnamfitagegaitf, dnamfitagegaitm, dnamfitagegripf, dnamfitagegripm, dnamfitagevo2max, reedbmi |
gestational_weeks, relative_score |
| What is a specific protein or lab value likely to be? | downsyndrome |
grimage2adm, grimage2b2m, grimage2cystatinc, grimage2gdf15, grimage2leptin, grimage2loga1c, grimage2logcrp, grimage2pai1, and 8 more |
bocklandt, compil6, garagnani |
relative_score |
| Is a named disease more likely? | depressionbarbu, prostatecancerkirby |
— | adbahadosingh, cvdwesterman, hepatoxu |
relative_score |
| What is the sample’s chromosomal sex, or its species? | — | — | mammalianfemale, xchrom, ychrom |
relative_score |
Every one of the 178 catalogued clocks appears above, routed by its declared predicts field. The full table with paper links is the clock catalogue.
“Ready to score” means the coefficients ship in the package. The rest are catalogued and need a file; see Availability below.
reg = fa.registry.load()
reg.filter(generation="second", availability="bundled")
reg.compatible_with(d)list_clocks(generation = "second", tier = "bundled")
compatible_clocks(d)Scale type and permitted operations
Every clock carries a scale_type, and it decides which downstream operations are defined.
| Scale | Clocks | Permitted | Refused, and why |
|---|---|---|---|
age_years |
80 | acceleration, correlate, difference, mean, residual | — |
relative_score |
53 | correlate, rank | everything except correlation and rank: there is no external unit to difference or average |
proportion |
18 | compositional, correlate, difference, mean | anything ignoring the sum-to-one constraint |
divisions |
10 | correlate, difference, mean, rank | acceleration: a division count is not elapsed time |
gestational_weeks |
8 | acceleration, correlate, difference, mean, residual | mixing with an age in years |
mortality_log_hazard |
3 | correlate, hazard ratio, mean, rank | acceleration: a log-hazard has no zero on the age scale |
telomere_kb |
2 | acceleration, correlate, difference, mean | comparison with an age in years; and higher is the younger direction here, unlike every age clock beside it |
pace_ratio |
2 | correlate, difference, mean, rank | acceleration: it is already a rate |
age_years_relative |
2 | correlate, difference, mean, residual | acceleration: the clock tracks age with a slope near one but its origin moves between cohorts, so predicted minus chronological measures the cohort |
acceleration() enforces this. Naming a clock whose scale does not support it is an error; leaving the clock list empty quietly excludes them.
Coverage and validity
A clock trained on 450K runs perfectly well on EPIC data that carries its probes, and fails on 450K data filtered to 20,000 probes. Only the feature list can answer whether a clock is usable, which is what compatible_clocks() checks.
But high coverage does not make a result meaningful. The mammalian methylation array was designed around CpGs conserved across mammals, and carries 96% of Horvath2013’s 353 probes, so a zebra scores at higher coverage than many human 450K datasets and returns a confident number from a clock fitted on people. Nothing in the arithmetic can notice. Set species and FALCONAge warns:
d <- falcon_data(betas, modality = "dna_methylation", species = "Equus grevyi")
res <- score(d, clocks = "horvath2013")
manifest(res)$warnings
#> trained on Homo sapiens, scored on Equus grevyi. Feature coverage says nothing
#> about whether the coefficients transfer across species.Feature count and coefficient mass
Coverage has two forms and they disagree exactly when it matters: how many of a clock’s features are present, and what share of the model’s total |coefficient| those present features carry. Check both before committing to a clock on a given array.
fa.probe_loss(d, clocks="scoreable")
#> horvath2013 coverage 0.9632 mass_coverage 0.6117 cg16867657 (14.1%), ...
Ninety-six per cent of probes present, sixty-one per cent of the model. The scoring floor applies to both, so this is refused with the heaviest absent probes named rather than scored into a confident number. The gap opens because elastic-net coefficients are far from uniform: a handful of probes carry a disproportionate share of the total weight, and losing four per cent of the probes loses thirty-five per cent of the model when the missing four per cent are those.
→ The science, §19.5 prices the same loss in years, per clock per platform, and explains why the PC clocks barely move where first-generation clocks shift by double digits.
Reliability
“Reliable” means two different things, and they do not track together:
| What is repeated | What it measures | |
|---|---|---|
| Technical ICC | the same DNA, assayed twice | the assay, and the model’s sensitivity to array noise |
| Biological ICC | the same person, sampled again days later | whether the number is a property of the person or of the morning |
Nearly every clock is technically excellent; biological ICCs are substantially lower. GrimAge2 and DunedinPACE are among the most biologically fragile, and they are the two most often used to claim an intervention worked. res.interpretation() carries both columns, with None meaning “not established” rather than “fine”.
The rule: measuring change within a person, prefer a clock with a published biological ICC, or a principal-component version. → The science, §17.7.
Measure it on your own cohort
A published ICC is somebody else’s laboratory. Measure yours:
se = fa.technical_se(res, data)
se.diagnostics[["median_se", "se_over_sd", "implied_cohort_icc"]]se_over_sd- measurement error as a fraction of the spread between your own samples. Above 1, the clock cannot distinguish two of your samples however different their point estimates look.
implied_cohort_icc- the reliability this cohort implies. Often below the published figure, because a narrow age range leaves less between-sample variance for the same assay noise.
For scale: on this package’s test corpus Horvath 2013 comes out at ±1.58 years, DunedinPoAm38 is least repeatable at 0.72, and the 319,607-probe BLUP clock reaches 1.00. With technical duplicates, fa.icc_from_replicates(data, "subject") replaces the published per-probe table with your own plates. → The science, §19.2 for the propagation itself.
Then ask how many samples you need
fa.power("horvath2013", effect=0.5, result=pilot)At ICC 0.9 a tenth of the sample size exists only to average out the instrument, and the function says so. It refuses to default the standard deviation, because n scales with its square.
Specimen type

Saliva clock ages ran 3.83–16.46 years above buffy coat in the same 91 people, while still correlating with them at Spearman 0.45–0.69. A correlation check passes and the output is a decade out, because correlation is not agreement. The left panel is the whole argument: every point sits above the line of identity, which is a bias, and the scatter around it stays tight, which is what a correlation coefficient reports. The paired lines on the right show the same donors shifting in the same direction, so no amount of averaging removes it.
The rule: set obs["tissue"] on every dataset. FALCONAge then compares it against what each clock was fitted on. Pan-tissue clocks say nothing, most warn with the measured discordance, and twelve refuse outright. Cell-free DNA is refused by every clock. No tissue column gets one warning saying the check did not run.
→ The science, §19.4 for the twelve refusals and the measurements behind each. Reliability, technical against biological, is [39].
Availability
| Availability | n | What you do |
|---|---|---|
bundled |
69 | Nothing. Coefficients ship inside the package, or the clock is a formula with none. |
untraced |
52 | Catalogued, but no primary source has been established, so no coefficients ship. |
licensed |
57 | Obtain a coefficient file and register it. The architecture is implemented and tested. |
list_clocks(tier = "C") # the ones needing author permission
list_clocks(untraced = TRUE)The distinction between B and C matters. C is a licensing decision by the authors. B is an honesty one by us: copying a coefficient set out of another package without knowing where that package got it is how the known paper-versus-implementation discrepancies spread in the first place, and a traced extractor is a welcome contribution.
The clock catalogue
reg = fa.registry.load()
reg.filter(availability="bundled")
reg.search("mortality")
reg.get("horvath2013")
reg.get("dnamphenoage").cite("bibtex")list_clocks(tier = "bundled")
list_clocks(search = "mortality")
clock_info("horvath2013")
cite_clock("dnamphenoage", "bibtex")Forty clocks are licensed scaffolds: the model, the feature list, the transform chain and the expected shapes all ship, but the coefficients are research-use-only or have no public source FALCONAge may redistribute. Asking about one says why, where to obtain a file, and which open clocks answer the same question.
fa.registry.register_local_weights("grimage2", "~/licensed/grimage2_coefs.csv")
fa.score(d, clocks=["grimage2"])register_local_weights("grimage2", "~/licensed/grimage2_coefs.csv")
score(d, clocks = "grimage2")