{ "cells": [ { "cell_type": "markdown", "id": "2f049633", "metadata": {}, "source": [ "# McCartneyWHR" ] }, { "cell_type": "markdown", "id": "e489b066", "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": "85073d3d", "metadata": {}, "source": [ "Let's first import some packages:" ] }, { "cell_type": "code", "execution_count": 1, "id": "904e837f", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:35.602200Z", "iopub.status.busy": "2026-07-04T19:26:35.602113Z", "iopub.status.idle": "2026-07-04T19:26:37.833821Z", "shell.execute_reply": "2026-07-04T19:26:37.833329Z" } }, "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": "bad5435e", "metadata": {}, "source": [ "## Instantiate model class" ] }, { "cell_type": "code", "execution_count": 2, "id": "3bbed7ed", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:37.835675Z", "iopub.status.busy": "2026-07-04T19:26:37.835467Z", "iopub.status.idle": "2026-07-04T19:26:37.838181Z", "shell.execute_reply": "2026-07-04T19:26:37.837848Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "class McCartneyWHR(LinearReferenceClock):\n", " pass\n", "\n" ] } ], "source": [ "def print_entire_class(cls):\n", " source = inspect.getsource(cls)\n", " print(source)\n", "\n", "print_entire_class(pya.models.McCartneyWHR)" ] }, { "cell_type": "code", "execution_count": 3, "id": "e3611a69", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:37.839593Z", "iopub.status.busy": "2026-07-04T19:26:37.839493Z", "iopub.status.idle": "2026-07-04T19:26:37.841189Z", "shell.execute_reply": "2026-07-04T19:26:37.840729Z" } }, "outputs": [], "source": [ "model = pya.models.McCartneyWHR()" ] }, { "cell_type": "markdown", "id": "93ef7fbb", "metadata": {}, "source": [ "## Define clock metadata" ] }, { "cell_type": "code", "execution_count": 4, "id": "83d7f31b", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:37.842548Z", "iopub.status.busy": "2026-07-04T19:26:37.842442Z", "iopub.status.idle": "2026-07-04T19:26:37.844707Z", "shell.execute_reply": "2026-07-04T19:26:37.844246Z" } }, "outputs": [], "source": [ "model.metadata[\"clock_name\"] = \"mccartneywhr\"\n", "model.metadata[\"data_type\"] = \"DNA methylation\" # Paper: The predictors use DNA methylation states at CpG sites.\n", "model.metadata[\"species\"] = \"Homo sapiens\" # Paper: Generation Scotland is a population-based cohort of human participants.\n", "model.metadata[\"year\"] = 2018\n", "model.metadata[\"approved_by_author\"] = \"⌛\"\n", "model.metadata[\"citation\"] = \"McCartney, D. L., et al. “Epigenetic prediction of complex traits and death.” Genome Biology 19, 136 (2018).\"\n", "model.metadata[\"doi\"] = \"https://doi.org/10.1186/s13059-018-1514-1\"\n", "model.metadata[\"notes\"] = \"Whole-blood DNAm LASSO score for waist-to-hip ratio, trained in Generation Scotland on an age-, sex-, and ancestry-adjusted phenotype residual and evaluated out of sample in LBC1936.\"\n", "model.metadata[\"research_only\"] = None\n", "model.metadata[\"tissue\"] = [\"whole blood\"] # Paper: Stored DNA from baseline blood samples was used to build the predictors.\n", "model.metadata[\"predicts\"] = [\"waist-to-hip ratio\"] # Paper: The study developed a DNAm predictor for waist-to-hip ratio.\n", "model.metadata[\"training_target\"] = [\"waist-to-hip ratio\"] # Paper: The waist-to-hip ratio phenotype was regressed on age, sex and ten genetic principal components; its residual was the LASSO outcome.\n", "model.metadata[\"unit\"] = [\"ratio\"] # Paper: No output transformation is applied; the score retains the dimensionless ratio-residual scale.\n", "model.metadata[\"model_type\"] = \"LASSO regression\" # Paper: The glmnet mixing parameter alpha was set to 1, applying a LASSO penalty with tenfold cross-validation.\n", "model.metadata[\"platform\"] = [\"Illumina EPIC\"] # Paper: Predictor training used quality-controlled HumanMethylationEPIC blood data; probes absent from 450K were filtered only to enable LBC1936 prediction.\n", "model.metadata[\"population\"] = \"adults\" # Paper: The predictors were built on a subset of 5,087 Generation Scotland participants; the parent cohort spans ages 18–99.\n", "model.metadata[\"journal\"] = \"Genome Biology\"\n", "model.metadata[\"last_author\"] = \"Riccardo E. Marioni\"\n", "model.metadata[\"n_features\"] = 226\n", "model.metadata[\"citations\"] = 301\n", "model.metadata[\"citations_date\"] = \"2026-07-05\"\n" ] }, { "cell_type": "markdown", "id": "e99584dc", "metadata": {}, "source": [ "## Download clock dependencies" ] }, { "cell_type": "code", "execution_count": 5, "id": "48ccfb0f", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:37.845923Z", "iopub.status.busy": "2026-07-04T19:26:37.845822Z", "iopub.status.idle": "2026-07-04T19:26:38.064258Z", "shell.execute_reply": "2026-07-04T19:26:38.063791Z" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "supplementary_url = \"https://static-content.springer.com/esm/art%3A10.1186%2Fs13059-018-1514-1/MediaObjects/13059_2018_1514_MOESM1_ESM.xlsx\"\n", "supplementary_file_name = \"mccartney_predictors.xlsx\"\n", "os.system(f\"curl -sL -o {supplementary_file_name} {supplementary_url}\")" ] }, { "cell_type": "markdown", "id": "2a2d56ff", "metadata": {}, "source": [ "## Load features" ] }, { "cell_type": "code", "execution_count": 6, "id": "538c921a", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.066420Z", "iopub.status.busy": "2026-07-04T19:26:38.066287Z", "iopub.status.idle": "2026-07-04T19:26:38.158868Z", "shell.execute_reply": "2026-07-04T19:26:38.158309Z" } }, "outputs": [], "source": [ "# Additional file 1, Table S9 - Waist-to-Hip ratio (McCartney et al. 2018)\n", "coef_df = pd.read_excel('mccartney_predictors.xlsx', sheet_name='Table S9 - Waist-to-Hip ratio')\n", "model.features = coef_df['CpG'].tolist()" ] }, { "cell_type": "markdown", "id": "b08e819d", "metadata": {}, "source": [ "## Load weights into base model" ] }, { "cell_type": "code", "execution_count": 7, "id": "d0df6cda", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.160391Z", "iopub.status.busy": "2026-07-04T19:26:38.160308Z", "iopub.status.idle": "2026-07-04T19:26:38.162629Z", "shell.execute_reply": "2026-07-04T19:26:38.162163Z" } }, "outputs": [], "source": [ "# The penalised (LASSO) predictor has no intercept term\n", "weights = torch.tensor(coef_df['Beta'].tolist()).unsqueeze(0).float()\n", "intercept = torch.tensor([0.0]).float()" ] }, { "cell_type": "code", "execution_count": 8, "id": "c25386fb", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.163847Z", "iopub.status.busy": "2026-07-04T19:26:38.163769Z", "iopub.status.idle": "2026-07-04T19:26:38.165958Z", "shell.execute_reply": "2026-07-04T19:26:38.165597Z" } }, "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": "dbd88d91", "metadata": {}, "source": [ "## Load reference values" ] }, { "cell_type": "code", "execution_count": 9, "id": "6d954324", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.167189Z", "iopub.status.busy": "2026-07-04T19:26:38.167108Z", "iopub.status.idle": "2026-07-04T19:26:38.168690Z", "shell.execute_reply": "2026-07-04T19:26:38.168254Z" } }, "outputs": [], "source": [ "model.reference_values = None" ] }, { "cell_type": "markdown", "id": "a412367e", "metadata": {}, "source": [ "## Load preprocess and postprocess objects" ] }, { "cell_type": "code", "execution_count": 10, "id": "2a9e673c", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.169926Z", "iopub.status.busy": "2026-07-04T19:26:38.169841Z", "iopub.status.idle": "2026-07-04T19:26:38.171582Z", "shell.execute_reply": "2026-07-04T19:26:38.171134Z" } }, "outputs": [], "source": [ "model.preprocess_name = None\n", "model.preprocess_dependencies = None" ] }, { "cell_type": "code", "execution_count": 11, "id": "0924078e", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.172596Z", "iopub.status.busy": "2026-07-04T19:26:38.172524Z", "iopub.status.idle": "2026-07-04T19:26:38.174089Z", "shell.execute_reply": "2026-07-04T19:26:38.173671Z" } }, "outputs": [], "source": [ "model.postprocess_name = None\n", "model.postprocess_dependencies = None" ] }, { "cell_type": "markdown", "id": "b2a06522", "metadata": {}, "source": [ "## Check all clock parameters" ] }, { "cell_type": "code", "execution_count": 12, "id": "6e2333ba", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.175126Z", "iopub.status.busy": "2026-07-04T19:26:38.175047Z", "iopub.status.idle": "2026-07-04T19:26:38.178121Z", "shell.execute_reply": "2026-07-04T19:26:38.177755Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "%==================================== Model Details ====================================%\n", "Model Attributes:\n", "\n", "training: True\n", "metadata: {'approved_by_author': '⌛',\n", " 'citation': 'McCartney, Daniel L., et al. \"Epigenetic prediction of complex '\n", " 'traits and death.\" Genome biology 19.1 (2018): 136.',\n", " 'clock_name': 'mccartneywhr',\n", " 'data_type': 'methylation',\n", " 'doi': 'https://doi.org/10.1186/s13059-018-1514-1',\n", " 'notes': None,\n", " 'research_only': None,\n", " 'species': 'Homo sapiens',\n", " 'version': None,\n", " 'year': 2018}\n", "reference_values: None\n", "preprocess_name: None\n", "preprocess_dependencies: None\n", "postprocess_name: None\n", "postprocess_dependencies: None\n", "features: ['cg03005261', 'cg21637050', 'cg16728516', 'cg14267725', 'cg06500161', 'cg04804648', 'cg18909879', 'cg25921813', 'cg21020089', 'cg16284674', 'cg15092239', 'cg07769588', 'cg07215298', 'cg09249494', 'cg10474597', 'cg11832534', 'cg25110523', 'cg04453364', 'cg19519737', 'cg18515624', 'cg18054578', 'cg18011760', 'cg08563994', 'cg01616956', 'cg02079413', 'cg06192883', 'cg15132749', 'cg22669566', 'cg25349939', 'cg04816311']... [Total elements: 226]\n", "base_model_features: None\n", "\n", "%==================================== Model Details ====================================%\n", "Model Structure:\n", "\n", "base_model: LinearModel(\n", " (linear): Linear(in_features=226, out_features=1, bias=True)\n", ")\n", "\n", "%==================================== Model Details ====================================%\n", "Model Parameters and Weights:\n", "\n", "base_model.linear.weight: [0.47226133942604065, 0.2731764614582062, 0.23187954723834991, 0.20890694856643677, 0.18225495517253876, 0.12761914730072021, 0.08936703205108643, 0.08627176284790039, 0.0851999968290329, 0.07440463453531265, 0.061605945229530334, 0.05795585736632347, 0.053718939423561096, 0.05213217809796333, 0.05174638330936432, 0.04884398728609085, 0.04772736132144928, 0.046122852712869644, 0.04307003319263458, 0.04154041036963463, 0.03536680340766907, 0.03154837712645531, 0.030331037938594818, 0.029633039608597755, 0.02755829133093357, 0.026697693392634392, 0.02443346194922924, 0.023988846689462662, 0.023213393986225128, 0.02202027104794979]... [Tensor of shape torch.Size([1, 226])]\n", "base_model.linear.bias: tensor([0.])\n", "\n", "%==================================== Model Details ====================================%\n", "\n" ] } ], "source": [ "pya.utils.print_model_details(model)" ] }, { "cell_type": "markdown", "id": "36ea9c99", "metadata": {}, "source": [ "## Basic test" ] }, { "cell_type": "code", "execution_count": 13, "id": "edf0ac81", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.179269Z", "iopub.status.busy": "2026-07-04T19:26:38.179201Z", "iopub.status.idle": "2026-07-04T19:26:38.183684Z", "shell.execute_reply": "2026-07-04T19:26:38.183205Z" } }, "outputs": [ { "data": { "text/plain": [ "tensor([[ 0.9362],\n", " [ 1.4919],\n", " [ 0.3194],\n", " [-0.1748],\n", " [ 0.6122],\n", " [ 0.8324],\n", " [-0.0945],\n", " [-0.4978],\n", " [ 1.0881],\n", " [-0.8827]], dtype=torch.float64, grad_fn=)" ] }, "execution_count": 13, "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": "330a77f1", "metadata": {}, "source": [ "## Save torch model" ] }, { "cell_type": "code", "execution_count": 14, "id": "476318c7", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.184871Z", "iopub.status.busy": "2026-07-04T19:26:38.184800Z", "iopub.status.idle": "2026-07-04T19:26:38.188425Z", "shell.execute_reply": "2026-07-04T19:26:38.188013Z" } }, "outputs": [], "source": [ "torch.save(model, f\"../weights/{model.metadata['clock_name']}.pt\")" ] }, { "cell_type": "markdown", "id": "bc1ca3bf", "metadata": {}, "source": [ "## Clear directory\n", "" ] }, { "cell_type": "code", "execution_count": 15, "id": "da3b542a", "metadata": { "execution": { "iopub.execute_input": "2026-07-04T19:26:38.189730Z", "iopub.status.busy": "2026-07-04T19:26:38.189651Z", "iopub.status.idle": "2026-07-04T19:26:38.192886Z", "shell.execute_reply": "2026-07-04T19:26:38.192562Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deleted file: mccartney_predictors.xlsx\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 }