{ "cells": [ { "cell_type": "markdown", "id": "8800a5a4", "metadata": {}, "source": [ "# CorticalClock" ] }, { "cell_type": "markdown", "id": "6d82d0f5", "metadata": {}, "source": [ "## Index\n", "1. [Instantiate model class](#Instantiate-model-class)\n", "2. [Define clock metadata](#Define-clock-metadata)\n", "3. [Download clock dependencies](#Download-clock-dependencies)\n", "5. [Load features](#Load-features)\n", "6. [Load weights into base model](#Load-weights-into-base-model)\n", "7. [Load reference values](#Load-reference-values)\n", "8. [Load preprocess and postprocess objects](#Load-preprocess-and-postprocess-objects)\n", "10. [Check all clock parameters](#Check-all-clock-parameters)\n", "10. [Basic test](#Basic-test)\n", "11. [Save torch model](#Save-torch-model)\n", "12. [Clear directory](#Clear-directory)" ] }, { "cell_type": "markdown", "id": "6f2531fe", "metadata": {}, "source": [ "Let's first import some packages:" ] }, { "cell_type": "code", "execution_count": 1, "id": "86a52380", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:05.843298Z", "iopub.status.busy": "2026-07-04T20:25:05.842864Z", "iopub.status.idle": "2026-07-04T20:25:07.923727Z", "shell.execute_reply": "2026-07-04T20:25:07.923249Z" } }, "outputs": [], "source": [ "import os\n", "import inspect\n", "import shutil\n", "import json\n", "import torch\n", "import pandas as pd\n", "import pyaging as pya" ] }, { "cell_type": "markdown", "id": "1c0b75a7", "metadata": {}, "source": [ "## Instantiate model class" ] }, { "cell_type": "code", "execution_count": 2, "id": "a796decb", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:07.925506Z", "iopub.status.busy": "2026-07-04T20:25:07.925284Z", "iopub.status.idle": "2026-07-04T20:25:07.928315Z", "shell.execute_reply": "2026-07-04T20:25:07.927880Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "class CorticalClock(LinearReferenceClock):\n", " def postprocess(self, x):\n", " \"\"\"Horvath anti-logarithmic linear transformation (adult age = 20).\"\"\"\n", " adult_age = 20\n", " mask_negative = x < 0\n", " mask_non_negative = ~mask_negative\n", " age = torch.empty_like(x)\n", " age[mask_negative] = (1 + adult_age) * torch.exp(x[mask_negative]) - 1\n", " age[mask_non_negative] = (1 + adult_age) * x[mask_non_negative] + adult_age\n", " return age\n", "\n" ] } ], "source": [ "def print_entire_class(cls):\n", " source = inspect.getsource(cls)\n", " print(source)\n", "\n", "print_entire_class(pya.models.CorticalClock)" ] }, { "cell_type": "code", "execution_count": 3, "id": "edb837f0", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:07.930866Z", "iopub.status.busy": "2026-07-04T20:25:07.930701Z", "iopub.status.idle": "2026-07-04T20:25:07.933416Z", "shell.execute_reply": "2026-07-04T20:25:07.932387Z" } }, "outputs": [], "source": [ "model = pya.models.CorticalClock()" ] }, { "cell_type": "markdown", "id": "b7babd9c", "metadata": {}, "source": [ "## Define clock metadata" ] }, { "cell_type": "code", "execution_count": 4, "id": "577aad3e", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:07.935376Z", "iopub.status.busy": "2026-07-04T20:25:07.935216Z", "iopub.status.idle": "2026-07-04T20:25:07.937499Z", "shell.execute_reply": "2026-07-04T20:25:07.937094Z" } }, "outputs": [], "source": [ "model.metadata[\"clock_name\"] = \"corticalclock\"\n", "model.metadata[\"data_type\"] = \"DNA methylation\" # Paper: methylation\n", "model.metadata[\"species\"] = \"Homo sapiens\" # Paper: Homo sapiens\n", "model.metadata[\"year\"] = 2020\n", "model.metadata[\"approved_by_author\"] = \"⌛\"\n", "model.metadata[\"citation\"] = \"Shireby, G. L., et al. \\\"Recalibrating the epigenetic clock: implications for assessing biological age in the human cortex.\\\" Brain 143.12 (2020): 3763-3775.\"\n", "model.metadata[\"doi\"] = \"https://doi.org/10.1093/brain/awaa334\"\n", "model.metadata[\"notes\"] = \"Cortex-specific DNA-methylation chronological-age estimator trained by elastic net on 1,047 post-mortem cortical samples; its 347-CpG weighted score is back-transformed to years.\"\n", "model.metadata[\"research_only\"] = None\n", "model.metadata[\"tissue\"] = [\"brain cortex\"] # Paper: brain cortex (post-mortem human cortical tissue)\n", "model.metadata[\"predicts\"] = [\"chronological age\"] # Paper: chronological age\n", "model.metadata[\"training_target\"] = [\"chronological age\"] # Paper: chronological age\n", "model.metadata[\"unit\"] = [\"years\"] # Paper: years\n", "model.metadata[\"model_type\"] = \"elastic net regression\" # Paper: Elastic net\n", "model.metadata[\"platform\"] = [\"Illumina 450K\"] # Paper: Illumina 450K\n", "model.metadata[\"population\"] = \"human, age unspecified\" # Paper: human cortex training set: 1,047 samples from 832 donors, ages 1–108 years\n", "model.metadata[\"journal\"] = \"Brain\"\n", "model.metadata[\"last_author\"] = \"Jonathan Mill\"\n", "model.metadata[\"n_features\"] = 347\n", "model.metadata[\"citations\"] = 206\n", "model.metadata[\"citations_date\"] = \"2026-07-05\"\n" ] }, { "cell_type": "markdown", "id": "5084fb52", "metadata": {}, "source": [ "## Download clock dependencies" ] }, { "cell_type": "code", "execution_count": 5, "id": "04065b6f", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:07.938792Z", "iopub.status.busy": "2026-07-04T20:25:07.938688Z", "iopub.status.idle": "2026-07-04T20:25:09.598296Z", "shell.execute_reply": "2026-07-04T20:25:09.597617Z" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.system(f\"curl -sL -o CorticalClockCoefs.txt https://raw.githubusercontent.com/gemmashireby/CorticalClock/80c3df19c01d9aac25c9fd5aecd5bbc42aba0939/PredCorticalAge/CorticalClockCoefs.txt\")\n", "os.system(f\"curl -sL -o Ref_DNAm_brain_values.rdat https://raw.githubusercontent.com/gemmashireby/CorticalClock/80c3df19c01d9aac25c9fd5aecd5bbc42aba0939/PredCorticalAge/Ref_DNAm_brain_values.rdat\")" ] }, { "cell_type": "code", "execution_count": 6, "id": "8cd791b7", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:09.600118Z", "iopub.status.busy": "2026-07-04T20:25:09.600003Z", "iopub.status.idle": "2026-07-04T20:25:09.602806Z", "shell.execute_reply": "2026-07-04T20:25:09.602375Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing download.r\n" ] } ], "source": [ "%%writefile download.r\n", "\n", "library(jsonlite)\n", "coefs <- read.table(\"CorticalClockCoefs.txt\", header = TRUE, stringsAsFactors = FALSE)\n", "coefs <- coefs[tolower(coefs$probe) != \"intercept\" & tolower(coefs$probe) != \"(intercept)\", ]\n", "load(\"Ref_DNAm_brain_values.rdat\")\n", "refvals <- as.numeric(ref[coefs$probe])\n", "write_json(list(probe = coefs$probe, coef = coefs$coef, ref = refvals), \"cortical.json\", digits = 10)" ] }, { "cell_type": "code", "execution_count": 7, "id": "fdc45fac", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:09.604027Z", "iopub.status.busy": "2026-07-04T20:25:09.603944Z", "iopub.status.idle": "2026-07-04T20:25:11.832575Z", "shell.execute_reply": "2026-07-04T20:25:11.832004Z" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.system(\"Rscript download.r\")" ] }, { "cell_type": "markdown", "id": "46be35b3", "metadata": {}, "source": [ "## Load features" ] }, { "cell_type": "code", "execution_count": 8, "id": "200eb498", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.834376Z", "iopub.status.busy": "2026-07-04T20:25:11.834247Z", "iopub.status.idle": "2026-07-04T20:25:11.836609Z", "shell.execute_reply": "2026-07-04T20:25:11.836198Z" } }, "outputs": [], "source": [ "d = json.load(open('cortical.json'))\n", "model.features = list(d['probe'])" ] }, { "cell_type": "markdown", "id": "0a57b01c", "metadata": {}, "source": [ "## Load weights into base model" ] }, { "cell_type": "code", "execution_count": 9, "id": "9f02ed0d", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.838022Z", "iopub.status.busy": "2026-07-04T20:25:11.837917Z", "iopub.status.idle": "2026-07-04T20:25:11.840143Z", "shell.execute_reply": "2026-07-04T20:25:11.839781Z" } }, "outputs": [], "source": [ "# Intercept 0.577682570446177 hard-coded in the CorticalClock reference script\n", "weights = torch.tensor(d['coef']).unsqueeze(0).float()\n", "intercept = torch.tensor([0.577682570446177]).float()" ] }, { "cell_type": "code", "execution_count": 10, "id": "fed7deeb", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.841490Z", "iopub.status.busy": "2026-07-04T20:25:11.841392Z", "iopub.status.idle": "2026-07-04T20:25:11.843603Z", "shell.execute_reply": "2026-07-04T20:25:11.843217Z" } }, "outputs": [], "source": [ "base_model = pya.models.LinearModel(input_dim=len(model.features))\n", "\n", "base_model.linear.weight.data = weights.float()\n", "base_model.linear.bias.data = intercept.float()\n", "\n", "model.base_model = base_model" ] }, { "cell_type": "markdown", "id": "2bdbd5f2", "metadata": {}, "source": [ "## Load reference values" ] }, { "cell_type": "code", "execution_count": 11, "id": "64dda625", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.845025Z", "iopub.status.busy": "2026-07-04T20:25:11.844927Z", "iopub.status.idle": "2026-07-04T20:25:11.848252Z", "shell.execute_reply": "2026-07-04T20:25:11.846206Z" } }, "outputs": [], "source": [ "model.reference_values = d['ref']" ] }, { "cell_type": "markdown", "id": "87094355", "metadata": {}, "source": [ "## Load preprocess and postprocess objects" ] }, { "cell_type": "code", "execution_count": 12, "id": "f8117483", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.849950Z", "iopub.status.busy": "2026-07-04T20:25:11.849839Z", "iopub.status.idle": "2026-07-04T20:25:11.851716Z", "shell.execute_reply": "2026-07-04T20:25:11.851295Z" } }, "outputs": [], "source": [ "model.preprocess_name = None\n", "model.preprocess_dependencies = None" ] }, { "cell_type": "code", "execution_count": 13, "id": "5842cecc", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.853193Z", "iopub.status.busy": "2026-07-04T20:25:11.853113Z", "iopub.status.idle": "2026-07-04T20:25:11.854837Z", "shell.execute_reply": "2026-07-04T20:25:11.854394Z" } }, "outputs": [], "source": [ "model.postprocess_name = 'anti_log_linear'\n", "model.postprocess_dependencies = None" ] }, { "cell_type": "markdown", "id": "45f7dc20", "metadata": {}, "source": [ "## Check all clock parameters" ] }, { "cell_type": "code", "execution_count": 14, "id": "7e0cfa19", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.856253Z", "iopub.status.busy": "2026-07-04T20:25:11.856160Z", "iopub.status.idle": "2026-07-04T20:25:11.859956Z", "shell.execute_reply": "2026-07-04T20:25:11.859432Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "%==================================== Model Details ====================================%\n", "Model Attributes:\n", "\n", "training: True\n", "metadata: {'approved_by_author': '⌛',\n", " 'citation': 'Shireby, Gemma L., et al. \"Recalibrating the epigenetic clock: '\n", " 'implications for assessing biological age in the human cortex.\" '\n", " 'Brain 143.12 (2020): 3763-3775.',\n", " 'clock_name': 'corticalclock',\n", " 'data_type': 'methylation',\n", " 'doi': 'https://doi.org/10.1093/brain/awaa334',\n", " 'notes': None,\n", " 'research_only': None,\n", " 'species': 'Homo sapiens',\n", " 'version': None,\n", " 'year': 2020}\n", "reference_values: [0.21611617687, 0.15740657645, 0.7341710846, 0.060822631032, 0.10575749654, 0.62440019335, 0.13956151931, 0.93910437046, 0.67922573527, 0.41269620156, 0.18058134103, 0.30387161589, 0.72916401957, 0.16249365613, 0.10514838761, 0.32006057604, 0.41533755409, 0.76224987996, 0.1012865392, 0.068627506888, 0.62711858238, 0.067610974155, 0.73657937789, 0.085680987307, 0.16091221649, 0.5857097145, 0.65691321067, 0.5219473048, 0.14838958913, 0.46603381304]... [Total elements: 347]\n", "preprocess_name: None\n", "preprocess_dependencies: None\n", "postprocess_name: 'anti_log_linear'\n", "postprocess_dependencies: None\n", "features: ['cg00059225', 'cg00088042', 'cg00252534', 'cg00297950', 'cg00384539', 'cg00491255', 'cg00521255', 'cg00648582', 'cg00771642', 'cg00924265', 'cg00935119', 'cg00940577', 'cg01091514', 'cg01122755', 'cg01162920', 'cg01194538', 'cg01264729', 'cg01311102', 'cg01529637', 'cg01532168', 'cg01616394', 'cg01639032', 'cg01641432', 'cg01655150', 'cg01745370', 'cg01899542', 'cg02046143', 'cg02047661', 'cg02357838', 'cg02361903']... [Total elements: 347]\n", "base_model_features: None\n", "\n", "%==================================== Model Details ====================================%\n", "Model Structure:\n", "\n", "base_model: LinearModel(\n", " (linear): Linear(in_features=347, out_features=1, bias=True)\n", ")\n", "\n", "%==================================== Model Details ====================================%\n", "Model Parameters and Weights:\n", "\n", "base_model.linear.weight: [0.24595920741558075, 0.18271556496620178, 0.16822335124015808, 0.14175772666931152, 0.17820188403129578, 0.14450779557228088, -0.2443627119064331, -0.03914619982242584, 0.1277257353067398, -0.06851626932621002, -0.038119036704301834, -0.061217304319143295, -0.03222978115081787, 0.06571090221405029, -0.1973567008972168, -0.151609405875206, -0.15242373943328857, -0.10921627283096313, 0.026809634640812874, 0.3346559405326843, -0.01871008612215519, 0.5150526762008667, -0.0013768351636826992, -0.32761350274086, -0.00586429750546813, -0.050962597131729126, -0.007779109291732311, -0.017116934061050415, 0.2379734218120575, -0.5452234148979187]... [Tensor of shape torch.Size([1, 347])]\n", "base_model.linear.bias: tensor([0.5777])\n", "\n", "%==================================== Model Details ====================================%\n", "\n" ] } ], "source": [ "pya.utils.print_model_details(model)" ] }, { "cell_type": "markdown", "id": "30266c3a", "metadata": {}, "source": [ "## Basic test" ] }, { "cell_type": "code", "execution_count": 15, "id": "692fecc2", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.861718Z", "iopub.status.busy": "2026-07-04T20:25:11.861597Z", "iopub.status.idle": "2026-07-04T20:25:11.869668Z", "shell.execute_reply": "2026-07-04T20:25:11.869207Z" } }, "outputs": [ { "data": { "text/plain": [ "tensor([[ 2.8786e+01],\n", " [-9.7972e-01],\n", " [ 1.7019e+01],\n", " [-9.1772e-01],\n", " [ 8.8761e+00],\n", " [ 1.7652e+01],\n", " [ 1.6345e+02],\n", " [-2.4936e-03],\n", " [-9.7389e-01],\n", " [-9.8054e-01]], dtype=torch.float64, grad_fn=)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "torch.manual_seed(42)\n", "input = torch.randn(10, len(model.features), dtype=float)\n", "model.eval()\n", "model.to(float)\n", "pred = model(input)\n", "pred" ] }, { "cell_type": "markdown", "id": "14e69f9f", "metadata": {}, "source": [ "## Save torch model" ] }, { "cell_type": "code", "execution_count": 16, "id": "7d8665ca", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.870971Z", "iopub.status.busy": "2026-07-04T20:25:11.870893Z", "iopub.status.idle": "2026-07-04T20:25:11.874624Z", "shell.execute_reply": "2026-07-04T20:25:11.874209Z" } }, "outputs": [], "source": [ "torch.save(model, f\"../weights/{model.metadata['clock_name']}.pt\")" ] }, { "cell_type": "markdown", "id": "ce2c1c1c", "metadata": {}, "source": [ "## Clear directory\n", "" ] }, { "cell_type": "code", "execution_count": 17, "id": "e802cfb6", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T20:25:11.875875Z", "iopub.status.busy": "2026-07-04T20:25:11.875802Z", "iopub.status.idle": "2026-07-04T20:25:11.880225Z", "shell.execute_reply": "2026-07-04T20:25:11.879838Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deleted file: CorticalClockCoefs.txt\n", "Deleted file: Ref_DNAm_brain_values.rdat\n", "Deleted file: download.r\n", "Deleted file: cortical.json\n" ] } ], "source": [ "# Function to remove a folder and all its contents\n", "def remove_folder(path):\n", " try:\n", " shutil.rmtree(path)\n", " print(f\"Deleted folder: {path}\")\n", " except Exception as e:\n", " print(f\"Error deleting folder {path}: {e}\")\n", "\n", "# Get a list of all files and folders in the current directory\n", "all_items = os.listdir('.')\n", "\n", "# Loop through the items\n", "for item in all_items:\n", " # Check if it's a file and does not end with .ipynb\n", " if os.path.isfile(item) and not item.endswith('.ipynb'):\n", " os.remove(item)\n", " print(f\"Deleted file: {item}\")\n", " # Check if it's a folder\n", " elif os.path.isdir(item):\n", " remove_folder(item)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.11" } }, "nbformat": 4, "nbformat_minor": 5 }