TwelveCellDeconvoluteBloodEPIC¶
Index¶
Let’s first import some packages:
[1]:
import os
import inspect
import shutil
import json
import math
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.TwelveCellDeconvoluteBloodEPIC)
class TwelveCellDeconvoluteBloodEPIC(DeconvolutionSingleCell):
def __init__(self):
super().__init__()
[3]:
model = pya.models.TwelveCellDeconvoluteBloodEPIC()
Define clock metadata¶
[4]:
model.metadata["clock_name"] = "twelvecelldeconvolutebloodepicbas"
model.metadata["data_type"] = "DNA methylation" # Paper: The TwelveCell model is a DeconvolutionModel whose reference rows are CpG loci.
model.metadata["species"] = "Homo sapiens" # Paper: The registry declares species Human for TwelveCellDeconvoluteBloodEPIC.
model.metadata["year"] = 2022
model.metadata["approved_by_author"] = "⌛"
model.metadata["citation"] = "Salas, L.A., Zhang, Z., Koestler, D.C. et al. Enhanced cell deconvolution of peripheral blood using DNA methylation for high-resolution immune profiling. Nature Communications 13, 761 (2022)."
model.metadata["doi"] = "https://doi.org/10.1038/s41467-021-27864-7"
model.metadata["notes"] = "Reference-based constrained deconvolution returning the basophil proportion from EPIC-array blood methylation. The published EPIC IDOL-Ext library used 1,200 CpGs selected to optimize recovery of known artificial-mixture cell-type proportions; pyaging instead inherits Biolearn’s undocumented 240-CpG replacement, whose rows reproducibly comprise 10 positive and 10 negative maximal cell-versus-other methylation contrasts per subtype and are not a subset of the published 1,200 probes."
model.metadata["research_only"] = None
model.metadata["tissue"] = ["purified blood leukocytes"] # Paper: The reference-matrix columns are twelve leukocyte subtypes used to deconvolve peripheral blood.
model.metadata["predicts"] = ["basophil proportion"] # Paper: The model output is Cell Proportions and Bas is one of the twelve returned columns.
model.metadata["training_target"] = ["cell-type proportions"] # Paper: IDOL used artificial-mixture ground truth with known, prespecified proportions of the 12 cell types and optimized libraries by comparing deconvolution estimates with those known proportions.
model.metadata["unit"] = ["proportion"] # Paper: The solver constrains each estimated cell proportion to 0–1 and all proportions to sum to one.
model.metadata["model_type"] = "reference-based constrained deconvolution" # Paper: The implementation minimizes reference-matrix reconstruction error by constrained quadratic programming.
model.metadata["platform"] = ["Illumina EPIC"] # Paper: The TwelveCell model registry sets platform to EPIC.
model.metadata["population"] = "adults" # Paper: Reference cells were isolated from 41 male and 15 female anonymous healthy donors with mean age 32.2 years and range 19–58 years, spanning multiple self-identified ancestries.
model.metadata["journal"] = "Nature Communications"
model.metadata["last_author"] = "Brock C. Christensen"
model.metadata["n_features"] = 240
model.metadata["citations"] = 302
model.metadata["citations_date"] = '2026-10-02'
Download clock dependencies¶
Download reference file¶
[5]:
coeff_url = "https://raw.githubusercontent.com/bio-learn/biolearn/master/biolearn/data/twelve_cell_deconv.csv"
os.system(f"curl -L {coeff_url} -o twelve_cell_deconv.csv")
[5]:
0
Load features¶
From CSV file¶
[6]:
import numpy as np
import pandas as pd
ref = pd.read_csv('twelve_cell_deconv.csv', index_col=0)
model.features = ref.index.astype(str).tolist()
ref_matrix = torch.tensor(ref.values, dtype=torch.float64)
pseudo_inv = torch.linalg.pinv(ref_matrix)
model.pseudo_inv = pseudo_inv
model.cell_index = 0
model.reference_values = torch.nanmean(ref_matrix, dim=1)
Load weights into base model¶
From CSV file¶
[7]:
# No linear base model; deconvolution logic in model forward
Linear model¶
[8]:
model.base_model = None
Load reference values¶
[9]:
# reference_values already set above
Load preprocess and postprocess objects¶
[10]:
model.preprocess_name = "fill_with_reference_means"
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': 'Salas, L.A., Zhang, Z., Koestler, D.C. et al. Enhanced cell '
'deconvolution of peripheral blood using DNA methylation for '
'high-resolution immune profiling. Nature Communications 13, 761 '
'(2022).',
'citations': 302,
'citations_date': '2026-10-02',
'clock_name': 'twelvecelldeconvolutebloodepicbas',
'data_type': 'DNA methylation',
'doi': 'https://doi.org/10.1038/s41467-021-27864-7',
'journal': 'Nature Communications',
'last_author': 'Brock C. Christensen',
'model_type': 'reference-based constrained deconvolution',
'n_features': 240,
'notes': 'Reference-based constrained deconvolution returning the basophil '
'proportion from EPIC-array blood methylation. The published EPIC '
'IDOL-Ext library used 1,200 CpGs selected to optimize recovery of '
'known artificial-mixture cell-type proportions; pyaging instead '
'inherits Biolearn’s undocumented 240-CpG replacement, whose rows '
'reproducibly comprise 10 positive and 10 negative maximal '
'cell-versus-other methylation contrasts per subtype and are not a '
'subset of the published 1,200 probes.',
'platform': ['Illumina EPIC'],
'population': 'adults',
'predicts': ['basophil proportion'],
'preprocess': 'fill_with_reference_means',
'reference_values': True,
'research_only': None,
'species': 'Homo sapiens',
'tissue': ['purified blood leukocytes'],
'training_target': ['cell-type proportions'],
'unit': ['proportion'],
'version': '0.5.7',
'year': 2022}
reference_values: [0.9106685833333333, 0.8335768333333333, 0.8720161666666666, 0.8286470833333333, 0.92628525, 0.9155413333333334, 0.8792788333333332, 0.9127326666666665, 0.9067565833333332, 0.84800775, 0.10073516666666667, 0.14440299999999998, 0.049399916666666675, 0.08766050000000002, 0.07896441666666666, 0.12878258333333334, 0.16374524999999998, 0.13815541666666667, 0.17540216666666666, 0.1698345, 0.9202164166666665, 0.8607305833333334, 0.9136148333333334, 0.9245049999999999, 0.8901303333333334, 0.9271245, 0.8883705833333333, 0.8855881666666668, 0.9244469166666666, 0.92171125]... [Tensor of shape torch.Size([240])]
preprocess_name: 'fill_with_reference_means'
preprocess_dependencies: None
postprocess_name: None
postprocess_dependencies: None
features: ['cg11778706', 'cg13745692', 'cg15163862', 'cg15667493', 'cg09590647', 'cg24160371', 'cg04200580', 'cg02177087', 'cg02690807', 'cg08486432', 'cg08674813', 'cg17565444', 'cg20208538', 'cg08747761', 'cg16576285', 'cg08269714', 'cg10931172', 'cg18421366', 'cg06805218', 'cg21268578', 'cg23635663', 'cg10712263', 'cg03984633', 'cg27576286', 'cg00789615', 'cg20591136', 'cg08012294', 'cg19811231', 'cg13335048', 'cg27109907']... [Total elements: 240]
base_model_features: None
base_model: None
pseudo_inv: [0.0062597217235298664, 0.01539477951995796, 0.003836934604777173, 0.005564101437478809, 0.002277140363479023, 0.006073633184626901, 0.008477183290511526, -0.0001975656527457585, 0.005601139656499912, 0.007094362071883866, -0.006240708820602573, -0.009260375574434002, -0.0010103501546960773, -2.0931822748843616e-05, -0.003443105142629081, -0.006973754074381415, -0.014081560765767536, -0.01165024465832652, -0.017254358157385866, -0.018101141218108914, 0.016011891819700665, 0.007716146910953028, 0.01145146135203884, 0.007735843885753704, 0.014959580407076114, 0.011932867918461359, 0.014137094238940516, 0.016570818459337337, 0.006383014308174419, 0.007057464604785132]... [Tensor of shape torch.Size([12, 240])]
cell_index: 0
%==================================== Model Details ====================================%
Model Structure:
%==================================== Model Details ====================================%
Model Parameters and Weights:
%==================================== Model Details ====================================%
Normal feature ranges¶
[ ]:
# Units and plausibility ranges come from the package registry, keyed by feature name.
feature_ranges = pya.utils.resolve_feature_ranges(model.features, model.metadata["data_type"])
model.feature_units = [record["unit"] for record in feature_ranges]
pd.DataFrame.from_records(feature_ranges).head()
Basic Test¶
[ ]:
# Exercise the clock with values in the middle of each feature's expected range.
records = pya.utils.resolve_feature_ranges(model.features, model.metadata["data_type"])
midpoints = [
(record["low"] + record["high"]) / 2 if math.isfinite(record["high"]) else max(record["low"], 1.0)
for record in records
]
input = torch.tensor([midpoints] * 10, dtype=torch.float64)
model.eval()
model.to(torch.float64)
pred = model(input)
pred
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: twelve_cell_deconv.csv