Contents

1 Overview of Implemented Clocks

The OmniAgeR package provides two primary, high-level interfaces: epiMarker(): For calculating a comprehensive suite of aging-related clocks (chronological, biological, mitotic, etc.). For advanced users or specific applications, the package also provides direct access to individual clock functions (e.g., horvath2013Clock()) and specialized “bundle” calculators (e.g., pcClocks(), systemsAge()) that manage complex dependencies. These tools are broadly categorized into three main groups:

  1. Epigenetic (DNAm) Clocks
  2. Transcriptomic (RNA) Clocks
  3. Other Aging-Related Surrogate Biomarkers

The suite of epigenetic aging clocks is particularly extensive, organized into several functional categories, including predictors for chronological age, biological age, cellular division (mitotic clocks), and gestational age.

2 Epigenetic (DNAm) Aging Clocks

2.1 Chronological Age Clocks

These DNAm clocks are designed for the prediction of chronological age.

  • Horvath2013: (Horvath 2013) The original pan-tissue clock from 353 CpGs.
  • Hannum: (Hannum et al. 2013) Blood-specific clock from 71 CpGs.
  • Lin: (Lin et al. 2016) Clock based on 99 CpGs.
  • VidalBralo: (Vidal-Bralo et al. 2016) Clock based on 8 CpGs.
  • ZhangClock: (Zhang et al. 2019) Improved-precision clock from 514 CpGs.
  • Horvath2018: (Horvath et al. 2018) Skin & blood clock.
  • Bernabeu_cAge: (Bernabeu et al. 2022) Clock based on 3225 CpGs.
  • CorticalClock: (Shireby et al. 2020) Brain cortical clock.
  • PedBE: (McEwen et al. 2019) Pediatric buccal epithelial clock for children.
  • CentenarianClock: (Eric Dec et al. 2023) Centenarian epigenetic clocks.
  • Retro_age: (Ndhlovu et al. 2024) Retroelement-based clock developed on the EPIC v1/v2 arrays.
  • ABEC: (Lee et al. 2020) Adult blood-based EPIC clock.
  • eABEC: (Lee et al. 2020) Extended adult blood-based EPIC clock.
  • cABEC: (Lee et al. 2020) Common adult blood-based EPIC clock.
  • PipekElasticNet: (Pipek et al. 2023) Pipek’s multi-tissue elastic Net epigenetic clock (239 CpGs).
  • PipekFilteredh: (Pipek et al. 2023) Pipek’s filtered horvath epigenetic clock (272 CpGs).
  • PipekRetrainedh: (Pipek et al. 2023) Pipek’s retrained horvath epigenetic clock (308 CpGs).
  • WuClock: (Wu et al. 2019) Wu’s Epigenetic Clock for Pediatric age estimation.
  • Weidner: (Weidner et al. 2014) Calculate weidner epigenetic age (3 CpGs).
  • IntrinClock: (Tomusiak et al. 2024) Calculates the intrinsic cellular age.
  • Garagnani: (Garagnani et al. 2012) The Garagnani ELOVL2-based epigenetic age score(1 CpG).
  • PCHorvath2013, PCHorvath2018, PCHannum: (Higgins-Chen et al. 2022) Computationally-bolstered versions of the original clocks. These PC clocks are retrained using principal components (PCs) derived from a large CpG set to minimize technical noise and improve reliability

2.2 Biological Age Clocks

These advanced clocks are designed to capture biological aging rather than chronological time, often showing stronger associations with health outcomes and mortality.

  • Zhang10: (Zhang et al. 2017) 10-CpG clock associated with mortality.
  • PhenoAge: (Levine et al. 2018) Predicts phenotypic age from 513 CpGs.
  • DunedinPACE: (Belsky et al. 2022) Quantifies the pace of biological aging.
  • GrimAge1: The original GrimAge clock (Lu et al. 2019).
  • GrimAge2: (Lu et al. 2022) Updated composite biomarker of mortality risk.
  • PCPhenoAge, PCGrimAge1: (Higgins-Chen et al. 2022) Computationally-bolstered PC versions of the PhenoAge and GrimAge1 clocks, retrained on principal components for enhanced reliability.
  • DNAmFitAge: (McGreevy et al. 2023) Biological age indicator incorporating DNAmGrimAge and 3 DNAm-based physical fitness markers.
  • IC_Clock: (Fuentealba et al. 2025) The intrinsic capacity (IC) clock based on 91 CpGs.
  • SystemsAge: (Sehgal et al. 2025) The systems age and 11 system-specific scores.

2.3 Cellular Aging Clocks

This category groups biomarkers that measure two key forms of cellular aging: cell proliferation (mitotic clocks) and telomere attrition (DNAmTL).

2.3.1 Mitotic Clocks (Cellular Division)

A specialized set of clocks that measure cell division history or stem cell divisions.

  • epiTOC1: (Yang et al. 2016) Average beta value of 385 promoter CpGs.
  • epiTOC2: (Teschendorff et al. 2020) Estimates stem cell divisions using a dynamic model.
  • epiTOC3: Estimates stem cell divisions based on unmethylated population doubling associated CpGs.
  • stemTOCvitro: (Zhu et al. 2024) The 0.95 upper quantile of 629 stemTOCvitro CpGs (promoter CpGs unmethylated in fetal tissue that hypermethylate with population-doublings).
  • stemTOC: (Zhu et al. 2024) The 0.95 upper quantile of 371 stemTOC CpGs, filtered for in-vivo hypermethylation with age.
  • RepliTali: (Endicott et al. 2022) Based on 87 population doubling associated hypomethylated CpGs.
  • HypoClock: (Teschendorff et al. 2020) Based on hypomethylation at 678 solo-WCGW sites.
  • EpiCMIT_hyper: (Duran-Ferrer et al. 2020) Average beta value of 184 age-associated hypermethylated CpGs.
  • EpiCMIT_hypo: (Duran-Ferrer et al. 2020) Average beta value of 1164 age-associated hypomethylated CpGs.

2.3.2 DNAm Telomere Length (TL) Clocks

  • DNAmTL: (Lu AT et al. 2019) Calculates the Leukocyte telomere length.
  • PCDNAmTL: (Lu AT et al. 2019; Higgins-Chen et al. 2022) A computationally-bolstered “PC” version of the original DNAmTL clock. This clock is retrained using principal components (PCs) derived from a large CpG set to minimize technical noise and improve reliability.

2.4 Causal Clocks

A new generation of clocks developed to reflect potential causal drivers of aging.

  • CausalAge, DamAge, AdaptAge: (Ying et al. 2024) Three clocks derived from a causality-enriched model. They dissect aging into distinct components: ‘Causal’ (CausalAge), ‘Damage’ (DamAge), and Adaptation’ (AdaptAge).

2.5 Stochastic Clocks

  • StocH, StocP, StocZ: (Tong et al. 2024) Stochastic analogues of the Horvath, PhenoAge, and Zhang clocks, trained on artificial cohorts to quantify the stochastic component of epigenetic aging.

2.6 Cell-Type Specific Clocks

These clocks are designed to measure aging in specific cell types or tissues, accounting for cellular heterogeneity.

  • Neu-In, Glia-In: (Tong et al. 2024) Intrinsic clocks for neurons, and glia.
  • Neu-Sin, Glia-Sin, Hep: (Tong et al. 2024) Semi-intrinsic clocks for neurons, glia, and hepatocytes

2.7 Gestational Age Clocks

This collection includes DNAm clocks designed to predict gestational age at birth.

  • BohlinGA: (Bohlin et al. 2016) The Bohlin Gestational Age.
  • EPICGA: (Haftorn et al. 2021) The EPIC Gestational Age.
  • LeeGA: (Lee et al. 2019) A list (LeeControl, LeeRobust, LeeRefinedRobust).
  • KnightGA: (Knight et al. 2016) The Knight Gestational Age.
  • MayneGA: (Mayne et al. 2017) The Mayne Placental Gestational Age.

2.8 Cross-Species Support

  • EnsembleAge: (Haghani et al. 2025) The multi-clock framework implementation. This includes mouse-specific estimators (EnsembleAgeMouse_Dynamic and EnsembleAgeMouse_Static) and a cross-species (EnsembleAgeMouse_HumanMouse) predictor applicable to both humans and mice

  • UniversalPanMammalianClocks: (Lu et al. 2023) The three pan-tissue clocks (Clock 1, 2, 3) applicable across all mammalian tissues.

  • PanMammalianBlood: (Lu et al. 2023) The two blood-specific pan-mammalian clocks (Clock 2, 3).

  • PanMammalianSkin: (Lu et al. 2023) The two skin-specific pan-mammalian clocks (Clock 2, 3).

3 Transcriptomic Clocks

In addition to DNAm-based models, the package implements cutting-edge transcriptomic (RNA-seq) clocks:

4 Surrogate Biomarkers & Scores

Besides, OmniAgeR includes functions to compute several other important surrogate markers associated with aging, lifestyle, and health status from DNAm data.

5 Disease Risk Prediction

OmniAgeR provides a suite of tools for disease risk assessment, including a DNA methylation-based smoking index derived from 1,501 CpG sites, as well as specialized scores for cancer risks. These tools leverage robust epigenetic signatures to quantify environmental exposures and clinical outcomes.

6 DNA Methylation Cell-Type Fraction

The OmniAgeR package also provides a DNA Methylation Cell-Type Fraction (CTF) clock (implemented as dnamCTFClock), which was trained on a large-scale NSPT dataset comprising more than 3,500 blood samples. The clock incorporates 12 immune cell types, estimated using the EpiDISH algorithm, to model aging based on cell-type composition.

7 Available Clocks and Functions

7.1 Epigenetic (DNAm) Aging Clocks

Clock Name Category Calculator / Function
Horvath2013 Chronological epiMarker(),horvath2013Clock()
Hannum Chronological epiMarker(),hannumClock()
Lin Chronological epiMarker(),linClock()
VidalBralo Chronological epiMarker(),vidalBraloClock()
ZhangClock Chronological epiMarker(),zhangClock()
Horvath2018 Chronological epiMarker(),horvath2018Clock()
Bernabeu_cAge Chronological epiMarker(),bernabeuCAge()
CorticalClock Chronological epiMarker(),corticalClock()
PedBE Chronological epiMarker(),pedBEClock()
CentenarianClock Chronological epiMarker(),centenarianClock()
Retro_age Chronological epiMarker(),retroAge()
ABEC Chronological epiMarker(),leeABEC()
eABEC Chronological epiMarker(),leeExtendedABEC()
cABEC Chronological epiMarker(),leeCommonABEC()
PipekElasticNet Chronological epiMarker(),pipekElasticNet()
PipekFilteredh Chronological epiMarker(),pipekFilteredh()
PipekRetrainedh Chronological epiMarker(),pipekRetrainedh()
WuClock Chronological epiMarker(),wuClock()
Weidner Chronological epiMarker(),weidnerClock()
IntrinClock Chronological epiMarker(),intrinClock()
Garagnani Chronological epiMarker(),garagnaniClock()
Zhang10 Biological epiMarker(),zhang10()
PhenoAge Biological epiMarker(),phenoAge()
DunedinPACE Biological epiMarker(),dunedinPACE()
GrimAge1 Biological epiMarker(),grimAge1()
GrimAge2 Biological epiMarker(),grimAge2()
DNAmFitAge Biological epiMarker(),dnamFitAge()
IC_Clock Biological epiMarker(),icClock()
SystemsAge Biological epiMarker(),systemsAge()
epiTOC1 Mitotic epiMarker(),epiTOC1()
epiTOC2 Mitotic epiMarker(),epiTOC2()
epiTOC3 Mitotic epiMarker(),epiTOC3()
stemTOC Mitotic epiMarker(),stemTOC()
stemTOCvitro Mitotic epiMarker(),stemTOCvitro()
RepliTali Mitotic epiMarker(),repliTali()
HypoClock Mitotic epiMarker(),hypoClock()
EpiCMIT_hyper, EpiCMIT_hypo Mitotic epiMarker(),epiCMIT()
DNAmTL Telomere length epiMarker(),dnamTL()
PCHorvath2013,PCHorvath2018, PCHannum, PCPhenoAge, PCGrimAge1, PCDNAmTL Chronological/Biological/Telomere length epiMarker(),pcClocks()
CausalAge,DamAge,AdaptAge Causal epiMarker(),causalClock()
StocH,StocP,StocZ Causal epiMarker(),stochClocks()
Neu-In,Glia-In,Neu-Sin,Glia-Sin,Hep Cell-Type Specific epiMarker(),ctsClocks()
BohlinGA Gestational epiMarker(),bohlinGa()
EPICGA Gestational epiMarker(),epicGa()
LeeGA Gestational epiMarker(),LeeGa()
KnightGA Gestational epiMarker(),knightGa()
MayneGA Gestational epiMarker(),mayneGa()
EnsembleAge_HumanMouse Mouse & Human epiMarker(),ensembleAge()
EnsembleAge_Static Mouse & Human epiMarker(),ensembleAge()
EnsembleAge_Dynamic Mouse & Human epiMarker(),ensembleAge()
UniversalPanMammalianClocks PanMammalian epiMarker(),universalPanMammalianClocks()
PanMammalianBlood PanMammalian epiMarker(),panMammalianBlood()
PanMammalianSkin PanMammalian epiMarker(),panMammalianSkin()
DNAm_CTF_Clock Cell-Type Fraction Clock epiMarker(),dnamCTFClock()

7.2 Transcriptomic Clocks

Clock Name Category Calculator / Function
scImmuAging Immune-cell-type-specific scImmuAging()
Brain_CT_Clock Brain-cell-type-specific brainCtClock()
PASTA Multi-tissue pastaScores()

7.3 Surrogate Biomarkers & Scores

Clock Name Target Calculator / Function
CRP CRP/intCRP score epiMarker(), compCRP()
CHIP CHIP score epiMarker(), compCHIP()
IL6 IL6 score epiMarker(), compIL6()
EpiScores 109 validated epigenetic scores epiMarker(), compEpiScores()
CompSmokeIndex Smoking Index epiMarker(), compSmokeIndex()
HepatoXuRisk Hepatocellular Carcinoma Risk epiMarker(), hepatoXuRisk()
SmokeIndex Smoking Index epiMarker(), compSmokeIndex()
McCartney_Trait Epigenetic surrogate scores for 10 different complex traits epiMarker(), mcCartneyTrait()

8 Tutorial Example 1: Apply mitotic clocks on Lung pre-cancerous lesions

8.1 Loading the lung pre-cancerous lesions dataset

This is an Illumina 450k dataset encompassing 21 normal lung tissue samples and 35 lung carcinoma in situ (LCIS) samples, of which 22 progressed to an invasive lung cancer (LC). Here we explore the correlation between mitotic age and cancer state with linear model, treating N, LCIS, and LC as ordinal variable (1,2,3) and adjusting for age.

library(OmniAgeR)
library(ggplot2)
library(patchwork)
library(ggpubr)
lungInv <- loadOmniAgeRdata(
    "omniager_lung_inv",
    verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
lungInvM <- lungInv$bmiq_m
phenoDf <- lungInv$PhenoTypes

my_comparisons <- list(c("N\nN=21", "LCIS\nN=13"), c("LCIS\nN=13", "LCIS->LC\nN=22"))
table(phenoDf$Group)
## 
##     LCIS\nN=13 LCIS->LC\nN=22        N\nN=21 
##             13             22             21
## Check available epigenetic clocks
listEpiMarker()
## $mitotic
## [1] "epiTOC1"       "epiTOC2"       "epiTOC3"       "stemTOCvitro" 
## [5] "stemTOC"       "RepliTali"     "HypoClock"     "EpiCMIT_Hyper"
## [9] "EpiCMIT_Hypo" 
## 
## $dnamtl
## [1] "DNAmTL"   "PCDNAmTL"
## 
## $chronological
##  [1] "Horvath2013"      "Hannum"           "Lin"              "VidalBralo"      
##  [5] "ZhangClock"       "Horvath2018"      "Bernabeu_cAge"    "CorticalClock"   
##  [9] "PedBE"            "CentenarianClock" "Retro_age"        "ABEC"            
## [13] "eABEC"            "cABEC"            "PipekElasticNet"  "PipekFilteredh"  
## [17] "PipekRetrainedh"  "WuClock"          "Weidner"          "IntrinClock"     
## [21] "Garagnani"        "PCHorvath2013"    "PCHorvath2018"    "PCHannum"        
## 
## $biological
##  [1] "Zhang10"     "PhenoAge"    "DunedinPACE" "GrimAge1"    "GrimAge2"   
##  [6] "PCPhenoAge"  "PCGrimAge1"  "DNAmFitAge"  "IC_Clock"    "SystemsAge" 
## 
## $causal
## [1] "CausalAge" "DamAge"    "AdaptAge" 
## 
## $cellTypeSpecific
## [1] "Neu-In"   "Neu-Sin"  "Glia-In"  "Glia-Sin" "Hep"     
## 
## $stochastic
## [1] "StocH" "StocZ" "StocP"
## 
## $gestationalAge
## [1] "BohlinGA" "EPICGA"   "KnightGA" "LeeGA"    "MayneGA" 
## 
## $surrogateBiomarkers
## [1] "CRP"       "CHIP"      "IL6"       "EpiScores"
## 
## $traitPred
## [1] "McCartneyTrait"
## 
## $diseaseRisk
## [1] "SmokeIndex"   "HepatoXuRisk"
## 
## $crossSpecies
## [1] "EnsembleAge_HumanMouse"      "EnsembleAge_Static"         
## [3] "EnsembleAge_Dynamic"         "UniversalPanMammalianClocks"
## [5] "PanMammalianBlood"           "PanMammalianSkin"

8.2 Estimation of epigenetic mitotic age

Mitotic age estimation of all the clocks is implemented in a general function EpiMitClocks. The required input is a DNAm beta value matrix with rows labeling Illumina 450k/EPIC CpGs and columns labeling samples.

epiMarkerRes <- epiMarker(
    betaM = lungInvM,
    clockNames = "mitotic",
    chronAge = phenoDf$Age,
    minCoverage = 0,
    verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
rm(lungInvM)
phenoDf$epiTOC1 <- epiMarkerRes$epiTOC1
phenoDf$epiTOC2 <- epiMarkerRes$epiTOC2$irS
phenoDf$epiTOC3 <- epiMarkerRes$epiTOC3$irS
phenoDf$stemTOCvitro <- epiMarkerRes$stemTOCvitro
phenoDf$stemTOC <- epiMarkerRes$stemTOC
phenoDf$HypoClock <- epiMarkerRes$HypoClock
phenoDf$RepliTali <- epiMarkerRes$RepliTali
phenoDf$EpiCMIT_Hyper <- epiMarkerRes$EpiCMIT_Hyper
phenoDf$EpiCMIT_Hypo <- epiMarkerRes$EpiCMIT_Hypo
g <- list()
for (i in 1:9) {
    tempDf <- data.frame("Group" = phenoDf$Group, "Age" = phenoDf$Age, "num" = phenoDf$num, "score" = phenoDf[, i + 3])
    y <- summary(lm(tempDf$score ~ tempDf$num + tempDf$Age))$coefficients[2, 4]
    g[[i]] <- ggplot(tempDf, aes(x = Group, y = score, fill = Group)) +
        guides(fill = "none") +
        geom_boxplot() +
        theme_bw() +
        scale_x_discrete(limits = c("N\nN=21", "LCIS\nN=13", "LCIS->LC\nN=22")) +
        stat_compare_means(method = "wilcox.test", comparisons = my_comparisons, size = 4) +
        xlab("") +
        ylab(colnames(phenoDf)[i + 3]) +
        annotate("text", x = 2, y = max(tempDf$score) * 0.9, label = paste0("P(Age-adjusted)=", signif(y, 2)), size = 4) +
        theme(
            axis.title = element_text(size = 11),
            axis.text = element_text(size = 11)
        )
}

ggpubr::ggarrange(plotlist = g, nrow = 3, ncol = 3)

9 Tutorial Example 2: Predict chronological age in blood tissue

The epigenetic age of 50 normal blood samples was estimated from their Illumina 450k methylation profiles using both the Horvath and Zhang epigenetic clocks.

library(OmniAgeR)
library(ggplot2)
library(patchwork)
library(ggpubr)
hannumExample <- loadOmniAgeRdata(
    "omniager_hannum_example",
    verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
hannumBmiqM <- hannumExample[[1]]
phenoTypesHannum <- hannumExample[[2]]
age <- phenoTypesHannum$Age
sex <- ifelse(phenoTypesHannum$Sex == "F", "Female", "Male")
epiMarkerOut <- epiMarker(hannumBmiqM,
    clockNames = c("Horvath2013", "ZhangClock"),
    minCoverage = 0, verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
gp <- list()

for (i in seq_along(epiMarkerOut)) {
    plot_df <- data.frame(ActualAge = phenoTypesHannum$Age, PredictedAge = epiMarkerOut[[i]])
    cor_test_result <- cor.test(plot_df$ActualAge, plot_df$PredictedAge)
    correlation <- cor_test_result$estimate
    p_value <- cor_test_result$p.value
    mae <- mean(abs(plot_df$PredictedAge - plot_df$ActualAge))
    p_value_formatted <- ifelse(p_value < 0.001,
        formatC(p_value, format = "e", digits = 2),
        round(p_value, 3)
    )
    annotation_text <- paste0(
        "R = ", round(correlation, 3), "\n",
        "P = ", p_value_formatted, "\n",
        "MAE = ", round(mae, 2)
    )
    gp[[i]] <- ggplot(data = plot_df, aes(x = ActualAge, y = PredictedAge)) +
        geom_point(color = "steelblue", size = 3, alpha = 0.8) +
        geom_smooth(method = "lm", color = "black", se = FALSE) +
        annotate("text",
            x = min(plot_df$ActualAge, na.rm = TRUE),
            y = max(plot_df$PredictedAge, na.rm = TRUE),
            label = annotation_text,
            hjust = 0,
            vjust = 1, size = 5
        ) +
        labs(
            title = NULL,
            x = "Chronological Age",
            y = paste0("Predicted Age(", c("Horvath2013", "ZhangClock")[i], ")")
        ) +
        theme_bw() +
        theme(
            plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
            plot.subtitle = element_text(hjust = 0.5, size = 12),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12)
        )
}

ggpubr::ggarrange(plotlist = gp, nrow = 1, ncol = 2)
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

10 Tutorial Example 3: Predict gestational age

A small demonstration dataset containing three samples, used to illustrate how to predict the gestational age of samples using DNAm Gestational Age Clocks.

library(OmniAgeR)
library(ggplot2)
library(patchwork)
library(ggpubr)
gaExample <- loadOmniAgeRdata(
    "omniager_ga_example",
    verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
gaM <- gaExample[[1]]
phenoTypesGa <- gaExample[[2]]
epiGA <- epiMarker(gaM,
    clockNames = c("KnightGA", "MayneGA"),
    minCoverage = 0, verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
gp <- list()

for (i in seq_along(epiGA)) {
    plot_df <- data.frame(Gestational_Age = phenoTypesGa$age, PredictedAge = epiGA[[i]])
    cor_test_result <- cor.test(plot_df$Gestational_Age, plot_df$PredictedAge)
    correlation <- cor_test_result$estimate
    p_value <- cor_test_result$p.value
    mae <- mean(abs(plot_df$PredictedAge - plot_df$Gestational_Age))
    p_value_formatted <- ifelse(p_value < 0.001,
        formatC(p_value, format = "e", digits = 2),
        round(p_value, 3)
    )
    annotation_text <- paste0(
        "R = ", round(correlation, 3), "\n",
        "P = ", p_value_formatted, "\n",
        "MAE = ", round(mae, 2)
    )
    gp[[i]] <- ggplot(data = plot_df, aes(x = Gestational_Age, y = PredictedAge)) +
        geom_point(color = "steelblue", size = 3, alpha = 0.8) +
        geom_smooth(method = "lm", color = "black", se = FALSE) +
        annotate("text",
            x = min(plot_df$Gestational_Age, na.rm = TRUE),
            y = max(plot_df$PredictedAge, na.rm = TRUE),
            label = annotation_text,
            hjust = 0,
            vjust = 1, size = 5
        ) +
        labs(
            title = NULL,
            x = "Gestational Age(weeks)",
            y = paste0("Predicted Age(", c("Knight GA", "Mayne GA")[i], ", weeks)")
        ) +
        theme_bw() +
        theme(
            plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
            plot.subtitle = element_text(hjust = 0.5, size = 12),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12)
        )
}

ggpubr::ggarrange(plotlist = gp, nrow = 1, ncol = 2)
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

11 Tutorial Example 4: Apply sc-ImmuAging to PBMC scRNA-seq dataset

In this example, we showcase the application of the cell-type-specific clock, scImmuAging, to a PBMC scRNA-seq dataset. The dataset includes 20 samples, profiled to contain only CD4+ and CD8+ T cells.

library(OmniAgeR)
library(Seurat)
library(glmnet)
library(ggplot2)
library(patchwork)
library(ggpubr)
#library(Seurat)
seuratObj <- loadOmniAgeRdata(
    "omniager_yazar_cd4t_cd8t_example",
    verbose = FALSE
)
scImmuAgingOut <- scImmuAging(seuratObj, c("CD4T", "CD8T"))
sc_gp <- list()

for (i in seq_along(scImmuAgingOut)) {
    plot_df <- data.frame(ActualAge = scImmuAgingOut[[i]]$donor$age, PredictedAge = scImmuAgingOut[[i]]$donor$predicted)
    cor_test_result <- cor.test(plot_df$ActualAge, plot_df$PredictedAge)
    correlation <- cor_test_result$estimate
    p_value <- cor_test_result$p.value
    mae <- mean(abs(plot_df$PredictedAge - plot_df$ActualAge))
    p_value_formatted <- ifelse(p_value < 0.001,
        formatC(p_value, format = "e", digits = 2),
        round(p_value, 3)
    )
    annotation_text <- paste0(
        "R = ", round(correlation, 3), "\n",
        "P = ", p_value_formatted, "\n",
        "MAE = ", round(mae, 2)
    )
    sc_gp[[i]] <- ggplot(data = plot_df, aes(x = ActualAge, y = PredictedAge)) +
        geom_point(color = "steelblue", size = 3, alpha = 0.8) +
        geom_smooth(method = "lm", color = "black", se = FALSE) +
        annotate("text",
            x = min(plot_df$ActualAge, na.rm = TRUE),
            y = max(plot_df$PredictedAge, na.rm = TRUE),
            label = annotation_text,
            hjust = 0,
            vjust = 1,
            size = 5
        ) +
        labs(
            title = c("CD4T", "CD8T")[i],
            x = "Chronological Age",
            y = "Predicted Age(sc-ImmuAging)"
        ) +
        theme_bw() +
        theme(
            plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
            plot.subtitle = element_text(hjust = 0.5, size = 12),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12)
        )
}

ggpubr::ggarrange(plotlist = sc_gp, nrow = 1, ncol = 2)

12 Tutorial Example 5: Apply Brain_CT_clock to Brain snRNA-seq dataset

In this example, we showcase the application of the brain cell-type-specific aging Clocks to a brain snRNA-seq dataset. The dataset includes 15 donors, profiled to contain only Oligodendrocytes.

library(OmniAgeR)
library(Seurat)
library(glmnet)
library(ggplot2)
library(patchwork)
library(ggpubr)
library(dplyr)

brainSeurat <- loadOmniAgeRdata(
    "omniager_brain_frohlich_control_example_15donors",
    verbose = FALSE
)

# Define cell types of interest
cellTypes <- c("Oligodendrocytes")

# Run all three models for the specified cell types
clockResults <- brainCtClock(
    seuratObj = brainSeurat,
    cellTypes = cellTypes
)

# Use lapply to iterate over each data frame in the list
averagePredictionsDonor <- lapply(clockResults, function(df) {
    df %>%
        group_by(donorId, age, sampleType, celltype) %>%
        summarise(mean_prediction = mean(prediction, na.rm = TRUE))
})
brainPlot <- list()

for (i in seq_along(averagePredictionsDonor)) {
    plot_df <- data.frame(
        ActualAge = averagePredictionsDonor[[i]]$age,
        PredictedAge = averagePredictionsDonor[[i]]$mean_prediction
    )
    cor_test_result <- cor.test(plot_df$ActualAge, plot_df$PredictedAge)
    correlation <- cor_test_result$estimate
    p_value <- cor_test_result$p.value
    mae <- mean(abs(plot_df$PredictedAge - plot_df$ActualAge))
    p_value_formatted <- ifelse(p_value < 0.001,
        formatC(p_value, format = "e", digits = 2),
        round(p_value, 3)
    )
    annotation_text <- paste0(
        "R = ", round(correlation, 3), "\n",
        "P = ", p_value_formatted, "\n",
        "MAE = ", round(mae, 2)
    )
    brainPlot[[i]] <- ggplot(data = plot_df, aes(x = ActualAge, y = PredictedAge)) +
        geom_point(color = "steelblue", size = 3, alpha = 0.8) +
        geom_smooth(method = "lm", color = "black", se = FALSE) +
        annotate("text",
            x = min(plot_df$ActualAge, na.rm = TRUE),
            y = max(plot_df$PredictedAge, na.rm = TRUE),
            label = annotation_text,
            hjust = 0,
            vjust = 1,
            size = 4
        ) +
        labs(
            title = "Oligodendrocytes",
            x = "Chronological Age",
            y = paste0("Predicted Age(", c("SC", "Pseudobulk", "Bootstrap")[i], ")")
        ) +
        theme_bw() +
        theme(
            plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
            plot.subtitle = element_text(hjust = 0.5, size = 12),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12)
        )
}

ggpubr::ggarrange(plotlist = brainPlot, nrow = 1, ncol = 3)

13 Tutorial Example 6: Apply Pasta to Brain (MTG) scRNA-seq dataset

In this example, we showcase the application of the Pasta clock to a single-nuclei RNA-seq dataset from the middle temporal gyrus (MTG) (Gabitto et al., 2024). The dataset includes 46 healthy donors and is filtered to contain extratelencephalic projecting glutamatergic cortical neurons.

# Load required libraries
library(OmniAgeR)
library(Seurat)
library(glmnet)
library(magrittr) # For %>% pipe
library(ggplot2)
library(patchwork)
library(ggpubr) # For simplified plot annotations

seu <- loadOmniAgeRdata(
    "omniager_seu_gabitto_2024_filtered",
    verbose = FALSE
)
# Extract and clean chronological age from metadata
seu$age <- seu$development_stage %>%
    gsub("-year.*", "", .) %>%
    gsub("-", " ", .) %>%
    gsub("80 year old and over stage", "85", .)

# Create pseudobulk samples, Pseudobulk construction may follow Salignon et al.
# or be performed using alternative approaches as appropriate
set.seed(42)
seuBulk <- makePseudobulksPasta(
    seu,
    poolBy = c("cell_type", "age"),
    chunkSize = 512,
    verbose = FALSE
)

# Extract the log-normalized expression matrix for prediction
lognormMatrix <- GetAssayData(seuBulk, assay = "RNA", layer = "data")
lognormMatrix <- as.matrix(lognormMatrix)

# Extract the corresponding metadata for the new pseudobulk samples
seuBulkMeta <- seuBulk[[c("chunkSize", "cell_type", "age")]]
seuBulkMeta$age <- as.numeric(seuBulkMeta$age)

# Apply the PASTA clock
# filter_genes = TRUE: Selects only the genes required by the PASTA model.
# rank_norm = TRUE: Applies the rank-normalization required by PASTA.
pastaRes <- pastaScores(lognormMatrix, filterGenes = TRUE, rankNorm = TRUE)
# Prepare the data frame for plotting
plot_df <- data.frame(
    ActualAge = as.numeric(seuBulk$age),
    Prediction = as.numeric(pastaRes$PASTA)
)

# Create the scatter plot
ggplot(data = plot_df, aes(x = ActualAge, y = Prediction)) +
    geom_point(color = "steelblue", size = 3, alpha = 0.8) +
    geom_smooth(method = "lm", color = "black", se = FALSE) +
    ggpubr::stat_cor(
        method = "pearson",
        label.x.npc = "left",
        label.y.npc = "top",
        hjust = 0,
        size = 5
    ) +
    labs(
        title = NULL,
        x = "Chronological Age",
        y = "PASTA Score"
    ) +
    theme_bw() +
    theme(
        plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        legend.position = "none"
    )

14 Tutorial Example 7: Calculate CPR and CHIP Scores

Here, we use DNAm data to calculate the CRP score and CHIP score.

library(OmniAgeR)
hannumBmiqM <- loadOmniAgeRdata(
    "omniager_hannum_example",
    verbose = FALSE
)[[1]]
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
crpRes <- compCRP(hannumBmiqM)
## [OmniAgeR] Retrieving resource: omniager_crp_cpg
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## [OmniAgeR] Successfully loaded 'omniager_crp_cpg'.
## [CRP] Found: 1764, Required: 1765 (Coverage: 99.9%)
## [intCRP] Found: 62, Required: 62 (Coverage: 100.0%)
print(lapply(crpRes, head, 5))
## $CRP
##  GSM990532  GSM990292  GSM989979  GSM989900  GSM990054 
## -0.4065040  0.3002201 -0.1465114 -0.1490234 -0.3857877 
## 
## $intCRP
##   GSM990532   GSM990292   GSM989979   GSM989900   GSM990054 
## -0.29625111  0.23775122 -0.06492197  0.03861159 -0.46205741
chipRes <- compCHIP(hannumBmiqM)
## [OmniAgeR] Retrieving resource: omniager_chip_cpg
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
## [OmniAgeR] Successfully loaded 'omniager_chip_cpg'.
## [AnyCHIP] Found: 9566, Required: 9615 (Coverage: 99.5%)
## [DNMT3A] Found: 5961, Required: 5990 (Coverage: 99.5%)
## [TET2] Found: 5611, Required: 5633 (Coverage: 99.6%)
## [ASXL1] Found: 6025, Required: 6078 (Coverage: 99.1%)
print(lapply(chipRes, head, 5))
## $AnyCHIP
##    GSM990532    GSM990292    GSM989979    GSM989900    GSM990054 
## -0.503068906 -0.359947191  0.006808985 -0.648675130  0.353746991 
## 
## $DNMT3A
##  GSM990532  GSM990292  GSM989979  GSM989900  GSM990054 
## -1.5686495 -0.8673509 -0.1098401 -0.3111652  1.1811087 
## 
## $TET2
##  GSM990532  GSM990292  GSM989979  GSM989900  GSM990054 
## -0.1877774 -0.5414346  0.2673744 -0.5044092  0.1782826 
## 
## $ASXL1
##  GSM990532  GSM990292  GSM989979  GSM989900  GSM990054 
## -0.1334423 -0.5384459  0.0903360 -0.5016612  0.1762066

15 Tutorial Example 8: Get freatures from Various Aging Biomarkers

library(OmniAgeR)
# 1. Get probes for a specific chronological aging predictor
chronologicalProbes <- getMarkerWeights(clockNames = "Horvath2013")
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## loading from cache
print(head(chronologicalProbes$Horvath2013))
##         probe        coef
## 1 (Intercept) 0.695507258
## 2  cg00075967 0.129336610
## 3  cg00374717 0.005017857
## 4  cg00864867 1.599764050
## 5  cg00945507 0.056852418
## 6  cg01027739 0.102862854

16 Tutorial Example 9: Celltype fraction clock based on DNAm

We apply the DNAm Cell-type Fraction (CTF) Clock to 50 blood samples (EPIC array) from the TZH cohort to predict chronological age.

library(OmniAgeR)
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
## 
##     margin
library(ggplot2)
library(patchwork)
library(ggpubr)

tzhExample <- loadOmniAgeRdata(
    "omniager_tzh_example_ctf",
    verbose = FALSE
)
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## downloading 1 resources
## retrieving 1 resource
## loading from cache
phenoTypesTzh <- tzhExample[[1]]
tzhFracM <- tzhExample[[2]]
dnamCTFClockOut <- dnamCTFClock(ctfM = tzhFracM)
## [OmniAgeR] Retrieving resource: omniager_dnam_ctf_model
## see ?OmniAgeRData and browseVignettes('OmniAgeRData') for documentation
## downloading 1 resources
## retrieving 1 resource
## loading from cache
## [OmniAgeR] Successfully loaded 'omniager_dnam_ctf_model'.
plot_df <- data.frame(ActualAge = phenoTypesTzh$Age, PredictedAge = dnamCTFClockOut)
cor_test_result <- cor.test(plot_df$ActualAge, plot_df$PredictedAge)
correlation <- cor_test_result$estimate
p_value <- cor_test_result$p.value
mae <- mean(abs(plot_df$PredictedAge - plot_df$ActualAge))
p_value_formatted <- ifelse(p_value < 0.001,
    formatC(p_value, format = "e", digits = 2),
    round(p_value, 3)
)
annotation_text <- paste0(
    "R = ", round(correlation, 3), "\n",
    "P = ", p_value_formatted, "\n",
    "MAE = ", round(mae, 2)
)
ggplot(data = plot_df, aes(x = ActualAge, y = PredictedAge)) +
    geom_point(color = "steelblue", size = 3, alpha = 0.8) +
    geom_smooth(method = "lm", color = "black", se = FALSE) +
    annotate("text",
        x = min(plot_df$ActualAge, na.rm = TRUE),
        y = max(plot_df$PredictedAge, na.rm = TRUE),
        label = annotation_text,
        hjust = 0,
        vjust = 1, size = 5
    ) +
    labs(
        title = NULL,
        x = "Chronological Age",
        y = paste0("Predicted Age(DNAm_CTF)")
    ) +
    theme_bw() +
    theme(
        plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
        plot.subtitle = element_text(hjust = 0.5, size = 12),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12)
    )
## `geom_smooth()` using formula = 'y ~ x'

# ggpubr::ggarrange(plotlist = gp, nrow = 1, ncol = 1)
## R version 4.6.0 RC (2026-04-17 r89917)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.4 LTS
## 
## Matrix products: default
## BLAS:   /home/biocbuild/bbs-3.23-bioc/R/lib/libRblas.so 
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_GB              LC_COLLATE=C              
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: America/New_York
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] randomForest_4.7-1.2 OmniAgeRData_0.99.3  ggpubr_0.6.3        
## [4] patchwork_1.3.2      ggplot2_4.0.3        OmniAgeR_0.99.4     
## [7] BiocStyle_2.40.0    
## 
## loaded via a namespace (and not attached):
##   [1] RcppAnnoy_0.0.23       splines_4.6.0          later_1.4.8           
##   [4] filelock_1.0.3         tibble_3.3.1           polyclip_1.10-7       
##   [7] preprocessCore_1.74.0  fastDummies_1.7.6      lifecycle_1.0.5       
##  [10] httr2_1.2.2            rstatix_0.7.3          globals_0.19.1        
##  [13] lattice_0.22-9         MASS_7.3-65            backports_1.5.1       
##  [16] magrittr_2.0.5         plotly_4.12.0          sass_0.4.10           
##  [19] rmarkdown_2.31         jquerylib_0.1.4        yaml_2.3.12           
##  [22] httpuv_1.6.17          otel_0.2.0             Seurat_5.5.0          
##  [25] sctransform_0.4.3      spam_2.11-3            sp_2.2-1              
##  [28] spatstat.sparse_3.1-0  reticulate_1.46.0      cowplot_1.2.0         
##  [31] pbapply_1.7-4          DBI_1.3.0              RColorBrewer_1.1-3    
##  [34] abind_1.4-8            Rtsne_0.17             purrr_1.2.2           
##  [37] BiocGenerics_0.58.1    rappdirs_0.3.4         IRanges_2.46.0        
##  [40] S4Vectors_0.50.1       ggrepel_0.9.8          irlba_2.3.7           
##  [43] listenv_0.10.1         spatstat.utils_3.2-3   goftest_1.2-3         
##  [46] RSpectra_0.16-2        spatstat.random_3.4-5  fitdistrplus_1.2-6    
##  [49] parallelly_1.47.0      codetools_0.2-20       tidyselect_1.2.1      
##  [52] shape_1.4.6.1          farver_2.1.2           matrixStats_1.5.0     
##  [55] stats4_4.6.0           BiocFileCache_3.2.0    spatstat.explore_3.8-0
##  [58] Seqinfo_1.2.0          jsonlite_2.0.0         progressr_0.19.0      
##  [61] Formula_1.2-5          ggridges_0.5.7         survival_3.8-6        
##  [64] iterators_1.0.14       foreach_1.5.2          tools_4.6.0           
##  [67] ica_1.0-3              Rcpp_1.1.1-1.1         glue_1.8.1            
##  [70] gridExtra_2.3          mgcv_1.9-4             qs2_0.2.1             
##  [73] xfun_0.57              dplyr_1.2.1            withr_3.0.2           
##  [76] BiocManager_1.30.27    fastmap_1.2.0          digest_0.6.39         
##  [79] R6_2.6.1               mime_0.13              scattermore_1.2       
##  [82] tensor_1.5.1           dichromat_2.0-0.1      spatstat.data_3.1-9   
##  [85] RSQLite_3.52.0         tidyr_1.3.2            generics_0.1.4        
##  [88] data.table_1.18.4      httr_1.4.8             htmlwidgets_1.6.4     
##  [91] uwot_0.2.4             pkgconfig_2.0.3        gtable_0.3.6          
##  [94] blob_1.3.0             lmtest_0.9-40          S7_0.2.2              
##  [97] XVector_0.52.0         htmltools_0.5.9        carData_3.0-6         
## [100] dotCall64_1.2          bookdown_0.46          SeuratObject_5.4.0    
## [103] scales_1.4.0           Biobase_2.72.0         png_0.1-9             
## [106] spatstat.univar_3.1-7  knitr_1.51             reshape2_1.4.5        
## [109] nlme_3.1-169           curl_7.1.0             cachem_1.1.0          
## [112] zoo_1.8-15             stringr_1.6.0          BiocVersion_3.23.1    
## [115] KernSmooth_2.23-26     parallel_4.6.0         miniUI_0.1.2          
## [118] AnnotationDbi_1.74.0   pillar_1.11.1          grid_4.6.0            
## [121] vctrs_0.7.3            RANN_2.6.2             promises_1.5.0        
## [124] stringfish_0.19.0      car_3.1-5              dbplyr_2.5.2          
## [127] xtable_1.8-8           cluster_2.1.8.2        evaluate_1.0.5        
## [130] magick_2.9.1           tinytex_0.59           cli_3.6.6             
## [133] compiler_4.6.0         crayon_1.5.3           rlang_1.2.0           
## [136] future.apply_1.20.2    ggsignif_0.6.4         labeling_0.4.3        
## [139] plyr_1.8.9             stringi_1.8.7          viridisLite_0.4.3     
## [142] deldir_2.0-4           Biostrings_2.80.0      lazyeval_0.2.3        
## [145] spatstat.geom_3.7-3    glmnet_5.0             Matrix_1.7-5          
## [148] ExperimentHub_3.2.0    RcppHNSW_0.6.0         bit64_4.8.0           
## [151] future_1.70.0          KEGGREST_1.52.0        shiny_1.13.0          
## [154] AnnotationHub_4.2.0    ROCR_1.0-12            igraph_2.3.1          
## [157] broom_1.0.13           memoise_2.0.1          RcppParallel_5.1.11-2 
## [160] bslib_0.11.0           bit_4.6.0