Decoding the role of transcriptomic clocks in the human prefrontal cortex.
The 24 matches
- [1] § STAR★Methods › Method details › Functional analyses › MENTOR network embedding of functional gene connections ↔ 10other-07302025/03plots-08272025/00plots-08282025.ipynb, lines 139–234 · score 0.96 · deMagalhaes, DESeq2, StocH, StocP, StocZ, GenAge
- [2] § STAR★Methods › Method details › Stochastic modeling of transcriptomic clocks ↔ 09stochastic-08172025/0stochastic-0817025.ipynb, lines 424–463 · score 0.92 · small jitter, standard deviations, reduce perfect correlations, multivariate normal, covariance matrices, gene correlation
- [3] § STAR★Methods › Method details › Transcriptomic clocks analyses ↔ 10other-07302025/04plots-0828205/00plots-08282025.ipynb, lines 79–100 · score 0.89 · de Magalhaes, DESeq2, GenAge, RNA seq, GTExAge, brain tissue
- [4] § STAR★Methods › Method details › Transcriptomic clocks analyses ↔ 11functional-08202025/00functional_analysis-08202025.ipynb, lines 78–97 · score 0.89 · de Magalhaes, DESeq2, GenAge, RNA seq, GTExAge, brain tissue
- [5] § STAR★Methods › Method details › Deep learning age prediction ↔ 08training-08152025/01deeplearning/00deeplearnin-08152025.ipynb, lines 271–312 · score 0.82 · dropout rate, Keras, Glorot, loss, Adam, MAE
- [6] § STAR★Methods › Method details › Deep learning age prediction ↔ 09stochastic-08172025/0stochastic-0817025.ipynb, lines 946–987 · score 0.82 · dropout rate, Keras, Glorot, loss, Adam, MAE
- [7] § STAR★Methods › Method details › Elastic net age prediction ↔ 08training-08152025/00elasticnet/00elasticnet-08152025.ipynb, lines 374–413 · score 0.79 · ElasticNetCV, l1_ratio, fold predictions, split, metrics, scores
- [8] § STAR★Methods › Method details › Elastic net age prediction ↔ 09stochastic-08172025/0stochastic-0817025.ipynb, lines 786–825 · score 0.78 · ElasticNetCV, l1_ratio, fold predictions, split, metrics, scores
- [9] § STAR★Methods › Method details › Functional analyses › MENTOR network embedding of functional gene connections ↔ src/mentor/cli.py, lines 195–340 · score 0.74 · pairwise distance, MENTOR, Spearman, linkage, singleton, subclustering
- [10] § Results › Comparison of transcriptomic clocks and epigenetic clocks ↔ 10other-07302025/03plots-08272025/00plots-08282025.ipynb, lines 139–234 · score 0.73 · DamAge, PhenoAge, RNAAgeCalc, retrained transcriptomic clock, deep learning, Epigenetic clocks
- [11] § STAR★Methods › Method details › Gene annotation of epigenetic clock ↔ 10other-07302025/00plots-07302025/01rnaagecalc-08032025.ipynb, lines 264–321 · score 0.71 · StocH, StocP, StocZ, PhenoAge, epigenetic clock, Zhang2019
- [12] § Results › Performance of the transcriptomic clocks in predicting age ↔ 01GEOcohorts/01rnagecalc-08052025.R, lines 50–103 · score 0.69 · DESeq2, GenAge, GTExAge, RNAAgeCalc, Dev, Peters
- [13] § Results › Performance of the transcriptomic clocks in predicting age ↔ 01ragecalc-07272025/00rnagecalc-07272025.R, lines 41–106 · score 0.69 · DESeq2, GenAge, GTExAge, RNAAgeCalc, Dev, Peters
- [14] § Results › Stochastic effects of the transcriptomic clocks ↔ 10other-07302025/02plots-08272025/00plots-08272025.ipynb, lines 256–358 · score 0.65 · R2 stochastic, R2 deterministic, variance explained, deep learning, RR2, Elastic
- [15] § STAR★Methods › Experimental model and study participant details › University of Texas health science center at Houston (UTHealth) brain collection ↔ 07assoc-08052025/00uthealth-08052025.ipynb, lines 180–214 · score 0.65 · consensus diagnoses, UTHealth Brain, autopsy, ethnicity, drug, alcohol
- [16] § STAR★Methods › Experimental model and study participant details › University of Texas health science center at Houston (UTHealth) brain collection ↔ 07assoc-08052025/02uthealth-08282025.ipynb, lines 195–229 · score 0.65 · consensus diagnoses, UTHealth Brain, autopsy, ethnicity, drug, alcohol
- [17] § Results › Associations of transcriptomic age with psychiatric-related phenotypes ↔ 07assoc-08052025/04vabb-09012025.ipynb, lines 388–467 · score 0.64 · anticonvulsants, antidepressants, hallucinogen, polysubstance, antipsychotics, lifetime
- [18] § Results › Associations of transcriptomic age with psychiatric-related phenotypes ↔ 07assoc-08052025/01vabb-08062025.ipynb, lines 198–290 · score 0.63 · anticonvulsants, antidepressants, hallucinogen, polysubstance, antipsychotics, lifetime
- [19] § STAR★Methods › Quantification and statistical analysis ↔ 10other-07302025/01plots-08242025/00plots-08242025.ipynb, lines 618–742 · score 0.60 · squared error, Pearson correlation, Correlation matrices, R2, RMSE, retrained
- [20] § STAR★Methods › Quantification and statistical analysis ↔ 10other-07302025/02plots-08272025/00plots-08272025.ipynb, lines 256–358 · score 0.56 · variance explained, Pearson correlation, RR2, squared, stochastic, Prediction
- [21] § Results › Performance of transcriptomic clocks in predicting delta of age ↔ 10other-07302025/01plots-08242025/01plots-08262025.ipynb, lines 508–585 · score 0.55 · concordant decrease, delta age, density, deep learning, retrained, elastic
- [22] § STAR★Methods › Method details › Retraining transcriptomic clocks ↔ 08training-08152025/00elasticnet/00elasticnet-08152025.ipynb, lines 252–306 · score 0.54 · Benjamini Hochberg, FDR, sex, fit, models, gene
- [23] § STAR★Methods › Method details › DNA methylation analysis ↔ 11functional-08202025/01CpG2genelistepigenetics-08202025.R, lines 1–71 · score 0.53 · Human Methylation, EPIC, San, Illumina
- [24] § Results › Comparison of transcriptomic clocks and epigenetic clocks ↔ 10other-07302025/00plots-07302025/01rnaagecalc-08032025.ipynb, lines 264–321 · score 0.51 · DamAge, PhenoAge, Epigenetic clocks, cortical, Zhang2019, transcriptomic clock
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 1,144 lines · 38 KB · no license · 3 matches
- # %%
- import os, random
- import re
- import pyreadr
- import numpy as np
- import pandas as pd
- import warnings
- import optuna
- from scipy import stats
- import tensorflow as tf
- from typing import List, Tuple
- from sklearn.decomposition import PCA
- from sklearn.preprocessing import StandardScaler
- from sklearn.exceptions import ConvergenceWarning
- from scipy.interpolate import interp1d
- from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
- from sklearn.model_selection import KFold, cross_val_score
- from sklearn.linear_model import ElasticNetCV
- import statsmodels.api as sm
- import seaborn as sns
- import matplotlib.pyplot as plt
- from sklearn.covariance import LedoitWolf
- from statsmodels.tools.sm_exceptions import ConvergenceWarning, PerfectSeparationWarning
- from tensorflow.keras.models import Sequential
- from tensorflow.keras.models import load_model
- from tensorflow.keras import losses # For mse
- from tensorflow.keras.losses import MeanSquaredError
- from tensorflow.keras.layers import Dense, Dropout
- from tensorflow.keras.optimizers import Adam
- from tensorflow.keras.callbacks import EarlyStopping
- from sklearn.model_selection import train_test_split
- from sklearn.feature_selection import SelectFromModel
- from tensorflow.keras.models import load_model
- import tensorflow.keras.metrics as metrics
- import matplotlib.pyplot as plt
- from tensorflow.keras.models import Sequential
- from tensorflow.keras.layers import Dense, Dropout
- from tensorflow.keras.optimizers import Adam
- from tensorflow.keras.initializers import GlorotUniform
- # Shutdown warnings
- warnings.simplefilter("ignore", ConvergenceWarning)
- warnings.simplefilter("ignore", PerfectSeparationWarning)
- # Suppress all sklearn convergence warnings globally
- warnings.filterwarnings("ignore", category=ConvergenceWarning)
- warnings.filterwarnings("ignore", category=RuntimeWarning)
- # %%
- # -----------------------------------------------------------------
- # Set working directory
- wkdir = r"C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/00databases/01transcriptome/00data/"
- os.chdir(wkdir)
- # -----------------------------------------------------------------
- # File paths
- #uthbCounts = r"00uthb_genecounts-08152025.csv.gz"
- vabbCounts = r"00vabb_genecounts-08152025.csv.gz"
- # Reading annotation
- #uthbAnnot = r"00uthb_annotation-08152025.csv.gz"
- vabbAnnot = r"00vabb_annotation-08152025.csv.gz"
- # -----------------------------------------------------------------
- # Read gzipped CSVs
- #expr_uthb = pd.read_csv(uthbCounts, index_col=0, compression='gzip')
- expr_vabb = pd.read_csv(vabbCounts, index_col=0, compression='gzip')
- #annot_uthb = pd.read_csv(uthbAnnot, index_col=0, compression='gzip')
- annot_vabb = pd.read_csv(vabbAnnot, index_col=0, compression='gzip')
- # -----------------------------------------------------------------
- # %%
- # -----------------------------------------------------------------
- # Loading metadata
- # loading phenotype data
- phenoPath = "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/00databases/00phenotype/00vabb/00PhenoMeta_trainclock-07272025.csv"
- phenoData = pd.read_csv(phenoPath)
- # Create suffixes
- suffixes = ["_9", "_24", "_25", "_11"]
- # Replicate and modify pheno_data
- phenoData = pd.concat([
- phenoData.assign(SampleID="Sample" + phenoData["SampleID"].astype(str) + suffix)
- for suffix in suffixes
- ], ignore_index=True)
- phenoData.shape
- # -----------------------------------------------------------------
- # %%
- # Load cell types and SVAs
- cells = pd.read_csv("C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/05celltype-08042025/00vabb_cellprop_svas-08042025.csv")
- cells = cells.rename(columns={'name': 'SampleID'})
- cells.shape
- # %%
- # Merge in sequence after conversion
- merged_df_com = pd.merge(phenoData, cells, on='SampleID')
- meta = merged_df_com
- meta.shape
- # %%
- # -----------------------------------------------------------------
- # Subset meta to only required columns
- meta = meta[['SampleID', 'Sex', 'PMI', 'RIN', 'AgeDeath', 'ast', 'end', 'mic', 'neu', 'oli', 'opc', 'W_1', 'W_2']]
- # Columns not to scale
- exclude_cols = ['SampleID', 'Sex', 'AgeDeath']
- # Identify columns to scale
- cols_to_scale = [col for col in meta.columns if col not in exclude_cols]
- # Initialize scaler
- scaler = StandardScaler(with_mean=True, with_std=True)
- # Scale selected columns
- meta_scaled = meta.copy()
- meta_scaled[cols_to_scale] = scaler.fit_transform(meta_scaled[cols_to_scale])
- # Set SampleID as index
- meta_scaled = meta_scaled.set_index('SampleID')
- meta_scaled.head()
- # %%
- # -----------------------------------------------------------------
- # Function to clean sample names
- def clean_sample_name(name):
- # Match "Sample<number><letters><number>" and replace with "Sample<number>_<number>"
- return re.sub(r"(Sample\d+)[A-Za-z]+(\d+)", r"\1_\2", name)
- # Apply to a DataFrame
- # Example: expr_uthb.columns = [clean_sample_name(c) for c in expr_uthb.columns]
- expr_vabb.columns = [clean_sample_name(c) for c in expr_vabb.columns]
- expr_vabb.head()
- # -----------------------------------------------------------------
- # %%
- # -----------------------------------------------------------------
- # Define a function for the estimation FPKM of transcriptome data
- def counts_to_fpkm(counts: pd.DataFrame, gene_lengths: pd.Series, lengths_in: str = "bp") -> pd.DataFrame:
- """
- Convert raw counts to FPKM.
- counts: genes x samples
- gene_lengths: length per gene (index aligned to counts)
- lengths_in: 'bp' or 'kb'
- """
- assert set(counts.index).issubset(set(gene_lengths.index)), "Gene lengths missing for some genes"
- # Convert lengths to kilobases
- L = gene_lengths.loc[counts.index].astype(float)
- if lengths_in == "bp":
- L = L / 1e3
- elif lengths_in != "kb":
- raise ValueError("lengths_in must be 'bp' or 'kb'")
- # RPK: counts / length_kb
- rpk = counts.divide(L, axis=0)
- # FPKM: RPK / (total_mapped_reads_in_millions)
- per_sample_scaler = counts.sum(axis=0) / 1e6
- fpkm = rpk.divide(per_sample_scaler, axis=1)
- return fpkm
- # -----------------------------------------------------------------
- # Define a function for the estimation of gene lengths
- def extract_gene_bounds(df: pd.DataFrame, start_col='Start', end_col='End'):
- """
- Extract first start and last end for genes where Start/End columns may contain multiple positions separated by ';'.
- Args:
- df: DataFrame with at least 'GeneID', start_col, end_col
- start_col: name of the start column
- end_col: name of the end column
- Returns:
- DataFrame with 'GeneID', 'Start', 'End', 'GeneLength'
- """
- starts = []
- ends = []
- for s, e in zip(df[start_col], df[end_col]):
- # Split by ',' and convert to int
- start_vals = [int(x) for x in str(s).split(';') if x.strip().isdigit()]
- end_vals = [int(x) for x in str(e).split(';') if x.strip().isdigit()]
- if start_vals and end_vals:
- starts.append(min(start_vals)) # first start = smallest
- ends.append(max(end_vals)) # last end = largest
- else:
- starts.append(None)
- ends.append(None)
- result = pd.DataFrame({
- 'GeneID': df['GeneID'],
- 'Start': starts,
- 'End': ends
- })
- # Compute gene length
- result['GeneLength'] = result['End'] - result['Start'] + 1
- return result
- # %%
- # -----------------------------------------------------------------
- # Transforming to FPKM
- gene_info_vabb = extract_gene_bounds(annot_vabb)
- # Make Series: index = GeneID, values = GeneLength
- gene_lengths = pd.Series(gene_info_vabb["GeneLength"].values, index=gene_info_vabb["GeneID"])
- fpkm_matrix = counts_to_fpkm(expr_vabb, gene_lengths, lengths_in="bp")
- fpkm_matrix.shape
- # %%
- # -----------------------------------------------------------------
- # Filtering to FPKM
- # To avoid the influence of low count genes on the analysis result, genes with more than 30% samples having count per million (CPM) less than one were filtered out
- mfilter = (fpkm_matrix < 1).sum(axis=1) / fpkm_matrix.shape[1] <= 0.3
- fpkm_matrix = fpkm_matrix.loc[mfilter] # assign back
- fpkm_matrix.shape
- # %%
- # -----------------------------------------------------------------
- # Standardize by gene (rows)
- scaler = StandardScaler(with_mean=True, with_std=True)
- expr_z = pd.DataFrame(
- scaler.fit_transform(fpkm_matrix.T).T, # transpose -> scale -> transpose back
- index=fpkm_matrix.index, # keep gene names
- columns=fpkm_matrix.columns # keep sample names
- )
- expr_z.head()
- # %%
- # -----------------------------------------------------------------
- # Subsetting to only unique samples
- # Example: columns in your expression matrix
- columns = expr_z.columns # expr is your genes x samples DataFrame
- # Extract the prefix before the underscore
- prefixes = [col.split('_')[0] for col in columns]
- # Create a DataFrame to associate columns with prefixes
- col_df = pd.DataFrame({'col': columns, 'prefix': prefixes})
- # Set random seed for reproducibility
- seed = 42
- np.random.seed(seed)
- # Sample one column per prefix
- unique_cols = col_df.groupby('prefix')['col'].apply(lambda x: np.random.choice(x)).values
- # Subset your expression matrix
- expr_unique = expr_z[unique_cols]
- # Check
- print(expr_unique.shape)
- print(expr_z.shape)
- # %%
- # -----------------------------------------------------------------
- # Columns that were selected
- unique_cols_set = set(unique_cols)
- # Columns that were not selected
- excluded_cols = [col for col in expr_z.columns if col not in unique_cols_set]
- # Check
- print("Number of selected columns:", len(unique_cols))
- print("Number of excluded columns:", len(excluded_cols))
- # Optional: print first 10 excluded columns
- print("Excluded columns:", excluded_cols[:10])
- # %%
- # ------------------------------
- # Align samples
- common_samples = expr_unique.columns.intersection(meta_scaled.index)
- expr_unique = expr_unique[common_samples]
- meta_scaled2 = meta_scaled.loc[common_samples]
- print(expr_unique.shape)
- print(meta_scaled2.shape)
- # Plotting Age
- plt.hist(meta_scaled2['AgeDeath'], bins=30, edgecolor='black')
- plt.xlabel("Age")
- plt.ylabel("Count")
- plt.title("Age distribution")
- plt.show()
- # %%
- # ------------------------------
- # Keep only samples <30 or >60
- meta_subset = meta_scaled2[(meta_scaled2['AgeDeath'] < 30) | (meta_scaled2['AgeDeath'] > 60)].copy()
- # Add new variable: 0 = young (<30), 1 = old (>60)
- meta_subset['age_group'] = (meta_subset['AgeDeath'] > 60).astype(int)
- # Subset expression matrix to the same samples
- expr_subset = expr_unique[meta_subset.index]
- print(meta_subset.shape, expr_subset.shape)
- meta_subset[['AgeDeath', 'age_group']].head()
- # %%
- # ------------------------------
- # Keep only genes in clock
- coefPath = "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/06tranningclock-08152025/00elasticnet-08152025/02elastic_net_nonzero_coefficients-08152025.csv"
- # Load coefficients
- df_coef = pd.read_csv(coefPath, index_col=0)
- # Convert the single-column DataFrame to a Series
- coef_series = pd.Series(df_coef['coef'].values, index=df_coef.index, name='coef')
- # Ensure it's a Series
- coef_series = pd.Series(coef_series, name='coef')
- coef = df_coef
- genes_to_keep = coef.index
- expr_subset = expr_subset.loc[genes_to_keep]
- expr_subset.shape
- # %%
- # ------------------------------
- # Prepare data
- # ------------------------------
- # Keep covariates of interest
- covariates = meta_subset[['Sex', 'PMI', 'RIN']].copy()
- # One-hot encode Sex if necessary
- covariates = pd.get_dummies(covariates, columns=['Sex'], drop_first=True)
- # Add intercept
- Xcov_only = sm.add_constant(covariates).astype(float)
- # Outcome variable: 0 = young, 1 = old
- y = meta_subset['age_group'].values
- # Align samples with expression matrix
- expr_use = expr_subset
- common_samples = expr_use.columns.intersection(meta_subset.index)
- expr_use = expr_use[common_samples]
- Xcov_only = Xcov_only.loc[common_samples]
- y = y[np.isin(meta_subset.index, common_samples)]
- # ------------------------------
- # Fit null logistic model
- # ------------------------------
- null_model = sm.Logit(y, Xcov_only).fit(disp=0)
- p_hat = null_model.predict()
- # W matrix: diagonal of p*(1-p)
- W = np.diag(p_hat * (1 - p_hat))
- # ------------------------------
- # Compute z-scores for each gene
- # ------------------------------
- results = []
- for gene_id in expr_use.index:
- x = expr_use.loc[gene_id].values.reshape(-1,1) # already standardized
- # If gene has zero variance, set z=0
- if np.var(x) == 0:
- results.append((gene_id, 0.0))
- continue
- # Wald z-score: z = (x^T (y - p_hat)) / sqrt(x^T W x)
- numerator = x.T @ (y - p_hat)
- denominator = np.sqrt(x.T @ W @ x)
- z = float(numerator / denominator)
- results.append((gene_id, z))
- # ------------------------------
- # Compile results
- # ------------------------------
- deg_z = pd.DataFrame(results, columns=['gene_id','z_expr']).set_index('gene_id')
- deg_z.head()
- # %%
- # Plot histogram of the first few genes (deg_z.head())
- plt.figure(figsize=(6,4))
- plt.hist(deg_z['z_expr'], bins=10, color='skyblue', edgecolor='black')
- plt.xlabel('Z-score')
- plt.ylabel('Frequency')
- plt.title('Histogram of Z-scores')
- plt.show()
- # %%
- # ------------------------------
- # Start stochastic modelling
- # ------------------------------
- # Define helper functions
- def cov_from_corr_sd(corr: pd.DataFrame, sd: pd.Series) -> pd.DataFrame:
- """Covariance = D * R * D, with D=diag(sd)."""
- D = np.diag(sd.values)
- cov = D @ corr.values @ D
- return pd.DataFrame(cov, index=corr.index, columns=corr.columns)
- def shrink_corr_from_matrix(X: pd.DataFrame) -> pd.DataFrame:
- """
- Estimate correlation via Ledoit-Wolf shrinkage
- from an expression matrix (samples x genes).
- X = samples x genes
- """
- Xc = X - X.mean(axis=0)
- lw = LedoitWolf().fit(Xc.values)
- cov = pd.DataFrame(lw.covariance_, index=X.columns, columns=X.columns)
- d = np.sqrt(np.diag(cov.values))
- corr = cov / d[:, None] / d[None, :]
- return corr
- # %%
- # ------------------------------
- # Convert z-scores → age-specific means
- # ------------------------------
- def z_to_means(genes, z_age80_vs_25, space="z",
- baseline_mean_age25=None, baseline_sd_age25=None, se_per_gene=None):
- """
- space = "z": assume standardized space
- - age 25 mean = 0, sd = 1
- - age 80 mean = z
- space = "raw": use delta = z * SE
- """
- z_age80_vs_25 = z_age80_vs_25.reindex(genes)
- if space == "z":
- mean25 = pd.Series(0.0, index=genes)
- sd25 = pd.Series(1.0, index=genes) if baseline_sd_age25 is None else baseline_sd_age25.reindex(genes)
- mean80 = z_age80_vs_25.copy()
- sd80 = sd25.copy()
- elif space == "raw":
- if se_per_gene is None or baseline_mean_age25 is None:
- raise ValueError("Need baseline_mean_age25 and se_per_gene for raw mode")
- mean25 = baseline_mean_age25.reindex(genes)
- sd25 = pd.Series(1.0, index=genes) if baseline_sd_age25 is None else baseline_sd_age25.reindex(genes)
- delta = z_age80_vs_25 * se_per_gene.reindex(genes)
- mean80 = mean25 + delta
- sd80 = sd25
- else:
- raise ValueError("space must be 'z' or 'raw'")
- return mean25, sd25, mean80, sd80
- # %%
- # ------------------------------
- # Adjusted MVN simulation to reduce excessive collinearity
- # ------------------------------
- def simulate_mvn(n, mean, corr=None, sd=None, jitter=1e-3):
- """
- Simulate multivariate normal samples for genes with slight jitter to reduce
- perfect correlations for better convergence in ElasticNet.
- Parameters
- ----------
- n : int
- Number of samples.
- mean : pd.Series
- Mean per gene.
- corr : pd.DataFrame, optional
- Gene-gene correlation matrix.
- sd : pd.Series, optional
- Standard deviation per gene.
- jitter : float, optional
- Small value to add to diagonal of covariance to reduce collinearity.
- """
- genes = mean.index
- p = len(genes)
- # Default identity correlation / unit SD
- if corr is None:
- corr = pd.DataFrame(np.eye(p), index=genes, columns=genes)
- if sd is None:
- sd = pd.Series(1.0, index=genes)
- # Covariance matrix from correlation and SD
- cov = cov_from_corr_sd(corr, sd)
- # Add small jitter to diagonal to reduce perfect correlation
- cov += np.eye(p) * jitter
- # Draw samples
- X = np.random.multivariate_normal(mean.values, cov.values, size=n)
- return pd.DataFrame(X, columns=genes)
- # %%
- # ------------------------------
- # Define a Multivariate Normal simulation
- # ------------------------------
- def negbin_params_from_mean_k(mu, k):
- n_param = k
- p_param = k / (k + mu)
- return n_param, p_param
- def gaussian_copula_transform(n, corr):
- R = corr.values
- Z = np.random.multivariate_normal(mean=np.zeros(len(R)), cov=R, size=n)
- U = stats.norm.cdf(Z)
- return U
- def simulate_gaussian_copula_negbin(n, corr, mean_counts, k_overdisp):
- genes = mean_counts.index
- U = gaussian_copula_transform(n, corr)
- out = np.empty_like(U, dtype=np.int64)
- for j, g in enumerate(genes):
- mu = float(mean_counts[g])
- k = float(k_overdisp[g])
- n_param, p_param = negbin_params_from_mean_k(mu, k)
- out[:, j] = stats.nbinom.ppf(U[:, j], n=n_param, p=p_param).astype(int)
- return pd.DataFrame(out, columns=genes)
- # %%
- # ------------------------------
- # Running simulations for ages 20 and 60
- # ------------------------------
- # This generates
- genes = deg_z.index
- z = deg_z
- # Convert z-scores → age-specific means in z-space
- mean20, sd20, mean60, sd60 = z_to_means(genes, z, space="z")
- # Correlation of genes
- expr_use_T = expr_use.T # shape = (samples, genes)
- corr = shrink_corr_from_matrix(expr_use_T)
- # Ensure 1D Series and alignment
- mean20 = mean20.squeeze()
- mean60 = mean60.squeeze()
- sd20 = sd20.squeeze()
- sd60 = sd60.squeeze()
- genes_common = mean20.index.intersection(corr.index)
- mean20 = mean20.loc[genes_common]
- mean60 = mean60.loc[genes_common]
- sd20 = sd20.loc[genes_common]
- sd60 = sd60.loc[genes_common]
- corr = corr.loc[genes_common, genes_common]
- # ---- MVN samples ----
- samples20 = simulate_mvn(2000, mean20, corr=corr, sd=sd20)
- samples60 = simulate_mvn(2000, mean60, corr=corr, sd=sd60)
- samples20["age"] = 20
- samples60["age"] = 60
- mvn_samples = pd.concat([samples20, samples60], ignore_index=True)
- mvn_samples.head()
- # %%
- # ------------------------------
- # Running Gaussian Copula–NB simulations for ages 20 and 60
- # ------------------------------
- # This generates COUNTS
- # If z is DataFrame with one column
- z = z.squeeze() # now a Series
- # Example: map z-scores to mean counts (toy example)
- # You can adjust this to your real counts
- baseline_mu = pd.Series(100.0, index=genes_common) # baseline counts
- fold_change = np.exp(z[genes_common] * 0.2) # simple mapping z -> fold change
- mu20 = baseline_mu # age 20
- mu60 = baseline_mu * fold_change # age 60
- # Overdispersion parameter k (can be gene-specific)
- k20 = pd.Series(10.0, index=genes_common)
- k60 = pd.Series(10.0, index=genes_common)
- # Simulate
- samples20_nb = simulate_gaussian_copula_negbin(2000, corr, mu20, k20)
- samples60_nb = simulate_gaussian_copula_negbin(2000, corr, mu60, k60)
- # Add age labels
- samples20_nb["age"] = 20
- samples60_nb["age"] = 60
- # Concatenate
- copula_nb_samples = pd.concat([samples20_nb, samples60_nb], ignore_index=True)
- copula_nb_samples.head()
- # %%
- # ------------------------------
- # PCA analysis of stochastic genes
- # Extract gene expression only
- X = mvn_samples.drop(columns=["age"]).values
- ages = mvn_samples["age"].values
- pca = PCA(n_components=2)
- pcs = pca.fit_transform(X)
- # Wrap into DataFrame
- pca_df = pd.DataFrame(pcs, columns=["PC1", "PC2"])
- pca_df["age"] = ages
- # Plotting
- plt.figure(figsize=(8,6))
- sns.scatterplot(data=pca_df, x="PC1", y="PC2", hue="age", palette="coolwarm", alpha=0.3)
- plt.title("PCA trajectory of ages 20 → 60")
- plt.xlabel(f"PC1 ({pca.explained_variance_ratio_[0]*100:.1f}% variance)")
- plt.ylabel(f"PC2 ({pca.explained_variance_ratio_[1]*100:.1f}% variance)")
- plt.show()
- # %%
- # ------------------------------
- # Compute mean expression per gene for each age group
- mean_expr = mvn_samples.groupby(['age']).mean().T # Transpose to have genes as rows
- mean_expr = mean_expr.loc[:, [20, 60]] # Select only ages 20 and 60
- # Scatter plots of stochastic genes
- plt.figure(figsize=(8, 6))
- # Draw a line for each gene
- for gene in mean_expr.index:
- plt.plot([20, 60], [mean_expr.loc[gene, 20], mean_expr.loc[gene, 60]],
- color='gray', alpha=0.5, lw=0.8)
- # Add points on top
- plt.scatter([20]*len(mean_expr), mean_expr[20], color='blue', label='Age 20')
- plt.scatter([60]*len(mean_expr), mean_expr[60], color='red', label='Age 60')
- plt.xlabel('Age')
- plt.ylabel('Mean Gene Expression')
- plt.title('Gene-level trajectories: Age 20 → 60')
- plt.legend()
- plt.grid(True)
- plt.show()
- # %%
- # ------------------------------
- # Scatter plots of expression per sample between ages 20 and 60
- # ------------------------------
- # ------------------------------
- # Scatter plots of expression per sample (no sample_id)
- # ------------------------------
- # Keep only age 20 and 60
- samples_20_60 = mvn_samples[mvn_samples['age'].isin([20, 60])]
- # Split by age
- samples_20 = samples_20_60[samples_20_60['age'] == 20].drop(columns=['age'])
- samples_60 = samples_20_60[samples_20_60['age'] == 60].drop(columns=['age'])
- plt.figure(figsize=(8, 6))
- # Loop over rows (each row = one sample’s expression vector)
- for i in range(min(len(samples_20), len(samples_60))):
- plt.plot([20, 60],
- [samples_20.iloc[i].mean(), samples_60.iloc[i].mean()],
- color='gray', alpha=0.5, lw=0.8)
- # Scatter points
- plt.scatter([20]*len(samples_20), samples_20.mean(axis=1),
- color='blue', label='Age 20')
- plt.scatter([60]*len(samples_60), samples_60.mean(axis=1),
- color='red', label='Age 60')
- plt.xlabel('Age')
- plt.ylabel('Sample-level Mean Gene Expression')
- plt.title('Sample-level trajectories: Age 20 → 60')
- plt.legend()
- plt.grid(True)
- plt.show()
- # %%
- # ------------------------------
- # Age range and interpolation
- # ------------------------------
- ages = [20, 30, 40, 50, 60]
- # Interpolate gene means for each age
- mean_df = pd.DataFrame(index=genes_common, columns=ages, dtype=float)
- for gene in genes_common:
- mean_df.loc[gene] = np.linspace(mean20[gene], mean60[gene], num=len(ages))
- # Optional: interpolate SDs across ages
- sd_df = pd.DataFrame(index=genes_common, columns=ages, dtype=float)
- for gene in genes_common:
- sd_df.loc[gene] = np.linspace(sd20[gene], sd60[gene], num=len(ages))
- # ------------------------------
- # Generate MVN samples for each age
- # ------------------------------
- n_samples_per_age = 2000
- all_samples = []
- for age in ages:
- mean_vec = mean_df[age].astype(float)
- sd_vec = sd_df[age].astype(float)
- samples_age = simulate_mvn(n_samples_per_age, mean_vec, corr=corr, sd=sd_vec)
- samples_age["age"] = age
- all_samples.append(samples_age)
- # Combine all ages
- mvn_samples_multi_age = pd.concat(all_samples, ignore_index=True)
- mvn_samples_multi_age.head()
- # %%
- # Compute mean expression per gene for each age group
- mean_expr = mvn_samples_multi_age.groupby('age').mean().T # genes as rows
- # Optional: select a subset of ages to plot
- ages_to_plot = [20, 30, 40, 50, 60]
- mean_expr = mean_expr.loc[:, ages_to_plot]
- # Plot
- plt.figure(figsize=(10, 6))
- # Draw a line for each gene
- for gene in mean_expr.index:
- plt.plot(ages_to_plot, mean_expr.loc[gene, ages_to_plot],
- color='gray', alpha=0.5, lw=0.8)
- # Add points on top
- for age in ages_to_plot:
- plt.scatter([age]*len(mean_expr), mean_expr[age],
- label=f'Age {age}' if age in [20, 60] else "", # label only 20 & 60
- s=20,
- color='blue' if age==20 else 'red' if age==60 else 'green')
- plt.xlabel('Age')
- plt.ylabel('Mean Gene Expression')
- plt.title('Gene-level trajectories: Age 20 → 60')
- plt.legend()
- plt.grid(True)
- plt.show()
- # %%
- # ------------------------------
- # Sample-level trajectories across ages
- # ------------------------------
- ages_to_plot = [20, 30, 40, 50, 60]
- samples_multi_age = mvn_samples_multi_age[mvn_samples_multi_age['age'].isin(ages_to_plot)]
- plt.figure(figsize=(10, 6))
- # Pivot: rows = sample index, cols = ages, values = mean expression
- sample_means = samples_multi_age.drop(columns=['age']).mean(axis=1) # per-sample mean
- sample_means = pd.DataFrame({
- "age": samples_multi_age['age'].values,
- "mean_expr": sample_means.values
- })
- # Group by index position (pseudo-IDs) across ages
- for idx in range(sample_means.shape[0] // len(ages_to_plot)):
- subset = sample_means.iloc[idx::(sample_means.shape[0] // len(ages_to_plot))]
- plt.plot(subset["age"], subset["mean_expr"], color="gray", alpha=0.5, lw=0.8)
- # Scatter points on top
- for age in ages_to_plot:
- subset = sample_means[sample_means['age'] == age]
- plt.scatter([age]*len(subset), subset['mean_expr'],
- color='blue' if age==20 else 'red' if age==60 else 'green',
- alpha=0.7, s=20, label=f'Age {age}' if age in [20, 60] else "")
- plt.xlabel('Age')
- plt.ylabel('Sample-level Mean Gene Expression')
- plt.title('Sample-level gene expression trajectories')
- plt.legend()
- plt.grid(True)
- plt.show()
- # %%
- # ------------------------------
- # Align samples
- # ------------------------------
- common_samples = expr_use.columns.intersection(meta_subset.index)
- expr_use_aligned = expr_use[common_samples]
- age_series = meta_subset.loc[common_samples, 'AgeDeath']
- # ------------------------------
- # Define age bins
- # ------------------------------
- bins = [20, 30, 40, 50, 60, 70, 80] # adjust as needed
- age_bins = pd.cut(age_series, bins=bins, right=False)
- # Compute mean expression per gene per age bin
- mean_expr = expr_use_aligned.T.groupby(age_bins).mean().T # genes x bins
- # Compute numeric bin centers
- bin_centers = [interval.left + (interval.right - interval.left)/2 for interval in mean_expr.columns]
- # Ensure numeric values and fill missing
- mean_expr_numeric = mean_expr.copy()
- mean_expr_numeric = mean_expr_numeric.astype(float).interpolate(axis=1) # fill NaNs if any
- # Plot
- plt.figure(figsize=(10, 6))
- for gene in mean_expr_numeric.index:
- y = mean_expr_numeric.loc[gene, :].values # numeric values across bins
- plt.plot(bin_centers, y, color='gray', alpha=0.5, lw=0.8)
- # Overlay points for each bin
- for i, center in enumerate(bin_centers):
- plt.scatter([center]*len(mean_expr_numeric),
- mean_expr_numeric.iloc[:, i].values,
- color='blue', s=15)
- plt.xlabel('Age')
- plt.ylabel('Mean Gene Expression')
- plt.title('Gene-level trajectories across AgeDeath')
- plt.grid(True)
- plt.show()
- # %%
- # ------------------------------
- # Model training
- # ------------------------------
- def train_elastic_net(
- z_expr: pd.DataFrame,
- y_age: pd.Series,
- selected_genes: List[str],
- n_splits: int = 5,
- random_state: int = 42,
- l1_ratios: Tuple[float, ...] = (0.1, 0.3, 0.5, 0.7, 0.9)
- ) -> Tuple[ElasticNetCV, dict]:
- """Fit ElasticNetCV on selected genes, return model and CV metrics."""
- # Align genes and transpose so samples are rows
- X = z_expr.loc[selected_genes].T
- y = y_age.loc[X.index] # ensure samples match
- # Initialize CV
- cv = KFold(n_splits=n_splits, shuffle=True, random_state=random_state)
- # Fit ElasticNetCV on all data
- model = ElasticNetCV(l1_ratio=l1_ratios, alphas=None, cv=cv, max_iter=1000)
- model.fit(X, y)
- # Out-of-fold predictions
- preds = np.zeros(len(y), dtype=float)
- for train_idx, test_idx in cv.split(X):
- Xtr, Xte = X.iloc[train_idx], X.iloc[test_idx]
- ytr = y.iloc[train_idx]
- m = ElasticNetCV(l1_ratio=l1_ratios, alphas=None, cv=cv, max_iter=1000)
- m.fit(Xtr, ytr)
- preds[test_idx] = m.predict(Xte)
- # CV metrics
- r2 = r2_score(y, preds)
- mae = mean_absolute_error(y, preds)
- metrics = {"cv_r2": float(r2), "cv_mae": float(mae)}
- return model, metrics
- # %%
- # ------------------------------
- # Model training
- # ------------------------------
- # Features: genes x samples
- # Drop the 'age' column from simulated data
- X_sim = mvn_samples_multi_age.drop(columns=['age'])
- y_sim = mvn_samples_multi_age['age']
- # Features: genes x samples
- X_sim_selected = X_sim[genes] # genes are currently columns
- X_sim_selected = X_sim_selected.T # now genes are rows, samples are columns
- # Train the model
- model, metrics = train_elastic_net(
- z_expr=X_sim_selected, # or X_sim if using all genes
- y_age=y_sim,
- selected_genes=genes, # can be None to use all genes
- n_splits=5,
- random_state=42,
- l1_ratios=(0.1, 0.3, 0.5, 0.7, 0.9)
- )
- # %%
- # ------------------------------
- # Plotting metrics
- # ------------------------------
- # Convert to lists for plotting
- names = list(metrics.keys())
- values = list(metrics.values())
- # Create bar plot
- plt.figure(figsize=(5, 4))
- plt.bar(names, values, color=['skyblue', 'salmon'])
- plt.ylabel("Value")
- plt.title("ElasticNet CV Metrics")
- plt.ylim(0, max(values)*1.2) # add some space on top
- plt.show()
- # %%
- # ------------------------------
- # Saving Coefficients
- # ------------------------------
- # Coefficients as Series (gene names as index)
- coef_series = pd.Series(model.coef_, index=genes, name='coef')
- coef_series.to_csv("C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/00elasticnet-08152025/01elastic_net_coefficients-08152025.csv", header=True)
- # Optional only non-zero coeficients
- nonzero_coef = coef_series[coef_series != 0]
- nonzero_coef.to_csv("C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/00elasticnet-08152025/02elastic_net_nonzero_coefficients-08152025.csv", header=True)
- # Print intercept
- print("Intercept:", model.intercept_)
- # Save intercept
- pd.Series({"intercept": model.intercept_}).to_csv(
- "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/00elasticnet-08152025/03elastic_net_intercept-08152025.csv"
- )
- # %%
- # ------------------------------
- # Predicting with Coefficients
- # ------------------------------
- # Expression matrix for excluded samples
- expr_excluded = expr_z
- # Make sure the same genes are used (selected_genes)
- X_excluded = expr_excluded.loc[genes]
- # Load coefficients
- df_coef = pd.read_csv("C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/00elasticnet-08152025/02elastic_net_nonzero_coefficients-08152025.csv", index_col=0)
- # Convert the single-column DataFrame to a Series
- coef_series = pd.Series(df_coef['coef'].values, index=df_coef.index, name='coef')
- # Ensure it's a Series
- coef_series = pd.Series(coef_series, name='coef')
- # Subset new expression matrix to selected genes
- X_new = X_excluded.loc[coef_series.index].T # samples x genes
- # Load the intercept CSV
- df_intercept = pd.read_csv(
- "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/00elasticnet-08152025/03elastic_net_intercept-08152025.csv",
- index_col=0
- )
- # Extract the value
- intercept = df_intercept.loc["intercept"].values[0]
- # Dot product to get predicted age
- predicted_age = X_new.dot(coef_series) + intercept
- # Convert to Series
- predicted_age = pd.Series(predicted_age, index=X_new.index, name='Predicted_Age')
- # %%
- # If predicted_age is a Pandas Series
- plt.figure(figsize=(6,4))
- plt.hist(predicted_age, bins=30, edgecolor="black")
- plt.xlabel("Predicted Age")
- plt.ylabel("Frequency")
- plt.title("Histogram of Predicted Age")
- plt.show()
- # %%
- # Now align with predicted_age
- common_ids = predicted_age.index.intersection(meta_scaled.index)
- ages = meta_scaled.loc[common_ids, "AgeDeath"]
- pred = predicted_age.loc[common_ids]
- # Correlation
- corr = pred.corr(ages)
- print(f"Correlation between predicted and AgeDeath: {corr:.3f}")
- # Scatter plot
- plt.figure(figsize=(5,5))
- plt.scatter(ages, pred, alpha=0.6, edgecolor="k")
- plt.xlabel("Age at Death")
- plt.ylabel("Predicted Age")
- plt.title(f"Predicted vs Actual Age at Death\nCorrelation = {corr:.3f}")
- plt.show()
- # %%
- # ------------------------------
- # Training Deep Learning Model
- # ------------------------------
- seed = 42
- os.environ['PYTHONHASHSEED'] = str(seed)
- os.environ['TF_DETERMINISTIC_OPS'] = '1'
- np.random.seed(seed)
- random.seed(seed)
- tf.random.set_seed(seed)
- # Define a flexible Keras model
- def create_model(trial, input_dim):
- n_layers = trial.suggest_int("n_layers", 1, 4)
- model = Sequential()
- for i in range(n_layers):
- units = trial.suggest_int(f"units_l{i}", 32, 512, step=32)
- dropout_rate = trial.suggest_float(f"dropout_l{i}", 0.0, 0.5)
- if i == 0:
- model.add(Dense(
- units, activation='relu', input_dim=input_dim,
- kernel_initializer=GlorotUniform(seed=42)
- ))
- else:
- model.add(Dense(
- units, activation='relu',
- kernel_initializer=GlorotUniform(seed=42)
- ))
- # Fix dropout randomness
- model.add(Dropout(dropout_rate, seed=42))
- # Output layer
- model.add(Dense(1, activation='linear', kernel_initializer=GlorotUniform(seed=42)))
- lr = trial.suggest_float("learning_rate", 1e-4, 1e-2, log=True)
- optimizer = Adam(learning_rate=lr)
- model.compile(optimizer=optimizer, loss='mse', metrics=['mae'])
- return model
- # %%
- # ------------------------------
- # Training deep models from the Elastic Net with cross-validation
- # ------------------------------
- # Setting seeds for replicability
- seed = 42
- np.random.seed(seed)
- # Adjust shape of samples
- X_sim_selected = X_sim[genes] # genes are currently columns
- # Specify folder to save models
- # change this to your desired path
- MODEL_SAVE_PATH = "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/01deeplearning-08162025/"
- os.makedirs(MODEL_SAVE_PATH, exist_ok=True)
- # Define a function
- def objective(trial):
- # Create model with trial hyperparameters
- model = create_model(trial, input_dim=X_sim_selected.shape[1])
- # Early stopping
- es = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
- # Train/validation split
- X_train, X_val, y_train, y_val = train_test_split(X_sim_selected, y_sim, test_size=0.2, random_state=42)
- # Train model
- history = model.fit(
- X_train, y_train,
- validation_data=(X_val, y_val),
- epochs=100,
- batch_size=trial.suggest_categorical("batch_size", [16, 32, 64, 128]),
- callbacks=[es],
- verbose=0
- )
- # Save the trained model in the specified folder
- model_filename = os.path.join(MODEL_SAVE_PATH, f"trial_{trial.number}_model.h5")
- model.save(model_filename)
- print(f"Saved model for trial {trial.number} as {model_filename}")
- # Return validation metric for optimization
- val_mae = min(history.history['val_mae'])
- return val_mae
- # Run Optuna optimization
- study = optuna.create_study(direction="minimize", sampler=optuna.samplers.TPESampler(seed=42))
- study.optimize(objective, n_trials=50)
- # Print best trial
- print("Best trial:")
- trial = study.best_trial
- print(trial.params)
- # %%
- # Build path to best model
- best_model_filename = os.path.join(MODEL_SAVE_PATH, f"trial_{study.best_trial.number}_model.h5")
- print(best_model_filename)
- # Print best trial
- print("Best trial:")
- trial = study.best_trial
- print(trial.params)
- # %%
- # ------------------------------
- # Prepare data for prediction
- # ------------------------------
- # Filter expr_excluded to only these genes
- expr_excluded_filtered = expr_z[excluded_cols].loc[genes.intersection(expr_z.index)]
- # Transpose to samples x genes
- X_excluded = expr_excluded_filtered.T
- X_excluded.shape
- # %%
- # ------------------------------
- # Load the best model (without compiling)
- # ------------------------------
- best_model_filename = "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/01deeplearning-08162025/trial_47_model.h5"
- best_model = load_model(best_model_filename, compile=False)
- print(f"Loaded best model from {best_model_filename}")
- # ------------------------------
- # Re-compile the model with standard loss/metrics
- # ------------------------------
- best_model.compile(optimizer='adam', loss='mse', metrics=['mae'])
- # ------------------------------
- # Save under a new name
- # ------------------------------
- new_model_filename = "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/00Stochasticdeepclock-08192025.h5"
- best_model.save(new_model_filename)
- print(f"Saved model as {new_model_filename}")
- # ------------------------------
- # Predict on new data
- # ------------------------------
- # X_excluded should have the same shape/features as the training data
- predicted_values = best_model.predict(X_excluded)
- print("Predictions shape:", predicted_values.shape)
- # ------------------------------
- # Predict
- # ------------------------------
- predicted_values = best_model.predict(X_excluded)
- print(predicted_values.shape)
- # %%
- # If predicted_age is a Pandas Series
- plt.figure(figsize=(6,4))
- plt.hist(predicted_values, bins=30, edgecolor="black")
- plt.xlabel("Predicted Age")
- plt.ylabel("Frequency")
- plt.title("Histogram of Predicted Age")
- plt.show()
- # %%
- # Assume X_excluded has samples as rows and their index contains the sample IDs
- pred = pd.Series(predicted_values.flatten(), index=X_excluded.index, name="PredictedAge")
- # Align with actual ages
- common_ids = pred.index.intersection(meta_scaled.index)
- ages = meta_scaled.loc[common_ids, "AgeDeath"]
- pred = pred.loc[common_ids]
- # Correlation
- corr = pred.corr(ages)
- print(f"Correlation between predicted and AgeDeath: {corr:.3f}")
- plt.figure(figsize=(5,5))
- plt.scatter(ages, pred, alpha=0.6, edgecolor="k")
- plt.xlabel("Age at Death")
- plt.ylabel("Predicted Age")
- plt.title(f"Predicted vs Actual Age at Death\nCorrelation = {corr:.3f}")
- plt.show()
- # %%
- # Saving predictions
- # Create a DataFrame with sample IDs and predicted age
- common_index = predicted_age.index.intersection(pred.index)
- df_pred = pd.DataFrame({
- "SampleID": common_index,
- "Predicted_Age_stochastic_elastic": predicted_age.loc[common_index].values,
- "Predicted_Age_stochastic_deep": pred.loc[common_index].values
- })
- # Save to CSV
- df_pred.to_csv(
- "C:/Users/jjm262/OneDrive - Yale University/Documents/Documents/00yale/04fourthyear/01projects/03tclock/02results/07stochasticclock-08172025/04vabb_prediction-08152025.csv",
- index=False
- )
- print("Predicted ages saved to 'predicted_age_samples.csv'.")
0stochastic-0817025.ipynb at commit a4dc4b1, no license · at the source
Overview
- Division of Human Genetics, Department of Psychiatry, Yale University School of Medicine, New Haven, CT, USA
- National Center for PTSD, US Department of Veterans Affairs, West Haven, CT, USA
- Biosciences Division, Oak Ridge National Laboratory, Oak Ridge, TN, USA
- Psychiatry Service, VA Connecticut Health Care System, West Haven, CT, USA
- The University of Tennessee, Knoxville, TN, USA
- Rhodes College, Memphis, TN, USA
- Department of Psychiatry, Geisel School of Medicine at Dartmouth, Lebanon, NH 03756, USA
- Louis A. Faillace, MD, Department of Psychiatry and Behavioral Sciences, McGovern Medical School, University of Texas Health Science Center at Houston, Houston, TX, USA
- MD Anderson Cancer Center, University of Texas Health Science Center at Houston Graduate School of Biomedical Sciences, Houston, TX, USA
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repositories
Its files are read in the Code ↔ Paper reader above, with 24 matches between paragraphs and lines of code.
martinezjaime/PFC-Transcriptomic
a4dc4b14a9abe71a0592da6aeb67085ad3e86026, 20 April 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
44 files
- 00GeneCount/
00aligment_03252025.sh , Shell, 64 lines - 00GeneCount/
01count_03282025.sh , Shell, 18 lines - 00GeneCount/
01count_07252025.sh , Shell, 14 lines - 00GeneCount/
02count_07252025.sh , Shell, 14 lines - 00GeneCount/
03count_07252025.sh , Shell, 17 lines - 00GeneCount/
04extractgenecounts-0815 , R, 64 lines2025.R - 00GeneCount/
05extractgenecounts_uthe , R, 111 linesalth-08182025.R - 01GEOcohorts/
00downloadgeo-08032025.R , R, 42 lines - 01GEOcohorts/
01rnagecalc-08052025.R , R, 150 lines, 1 match - 01GEOcohorts/
03geocohorts_tomatrix-08 , R, 58 lines162025.R - 01ragecalc-07272025/
00rnagecalc-07272025.R , R, 106 lines, 1 match - 03epigenetic-07302025/
00epigenetic-08042025.ip , Jupyter, 67 linesynb - 03epigenetic-07302025/
01epigenetic-08022025.ip , Jupyter, 180 linesynb - 03epigenetic-07302025/
02epigenetic-07302025.ip , Jupyter, 146 linesynb - 03epigenetic-07302025/
04epigenetic-07302025.sh , Shell, 17 lines - 03epigenetic-07302025/
RunStochClocks.R , R, 55 lines - 03epigenetic-07302025/
Untitled.ipynb , Jupyter, 4 lines - 05stochastic-07272025/
00modelling-08012025.ipy , Jupyter, 265 linesnb - 05stochastic-07272025/
43587_2024_600_MOESM3_ES , R, 55 linesM/ RunStochClocks.R - 06celltype_prop-08042025
/ , R, 164 lines00celltype_proportions-0 8042025.R - 07assoc-08052025/
00uthealth-08052025.ipyn , Jupyter, 334 lines, 1 matchb - 07assoc-08052025/
01vabb-08062025.ipynb , Jupyter, 308 lines, 1 match - 07assoc-08052025/
02uthealth-08282025.ipyn , Jupyter, 349 lines, 1 matchb - 07assoc-08052025/
03uthealth-09012025.ipyn , Jupyter, 382 linesb - 07assoc-08052025/
04vabb-09012025.ipynb , Jupyter, 554 lines, 1 match - 08training-08152025/
00elasticnet/ , Jupyter, 543 lines, 2 matches00elasticnet-08152025.ip ynb - 08training-08152025/
00predictage-08162025.ip , Jupyter, 528 linesynb - 08training-08152025/
01deeplearning/ , Jupyter, 441 lines, 1 match00deeplearnin-08152025.i pynb - 08training-08152025/
01predictage-08172025.ip , Jupyter, 528 linesynb - 09stochastic-08172025/
0stochastic-0817025.ipyn , Jupyter, 1,144 lines, 3 matchesb - 10other-07302025/
00plots-07302025/ , Jupyter, 161 lines00forestplots-08062025.i pynb - 10other-07302025/
00plots-07302025/ , Jupyter, 134 lines01forestplots-08062025.i pynb - 10other-07302025/
00plots-07302025/ , Jupyter, 377 lines, 2 matches01rnaagecalc-08032025.ip ynb - 10other-07302025/
00plots-07302025/ , Jupyter, 152 lines02rnaagecalc-07302025.ip ynb - 10other-07302025/
01plots-08242025/ , Jupyter, 742 lines, 1 match00plots-08242025.ipynb - 10other-07302025/
01plots-08242025/ , Jupyter, 585 lines, 1 match01plots-08262025.ipynb - 10other-07302025/
01plots-08242025/ , Jupyter, 220 lines02age_squared_correlatio n-08272025.ipynb - 10other-07302025/
02plots-08272025/ , Jupyter, 358 lines, 2 matches00plots-08272025.ipynb - 10other-07302025/
03plots-08272025/ , Jupyter, 234 lines, 2 matches00plots-08282025.ipynb - 10other-07302025/
04plots-0828205/ , Jupyter, 650 lines, 1 match00plots-08282025.ipynb - 11functional-08202025/
00functional_analysis-08 , Jupyter, 463 lines, 1 match202025.ipynb - 11functional-08202025/
01CpG2genelistepigenetic , R, 96 lines, 1 matchs-08202025.R - 11functional-08202025/
02genelistRNAAgeCalc-082 , R, 58 lines02025.R - README.md, Text, 81 lines
Jacobson-CompSysBio/MENTOR-py
9bea8a4963bccf5d5842c72dcc6f6207b164a752, 17 July 2024Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
24 files
- src/
mentor/ , Python, 10 lines__init__.py - src/
mentor/ , Python, 93 lines_cluster.py - src/
mentor/ , Python, 52 lines_datasets.py - src/
mentor/ , Python, 91 lines_fancydend.py - src/
mentor/ , Python, 210 lines_metrics.py - src/
mentor/ , Python, 132 lines_rwrtoolkit.py - src/
mentor/ , Python, 6 lines_utils.py - src/
mentor/ , Python, 1 line_version.py - src/
mentor/ , Python, 340 lines, 1 matchcli.py - src/
mentor/ , R, 432 linescreate_dendogram.R - src/
mentor/ , R, 163 linesdendrogram.R - src/
mentor/ , R, 207 linesheatmaps.R - src/
mentor/ , R, 352 linespolar_dendrogram.R - src/
mentor/ , R, 118 linesrun_cv.R - src/
mentor/ , R, 309 linesrwr_cv.R - src/
mentor/ , R, 287 linesrwr_utils.R - src/
mentor/ , R, 288 linessubclustered_dendrogram. R - tests/
test_basic.py , Python, 77 lines - tests/
test_cli.py , Python, 123 lines - tests/
test_clustering.py , Python, 149 lines - tests/
test_rwrtoolkit.py , Python, 54 lines - tests/
test_sample_data.py , Python, 80 lines - LICENSE, License, 8 lines
- README.md, Text, 163 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 65 scripts, each with its path and the digest of its content;
- 24 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
- geo:GSE102556, at NCBI GEO; found in “Data and code availability”
Code and data availability statement
The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to a dataset: NCBI GEO GSE102556
- it points to the authors' code: Jacobson-CompSysBio/
MENTOR-py - it says that the data are available on request
- it says that the code is available on request
Read it in the paper: doi.org/10.1016/j.isci.2026.116439.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 2, 28 September 2026
- Authors: added José J Martínez-Magaña (0000-0003-0390-8252); Janitza L Montalvo-Ortiz (0000-0002-7657-8365); removed José J Martínez-Magaña; Janitza L Montalvo-Ortiz
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 18 authors, 7 keywords, 9 funders, 125 references.
Cite
This paper
Martínez-Magaña, J. J., Vlot, A. H., Sullivan, K. A., Jacobson, D. A., Krystal, J. H., Girgenti, M. J., Núnez-Ríos, D. L., Nagamatsu, S. T., Andrade-Brito, D. E., Merlet, J., Townsend, A., Wells, A., Alvarez, C., Lane, M., Holtzheimer, P. E., Traumatic Stress Brain Research Group, Walss-Bass, C., & Montalvo-Ortiz, J. L. (2026). Decoding the role of transcriptomic clocks in the human prefrontal cortex. iScience, 29(7), 116439. https://
BibTeX
@article{martinezmagana2
author = {Martínez-Magaña, José J and Vlot, Anna HC and Sullivan, Kyle A and Jacobson, Daniel A and Krystal, John H and Girgenti, Matthew J and Núnez-Ríos, Diana L and Nagamatsu, Sheila T and Andrade-Brito, Diego E and Merlet, Jean and Townsend, Alice and Wells, Alana and Alvarez, Christiane and Lane, Matthew and Holtzheimer, Paul E and {Traumatic Stress Brain Research Group} and Walss-Bass, Consuelo and Montalvo-Ortiz, Janitza L},
title = {{Decoding the role of transcriptomic clocks in the human prefrontal cortex}},
journal = {iScience},
year = {2026},
month = jul,
volume = {29},
number = {7},
pages = {116439},
publisher = {Elsevier},
issn = {2589-0042},
doi = {10.1016/
url = {https://
pmid = {42491807},
pmcid = {PMC13378135}
}
RIS
TY - JOUR
AU - Martínez-Magaña, José J
AU - Vlot, Anna HC
AU - Sullivan, Kyle A
AU - Jacobson, Daniel A
AU - Krystal, John H
AU - Girgenti, Matthew J
AU - Núnez-Ríos, Diana L
AU - Nagamatsu, Sheila T
AU - Andrade-Brito, Diego E
AU - Merlet, Jean
AU - Townsend, Alice
AU - Wells, Alana
AU - Alvarez, Christiane
AU - Lane, Matthew
AU - Holtzheimer, Paul E
AU - Traumatic Stress Brain Research Group
AU - Walss-Bass, Consuelo
AU - Montalvo-Ortiz, Janitza L
TI - Decoding the role of transcriptomic clocks in the human prefrontal cortex
T2 - iScience
J2 - iScience
PY - 2026
DA - 2026/
VL - 29
IS - 7
SP - 116439
SN - 2589-0042
PB - Elsevier
DO - 10.1016/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1016/
"type": "article-journal",
"title": "Decoding the role of transcriptomic clocks in the human prefrontal cortex",
"container-title": "iScience",
"author": [
{
"family": "Martínez-Magaña",
"given": "José J"
},
{
"family": "Vlot",
"given": "Anna HC"
},
{
"family": "Sullivan",
"given": "Kyle A"
},
{
"family": "Jacobson",
"given": "Daniel A"
},
{
"family": "Krystal",
"given": "John H"
},
{
"family": "Girgenti",
"given": "Matthew J"
},
{
"family": "Núnez-Ríos",
"given": "Diana L"
},
{
"family": "Nagamatsu",
"given": "Sheila T"
},
{
"family": "Andrade-Brito",
"given": "Diego E"
},
{
"family": "Merlet",
"given": "Jean"
},
{
"family": "Townsend",
"given": "Alice"
},
{
"family": "Wells",
"given": "Alana"
},
{
"family": "Alvarez",
"given": "Christiane"
},
{
"family": "Lane",
"given": "Matthew"
},
{
"family": "Holtzheimer",
"given": "Paul E"
},
{
"literal": "Traumatic Stress Brain Research Group"
},
{
"family": "Walss-Bass",
"given": "Consuelo"
},
{
"family": "Montalvo-Ortiz",
"given": "Janitza L"
}
],
"container-title-short":
"volume": "29",
"issue": "7",
"page": "116439",
"DOI": "10.1016/
"PMID": "42491807",
"PMCID": "PMC13378135",
"ISSN": "2589-0042",
"publisher": "Elsevier",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
2
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s41514-026-00391-9 [code]
- Region-specific transcriptional signatures of brain aging in the absence of neuropathology at the single-cell level.Journal: npj agingIn common: circlize, ComplexHeatmap, cowplot, 12 other tools, genetics / omics, 5 references
- [2] doi:10.1093/bioinformatics/btag592 [code]
- Network-based stratification of allele-specific expression reveals patient subgroups in Huntington's disease.Journal: Bioinformatics (Oxford, England)In common: STAR, igraph, circlize, 14 other tools, genetics / omics, 3 references
- [3] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: glmnet, Keras, igraph, 16 other tools
- [4] doi:10.1016/j.xcrm.2026.102766 [code]
- A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.Journal: Cell reports. MedicineIn common: Keras, igraph, circlize, 15 other tools, genetics / omics, 1 reference
- [5] doi:10.1038/s41598-026-48613-0 [code]
- An snRNA-seq aging clock for the fruit fly head sheds light on sex-biased aging.Journal: Scientific reportsIn common: Keras, TensorFlow, seaborn, 5 other tools, genetics / omics, 8 references
- [6] doi:10.1126/sciadv.aeg3223 [code]
- The extreme diversity of retinal amacrine cells has deep evolutionary roots.Journal: Science advancesIn common: glmnet, igraph, circlize, 13 other tools, genetics / omics, 1 reference
- [7] doi:10.1093/neuonc/noag128 [code]
- Spatially-resolved single-cell imaging of melanoma brain metastases identifies localized immune patterns predictive of immune checkpoint blockade response.Journal: Neuro-oncologyIn common: glmnet, Keras, circlize, 12 other tools, genetics / omics
- [8] doi:10.1038/s41586-026-10629-x [code]
- Whole-genome duplication shaped cell-type evolution in the vertebrate brain.Journal: NatureIn common: igraph, circlize, NetworkX, 12 other tools, genetics / omics, 2 references
- [9] doi:10.1038/s41467-026-76676-0 [code]
- Determinants of functional burden pleiotropy and gene dosage responses across human traits.Journal: Nature communicationsIn common: igraph, circlize, ComplexHeatmap, 13 other tools, genetics / omics
- [10] doi:10.1038/s41467-026-76675-1 [code]
- Long-read proteogenomic atlas of human neuronal differentiation reveals isoform diversity informing neurodevelopmental risk mechanisms.Journal: Nature communicationsIn common: STAR, igraph, NetworkX, 12 other tools, genetics / omics, 1 reference
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 65 scripts, and 24 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:8e587c8ef7b52599…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
