FALCONAge for R

Biological age and aging-clock scoring from DNA methylation and clinical chemistry, against a catalogue of 178 published clocks.

This is the R half. It is a native R package: S3 classes, data.frame in and out, roxygen2 documentation, ggplot2 figures, but it does not reimplement anything. Every number comes from one Python core that this package calls through reticulate, so an R result and a Python result are the same bits, and the test suite asserts that at tolerance exactly zero rather than to six decimals.

That design decision is the reason this page exists in the form it does: if you are reading the R reference and something is unclear, the Python reference documents the same computation, and the table below tells you which entry to look at.

Install

Not on CRAN yet. The package is the r/ subdirectory of the repository, hence subdir:


remotes::install_github("bhagesh-h/FALCONAge", subdir = "r")
# or, resolving dependencies better and taking the subdirectory inline:
pak::pak("bhagesh-h/FALCONAge/r")

Then, once per machine:


library(FALCONAge)
falconage_install()   # builds the managed Python environment; a few minutes
falconage_config()    # what resolved: versions, devices, registry size

falconage_install() fetches the Python core from the same GitHub tag this package was built from, so the two halves cannot drift apart. falconage_config() is the first thing to run when anything looks wrong.

A whole analysis


d   <- prepare(read_betas("betas.csv"))
res <- score(d, clocks = "compatible")

summary(res)
as.data.frame(res)                        # samples x clocks
as.data.frame(res, form = "long")         # one row per sample per clock, with its scale

coverage(res)                             # what was scored, what was refused, and why
manifest(res)                             # versions, device, coefficient checksums

acceleration(res, method = "both")
plot_ba_vs_ca(res, "horvath2013")
report(res, "report.html", group = "condition")

clocks = "compatible" scores what the data can support and reports the rest as skipped with a reason. Naming clocks explicitly is different: then every one must work, because an explicit request should never be dropped quietly.

Reading the R reference alongside the Python one

The two APIs are one-to-one in intent and deliberately not in spelling. R gets as.data.frame(res, form = "long") where Python gets FalconResult.long(), because forcing identical names would make one language read like a translation of the other, which is exactly what the reticulate bridge exists to avoid.

So the mapping is worth having explicitly. Every row is the same computation reached two ways.

R Python
Reading data read_betas() fa.read_betas()
read_series_matrix() fa.read_series_matrix()
read_clinical() fa.read_clinical()
read_rrbs_dir() fa.read_rrbs_dir()
falcon_data() fa.FalconData()
obs() data.obs
write_h5ad() data.write_h5ad()
— fa.read()
— fa.read_bedmethyl()
— fa.read_bedmethyl_dir()
— fa.read_panel()
— fa.read_computage_bench()
— fa.list_computage_bench()
— fa.preprocess.read_olink()
— fa.preprocess.read_somascan()
— fa.preprocess.read_counts()
— fa.read_nightingale()
Raw arrays — fa.idat_to_betas()
— fa.read_idat_dir()
— fa.preprocess.poobah()
— fa.preprocess.noob()
— fa.preprocess.dye_bias()
— fa.preprocess.bmiq()
— fa.preprocess.RawSignal()
— fa.preprocess.load_manifest()
— fa.preprocess.manifest_record()
— fa.preprocess.load_mask()
— fa.preprocess.mask_report()
— fa.preprocess.apply_mask()
Preprocessing prepare() fa.prepare()
qc() fa.qc()
probe_loss() fa.probe_loss()
fit_batch_reference() fa.fit_batch_reference()
apply_batch_reference() fa.apply_batch_reference()
horvath_normalise() —
— fa.prepare_clinical()
— fa.preprocess.aggregate_replicate_probes()
— fa.preprocess.impute()
— fa.preprocess.BatchReference()
— fa.preprocess.prepare_proteomic()
— fa.preprocess.prepare_transcriptomic()
— fa.preprocess.rle_normalise()
— fa.preprocess.yugene()
— fa.preprocess.median_centre()
— fa.preprocess.horvath_normalise()
Uncertainty technical_se() fa.technical_se()
interval() fa.interval()
conformal_interval() fa.conformal_interval()
icc_from_replicates() fa.icc_from_replicates()
— fa.uncertainty.load_probe_icc()
— fa.uncertainty.probe_icc_source()
— fa.uncertainty.load_conformal()
Study design power_n() fa.power()
consensus() fa.consensus()
— fa.detectable_effect()
— fa.registry.evidence()
Scoring score() fa.score()
combine() fa.combine()
as.data.frame.falcon_result() fa.FalconResult()
interpretation() res.interpretation()
coverage() res.coverage
manifest() res.manifest
write_results() res.write()
The clock catalogue list_clocks() fa.registry.ClockRegistry()
clock_info() fa.registry.Clock()
register_local_weights() fa.registry.register_local_weights()
import_pc_clocks() fa.registry.load().import_pc_clocks()
import_dunedinpace() fa.registry.load().import_dunedinpace()
cite_clock() reg.get(id).cite()
compatible_clocks() reg.compatible_with(data)
— fa.registry.load()
Analysis acceleration() fa.acceleration()
cell_composition() fa.cell_composition()
associate() fa.associate()
cox_hazard() fa.cox_hazard()
agreement() fa.agreement()
icc() fa.icc()
run_benchmark() fa.run_benchmark()
— fa.leave_one_marker_out()
— fa.fit_clock()
Clinical references fit_kdm() fa.models.fit_kdm()
fit_hd() fa.models.fit_hd()
— fa.models.clinical.phenoage()
— fa.blood_count_ratios()
Model architectures — fa.models.AggregationClock()
— fa.models.NeuralClock()
— fa.models.read_neural_weights()
— fa.models.PCLinearClock()
— fa.models.read_rotation()
— fa.models.ScAgeReference()
— fa.models.fit_scage_reference()
— fa.models.scage()
Figures and reports plot_ba_vs_ca() fa.plot.ba_vs_ca()
plot_bland_altman() fa.plot.bland_altman()
plot_calibration() fa.plot.calibration()
plot_acceleration() fa.plot.acceleration_density()
plot_acceleration_by_group() fa.plot.acceleration_by_group()
plot_acceleration_heatmap() fa.plot.acceleration_heatmap()
plot_forest() fa.plot.forest()
plot_kaplan_meier() fa.plot.kaplan_meier()
plot_volcano() fa.plot.volcano()
plot_agreement() fa.plot.clock_corr()
plot_clock_radar() fa.plot.clock_radar()
plot_clock_chord() fa.plot.clock_chord()
plot_clock_pca() fa.plot.clock_pca()
plot_clock_atlas() fa.plot.clock_atlas()
plot_coverage() fa.plot.coverage_bar()
plot_by_platform() fa.plot.platform_comparison()
plot_benchmark() fa.plot.benchmark_bars()
plot_benchmark_error_bias() fa.plot.benchmark_error_bias()
plot_benchmark_heatmap() fa.plot.benchmark_heatmap()
falcon_palette() fa.plot.palette()
report() fa.report.write_report()
run_report() fa.report.run_report()
quarto_report() fa.report.write_quarto_report()
falcon_scheme() R only: loads the shared palette into R
falcon_theme() R only: a ggplot2 theme; Python styles via matplotlib
— fa.plot.missingness()
— fa.plot.beta_density()
— fa.plot.study_comparison()
— fa.plot.reliability_forest()
— fa.plot.score_interval()
— fa.plot.platform_bias()
— fa.plot.consensus_plot()
— fa.plot.save_all()
Downloading public data download() fa.download()
cache_info() fa.download.cache_info()
clear_cache() fa.download.clear_cache()
Configuration and setup falconage_config() fa.config()
falconage_install() R only: builds the Python environment R calls into
falconage_available() R only: is that environment resolvable yet
— fa.configure()
— fa.core.resolve()
— fa.core.describe()
— fa.FalconConfig()
— fa.RunManifest()

What the R side does not have

download() and the preprocessing entry points are thinner here than in Python, and a few things are Python-only by design rather than by omission, an R user reaches them through the same core, and wrapping every one in an S3 method would be surface area with no reader.

Where a group above shows a Python entry and no R counterpart: that is the reason.

Where the numbers come from

A clock is an architecture plus a coefficient set, and the two have very different licences.

Every architecture here is written from its published description, no clock implementation is imported from another package. Coefficients are fitted data and cannot be written, only obtained, so the 178 clocks fall into three groups:

Availability n What you do
bundled 69 Nothing. Coefficients ship inside the package, or the clock is a formula with none to ship.
untraced 52 Catalogued, but no primary source has been traced, so no coefficients ship.
licensed 57 Obtain a coefficient file and register it with register_local_weights(). The architecture is implemented and tested.

list_clocks(tier = "bundled")    # what runs offline, right now
list_clocks(tier = "licensed")   # what needs a licence, and where to get it
clock_info("grimage2")        # why, and which open clock answers the same question

Further reading

Citation

citation("FALCONAge"), or CITATION.cff.

The clock FALCONAge computed for you is somebody else’s work, and citing FALCONAge does not cite it. cite_clock("grimage2", "bibtex") returns the reference for any clock in the registry.