Predicted vs. actual age on the held-out EPIC v2 test set (GSE246337, n=500). Left to right: HistGBR (MAE 3.79), ElasticNet (MAE 3.37), DeepStrataAge (MAE 2.45).
This repository contains the code and results for building an epigenetic age clock from publicly available DNA methylation data. We train an ElasticNet model on 11,328 samples from 17 GEO cohorts and evaluate on an independent Illumina EPIC v2 test set, achieving MAE 3.37 years — closing 53% of the performance gap to DeepStrataAge (De Lima Camillo et al., npj Aging, 2026), a 40.7M-parameter DNN trained on 29,167 proprietary samples.
We show that the remaining gap is attributable to data volume, not model architecture: DeepStrataAge's own ElasticNet baseline achieves MAE 2.02 on their data — only 0.13 years behind the full DNN.
| Model | Training Data | Test MAE | MedAE | R² | Pearson r |
|---|---|---|---|---|---|
| DeepStrataAge (DNN, 40.7M params) | 29,167 (proprietary) | 2.45 | 1.79 | 0.959 | 0.980 |
| ElasticNet (this work) | 11,328 (public) | 3.37 | 2.23 | 0.937 | 0.977 |
| HistGBR (this work) | 11,328 (public) | 3.79 | 2.77 | 0.926 | 0.965 |
Test set: GSE246337 (500 samples, EPIC v2, age 18–89). Held out during training and hyperparameter tuning. 95% CI for ElasticNet: [3.03, 3.77].
| Train N | Cohorts | Test MAE | Gap to DSA |
|---|---|---|---|
| 3,140 | 7 | 4.38 | 1.93 |
| 5,329 | 9 | 3.57 | 1.12 |
| 8,722 | 11 | 3.52 | 1.06 |
| 11,328 | 17 | 3.37 | 0.91 |
- Cross-validation: Leave-One-Cohort-Out (LOCO) across 17 cohorts. Random CV inflated results by ~50%.
- Tuning: Optuna TPE with fold-level pruning (60 trials, 40% subsampling). Test set excluded by assertion.
- Features: 12,234 CpGs + sex + platform flag + 5 batch features. Population-mean imputation for cross-platform CpGs.
- Regularization: Optimal l1_ratio shifted from 0.06 (near-Ridge) at 5K samples to 0.45 (true ElasticNet) at 11K — sparsity helps with heterogeneous cohorts.
Three bugs in public GEO datasets caused initial MAE > 24. Fixing them was the largest single improvement:
| Issue | Dataset | Effect |
|---|---|---|
| Detection p-values interleaved with betas | GSE196696 | Mean beta collapsed to 0.13 |
| G-CSF mobilized transplant donors | GSE196696 | +22 years epigenetic age shift |
| Sentrix / GSM ID order mismatch | GSE246337 | All age labels shuffled |
All data is publicly available from GEO.
17 training cohorts (click to expand)
| GEO ID | N | Platform | Age | Description |
|---|---|---|---|---|
| GSE40279 | 656 | 450K | 19–101 | Hannum et al. (2013) |
| GSE41169 | 95 | 450K | 18–65 | Dutch population |
| GSE42861 | 335 | 450K | 20–70 | EIRA RA study (controls) |
| GSE50660 | 464 | 450K | 38–67 | UK Airwave |
| GSE51032 | 845 | 450K | 34–72 | EPIC-Italy |
| GSE51057 | 329 | 450K | 35–70 | Menarcheal timing |
| GSE55763 | 2,664 | 450K | 24–75 | LOLIPOP UK |
| GSE56105 | 614 | 450K | 10–75 | BSGS (Australia) |
| GSE72773 | 310 | 450K | 35–92 | Horvath multi-ethnic |
| GSE72775 | 335 | 450K | 36–90 | Horvath Hispanic |
| GSE73103 | 355 | 450K | 14–34 | Swedish young adults |
| GSE87571 | 729 | 450K | 14–94 | Swedish lifespan |
| GSE106648 | 139 | 450K | 20–65 | MS study (controls) |
| GSE125105 | 699 | 450K | 17–87 | MPI Psychiatry |
| GSE132203 | 795 | EPIC v1 | 18–76 | GTP cohort |
| GSE196696 | 570 | EPIC v1 | 19–40 | CIBMTR |
| GSE210255 | 1,394 | EPIC v1 | 21–87 | Multi-ethnic |
Test: GSE246337 (500 samples, EPIC v2, age 18–89)
python >= 3.11
numpy pandas scikit-learn scipy optuna torch matplotlib
# 1. Download DeepStrataAge weights
# https://zenodo.org/records/14768481 → data/zenodo/DeepStratv3/
# 2. Download and parse GEO datasets into data/processed/*.parquet
# (see training data table for accessions)
# 3. Hyperparameter tuning (optional — tuned params included in tuning/)
python tune_optuna.py --model elasticnet --n-trials 60 --subsample 0.4
# 4. Evaluate
python run_definitive_tuned.py├── paper/
│ ├── paper.tex # Manuscript source (LaTeX)
│ └── paper.pdf # Compiled PDF
├── run_definitive_tuned.py # Main pipeline (tuned params)
├── run_definitive.py # Pipeline (default params)
├── tune_optuna.py # Hyperparameter search
├── src/ # Utilities (imputation, evaluation, plots)
├── tuning/ # Best hyperparameters (JSON)
└── results/ # Final CSVs and figures
@misc{elouahabi2026epigenetic,
author = {El Ouahabi, Maher},
title = {Closing the Gap: Epigenetic Age Prediction with ElasticNet and Public Data},
year = {2026},
howpublished = {\url{https://github.com/maher-coder/epigenetic-clock}},
}- De Lima Camillo et al. (2026). DeepStrataAge. npj Aging. DOI: 10.1038/s41514-026-00358-w
- Horvath (2013). DNA methylation age of human tissues and cell types. Genome Biology, 14(10), R115.
- Hannum et al. (2013). Genome-wide methylation profiles reveal quantitative views of human aging rates. Molecular Cell, 49(2), 359–367.
MIT
