McCartneyTotalHDLRatio#

Index#

  1. Instantiate model class

  2. Define clock metadata

  3. Download clock dependencies

  4. Load features

  5. Load weights into base model

  6. Load reference values

  7. Load preprocess and postprocess objects

  8. Check all clock parameters

  9. Basic test

  10. Save torch model

  11. Clear directory

Let’s first import some packages:

[1]:
import os
import inspect
import shutil
import json
import torch
import pandas as pd
import pyaging as pya

Instantiate model class#

[2]:
def print_entire_class(cls):
    source = inspect.getsource(cls)
    print(source)

print_entire_class(pya.models.McCartneyTotalHDLRatio)
class McCartneyTotalHDLRatio(LinearReferenceClock):
    pass

[3]:
model = pya.models.McCartneyTotalHDLRatio()

Define clock metadata#

[4]:
model.metadata["clock_name"] = "mccartneytotalhdlratio"
model.metadata["data_type"] = "DNA methylation"  # Paper: The predictors use DNA methylation states at CpG sites.
model.metadata["species"] = "Homo sapiens"  # Paper: Generation Scotland is a population-based cohort of human participants.
model.metadata["year"] = 2018
model.metadata["approved_by_author"] = "⌛"
model.metadata["citation"] = "McCartney, D. L., et al. “Epigenetic prediction of complex traits and death.” Genome Biology 19, 136 (2018)."
model.metadata["doi"] = "https://doi.org/10.1186/s13059-018-1514-1"
model.metadata["notes"] = "Whole-blood DNAm LASSO score for total-to-HDL cholesterol ratio, trained in Generation Scotland on an age-, sex-, and ancestry-adjusted phenotype residual and evaluated out of sample in LBC1936."
model.metadata["research_only"] = None
model.metadata["tissue"] = ["whole blood"]  # Paper: Stored DNA from baseline blood samples was used to build the predictors.
model.metadata["predicts"] = ["total-to-HDL cholesterol ratio"]  # Paper: The study developed a DNAm predictor for total-to-HDL cholesterol ratio.
model.metadata["training_target"] = ["total-to-HDL cholesterol ratio"]  # Paper: The total-to-HDL cholesterol ratio phenotype was regressed on age, sex and ten genetic principal components; its residual was the LASSO outcome.
model.metadata["unit"] = ["ratio"]  # Paper: No output transformation is applied; the score retains the dimensionless ratio-residual scale.
model.metadata["model_type"] = "LASSO regression"  # Paper: The glmnet mixing parameter alpha was set to 1, applying a LASSO penalty with tenfold cross-validation.
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.
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.
model.metadata["journal"] = "Genome Biology"
model.metadata["last_author"] = "Riccardo E. Marioni"
model.metadata["n_features"] = 412
model.metadata["citations"] = 301
model.metadata["citations_date"] = "2026-07-05"

Download clock dependencies#

[5]:
supplementary_url = "https://static-content.springer.com/esm/art%3A10.1186%2Fs13059-018-1514-1/MediaObjects/13059_2018_1514_MOESM1_ESM.xlsx"
supplementary_file_name = "mccartney_predictors.xlsx"
os.system(f"curl -sL -o {supplementary_file_name} {supplementary_url}")
[5]:
0

Load features#

[6]:
# Additional file 1, Table S8 - Total-HDL ratio (McCartney et al. 2018)
coef_df = pd.read_excel('mccartney_predictors.xlsx', sheet_name='Table S8 - Total-HDL ratio')
model.features = coef_df['CpG'].tolist()

Load weights into base model#

[7]:
# The penalised (LASSO) predictor has no intercept term
weights = torch.tensor(coef_df['Beta'].tolist()).unsqueeze(0).float()
intercept = torch.tensor([0.0]).float()
[8]:
base_model = pya.models.LinearModel(input_dim=len(model.features))

base_model.linear.weight.data = weights.float()
base_model.linear.bias.data = intercept.float()

model.base_model = base_model

Load reference values#

[9]:
model.reference_values = None

Load preprocess and postprocess objects#

[10]:
model.preprocess_name = None
model.preprocess_dependencies = None
[11]:
model.postprocess_name = None
model.postprocess_dependencies = None

Check all clock parameters#

[12]:
pya.utils.print_model_details(model)

%==================================== Model Details ====================================%
Model Attributes:

training: True
metadata: {'approved_by_author': '⌛',
 'citation': 'McCartney, Daniel L., et al. "Epigenetic prediction of complex '
             'traits and death." Genome biology 19.1 (2018): 136.',
 'clock_name': 'mccartneytotalhdlratio',
 'data_type': 'methylation',
 'doi': 'https://doi.org/10.1186/s13059-018-1514-1',
 'notes': None,
 'research_only': None,
 'species': 'Homo sapiens',
 'version': None,
 'year': 2018}
reference_values: None
preprocess_name: None
preprocess_dependencies: None
postprocess_name: None
postprocess_dependencies: None
features: ['cg24576270', 'cg25158622', 'cg06500161', 'cg26033520', 'cg18615457', 'cg10993470', 'cg19250790', 'cg09177238', 'cg22813794', 'cg01511534', 'cg01559436', 'cg22488164', 'cg05391946', 'cg09503194', 'cg14697880', 'cg06136443', 'cg14545305', 'cg26955383', 'cg26800462', 'cg08854185', 'cg14891022', 'cg23719318', 'cg12332083', 'cg02150767', 'cg08864105', 'cg11770080', 'cg12422930', 'cg06898549', 'cg03546163', 'cg26245667']... [Total elements: 412]
base_model_features: None

%==================================== Model Details ====================================%
Model Structure:

base_model: LinearModel(
  (linear): Linear(in_features=412, out_features=1, bias=True)
)

%==================================== Model Details ====================================%
Model Parameters and Weights:

base_model.linear.weight: [16.199861526489258, 1.5791822671890259, 1.3397550582885742, 1.2062699794769287, 1.1696120500564575, 1.1656070947647095, 1.0623283386230469, 0.9755944609642029, 0.9740409851074219, 0.9441574811935425, 0.8715856671333313, 0.8420827984809875, 0.8233152627944946, 0.8100341558456421, 0.7970923781394958, 0.7889676690101624, 0.7783008217811584, 0.7704609632492065, 0.7500393986701965, 0.7299103736877441, 0.7206714153289795, 0.6958703994750977, 0.6573514342308044, 0.6428144574165344, 0.637154221534729, 0.6069923043251038, 0.600265383720398, 0.5983773469924927, 0.5772433876991272, 0.5692275166511536]... [Tensor of shape torch.Size([1, 412])]
base_model.linear.bias: tensor([0.])

%==================================== Model Details ====================================%

Basic test#

[13]:
torch.manual_seed(42)
input = torch.randn(10, len(model.features), dtype=float)
model.eval()
model.to(float)
pred = model(input)
pred
[13]:
tensor([[ 17.9949],
        [ -3.0065],
        [-38.9142],
        [  1.3466],
        [  9.2999],
        [  5.4597],
        [ 14.3734],
        [ -9.2779],
        [ -9.3156],
        [-27.2043]], dtype=torch.float64, grad_fn=<AddmmBackward0>)

Save torch model#

[14]:
torch.save(model, f"../weights/{model.metadata['clock_name']}.pt")

Clear directory#

[15]:
# Function to remove a folder and all its contents
def remove_folder(path):
    try:
        shutil.rmtree(path)
        print(f"Deleted folder: {path}")
    except Exception as e:
        print(f"Error deleting folder {path}: {e}")

# Get a list of all files and folders in the current directory
all_items = os.listdir('.')

# Loop through the items
for item in all_items:
    # Check if it's a file and does not end with .ipynb
    if os.path.isfile(item) and not item.endswith('.ipynb'):
        os.remove(item)
        print(f"Deleted file: {item}")
    # Check if it's a folder
    elif os.path.isdir(item):
        remove_folder(item)
Deleted file: mccartney_predictors.xlsx