{ "cells": [ { "cell_type": "markdown", "id": "4eaba91a", "metadata": {}, "source": [ "# Pasta Mouse" ] }, { "cell_type": "markdown", "id": "e0c198ef", "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", "4. [Load features](#Load-features)\n", "5. [Map to mouse orthologs](#Map-to-mouse-orthologs)\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", "9. [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)\n" ] }, { "cell_type": "markdown", "id": "fc6ef12a", "metadata": {}, "source": [ "Let's first import some packages:" ] }, { "cell_type": "code", "execution_count": 1, "id": "f9cf0fd1", "metadata": {}, "outputs": [], "source": [ "import os\n", "import inspect\n", "import shutil\n", "import json\n", "import subprocess\n", "\n", "import torch\n", "import pandas as pd\n", "import pyaging as pya" ] }, { "cell_type": "markdown", "id": "903c4c1c", "metadata": {}, "source": [ "## Instantiate model class" ] }, { "cell_type": "code", "execution_count": 2, "id": "328ab247", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "class PastaMouse(Pasta):\n", " def __init__(self):\n", " super().__init__()\n", " self.base_model_features = None\n", " self.mouse_feature_indices = None\n", " self.full_reference_values = None\n", "\n", " def set_mouse_features(self, full_features, full_reference_values=None, mouse_prefix=\"ENSMUSG\"):\n", " \"\"\"\n", " Configure the mouse-only interface while keeping the full feature space for the base model.\n", " \"\"\"\n", " self.base_model_features = list(full_features)\n", " self.full_reference_values = full_reference_values\n", "\n", " self.mouse_feature_indices = [\n", " i\n", " for i, feature in enumerate(self.base_model_features)\n", " if isinstance(feature, str) and feature.startswith(mouse_prefix)\n", " ]\n", "\n", " if len(self.mouse_feature_indices) == 0:\n", " raise ValueError(\"No mouse features were identified when configuring PastaMouse.\")\n", "\n", " self.features = [self.base_model_features[i] for i in self.mouse_feature_indices]\n", "\n", " if self.full_reference_values is None:\n", " self.reference_values = None\n", " elif isinstance(self.full_reference_values, torch.Tensor):\n", " self.reference_values = self.full_reference_values[self.mouse_feature_indices].detach().clone()\n", " else:\n", " self.reference_values = [self.full_reference_values[i] for i in self.mouse_feature_indices]\n", "\n", " def _expand_with_reference(self, x):\n", " \"\"\"\n", " Reconstruct the full 8113-length input expected by the base model by\n", " inserting reference values for human-only genes.\n", " \"\"\"\n", " if self.base_model_features is None or self.mouse_feature_indices is None:\n", " raise ValueError(\"PastaMouse must be configured with set_mouse_features before inference.\")\n", "\n", " if self.full_reference_values is None:\n", " ref_full = torch.zeros(len(self.base_model_features), device=x.device, dtype=x.dtype)\n", " elif isinstance(self.full_reference_values, torch.Tensor):\n", " ref_full = self.full_reference_values.to(device=x.device, dtype=x.dtype)\n", " else:\n", " ref_full = torch.tensor(self.full_reference_values, device=x.device, dtype=x.dtype)\n", "\n", " full_x = ref_full.unsqueeze(0).repeat(x.size(0), 1)\n", " full_x[:, self.mouse_feature_indices] = x\n", " return full_x\n", "\n", " def forward(self, x):\n", " # Build the full feature vector (mouse data + human reference values) before preprocessing.\n", " x_full = self._expand_with_reference(x)\n", " x_full = self.preprocess(x_full)\n", " x_full = self.base_model(x_full)\n", " x_full = self.postprocess(x_full)\n", " return x_full\n", "\n" ] } ], "source": [ "def print_entire_class(cls):\n", " source = inspect.getsource(cls)\n", " print(source)\n", "\n", "print_entire_class(pya.models.PastaMouse)" ] }, { "cell_type": "code", "execution_count": 3, "id": "3fd0b239", "metadata": {}, "outputs": [], "source": [ "model = pya.models.PastaMouse()" ] }, { "cell_type": "markdown", "id": "9c4ed287", "metadata": {}, "source": [ "## Define clock metadata" ] }, { "cell_type": "code", "execution_count": 4, "id": "2e6c2b91", "metadata": {}, "outputs": [], "source": [ "model.metadata[\"clock_name\"] = \"pastamouse\"\n", "model.metadata[\"data_type\"] = \"transcriptomics\" # Paper: Pasta is a multi-platform, multi-tissue transcriptomic aging clock.\n", "model.metadata[\"species\"] = \"Mus musculus\" # Paper: Pasta was extended for application to mouse samples.\n", "model.metadata[\"year\"] = 2025\n", "model.metadata[\"approved_by_author\"] = \"✅\"\n", "model.metadata[\"citation\"] = \"Salignon, J. et al. Pasta, a versatile transcriptomic clock, maps the chemical and genetic determinants of aging and rejuvenation. bioRxiv 2025.06.04.657785 (2025).\"\n", "model.metadata[\"doi\"] = \"https://doi.org/10.1101/2025.06.04.657785\"\n", "model.metadata[\"notes\"] = \"Mouse application of the human Pasta model after mapping one-to-one orthologues, rank transformation, and median imputation of missing model genes.\"\n", "model.metadata[\"research_only\"] = None\n", "model.metadata[\"tissue\"] = [\"multi-tissue\"] # Paper: Mouse validation used diverse tissues and datasets.\n", "model.metadata[\"predicts\"] = [\"transcriptomic age\"] # Paper: Mouse expression was mapped and the Pasta age model was applied.\n", "model.metadata[\"training_target\"] = [\"age ordering\"] # Paper: The transferred model retains the human age-shift classifier.\n", "model.metadata[\"unit\"] = [\"years\"] # Paper: Pasta classifier scores are converted into age differences.\n", "model.metadata[\"model_type\"] = \"orthologue-transferred ridge logistic regression\" # Paper: Ridge-regularized generalized linear models used 10-fold cross-validation.\n", "model.metadata[\"platform\"] = [\"RNA-seq\", \"gene expression microarray\"] # Paper: Training studies included bulk RNA-seq and microarray.\n", "model.metadata[\"population\"] = \"human, age unspecified\" # Paper: Mouse genes were restricted to one-to-one human orthologues.\n", "model.metadata[\"journal\"] = \"bioRxiv\"\n", "model.metadata[\"last_author\"] = \"Christian G. Riedel\"\n", "model.metadata[\"n_features\"] = 1600\n", "model.metadata[\"citations\"] = 1\n", "model.metadata[\"citations_date\"] = \"2026-07-05\"\n" ] }, { "cell_type": "markdown", "id": "d6cc670a", "metadata": {}, "source": [ "## Download clock dependencies" ] }, { "cell_type": "markdown", "id": "a7fe1316", "metadata": {}, "source": [ "#### Download coefficient file" ] }, { "cell_type": "code", "execution_count": 5, "id": "cb8f3e6d", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ " % Total % Received % Xferd Average Speed Time Time Time Current\n", " Dload Upload Total Spent Left Speed\n", "100 322k 100 322k 0 0 1477k 0 --:--:-- --:--:-- --:--:-- 1478k\n" ] }, { "data": { "text/plain": [ "0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "coeff_url = \"https://raw.githubusercontent.com/bio-learn/biolearn/master/biolearn/data/Pasta.csv\"\n", "os.system(f\"curl -L {coeff_url} -o Pasta.csv\")" ] }, { "cell_type": "markdown", "id": "99eba2db", "metadata": {}, "source": [ "#### Download ortholog mapping" ] }, { "cell_type": "code", "execution_count": 6, "id": "354580d9", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ " % Total % Received % Xferd Average Speed Time Time Time Current\n", " Dload Upload Total Spent Left Speed\n", "100 24796 100 24796 0 0 129k 0 --:--:-- --:--:-- --:--:-- 129k\n" ] }, { "data": { "text/plain": [ "0" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ortholog_url = \"https://raw.githubusercontent.com/jsalignon/pasta/main/data/v_human_mouse_one2one.rda\"\n", "os.system(f\"curl -L {ortholog_url} -o v_human_mouse_one2one.rda\")" ] }, { "cell_type": "markdown", "id": "dd9b234c", "metadata": {}, "source": [ "## Load features" ] }, { "cell_type": "markdown", "id": "8e1794e1", "metadata": {}, "source": [ "#### From CSV file" ] }, { "cell_type": "code", "execution_count": 7, "id": "7646fdf9", "metadata": {}, "outputs": [], "source": [ "coeffs = pd.read_csv('Pasta.csv')\n", "coeffs['feature'] = coeffs['GeneID']\n", "coeffs['coefficient'] = coeffs['CoefficientTraining']\n", "\n", "model.features = coeffs['feature'].tolist()" ] }, { "cell_type": "markdown", "id": "a4173eb9", "metadata": {}, "source": [ "## Map to mouse orthologs" ] }, { "cell_type": "code", "execution_count": 8, "id": "1360df9c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mapped 1600 of 8113 features to mouse orthologs.\n" ] } ], "source": [ "r_cmd = (\n", " \"load('v_human_mouse_one2one.rda'); \"\n", " \"df <- data.frame(mouse=names(v_human_mouse_one2one), human=as.character(v_human_mouse_one2one)); \"\n", " \"write.csv(df, 'v_human_mouse_one2one.csv', row.names=FALSE)\"\n", ")\n", "os.system(f\"Rscript -e \\\"{r_cmd}\\\"\")\n", "\n", "ortholog_df = pd.read_csv('v_human_mouse_one2one.csv')\n", "human_to_mouse = dict(zip(ortholog_df['human'], ortholog_df['mouse']))\n", "\n", "mapped_features = [human_to_mouse.get(gene, gene) for gene in model.features]\n", "mapped_count = sum(gene in human_to_mouse for gene in model.features)\n", "print(f\"Mapped {mapped_count} of {len(model.features)} features to mouse orthologs.\")\n", "model.features = mapped_features" ] }, { "cell_type": "code", "execution_count": 9, "id": "864e9ac0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1600" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "len(np.intersect1d(list(human_to_mouse.keys()), list(coeffs['feature'])))" ] }, { "cell_type": "code", "execution_count": 10, "id": "368d4750", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8113" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(np.unique(list(coeffs['feature'])))" ] }, { "cell_type": "markdown", "id": "0fc85cb9", "metadata": {}, "source": [ "## Load weights into base model" ] }, { "cell_type": "markdown", "id": "913f38a0", "metadata": {}, "source": [ "#### From CSV file" ] }, { "cell_type": "code", "execution_count": 11, "id": "4ee12ddc", "metadata": {}, "outputs": [], "source": [ "weights = torch.tensor(coeffs['coefficient'].tolist()).unsqueeze(0)\n", "intercept = torch.tensor([0.0])" ] }, { "cell_type": "markdown", "id": "fdd2571a", "metadata": {}, "source": [ "#### Linear model" ] }, { "cell_type": "code", "execution_count": 12, "id": "93e2d66b", "metadata": {}, "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": "90e58321", "metadata": {}, "source": [ "## Load reference values" ] }, { "cell_type": "code", "execution_count": 13, "id": "e95c081e", "metadata": {}, "outputs": [], "source": [ "full_features = list(model.features)\n", "full_reference_values = [float('nan')] * len(full_features)\n", "model.reference_values = full_reference_values\n", "model.set_mouse_features(full_features, full_reference_values)\n" ] }, { "cell_type": "markdown", "id": "903e2d24", "metadata": {}, "source": [ "## Load preprocess and postprocess objects" ] }, { "cell_type": "code", "execution_count": 14, "id": "64d7f83c", "metadata": {}, "outputs": [], "source": [ "model.preprocess_name = \"median_fill_and_rank_normalization\"\n", "model.preprocess_dependencies = None" ] }, { "cell_type": "code", "execution_count": 15, "id": "533b8140", "metadata": {}, "outputs": [], "source": [ "model.postprocess_name = \"scale_and_shift\"\n", "model.postprocess_dependencies = [-4.76348378687217, -0.0502893445253186]" ] }, { "cell_type": "markdown", "id": "e9df7c08", "metadata": {}, "source": [ "## Check all clock parameters" ] }, { "cell_type": "code", "execution_count": 16, "id": "4504e605", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "%==================================== Model Details ====================================%\n", "Model Attributes:\n", "\n", "training: True\n", "metadata: {'approved_by_author': '✅',\n", " 'citation': 'Salignon, Jerome, et al. \"Pasta, an age-shift transcriptomic '\n", " 'clock, maps the chemical and genetic determinants of aging and '\n", " 'rejuvenation.\" bioRxiv (2025): 2025-06.',\n", " 'clock_name': 'pastamouse',\n", " 'data_type': 'transcriptomics',\n", " 'doi': 'https://doi.org/10.1101/2025.06.04.657785',\n", " 'notes': 'Rank-normalized Pasta clock using mouse one-to-one ortholog genes '\n", " 'when available.',\n", " 'research_only': None,\n", " 'species': 'Mus musculus',\n", " 'version': None,\n", " 'year': 2025}\n", "reference_values: [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]... [Total elements: 1600]\n", "preprocess_name: 'median_fill_and_rank_normalization'\n", "preprocess_dependencies: None\n", "postprocess_name: 'scale_and_shift'\n", "postprocess_dependencies: [-4.76348378687217, -0.0502893445253186]\n", "features: ['ENSMUSG00000017307', 'ENSMUSG00000064289', 'ENSMUSG00000032783', 'ENSMUSG00000039047', 'ENSMUSG00000043448', 'ENSMUSG00000052997', 'ENSMUSG00000052833', 'ENSMUSG00000000244', 'ENSMUSG00000024566', 'ENSMUSG00000006304', 'ENSMUSG00000070733', 'ENSMUSG00000020572', 'ENSMUSG00000022634', 'ENSMUSG00000024785', 'ENSMUSG00000024873', 'ENSMUSG00000022607', 'ENSMUSG00000058407', 'ENSMUSG00000097485', 'ENSMUSG00000030521', 'ENSMUSG00000052593', 'ENSMUSG00000028969', 'ENSMUSG00000031843', 'ENSMUSG00000038481', 'ENSMUSG00000022323', 'ENSMUSG00000009555', 'ENSMUSG00000078154', 'ENSMUSG00000022816', 'ENSMUSG00000021963', 'ENSMUSG00000025024', 'ENSMUSG00000006941']... [Total elements: 1600]\n", "base_model_features: ['ENSG00000196839', 'ENSG00000170558', 'ENSG00000133997', 'ENSG00000168060', 'ENSMUSG00000017307', 'ENSG00000136754', 'ENSG00000113552', 'ENSG00000177485', 'ENSMUSG00000064289', 'ENSG00000094631', 'ENSG00000108840', 'ENSG00000170248', 'ENSG00000153094', 'ENSG00000159921', 'ENSG00000165879', 'ENSMUSG00000032783', 'ENSMUSG00000039047', 'ENSG00000179776', 'ENSG00000167670', 'ENSG00000129484', 'ENSG00000041880', 'ENSG00000113361', 'ENSG00000141198', 'ENSG00000100284', 'ENSG00000013619', 'ENSG00000010017', 'ENSG00000105993', 'ENSG00000113810', 'ENSMUSG00000043448', 'ENSMUSG00000052997']... [Total elements: 8113]\n", "mouse_feature_indices: [4, 8, 15, 16, 28, 29, 30, 42, 44, 63, 77, 82, 83, 105, 107, 111, 112, 117, 118, 119, 120, 121, 147, 153, 155, 164, 171, 179, 180, 183]... [Total elements: 1600]\n", "full_reference_values: [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]... [Total elements: 8113]\n", "\n", "%==================================== Model Details ====================================%\n", "Model Structure:\n", "\n", "base_model: LinearModel(\n", " (linear): Linear(in_features=8113, out_features=1, bias=True)\n", ")\n", "\n", "%==================================== Model Details ====================================%\n", "Model Parameters and Weights:\n", "\n", "base_model.linear.weight: [-2.4399256290053017e-05, -1.774273368937429e-05, 1.554851587570738e-05, 1.1031659596483223e-05, 1.6993128156173043e-05, 3.9308954001171514e-05, -0.00012627331307157874, 2.8949250463483622e-06, -6.271281017689034e-05, 2.9893646569689736e-05, 3.6174697015667334e-05, 6.864466558909044e-05, -2.3814825908630155e-05, 3.11008479911834e-05, 1.0880126865231432e-05, 9.605172635929193e-06, 1.1990639904979616e-05, 9.29949510464212e-06, 6.331568147288635e-05, -3.362866482348181e-05, -0.00022874546993989497, -2.7509766368893906e-05, 6.674586074950639e-06, 1.986255301744677e-05, -3.5506527638062835e-05, 2.922421663242858e-05, -4.5067787141306326e-05, 5.991863872623071e-05, 3.728850060724653e-05, 4.235586311551742e-05]... [Tensor of shape torch.Size([1, 8113])]\n", "base_model.linear.bias: tensor([0.])\n", "\n", "%==================================== Model Details ====================================%\n", "\n" ] } ], "source": [ "pya.utils.print_model_details(model)" ] }, { "cell_type": "markdown", "id": "792abd75", "metadata": {}, "source": [ "## Basic test" ] }, { "cell_type": "code", "execution_count": 17, "id": "4e40b0db", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "tensor([[ 4.9918],\n", " [ -8.4652],\n", " [ 15.1181],\n", " [-31.3271],\n", " [ 27.5393],\n", " [-10.9938],\n", " [ -3.4235],\n", " [-14.1880],\n", " [-24.5564],\n", " [ -7.9826]], dtype=torch.float64, grad_fn=)" ] }, "execution_count": 17, "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": "4692e691", "metadata": {}, "source": [ "## Save torch model" ] }, { "cell_type": "code", "execution_count": 18, "id": "95a2f252", "metadata": {}, "outputs": [], "source": [ "torch.save(model, f\"../weights/{model.metadata['clock_name']}.pt\")" ] }, { "cell_type": "markdown", "id": "1210bd3f", "metadata": {}, "source": [ "## Clear directory\n", "" ] }, { "cell_type": "code", "execution_count": 19, "id": "9538b4bd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Deleted file: v_human_mouse_one2one.rda\n", "Deleted file: Pasta.csv\n", "Deleted file: v_human_mouse_one2one.csv\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)" ] }, { "cell_type": "code", "execution_count": null, "id": "cac3451f", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.7" } }, "nbformat": 4, "nbformat_minor": 5 }