OSCR

Single-cell machine learning uncovers genetically anchored, cell-type specific programs of Alzheimer's disease.

Code ↔ Paper

40 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 40 matches
  1. [1] § Methods › Cell type annotations across datasets ↔ revision/sea-ad_validation_analysis/pseudotime/label_seaad_subpops_centroid.py, lines 1 to 54 · score 0.96 · highest Pearson correlation, Mathys reference matrix, centroid correlations, expression vector, SEA AD cell, shared transcriptome · Report a mismatch
  2. [2] § Methods › GWAS enrichment ↔ data_tables.ipynb, lines 1190 to 1337 · score 0.88 · mappedGenes, NHGRI EBI, background gene, odds ratios, Overlapping genes, mondo · Report a mismatch
  3. [3] § Results › Cell type-specific biomarker discovery beyond demographics and APOE genotype ↔ revision/Revision_Figures/drug_bank_target_identification.ipynb, lines 172 to 229 · score 0.85 · HIF1A, COL4A3, IFI44L, ARL17B, CLDN11, stress · Report a mismatch
  4. [4] § Methods › Pseudotime inference and trajectory analysis ↔ revision/sea-ad_validation_analysis/pseudotime/pseudotime_seaad_centroid.py, lines 193 to 232 · score 0.84 · neighbor graph, highly variable genes, diffusion map, Scanpy, fewer, Seurat · Report a mismatch
  5. [5] § Methods › TriSCOPE predictive modelling ↔ revision/sea-ad_validation_analysis/Multirun_cell_on_cell_genes_apoe_seaad.py, lines 490 to 575 · score 0.82 · precision recall curve, Prediction probabilities, AutoML, ROC AUC, F1 score, retraining · Report a mismatch
  6. [6] § Methods › TriSCOPE predictive modelling ↔ revision/sea-ad_validation_analysis/ein_Multirun_cell_on_cell_genes_apoe_seaad.py, lines 509 to 594 · score 0.82 · precision recall curve, Prediction probabilities, AutoML, ROC AUC, F1 score, retraining · Report a mismatch
  7. [7] § Results › Subcluster profiles reveal transcriptional gradients of ad progression ↔ revision/Revision_Figures/drug_bank_target_identification.ipynb, lines 361 to 427 · score 0.81 · LRP1B, HSP90AA1, trajectory influencing, AD genes, canonical, NKAIN2 · Report a mismatch
  8. [8] § Methods › GWAS–eQTL colocalization analysis ↔ revision/code/Coloc.R, lines 139 to 203 · score 0.81 · eQTLs, odds ratios, deduplicated, pos, chromosomes, allele · Report a mismatch
  9. [9] § Methods › Cell type annotations across datasets ↔ revision/sea-ad_validation_analysis/pseudotime/label_seaad_subpops.py, lines 1 to 80 · score 0.78 · Mathys reference matrix, SEA AD broad, centroid correlations, Pearson, margin, confidence · Report a mismatch
  10. [10] § Methods › Cell type annotations across datasets ↔ revision/sea-ad_validation_analysis/pseudotime/pseudotime_seaad_centroid.py, lines 193 to 232 · score 0.77 · highly variable genes, Scanpy, flavor, graph, seurat, neighbors · Report a mismatch
  11. [11] § Methods › Cell type annotations across datasets ↔ revision/Revision_Figures/pseudotime_hvg.ipynb, lines 44 to 82 · score 0.77 · highly variable genes, AnnData, Scanpy, HVGs, flavor, seurat · Report a mismatch
  12. [12] § Methods › GWAS–eQTL colocalization analysis ↔ revision/code/Coloc.R, lines 139 to 203 · score 0.76 · GTEx v10, eQTLs, chromosome, allele, variant, position · Report a mismatch
  13. [13] § Methods › Pathway analysis of predictive and differentially expressed gene features ↔ figure2.ipynb, lines 705 to 844 · score 0.75 · GO_Molecular_Function_2023, prerank, GSEA, barplots, Inhibitory Neurons, pathway · Report a mismatch
  14. [14] § Methods › Data source and cohort selection ↔ revision/sea-ad_validation_analysis/pseudotime/pseudotime_seaad_centroid.py, lines 1 to 68 · score 0.74 · AD neuropathological Change, cognitive status, SEA AD, Intermediate, dementia, definitions · Report a mismatch
  15. [15] § Methods › Pathway analysis of predictive and differentially expressed gene features ↔ revision/Revision_Figures/figure_6_revision.ipynb, lines 190 to 336 · score 0.73 · GO_Molecular_Function_2023, prerank, GSEA, barplots, Inhibitory Neurons, pathway · Report a mismatch
  16. [16] § Results › Subcluster level expression improves AD prediction ↔ revision/Revision_Figures/drug_bank_target_identification.ipynb, lines 172 to 229 · score 0.73 · P2RY14, RASGEF1B, CA4, CPLX3, PNISR, TIMP3 · Report a mismatch
  17. [17] § Results › Genetic colocalization anchors cell-type–specific transcriptional predictors ↔ revision/Revision_Figures/Figure_10_revision.ipynb, lines 1 to 95 · score 0.72 · Strong colocalization, pp h4, AD GWAS, eQTL, tissue, 1 % · Report a mismatch
  18. [18] § Results › Cell type-specific biomarker discovery beyond demographics and APOE genotype ↔ figure2.ipynb, lines 451 to 566 · score 0.70 · square root, raw importance, APOE genotype, Inhibitory Neurons, Top predictive, predictive features · Report a mismatch
  19. [19] § Methods › External validation using independent differential expression studies ↔ revision/Revision_Figures/correlations_table.ipynb, lines 2796 to 2833 · score 0.69 · external DEG ranks, absolute log fold, Spearman rank correlation, predictor ranks, fold change · Report a mismatch
  20. [20] § Methods › Pseudotime inference and trajectory analysis ↔ revision/Revision_Figures/pseudotime_hvg.ipynb, lines 44 to 82 · score 0.68 · highly variable genes, Scanpy, HVGs, fewer, circularity, Seurat · Report a mismatch
  21. [21] § Methods › Differential expression analysis ↔ revision/code/Contrasts_DE_w_RandomEffect.R, lines 298 to 339 · score 0.67 · edgeR, RUV covariates, Deviance, RUVr, residual, seq · Report a mismatch
  22. [22] § Methods › Clinical and neuropathological definitions of alzheimer’s disease ↔ revision/sea-ad_validation_analysis/pseudotime/pseudotime_seaad_centroid.py, lines 1 to 68 · score 0.66 · probable AD, cognitive status, moderate, Dementia, definitions, DCFDX · Report a mismatch
  23. [23] § Methods › Differential expression analysis ↔ revision/sea-ad_validation_analysis/DEG_SEAAD_chunking.R, lines 265 to 344 · score 0.66 · nAGQ, lme4, bobyqa, glmer, optimizer, coefficients · Report a mismatch
  24. [24] § Methods › Differential expression analysis ↔ revision/code/Contrasts_DE_w_RandomEffect.R, lines 385 to 439 · score 0.66 · nAGQ, lme4, bobyqa, glmer, optimizer, coefficients · Report a mismatch
  25. [25] § Methods › Data source and cohort selection ↔ revision/sea-ad_validation_analysis/data_confirmation.ipynb, lines 27 to 84 · score 0.65 · AD neuropathological Change, cognitive status, neurotypical, Intermediate, dementia, SEA AD · Report a mismatch
  26. [26] § Methods › Pseudotime inference and trajectory analysis ↔ revision/Revision_Figures/pseudotime_hvg.ipynb, lines 84 to 132 · score 0.64 · selected root cell, random seed, Diffusion, DPT, candidate, robustness · Report a mismatch
  27. [27] § Methods › Pseudotime inference and trajectory analysis ↔ revision/Revision_Figures/runtime_analysis.ipynb, lines 303 to 351 · score 0.63 · selected root cell, random seed, Diffusion, DPT, candidate, robustness · Report a mismatch
  28. [28] § Methods › Pseudotime inference and trajectory analysis ↔ Trajectory_Inference.py, lines 243 to 283 · score 0.63 · Trajectory inference, odds ratios, logistic regression, pseudotime, seed, ROC · Report a mismatch
  29. [29] § Results › Subcluster level expression improves AD prediction ↔ Trajectory_Inference.py, lines 372 to 413 · score 0.63 · Smoothed expression, Pseudotime trajectory, UMAP, top trajectory, DPT, status · Report a mismatch
  30. [30] § Methods › Differential expression analysis ↔ revision/code/Contrasts_DE_w_RandomEffect.R, lines 211 to 253 · score 0.62 · RUVSeq, dcfdx_lv, clinical diagnosis, neuropathological, CERAD, donor · Report a mismatch
  31. [31] § Results › Genetic colocalization anchors cell-type–specific transcriptional predictors ↔ revision/Revision_Figures/drug_bank_target_identification.ipynb, lines 361 to 427 · score 0.62 · USP6NL, ARL17B, NDUFAF6, EGFR, CLU, colocalization · Report a mismatch
  32. [32] § Results › Donor-aware differential expression contextualizes cell-type-specific predictors of AD ↔ figure6.ipynb, lines 725 to 864 · score 0.62 · log2 fold change, FDR adjusted, overlapping genes, Volcano, Inhibitory Neurons, Excitatory Neurons · Report a mismatch
  33. [33] § Results › Subcluster level expression improves AD prediction ↔ revision/sea-ad_validation_analysis/pseudotime/pseudotime_seaad_centroid.py, lines 400 to 465 · score 0.61 · Smoothed expression, Pseudotime trajectory, UMAP, top trajectory, subcluster, ranked · Report a mismatch
  34. [34] § Results › Donor-aware differential expression contextualizes cell-type-specific predictors of AD ↔ data_tables.ipynb, lines 1039 to 1188 · score 0.61 · Benjamini Hochberg, Oligodendrocyte Progenitor Cells, overlapping genes, Inhibitory Neurons, Excitatory Neurons, log10 · Report a mismatch
  35. [35] § Results › Genetic colocalization anchors cell-type–specific transcriptional predictors ↔ revision/code/Coloc.R, lines 1 to 45 · score 0.61 · eQTLs, brain tissue, coloc, predictive gene, GWAS · Report a mismatch
  36. [36] § Methods › Differential expression analysis ↔ figure8.ipynb, lines 171 to 212 · score 0.60 · post mortem interval, dcfdx_lv, PMI, diagnosis · Report a mismatch
  37. [37] § Methods › Predictive validation cohort ↔ revision/Revision_Figures/correlations_table.ipynb, lines 593 to 732 · score 0.58 · smaller predictor, nonzero importance, correlations, recall, overlap, splits · Report a mismatch
  38. [38] § Methods › Single-nucleus RNA-seq preprocessing and quality control ↔ figure8.ipynb, lines 171 to 212 · score 0.58 · Post mortem interval, FDR adjusted, PMI, diagnostic, metadata · Report a mismatch
  39. [39] § Methods › SHAP-based model interpretation stratified by APOE genotype and sex ↔ revision/Revision_Figures/Figure_9_revision.ipynb, lines 58 to 112 · score 0.58 · TreeExplainer, SHAP, booster, female, aggregated, sex · Report a mismatch
  40. [40] § Methods › GWAS–eQTL colocalization analysis ↔ revision/code/Coloc.R, lines 301 to 364 · score 0.58 · coloc.abf, eQTL, SNPs, tissue, GWAS, brain · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 609 lines · 28 KB · MIT · 5 matches

  1. """
  2. pseudotime_seaad_centroid.py
  3. Per-subcluster pseudotime pipeline on SEA-AD, using centroid-labeled
  4. subpopulations (top1 from label_seaad_subpops_centroid.py outputs).
  5. Script body is a copy of the Mathys per-subcluster pseudotime analysis from
  6. /home/adm808/Analysis_Scripts/Plotting_Figures/pseudotime_hvg.ipynb (cell 0)
  7. with the following and ONLY the following changes:
  8. 1. Data loaded from SEA-AD parquets instead of the Mathys CellMatrix:
  9. expression -> /n/groups/patel/adithya/SEAAD_Outputs/SEAAD_Matrix_{broad}.parquet
  10. (cells x genes, float32, already CPM->log2(x+1) normalized)
  11. metadata -> /n/groups/patel/adithya/SEAAD_Outputs/SEAAD_CellMetadata.parquet
  12. subcluster -> top1 from .../labels_centroid/seaad_subpop_centroid_{broad}.parquet
  13. (Mathys subpop name assigned per SEA-AD cell by Pearson centroid corr)
  14. 2. Two scanpy normalization lines REMOVED because the SEA-AD matrix is
  15. already log-normalized (see sea_ad_normalization_matrix.py):
  16. sc.pp.normalize_total(adata_template, target_sum=1e4)
  17. sc.pp.log1p(adata_template)
  18. 3. Mathys clinical column names replaced with the SEA-AD column names that
  19. carry the same information (no derived/injected columns):
  20. dcfdx_lv in {4,5} -> alzheimers_or_control == 1
  21. ceradsc -> 'Overall AD neuropathological Change'
  22. (ordered Categorical: High < Intermediate < Low < Not AD,
  23. so .max() = 'Not AD' = healthiest, matching ceradsc=4)
  24. cogdx -> 'Cognitive Status'
  25. (ordered Categorical: No dementia < Dementia,
  26. so .min() = 'No dementia' = healthiest, matching cogdx=1)
  27. projid -> 'Donor ID'
  28. Subcluster -> centroid top1
  29. The CERAD classifier binary outcome 'ceradsc in {1,2}' (definite/probable AD)
  30. becomes 'Overall AD neuropathological Change' in {'High','Intermediate'}
  31. (high/moderate AD pathology) -- same clinical definition.
  32. The pd.to_numeric(...) coercions that the Mathys script applied to ceradsc
  33. and cogdx were no-ops on already-typed numeric columns; with SEA-AD's
  34. ordered Categorical encoding those coercions are removed (they would error
  35. on string categories). The .max()/.min()/.isin() and dropna() that consume
  36. those columns are otherwise unchanged.
  37. 4. The outer for-loop runs for the single subcluster passed on the CLI; the
  38. per-subcluster validation row and ROC interpolation arrays are written
  39. to their own files so a separate aggregator can combine them into the
  40. final publication CSV + ROC plots after all jobs complete.
  41. Usage:
  42. python pseudotime_seaad_centroid.py --subcluster Ex5
  43. """
  44. import argparse
  45. import os
  46. import re
  47. import shutil
  48. import scanpy as sc
  49. import pandas as pd
  50. import numpy as np
  51. import matplotlib.pyplot as plt
  52. import statsmodels.api as sm
  53. from sklearn.metrics import roc_auc_score, roc_curve
  54. from scipy.stats import combine_pvalues, spearmanr
  55. from statsmodels.stats.multitest import multipletests
  56. from statsmodels.nonparametric.smoothers_lowess import lowess
  57. import seaborn as sns
  58. # Mathys subcluster prefix -> SEA-AD broad_cell_type (matches the centroid pipeline).
  59. SUBCLUSTER_PREFIX_TO_BROAD = {
  60. 'Ex': 'Ex', 'In': 'Inh', 'Ast': 'Ast',
  61. 'Oli': 'Oli', 'Mic': 'Mic', 'Opc': 'Opc',
  62. }
  63. # Ordered Categoricals so .max()/.min() pick the healthiest level on each axis:
  64. # Mathys ceradsc: 1 (def AD) < 2 < 3 < 4 (no AD) -> max = healthiest
  65. # SEA-AD pathology: High < Intermediate < Low < Not AD -> max = 'Not AD' (healthiest)
  66. # Mathys cogdx: 1 (NCI) < 2 < ... (higher = more dementia) -> min = healthiest
  67. # SEA-AD cognition: No dementia < Dementia -> min = 'No dementia' (healthiest)
  68. PATHOLOGY_CATEGORIES = ['High', 'Intermediate', 'Low', 'Not AD']
  69. COGNITION_CATEGORIES = ['No dementia', 'Dementia']
  70. def subcluster_to_broad(sub):
  71. m = re.match(r'^([A-Za-z]+)\d+$', sub)
  72. if not m:
  73. raise ValueError(f'Unrecognized subcluster name: {sub!r}')
  74. prefix = m.group(1)
  75. if prefix not in SUBCLUSTER_PREFIX_TO_BROAD:
  76. raise ValueError(f'Unknown subcluster prefix {prefix!r} for {sub!r}')
  77. return SUBCLUSTER_PREFIX_TO_BROAD[prefix]
  78. def parse_args():
  79. p = argparse.ArgumentParser()
  80. p.add_argument('--subcluster', required=True,
  81. help='Mathys subpop name (e.g. Ex5, In3, Opc2) to process.')
  82. p.add_argument('--matrix_dir',
  83. default='/n/groups/patel/adithya/SEAAD_Outputs')
  84. p.add_argument('--meta_path',
  85. default='/n/groups/patel/adithya/SEAAD_Outputs/SEAAD_CellMetadata.parquet')
  86. p.add_argument('--labels_dir',
  87. default='/n/groups/patel/adithya/Alz_Outputs/Final_Outputs/seaad_pseudotime/labels_centroid')
  88. p.add_argument('--output_dir',
  89. default='/n/groups/patel/adithya/Alz_Outputs/Final_Outputs/seaad_pseudotime/pseudotime_centroid')
  90. return p.parse_args()
  91. def main():
  92. args = parse_args()
  93. broad = subcluster_to_broad(args.subcluster)
  94. # --- Set up Paths ---
  95. expr_path = os.path.join(args.matrix_dir, f'SEAAD_Matrix_{broad}.parquet')
  96. meta_path = args.meta_path
  97. labels_path = os.path.join(args.labels_dir, f'seaad_subpop_centroid_{broad}.parquet')
  98. output_path = args.output_dir + '/'
  99. os.makedirs(output_path, exist_ok=True)
  100. # --- Load Data ---
  101. # SEA-AD per-cell metadata (TAG index).
  102. meta_df = pd.read_parquet(meta_path)
  103. # Centroid labels for this broad (TAG index, has 'top1' column with assigned Mathys subpop).
  104. labels_df = pd.read_parquet(labels_path)
  105. # 'Subcluster' column = centroid-assigned Mathys subpop (replaces Mathys's own Subcluster).
  106. meta_df = meta_df.join(labels_df['top1'].rename('Subcluster'), how='inner')
  107. # SEA-AD pathology and cognition columns -> ordered Categoricals so .max()/.min()
  108. # preserve clinical semantics directly (no derived numeric columns).
  109. meta_df['Overall AD neuropathological Change'] = pd.Categorical(
  110. meta_df['Overall AD neuropathological Change'],
  111. categories=PATHOLOGY_CATEGORIES, ordered=True,
  112. )
  113. meta_df['Cognitive Status'] = pd.Categorical(
  114. meta_df['Cognitive Status'],
  115. categories=COGNITION_CATEGORIES, ordered=True,
  116. )
  117. # AD/Control label (direct replacement for Mathys's `dcfdx_lv in {4,5}` derivation).
  118. meta_df['AD'] = meta_df['alzheimers_or_control'].map({1: 'AD', 0: 'Control'}).astype('category')
  119. print("AD status defined based on 'alzheimers_or_control':")
  120. print(meta_df['AD'].value_counts())
  121. # Mathys metadata had TAG as a column; restore that so the script body's
  122. # meta_sub['TAG'].tolist() / meta_sub.set_index('TAG') work unchanged.
  123. meta_df = meta_df.reset_index()
  124. # SEA-AD expression matrix (cells on rows; no transpose needed -- the Mathys
  125. # script transposed because its source was genes-on-rows).
  126. expr_df = pd.read_parquet(expr_path)
  127. expr_df_transposed = expr_df # name kept identical to the Mathys script body
  128. # --- Analysis Settings ---
  129. all_subclusters = [args.subcluster] # one per SLURM job
  130. random_seeds = [41, 42, 43]
  131. # 57, 62, 72, 73
  132. validation_results = []
  133. summary_roc_data = []
  134. summary_roc_data_cerad = []
  135. roc_plot_data_cerad = []
  136. # --- Main Loop (single iteration; loop kept for byte-identical script body) ---
  137. for current_subcluster in all_subclusters:
  138. print(f"\n--- Processing Subcluster: {current_subcluster} ---")
  139. meta_sub = meta_df[meta_df['Subcluster'] == current_subcluster]
  140. if meta_sub.shape[0] < 50 or len(meta_sub['AD'].unique()) < 2:
  141. print(f"Skipping {current_subcluster}: <50 cells or only one AD/Control group.")
  142. continue
  143. safe_subcluster_name = re.sub(r'[^a-zA-Z0-9_.-]', '_', current_subcluster)
  144. subcluster_cells = meta_sub['TAG'].tolist()
  145. expr_sub = expr_df_transposed.loc[subcluster_cells]
  146. # --- Create AnnData ---
  147. adata_template = sc.AnnData(X=expr_sub)
  148. adata_template.obs = meta_sub.set_index('TAG').loc[adata_template.obs.index]
  149. # --- NON-CIRCULAR: Use HVGs instead of DEGs ---
  150. # Two normalization lines from the Mathys script are REMOVED here because
  151. # the SEA-AD matrix is already CPM -> log2(x+1) normalized upstream
  152. # (see sea_ad_normalization_matrix.py):
  153. # sc.pp.normalize_total(adata_template, target_sum=1e4)
  154. # sc.pp.log1p(adata_template)
  155. print("Selecting HVGs for pseudotime (non-circular approach)...")
  156. # Select top 2000 HVGs (or all genes if fewer available)
  157. n_top = min(2000, adata_template.n_vars - 1)
  158. sc.pp.highly_variable_genes(adata_template, n_top_genes=n_top, flavor='seurat')
  159. hvg_genes = adata_template.var[adata_template.var['highly_variable']].index.tolist()
  160. if len(hvg_genes) < 50:
  161. print(f"Skipping {current_subcluster}: only {len(hvg_genes)} HVGs found.")
  162. continue
  163. adata_template = adata_template[:, hvg_genes].copy()
  164. print(f"Using {len(hvg_genes)} HVGs to build trajectories.")
  165. # --- PCA (no Harmony - not needed within subclusters) ---
  166. n_pcs = min(30, len(hvg_genes) - 1)
  167. sc.pp.pca(adata_template, n_comps=n_pcs)
  168. print(f"PCA complete: {adata_template.obsm['X_pca'].shape}")
  169. # --- FAST neighbor graph ---
  170. print(f"Building neighbor graph for {adata_template.n_obs} cells...")
  171. sc.pp.neighbors(adata_template, n_neighbors=10, n_pcs=n_pcs, method='umap')
  172. print("Neighbor graph complete!")
  173. # --- Downstream: UMAP, clustering, diffusion map ---
  174. sc.tl.umap(adata_template)
  175. sc.tl.leiden(adata_template, resolution=0.5)
  176. sc.tl.diffmap(adata_template)
  177. # --- Loop over random seeds to test trajectory robustness ---
  178. subcluster_aucs = []
  179. roc_plot_data = []
  180. top_trajectory_genes_from_gt = [] # will be filled after seed loop
  181. top_50_trajectory_genes_from_gt = [] # defensive init (used in final row regardless)
  182. subcluster_pvals, subcluster_odds_ratios = [], []
  183. all_fprs, all_tprs = [], [] # For mean ROC over seeds
  184. cerad_aucs = []
  185. cerad_odds_ratios = []
  186. cerad_pvals = []
  187. cerad_or_cis = []
  188. # Store pseudotime per seed (for later averaging)
  189. pseudotime_per_seed = []
  190. for seed in random_seeds:
  191. print(f"\n -- Running analysis for seed: {seed} --")
  192. # Create a fresh copy for each run
  193. adata = adata_template.copy()
  194. # --- Select Root Cell Based on pathology and cognition (with current seed) ---
  195. # SEA-AD analogs of Mathys (max ceradsc, then min cogdx):
  196. # max('Overall AD neuropathological Change') = 'Not AD' (healthiest pathology)
  197. # min('Cognitive Status') = 'No dementia' (healthiest cognition)
  198. subcluster_meta_dpt = adata.obs.copy()
  199. subcluster_meta_dpt.dropna(
  200. subset=['Overall AD neuropathological Change', 'Cognitive Status'],
  201. inplace=True,
  202. )
  203. if subcluster_meta_dpt.empty:
  204. print(" Skipping trajectory ordering: No cells with valid pathology/cognition values.")
  205. break
  206. max_pathology = subcluster_meta_dpt['Overall AD neuropathological Change'].max()
  207. root_candidates = subcluster_meta_dpt[
  208. subcluster_meta_dpt['Overall AD neuropathological Change'] == max_pathology
  209. ]
  210. min_cognition = root_candidates['Cognitive Status'].min()
  211. root_candidates = root_candidates[
  212. root_candidates['Cognitive Status'] == min_cognition
  213. ]
  214. root_cell_id = root_candidates.sample(n=1, random_state=seed).index[0]
  215. adata.uns['iroot'] = np.where(adata.obs_names == root_cell_id)[0][0]
  216. # --- Calculate Pseudotime (DPT) ---
  217. sc.tl.dpt(adata)
  218. # Store pseudotime for this seed (full vector, all cells)
  219. pseudotime_per_seed.append(adata.obs['dpt_pseudotime'].to_numpy())
  220. # --- Calculate AUC for the current seed ---
  221. validation_df = adata.obs[['dpt_pseudotime', 'AD']].copy()
  222. validation_df.dropna(subset=['dpt_pseudotime'], inplace=True)
  223. y_true = (validation_df['AD'] == 'AD').astype(int)
  224. y_score = validation_df['dpt_pseudotime']
  225. auc_score = roc_auc_score(y_true=y_true, y_score=y_score)
  226. subcluster_aucs.append(auc_score)
  227. print(f" --> Seed {seed}: AUC = {auc_score:.4f}")
  228. # Logistic regression for this seed
  229. X = sm.add_constant(validation_df['dpt_pseudotime'])
  230. if len(y_true.unique()) < 2:
  231. print(f" Skipping seed {seed}: only one class present after dropping NaNs.")
  232. continue
  233. log_reg = sm.Logit(y_true, X).fit(disp=0)
  234. subcluster_pvals.append(log_reg.pvalues['dpt_pseudotime'])
  235. subcluster_odds_ratios.append(np.exp(log_reg.params['dpt_pseudotime']))
  236. # ROC curve for this seed (clinical AD)
  237. fpr, tpr, _ = roc_curve(y_true=y_true, y_score=y_score)
  238. all_fprs.append(fpr)
  239. all_tprs.append(tpr)
  240. if auc_score > 0.55:
  241. roc_plot_data.append({'fpr': fpr, 'tpr': tpr, 'auc': auc_score, 'seed': seed})
  242. # CERAD-based classifier
  243. # SEA-AD analog of Mathys' ceradsc.isin([1,2]) (definite/probable AD):
  244. # 'Overall AD neuropathological Change'.isin(['High','Intermediate'])
  245. try:
  246. cerad_df = adata.obs[['dpt_pseudotime']].copy()
  247. # Clear the index name so the next line doesn't create a collision
  248. # between an index named 'TAG' and a column named 'TAG' (which newer
  249. # pandas rejects on .merge as ambiguous; latent bug from Mathys notebook).
  250. cerad_df.index.name = None
  251. cerad_df['TAG'] = cerad_df.index
  252. meta_subset = meta_df[['TAG', 'Overall AD neuropathological Change']].dropna()
  253. cerad_df = cerad_df.merge(meta_subset, on="TAG", how="left")
  254. cerad_df = cerad_df.dropna(subset=["Overall AD neuropathological Change", "dpt_pseudotime"])
  255. cerad_df['AD_cerad'] = cerad_df['Overall AD neuropathological Change'].isin(['High', 'Intermediate']).astype(int)
  256. if cerad_df['AD_cerad'].nunique() < 2:
  257. print(f" CERAD classifier skipped for seed {seed}: only one class present.")
  258. else:
  259. X_cerad = sm.add_constant(cerad_df['dpt_pseudotime'])
  260. y_cerad = cerad_df['AD_cerad']
  261. cerad_model = sm.Logit(y_cerad, X_cerad).fit(disp=0)
  262. cerad_auc = roc_auc_score(y_cerad, cerad_df['dpt_pseudotime'])
  263. cerad_or = np.exp(cerad_model.params['dpt_pseudotime'])
  264. cerad_p = cerad_model.pvalues['dpt_pseudotime']
  265. cerad_conf = cerad_model.conf_int().loc['dpt_pseudotime']
  266. cerad_or_lower = np.exp(cerad_conf[0])
  267. cerad_or_upper = np.exp(cerad_conf[1])
  268. cerad_aucs.append(cerad_auc)
  269. cerad_odds_ratios.append(cerad_or)
  270. cerad_pvals.append(cerad_p)
  271. cerad_or_cis.append((cerad_or_lower, cerad_or_upper))
  272. fpr_cerad, tpr_cerad, _ = roc_curve(y_cerad, cerad_df['dpt_pseudotime'])
  273. if cerad_auc > 0.55:
  274. roc_plot_data_cerad.append({
  275. 'fpr': fpr_cerad,
  276. 'tpr': tpr_cerad,
  277. 'auc': cerad_auc,
  278. 'seed': seed
  279. })
  280. print(f" CERAD AUC (seed {seed}): {cerad_auc:.3f}")
  281. except Exception as e:
  282. print(f" CERAD model failed for seed {seed}: {e}")
  283. # --- After all seeds are run, aggregate results and create combined outputs ---
  284. if not subcluster_aucs:
  285. print(f"No valid results generated for {current_subcluster}. Skipping final aggregation.")
  286. continue
  287. # === 1) Aggregate pseudotime across seeds (Option A: rank-normalized) ===
  288. if pseudotime_per_seed:
  289. pseudo_array = np.column_stack(pseudotime_per_seed) # cells x n_seeds_used
  290. n_cells, n_seeds_used = pseudo_array.shape
  291. ranked = np.zeros_like(pseudo_array, dtype=float)
  292. for j in range(n_seeds_used):
  293. col = pseudo_array[:, j]
  294. valid = ~np.isnan(col)
  295. ranks = np.empty_like(col, dtype=float)
  296. ranks[~valid] = np.nan
  297. # rank 0..(n_valid-1)
  298. valid_vals = col[valid]
  299. order = np.argsort(valid_vals)
  300. rank_values = np.empty_like(order, dtype=float)
  301. rank_values[order] = np.arange(len(order), dtype=float)
  302. ranks[valid] = rank_values
  303. denom = max(len(order) - 1, 1)
  304. ranked[:, j] = ranks / denom
  305. mean_pseudotime = np.nanmean(ranked, axis=1)
  306. adata_template.obs['mean_pseudotime'] = mean_pseudotime
  307. adata_template.obs['TAG'] = adata_template.obs.index
  308. # --- Save GAM input using mean pseudotime ---
  309. # Mathys 'projid' -> SEA-AD 'Donor ID' (subject identifier)
  310. gam_df = adata_template.obs[['Donor ID', 'AD', 'TAG']].copy()
  311. gam_df['AD_binary'] = (gam_df['AD'] == 'AD').astype(int)
  312. gam_df['dpt_pseudotime'] = mean_pseudotime
  313. gam_df = gam_df.dropna(subset=['Donor ID', 'AD_binary', 'TAG', 'dpt_pseudotime'])
  314. gam_df.to_csv(os.path.join(output_path, f"pseudotime_gam_input_{safe_subcluster_name}.csv"), index=False)
  315. # === 2) PC selection by correlation with mean pseudotime ===
  316. pc_scores = adata_template.obsm['X_pca'][:, :n_pcs] # cells x PCs
  317. pc_correlations = []
  318. for k in range(pc_scores.shape[1]):
  319. rho, _ = spearmanr(pc_scores[:, k], mean_pseudotime)
  320. pc_correlations.append(rho)
  321. best_pc = int(np.nanargmax(np.abs(pc_correlations)))
  322. # "Average" gene loadings across seeds (PCA is shared; average = original)
  323. loadings_matrix = adata_template.varm['PCs'] # genes x PCs
  324. avg_loadings = loadings_matrix[:, best_pc]
  325. top_idx = np.argsort(np.abs(avg_loadings))[::-1][:10]
  326. top_trajectory_genes_from_gt = adata_template.var_names[top_idx].tolist()
  327. print(f" Top trajectory-informing genes (PC{best_pc + 1}): {top_trajectory_genes_from_gt}")
  328. top_50 = np.argsort(np.abs(avg_loadings))[::-1][:50]
  329. top_50_trajectory_genes_from_gt = adata_template.var_names[top_50].tolist()
  330. # --- Save top 50 gene loadings in rank order ---
  331. top50_df = pd.DataFrame({
  332. 'rank': np.arange(1, 51),
  333. 'gene': adata_template.var_names[top_50],
  334. 'loading': avg_loadings[top_50],
  335. 'abs_loading': np.abs(avg_loadings[top_50])
  336. })
  337. top50_path = os.path.join(output_path, f"top50_PC_loadings_{safe_subcluster_name}.csv")
  338. top50_df.to_csv(top50_path, index=False)
  339. print(f"Saved top-50 PC loadings -> {top50_path}")
  340. # === 3a) UMAP plot colored by mean pseudotime ===
  341. plot_filename_umap = os.path.join(output_path, f"umap_pseudotime_{safe_subcluster_name}.png")
  342. sc.pl.umap(
  343. adata_template,
  344. color='mean_pseudotime',
  345. title=f'{current_subcluster}: Mean Pseudotime Trajectory',
  346. show=False,
  347. save=f"_{safe_subcluster_name}_umap.png",
  348. cmap="viridis"
  349. )
  350. shutil.move(f'figures/umap_{safe_subcluster_name}_umap.png', plot_filename_umap)
  351. # === 3b) Smoothed expression of top genes vs mean pseudotime ===
  352. if top_trajectory_genes_from_gt:
  353. fig, ax = plt.subplots(figsize=(10, 6))
  354. genes_to_plot = top_trajectory_genes_from_gt[:4]
  355. colors = sns.color_palette("colorblind", n_colors=len(genes_to_plot))
  356. pseudo_vals = mean_pseudotime
  357. valid_idx = ~np.isnan(pseudo_vals)
  358. sorted_idx = np.argsort(pseudo_vals[valid_idx])
  359. for gene, color in zip(genes_to_plot, colors):
  360. expr_vals = adata_template[:, gene].X
  361. # Handle sparse
  362. if not isinstance(expr_vals, np.ndarray):
  363. expr_vals = expr_vals.toarray()
  364. expr_vals = np.asarray(expr_vals).flatten()
  365. expr_valid = expr_vals[valid_idx][sorted_idx]
  366. pseudo_sorted = pseudo_vals[valid_idx][sorted_idx]
  367. smoothed = lowess(expr_valid, pseudo_sorted, frac=0.3)
  368. smoothed_z = (smoothed[:, 1] - smoothed[:, 1].mean()) / smoothed[:, 1].std()
  369. ax.plot(smoothed[:, 0], smoothed_z, label=gene, color=color)
  370. ax.set_xlabel('Mean Pseudotime (rank-normalized)')
  371. ax.set_ylabel('Z-scored Expression')
  372. ax.set_title(f'{current_subcluster}: Smoothed Expression of Top Genes')
  373. ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
  374. fig.savefig(os.path.join(output_path, f"lineplot_top_genes_{safe_subcluster_name}.png"),
  375. bbox_inches='tight')
  376. plt.close(fig)
  377. # === Aggregate CERAD stats across seeds ===
  378. if cerad_aucs:
  379. mean_auc_cerad = np.mean(cerad_aucs)
  380. se_auc_cerad = np.std(cerad_aucs, ddof=1) / np.sqrt(len(cerad_aucs))
  381. auc_cerad_ci = (mean_auc_cerad - 1.96 * se_auc_cerad, mean_auc_cerad + 1.96 * se_auc_cerad)
  382. log_cerad_ors = np.log(cerad_odds_ratios)
  383. mean_log_or_cerad = np.mean(log_cerad_ors)
  384. se_log_or_cerad = np.std(log_cerad_ors, ddof=1) / np.sqrt(len(log_cerad_ors))
  385. or_cerad_ci = (
  386. np.exp(mean_log_or_cerad - 1.96 * se_log_or_cerad),
  387. np.exp(mean_log_or_cerad + 1.96 * se_log_or_cerad)
  388. )
  389. mean_or_cerad = np.exp(mean_log_or_cerad)
  390. _, combined_p_cerad = combine_pvalues(cerad_pvals, method='fisher')
  391. print(f" CERAD mean AUC: {mean_auc_cerad:.3f} (95% CI: {auc_cerad_ci[0]:.3f}-{auc_cerad_ci[1]:.3f})")
  392. print(f" CERAD mean OR: {mean_or_cerad:.2f} (95% CI: {or_cerad_ci[0]:.2f}-{or_cerad_ci[1]:.2f}), p={combined_p_cerad:.3g}")
  393. if mean_auc_cerad > 0.55:
  394. mean_fpr_cerad = np.linspace(0, 1, 100)
  395. interp_tprs_cerad = []
  396. for data in roc_plot_data_cerad:
  397. interp_tprs_cerad.append(np.interp(mean_fpr_cerad, data['fpr'], data['tpr']))
  398. mean_tpr_cerad = np.mean(interp_tprs_cerad, axis=0)
  399. mean_tpr_cerad[0], mean_tpr_cerad[-1] = 0, 1
  400. summary_roc_data_cerad.append({
  401. 'fpr': mean_fpr_cerad,
  402. 'tpr': mean_tpr_cerad,
  403. 'Subcluster': current_subcluster,
  404. 'mean_auc': mean_auc_cerad,
  405. 'se_auc': se_auc_cerad
  406. })
  407. else:
  408. mean_auc_cerad = np.nan
  409. auc_cerad_ci = (np.nan, np.nan)
  410. mean_or_cerad = np.nan
  411. or_cerad_ci = (np.nan, np.nan)
  412. combined_p_cerad = np.nan
  413. # === Aggregate clinical AD stats across seeds ===
  414. mean_auc = np.mean(subcluster_aucs)
  415. se_auc = np.std(subcluster_aucs, ddof=1) / np.sqrt(len(subcluster_aucs))
  416. auc_ci_95 = (mean_auc - 1.96 * se_auc, mean_auc + 1.96 * se_auc)
  417. log_ors = np.log(subcluster_odds_ratios)
  418. mean_log_or = np.mean(log_ors)
  419. se_log_or = np.std(log_ors, ddof=1) / np.sqrt(len(log_ors))
  420. log_or_ci_95 = (mean_log_or - 1.96 * se_log_or, mean_log_or + 1.96 * se_log_or)
  421. mean_or = np.exp(mean_log_or)
  422. or_ci_95 = (np.exp(log_or_ci_95[0]), np.exp(log_or_ci_95[1]))
  423. _, combined_p_value = combine_pvalues(subcluster_pvals, method='fisher')
  424. print(f"\n--- Aggregated Results for {current_subcluster} ---")
  425. print(f"Mean AUC: {mean_auc:.4f} (95% CI: {auc_ci_95[0]:.4f} - {auc_ci_95[1]:.4f})")
  426. print(f"Mean Odds Ratio: {mean_or:.4f} (95% CI: {or_ci_95[0]:.4f} - {or_ci_95[1]:.4f})")
  427. print(f"Combined P-value (Fisher's): {combined_p_value:.4g}")
  428. # === Mean ROC curve across seeds for this subcluster ===
  429. if mean_auc > 0.55:
  430. mean_fpr = np.linspace(0, 1, 100)
  431. interp_tprs = []
  432. for fpr, tpr in zip(all_fprs, all_tprs):
  433. interp_tprs.append(np.interp(mean_fpr, fpr, tpr))
  434. if interp_tprs:
  435. mean_tpr = np.mean(interp_tprs, axis=0)
  436. mean_tpr[0], mean_tpr[-1] = 0, 1
  437. summary_roc_data.append({
  438. 'fpr': mean_fpr,
  439. 'tpr': mean_tpr,
  440. 'Subcluster': current_subcluster,
  441. 'mean_auc': mean_auc,
  442. 'se_auc': se_auc
  443. })
  444. # Store per-subcluster summary row
  445. validation_results.append({
  446. 'Subcluster': current_subcluster,
  447. 'mean_auc': mean_auc,
  448. 'auc_ci_lower': auc_ci_95[0],
  449. 'auc_ci_upper': auc_ci_95[1],
  450. 'mean_odds_ratio': mean_or,
  451. 'or_ci_lower': or_ci_95[0],
  452. 'or_ci_upper': or_ci_95[1],
  453. 'combined_p_value': combined_p_value,
  454. 'mean_auc_cerad': mean_auc_cerad,
  455. 'auc_cerad_lower': auc_cerad_ci[0],
  456. 'auc_cerad_upper': auc_cerad_ci[1],
  457. 'odds_ratio_cerad': mean_or_cerad,
  458. 'or_cerad_lower': or_cerad_ci[0],
  459. 'or_cerad_upper': or_cerad_ci[1],
  460. 'p_value_cerad': combined_p_cerad,
  461. 'n_cells': adata_template.n_obs,
  462. 'top_trajectory_genes': ', '.join(top_trajectory_genes_from_gt) if top_trajectory_genes_from_gt else "N/A",
  463. 'top_pc_loadings': ', '.join(top_50_trajectory_genes_from_gt) if top_50_trajectory_genes_from_gt else "N/A",
  464. })
  465. # --- Save per-subcluster outputs (this job's piece of the final aggregation) ---
  466. # The Mathys notebook accumulated across all subclusters in one process and
  467. # wrote a single publication_summary_statistics.csv + combined ROC plots at
  468. # the end. With per-subcluster SLURM parallelization, each job writes its
  469. # own row/ROC arrays; a separate aggregator combines them after all jobs run.
  470. if not validation_results:
  471. print(f"\nNo valid results for {args.subcluster}. Nothing to save.")
  472. else:
  473. safe = re.sub(r'[^a-zA-Z0-9_.-]', '_', args.subcluster)
  474. row_df = pd.DataFrame(validation_results)
  475. row_path = os.path.join(output_path, f"summary_row_{safe}.csv")
  476. row_df.to_csv(row_path, index=False)
  477. print(f"Saved per-subcluster summary row -> {row_path}")
  478. if summary_roc_data:
  479. d = summary_roc_data[0]
  480. roc_clin_path = os.path.join(output_path, f"roc_clinical_{safe}.npz")
  481. np.savez(roc_clin_path,
  482. fpr=d['fpr'], tpr=d['tpr'],
  483. mean_auc=d['mean_auc'], se_auc=d['se_auc'],
  484. subcluster=current_subcluster)
  485. print(f"Saved per-subcluster clinical ROC -> {roc_clin_path}")
  486. if summary_roc_data_cerad:
  487. d = summary_roc_data_cerad[0]
  488. roc_cerad_path = os.path.join(output_path, f"roc_cerad_{safe}.npz")
  489. np.savez(roc_cerad_path,
  490. fpr=d['fpr'], tpr=d['tpr'],
  491. mean_auc=d['mean_auc'], se_auc=d['se_auc'],
  492. subcluster=current_subcluster)
  493. print(f"Saved per-subcluster CERAD ROC -> {roc_cerad_path}")
  494. print("\n--- Analysis complete. ---")
  495. print("\n--- Pseudotime robustness analysis complete for all subclusters. ---")
  496. if __name__ == '__main__':
  497. main()

pseudotime_seaad_centroid.py at commit cf785a2, under MIT · at the source

Overview

Authors: Adithya V. Madduri1,2, Randall J. Ellis1, Chirag M. Lakhani3, David A. Bennett4, Chirag J. Patel1
ORCID iDs: Chirag J. Patel
  1. Department of Biomedical Informatics, Harvard Medical School,Boston, MA USA
  2. Harvard College,Cambridge, MA USA
  3. New York Genome Center,New York, NY USA
  4. Rush Alzheimer’s Disease Center, Rush University,Chicago, IL USA
Institutions: Harvard University (United States); New York Genome Center (United States); Rush University (United States)
Journal: Nature communications, volume 17, issue 1, article 11124
Dates: received 5 September 2025; accepted 28 August 2026; published online 9 November 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-77651-5 · PMID 43003833 · PMCID PMC13748584 · OpenAlex W7128295611
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: genetics / omics (modality), human (organism), Alzheimer's / dementia (population), cellular / molecular (subfield)
Methods: Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning
Keywords: Alzheimer's disease, Gene expression, Machine learning
MeSH: Alzheimer Disease*, Machine Learning*, Single-Cell Analysis*, Brain, Gene Expression Profiling, Genetic Predisposition to Disease, Genome-Wide Association Study, Humans, Quantitative Trait Loci, Single-Cell Gene Expression Analysis, Transcriptome (* major topic)
Topic: Single-cell and spatial transcriptomics (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Citations: not cited yet (Europe PMC); 68 references in the paper

Abstract

Univariate differential expression analysis dominates single-cell transcriptomic studies of Alzheimer’s disease, yet marginal testing alone cannot resolve multivariate disease-associated structure or distinguish clinical from neuropathological signatures. Here we introduce TriSCOPE, a framework integrating multivariate predictive modeling, donor-aware differential expression, and pseudotime inference, applied to six Alzheimer’s disease snRNA-seq datasets comprising ~4.2 million nuclei from 627 donors. Across six major brain cell types and 41 subclusters, we identify cell-type-specific predictively expressed genes that are largely non-overlapping with differentially expressed genes, revealing disease-relevant signals invisible to univariate approaches. Conditional GWAS–eQTL colocalization anchors a subset of these predictively expressed genes to inherited Alzheimer’s disease risk loci, including 27 genetically supported gene–cell type pairs (among them ARL17B, JAZF1) with no significant differential expression. Integration across all three analytical branches nominates high-confidence convergent programs including astrocytic stress markers (such as GFAP, MT2A), neuronal signaling genes (like RASGEF1B, LINGO1, NRG1), and microglial and oligodendrocyte regulators (including SYTL3, SPP1, APOD). Comparison of clinical and neuropathological models reveals cell-type-specific divergence between cognitive and pathological Alzheimer’s disease signatures. Key predictively expressed genes including ARL17B, LINGO1, and APOE replicate across independent cohorts, with cross-cohort generalizability confirmed across all three analytical branches of TriSCOPE.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 40 matches between paragraphs and lines of code.

AdiVM/TriSCOPE_RNASeq_Alzheimers_Analysis

License: MIT
State: the link answers, verified on 12 November 2026
Evidence: files inventoried
Commit: cf785a20811d40284f647d0323346b18a29b28e6, 8 August 2026
Languages: Shell (55), Jupyter (45), Python (29), R (7)
Size: 158 files, 136 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (requirements.txt, rnaseq-de-fi-mismatch-main/requirements.txt), 45 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: pandas (63 files), NumPy (60 files), Matplotlib (45 files), scikit-learn (36 files), statsmodels (30 files), SciPy (16 files), seaborn (16 files), Scanpy (9 files), tidyverse (5 files), glmmTMB (4 files), lme4 (4 files), edgeR (3 files), Seurat (3 files), SHAP (3 files), anndata (1 file), data.table (1 file), UMAP (1 file)
Availability: 1 check, the latest on 12 November 2026: the link answers
  • 12 November 2026: the link answers
110 files

Zenodo 21793738

License: MIT
State: the link answers, verified on 12 November 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: pandas (63 files), NumPy (60 files), Matplotlib (45 files), scikit-learn (36 files), statsmodels (30 files), SciPy (16 files), seaborn (16 files), Scanpy (9 files), tidyverse (5 files), glmmTMB (4 files), lme4 (4 files), edgeR (3 files), Seurat (3 files), SHAP (3 files), anndata (1 file), data.table (1 file), UMAP (1 file)
Availability: 1 check, the latest on 12 November 2026: the link answers (HTTP 200)
  • 12 November 2026: the link answers (HTTP 200)
110 files
At the source:

Code availability

All code used in this study is available in a public GitHub repository (https://github.com/AdiVM/TriSCOPE_RNASeq_Alzheimers_Analysis). The version of the code used in this study is archived on Zenodo68 and permanently citable at (https://doi.org/10.5281/zenodo.21793738). A synthetic demonstration dataset for testing the software is included in the repository (triscope_synthetic_demo.zip). The repository is released under the MIT License, an Open Source Initiative–approved licence. No restrictions apply to access or reuse of the code.

Reproduced under the paper's license (CC BY), from the paper cited above.

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;
  • 216 scripts, each with its path and the digest of its content;
  • 40 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

Data availability

The data used in this study are available as follows. 1) The Mathys-48 dataset is available on Synapse under accession syn18485175 (http://doi.org/10.7303/syn18485175). ROSMAP metadata is also accessible via Synapse under accession syn3157322 (http://doi.org/10.7303/syn3157322). This same snRNA-seq dataset with further quality control measures applied to it is publicly available on Synapse under accession number syn51758062.1 (http://doi.org/10.7303/syn51758062.1). 2) The Lau-21 dataset is deposited on the NCBI Gene Expression Omnibus under accession GSE157827 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE157827). 3) The ROSMAP-48b dataset was generated as part of the MIT ROSMAP Single-Nucleus Multiomics Study, and is available through Synapse under accession syn52293442 (http://www.synapse.org/Synapse:syn52293442). 4) The ROSMAP-427 dataset is available through Synapse under accession syn52293417 (http://www.synapse.org/Synapse:syn52293417). 5) Single-nucleus RNA-seq data from the Seattle Alzheimer’s Disease Brain Cell Atlas (SEA-AD) are available through the Allen Institute Open Data Registry on AWS (https://sea-ad-single-cell-profiling.s3.amazonaws.com/index.html) and through controlled access on the AD Knowledge Portal on Synapse under accession syn26223298 (http://www.synapse.org/Synapse:syn26223298). Donor, library, and cell-level metadata are available on the Allen Institute Brain-Map portal (https://portal.brain-map.org/explore/seattle-alzheimers-disease). 6) Cis-eQTL data: GTEx v10 significant cis-eQTL variant–gene pairs for all brain tissues were obtained from the GTEx Portal (https://gtexportal.org/home/) on 07/26/25. 7) Alzheimer’s disease GWAS summary statistics: GWAS summary statistics were obtained from the NHGRI-EBI GWAS Catalog (https://www.ebi.ac.uk/gwas/efotraits/MONDO_0004975). 8) Processed differential expression results were obtained from four previously published studies: Morabito et al. 63 Supplementary Data 1C (https://doi.org/10.1038/s41588-021-00894-z), Sadick et al. 64 Tables S4 and S6 (https://doi.org/10.1016/j.neuron.2022.03.008), Su et al. 65 Table S5A (https://doi.org/10.1016/j.stem.2022.09.010), and Grubman et al. 66 Supplementary Data 2 (https://doi.org/10.1038/s41593-019-0539-4). Each file is available from the supplementary information of the corresponding publication. Access to the ROSMAP-derived and SEA-AD datasets is controlled to protect participant privacy, as these are classified as sensitive NIH controlled-access data. Access requires an executed Data Use Agreement and registration with the data provider; conditions of access, required approvals and expected response times are maintained by the providers at https://adknowledgeportal.synapse.org and https://www.radc.rush.edu/docs/omics/overview.htm. We did not generate these data and had no role in setting the conditions of access. Source data are provided with this paper.

Reproduced under the paper's license (CC BY), from the paper cited above.

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 1, 12 November 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 3 keywords, 11 MeSH terms, 3 funders, 67 references.

Cite

This paper

Madduri, A. V., Ellis, R. J., Lakhani, C. M., Bennett, D. A., & Patel, C. J. (2026). Single-cell machine learning uncovers genetically anchored, cell-type specific programs of Alzheimer's disease. Nature communications, 17(1), 11124. https://doi.org/10.1038/s41467-026-77651-5

BibTeX

@article{madduri2026single,
author = {Madduri, Adithya V. and Ellis, Randall J. and Lakhani, Chirag M. and Bennett, David A. and Patel, Chirag J.},
title = {{Single-cell machine learning uncovers genetically anchored, cell-type specific programs of Alzheimer's disease}},
journal = {Nature communications},
year = {2026},
month = nov,
volume = {17},
number = {1},
pages = {11124},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-77651-5},
url = {https://doi.org/10.1038/s41467-026-77651-5},
pmid = {43003833},
pmcid = {PMC13748584}
}

RIS

TY - JOUR
AU - Madduri, Adithya V.
AU - Ellis, Randall J.
AU - Lakhani, Chirag M.
AU - Bennett, David A.
AU - Patel, Chirag J.
TI - Single-cell machine learning uncovers genetically anchored, cell-type specific programs of Alzheimer's disease
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/11/09
VL - 17
IS - 1
SP - 11124
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-77651-5
UR - https://doi.org/10.1038/s41467-026-77651-5
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-77651-5",
"type": "article-journal",
"title": "Single-cell machine learning uncovers genetically anchored, cell-type specific programs of Alzheimer's disease",
"container-title": "Nature communications",
"author": [
{
"family": "Madduri",
"given": "Adithya V."
},
{
"family": "Ellis",
"given": "Randall J."
},
{
"family": "Lakhani",
"given": "Chirag M."
},
{
"family": "Bennett",
"given": "David A."
},
{
"family": "Patel",
"given": "Chirag J."
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "11124",
"DOI": "10.1038/s41467-026-77651-5",
"PMID": "43003833",
"PMCID": "PMC13748584",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-77651-5",
"language": "en",
"issued": {
"date-parts": [
[
2026,
11,
9
]
]
}
}

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.1016/j.cell.2025.06.031 [code]
Single-cell multiregion epigenomic rewiring in Alzheimer's disease progression and cognitive resilience.
Journal: Cell
In common: anndata, Scanpy, UMAP, 9 other tools, synapse.org/synapse:syn52293442, synapse.org/synapse:syn52293417, Alzheimer's / dementia, genetics / omics, cellular / molecular, 12 references
[2] doi:10.14336/ad.2024.0429 [code]
Systematic Analysis of Biological Processes Reveals Gene Co-expression Modules Driving Pathway Dysregulation in Alzheimer's Disease.
Journal: Aging and disease
In common: glmmTMB, edgeR, anndata, 13 other tools, portal.brain-map.org/explore/seattle-alzheimers-disease, Alzheimer's / dementia, genetics / omics, cellular / molecular, 8 references
[3] doi:10.1038/s41586-024-07606-7 [code]
Single-cell multiregion dissection of Alzheimer's disease.
Journal: Nature
In common: anndata, Scanpy, UMAP, 11 other tools, Alzheimer's / dementia, genetics / omics, cellular / molecular, 12 references
[4] doi:10.1038/s41593-024-01774-5 [code]
Integrated multimodal cell atlas of Alzheimer's disease.
Journal: Nature neuroscience
In common: anndata, Scanpy, UMAP, 10 other tools, portal.brain-map.org/explore/seattle-alzheimers-disease, Alzheimer's / dementia, genetics / omics, cellular / molecular, 7 references
[5] doi:10.1093/brain/awaf285 [code]
Deep learning-based cell type profiles reveal signatures of Alzheimer's disease resilience and resistance.
Journal: Brain : a journal of neurology
In common: edgeR, anndata, Scanpy, 10 other tools, portal.brain-map.org/explore/seattle-alzheimers-disease, Alzheimer's / dementia, genetics / omics, cellular / molecular, 5 references
[6] doi:10.14336/ad.2025.0409 [code]
Layer-Specific Colocalization of Microglia with Amyloid Plaques in the Middle Temporal Gyrus Predicts Cognitive Decline in Alzheimer's Disease.
Journal: Aging and disease
In common: anndata, Scanpy, UMAP, 10 other tools, portal.brain-map.org/explore/seattle-alzheimers-disease, Alzheimer's / dementia, cellular / molecular, 5 references
[7] doi:10.1016/j.celrep.2024.115149 [code]
Single-nucleus and spatial landscape of the sub-ventricular zone in human glioblastoma.
Journal: Cell reports
In common: glmmTMB, edgeR, anndata, 13 other tools, cellular / molecular, 2 references
[8] doi:10.1038/s41467-025-59949-y [code]
Whole-genome sequencing analyses suggest novel genetic factors associated with Alzheimer's disease and a cumulative effects model for risk liability.
Journal: Nature communications
In common: edgeR, Scanpy, data.table, 8 other tools, portal.brain-map.org/explore/seattle-alzheimers-disease, Alzheimer's / dementia, genetics / omics, 5 references
[9] doi:10.1038/s41467-025-59654-w [code]
An integrative systems-biology approach defines mechanisms of Alzheimer's disease neurodegeneration.
Journal: Nature communications
In common: edgeR, Scanpy, Seurat, 10 other tools, Alzheimer's / dementia, genetics / omics, cellular / molecular, 6 references
[10] doi:10.1038/s41467-026-68920-4 [code]
Molecular signatures of resilience to Alzheimer's disease in neocortical layer 4 neurons.
Journal: Nature communications
In common: anndata, Scanpy, Seurat, 9 other tools, Alzheimer's / dementia, cellular / molecular, 7 references

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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1038/s41467-026-77651-5.svg)](https://openscicode.org/badge/doi/10.1038/s41467-026-77651-5)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1038/s41467-026-77651-5.svg
   :target: https://openscicode.org/badge/doi/10.1038/s41467-026-77651-5
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1038/s41467-026-77651-5"><img src="https://openscicode.org/badge/doi/10.1038/s41467-026-77651-5.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.

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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.