MethylSurroGetR

R Package for Calculating DNA Methylation-Based
Surrogate Biomarkers

Joshua A. Goode

University of Michigan

November 7, 2026

What Are DNAm Surrogates?

  • DNA methylation (DNAm) is an important epigenetic mechanism contributing to gene expression.
  • DNAm surrogates are composite scores derived from CpG sites that predict a wide variety of exposures and outcomes.
  • The most popular examples are epigenetic clocks, such as PhenoAge, GrimAge, and DunedinPACE.

Some Assembly Required

  • Published DNAm surrogate weight sets now exist for a growing number of exposures and traits.
  • Large population cohorts with array-based methylation data create a real opportunity to apply these surrogates at scale.

Weights are published, but there is no standard way to generate predicted values.

The Devil’s in the Details

Core computation is just matrix multiplication, which is rarely where things go wrong.

Real failures happen before computation:

  • Improperly aligning probe sets
  • Failing to handle missing data

Choices are often undocumented in papers, and they can quietly change results.

Why Should We Care?

Reproducibility

Same weights, same data, different scores. Results can’t be compared across studies.

Silent Errors

A flawed setup rarely throws an error. It returns a plausible but wrong number.

Access

The researchers with the most to gain shouldn’t need a bioinformatician to use surrogates safely.

Meet MethylSurroGetR

  • Applies existing published weight sets through a standardized three-step pipeline.
    • Generates predicted values in target data
    • Not a tool for developing new surrogates
  • Makes setup decisions explicit, consistent, and reportable, rather than buried in one-off scripts.

MethylSurroGetR’s Three-Step Pipeline

MethylSurroGetR Demo Setup

Install Package

install.packages("remotes")
remotes::install_github("jagoode27/MethylSurroGetR")

Load Package

MethylSurroGetR Demo Inputs

Load Sample Weights

data(wts_df)
wts_vec_lin <- with(wts_df, setNames(wt_lin, rownames(wts_df)))
wts_vec_lin <- wts_vec_lin[!is.na(wts_vec_lin)]
print(wts_vec_lin)
        cg02         cg03         cg06         cg07         cg08         cg11 
-0.009083377 -0.001155999  0.005978497 -0.007562015  0.001218960 -0.005869372 
        cg13         cg15         cg17         cg18    Intercept 
-0.007449367  0.005066157  0.007900907 -0.002510744  1.211000000 

MethylSurroGetR Demo Inputs

Load Sample Methylation Data

data(beta_matrix_miss)
print(beta_matrix_miss)
         samp1      samp2      samp3     samp4      samp5
cg01 0.1028646 0.32037324 0.48290240        NA 0.36948887
cg02 0.2875775         NA         NA 0.8830174         NA
cg04 0.4348927 0.18769112 0.89035022 0.1422943 0.98421920
cg05 0.9849570 0.78229430 0.91443819 0.5492847 0.15420230
cg07 0.8998250 0.24608773         NA 0.3279207 0.95450365
cg08 0.8895393 0.69280341 0.64050681 0.9942698 0.65570580
cg09        NA 0.09359499 0.60873498 0.9540912         NA
cg10 0.8864691         NA         NA 0.5854834 0.14190691
cg12 0.1750527         NA 0.14709469        NA 0.69000710
cg13 0.9630242 0.90229905 0.69070528 0.7954674 0.02461368
cg14 0.1306957         NA 0.93529980 0.6478935         NA
cg16 0.6531019 0.33282354 0.30122890 0.3198206 0.89139412
cg17 0.1428000 0.41454634 0.41372433 0.3688455 0.15244475
cg19 0.3435165 0.48861303 0.06072057 0.3077200         NA
cg20 0.6567581 0.95447383 0.94772694 0.2197676         NA

MethylSurroGetR Demo Inputs

CpG Weight Target
cg01 80%
cg02 ✔ 40%
cg03 ✔ 0%
cg04 100%
cg05 100%
cg06 ✔ 0%
cg07 ✔ 80%
cg08 ✔ 100%
cg09 60%
cg10 60%
cg11 ✔ 0%
cg12 60%
cg13 ✔ 100%
cg14 60%
cg15 ✔ 0%
cg16 100%
cg17 ✔ 100%
cg18 ✔ 0%
cg19 80%
cg20 80%

10 CpGs in the target data are not part of the weight set

5 weight-set CpGs were not found in the target data

2 matched CpGs have missing values in some target samples

MethylSurroGetR Demo Step 1 · Prepare Data

Create methyl_surro Object

my_surro <- surro_set(
  methyl = beta_matrix_miss,
  weights = wts_vec_lin,
  intercept = "Intercept"
)
Added 5 missing probes with NA values (50.0% of weight probes missing from methylation data).
Filtered methylation matrix from 15 to 10 probes to match weights.

MethylSurroGetR Demo Step 1 · Prepare Data

Create methyl_surro Object

print(my_surro)
$methyl
         samp1     samp2     samp3     samp4      samp5
cg02 0.2875775        NA        NA 0.8830174         NA
cg07 0.8998250 0.2460877        NA 0.3279207 0.95450365
cg08 0.8895393 0.6928034 0.6405068 0.9942698 0.65570580
cg13 0.9630242 0.9022990 0.6907053 0.7954674 0.02461368
cg17 0.1428000 0.4145463 0.4137243 0.3688455 0.15244475
cg03        NA        NA        NA        NA         NA
cg06        NA        NA        NA        NA         NA
cg11        NA        NA        NA        NA         NA
cg15        NA        NA        NA        NA         NA
cg18        NA        NA        NA        NA         NA

$weights
        cg02         cg03         cg06         cg07         cg08         cg11 
-0.009083377 -0.001155999  0.005978497 -0.007562015  0.001218960 -0.005869372 
        cg13         cg15         cg17         cg18 
-0.007449367  0.005066157  0.007900907 -0.002510744 

$intercept
Intercept 
    1.211 

attr(,"class")
[1] "methyl_surro"

MethylSurroGetR Demo Step 2 · Missing Data

CpG Sample1 Sample2 Sample3 Sample4 Sample5
cg02 0.288 NA NA 0.883 NA
cg07 0.9 0.246 NA 0.328 0.955
cg08 0.89 0.693 0.641 0.994 0.656
cg13 0.963 0.902 0.691 0.795 0.025
cg17 0.143 0.415 0.414 0.369 0.152
cg03 NA NA NA NA NA
cg06 NA NA NA NA NA
cg11 NA NA NA NA NA
cg15 NA NA NA NA NA
cg18 NA NA NA NA NA

Two Types of Missing Data

Missing Observations: CpGs present in the target data but missing for some samples

Missing Probes: CpGs absent from the target data entirely

MethylSurroGetR Demo Step 2 · Missing Data

Check for Missing Data

methyl_miss(my_surro)
Missing Data Summary for methyl_surro Object
============================================

Total probes: 10
Total samples: 5
Complete probes: 3 (30.0%)
Probes with missing observations: 2 (20.0%)
Completely missing probes: 5 (50.0%)
Overall missing rate: 58.0%

Probes with partial missing data:
cg02 cg07 
 0.6  0.2 

Completely missing probes:
cg03, cg06, cg11, cg15, cg18

MethylSurroGetR Demo Step 2 · Missing Data

Impute Missing Observations

my_surro <- impute_obs(
  methyl_surro = my_surro,
  method = "median",
  min_nonmiss_prop = 0
)
Imputed 4 values in 2 probes using median method.
print(my_surro$methyl)
         samp1     samp2     samp3     samp4      samp5
cg02 0.2875775 0.5852975 0.5852975 0.8830174 0.58529746
cg07 0.8998250 0.2460877 0.6138728 0.3279207 0.95450365
cg08 0.8895393 0.6928034 0.6405068 0.9942698 0.65570580
cg13 0.9630242 0.9022990 0.6907053 0.7954674 0.02461368
cg17 0.1428000 0.4145463 0.4137243 0.3688455 0.15244475
cg03        NA        NA        NA        NA         NA
cg06        NA        NA        NA        NA         NA
cg11        NA        NA        NA        NA         NA
cg15        NA        NA        NA        NA         NA
cg18        NA        NA        NA        NA         NA

MethylSurroGetR Demo Inputs

Load External Reference Data

data(ref_df)
ref_vec_mean <- with(ref_df, setNames(mean, rownames(ref_df)))
ref_vec_mean <- ref_vec_mean[!is.na(ref_vec_mean)]
print(ref_vec_mean)
     cg01      cg02      cg03      cg04      cg05      cg06      cg07      cg08 
0.3992451 0.6616689 0.4948262 0.5278895 0.6770353 0.5526592 0.4940793 0.7745650 
     cg09      cg10      cg11      cg12      cg13      cg14      cg15      cg16 
0.5281033 0.4982656 0.4566024 0.3856340 0.6752219 0.5866269 0.4004940 0.4996738 
     cg17      cg18      cg19      cg20 
0.2984722 0.3923206 0.3747138 0.7031609 

MethylSurroGetR Demo Step 2 · Missing Data

Fill Missing Probes from Reference

my_surro <- reference_fill(
  methyl_surro = my_surro,
  reference = ref_vec_mean,
  type = "probes"
)
Filled 25 values using reference data (probes strategy).
print(my_surro$methyl)
         samp1     samp2     samp3     samp4      samp5
cg02 0.2875775 0.5852975 0.5852975 0.8830174 0.58529746
cg07 0.8998250 0.2460877 0.6138728 0.3279207 0.95450365
cg08 0.8895393 0.6928034 0.6405068 0.9942698 0.65570580
cg13 0.9630242 0.9022990 0.6907053 0.7954674 0.02461368
cg17 0.1428000 0.4145463 0.4137243 0.3688455 0.15244475
cg03 0.4948262 0.4948262 0.4948262 0.4948262 0.49482616
cg06 0.5526592 0.5526592 0.5526592 0.5526592 0.55265924
cg11 0.4566024 0.4566024 0.4566024 0.4566024 0.45660238
cg15 0.4004940 0.4004940 0.4004940 0.4004940 0.40049405
cg18 0.3923206 0.3923206 0.3923206 0.3923206 0.39232059

MethylSurroGetR Demo Step 3 · Calculate Score

Calculate Predicted Values

surro_predvals <- surro_calc(
  methyl_surro = my_surro,
  transform = "linear"
)
print(surro_predvals)
   samp1    samp2    samp3    samp4    samp5 
1.197718 1.202317 1.201042 1.199796 1.201382 

MethylSurroGetR Demo Steps 1–3

Piping Commands

surro_predvals <- beta_matrix_miss |>
  surro_set(weights = wts_vec_lin, intercept = "Intercept") |>
  impute_obs(method = "median", min_nonmiss_prop = 0) |>
  reference_fill(reference = ref_vec_mean, type = "probes") |>
  surro_calc(transform = "linear")

print(surro_predvals)
   samp1    samp2    samp3    samp4    samp5 
1.197718 1.202317 1.201042 1.199796 1.201382 

Where Does MethylSurroGetR Fit?

Not the Right Solution

  • Developing or training new surrogates
  • Well-known clocks already implemented in dedicated packages (e.g., methylCIPHER)

Great Fit

  • Your own prediction models (e.g., elastic net) applied to new cohorts
  • Newer or lesser-known surrogates published only as a table of weights
  • Methylation risk scores built from EWAS summary statistics
  • Running many surrogates through one consistent workflow

Coauthors

  • Trey Smith
  • Jessica D. Faul
  • Colter Mitchell

Testing

  • Mateo Farina
  • Eric Klopack

Funding and Disclosures

Funding

This work was supported in part by the National Institute on Aging (NIA; R01AG071071) and a Eunice Kennedy Shriver National Institute of Child Health and Human Development (NICHD) training grant to the Population Studies Center at the University of Michigan (T32HD007339).

The content is solely the responsibility of the authors and does not necessarily represent the official views of the National Institutes of Health.

Disclosures

The authors have no financial conflicts of interest to disclose.

AI tools were used for proofreading and slide formatting.

More Information

Personal Website

bit.ly/jagoode

Package Website

jagoode27.github.io/MethylSurroGetR

Scan/Click QR Code to Visit