PANDA / scripts /analysis /57_pathway_analysis.py
bryan7264's picture
Correction pass: gate-matched Dahlin, retracted unsupported claims, complete HF-placode DEG set, restyled figures
141bacd verified
Raw
History Blame Contribute Delete
27.4 kB
"""per-class pathway module scoring cKO/mutant vs WT across pan_skin, hematopoiesis, pancreas.
pancreas contrast is Veres stage 6 vs stage 5 (HUMAN gene symbols)."""
from __future__ import annotations
import argparse
import warnings
from pathlib import Path
import anndata as ad
import numpy as np
import pandas as pd
import scanpy as sc
import scipy.sparse as sp
from scipy.stats import mannwhitneyu
warnings.filterwarnings("ignore")
sc.settings.verbosity = 0
import os as _os
from pathlib import Path as _Path
PANDA_ROOT = _Path(_os.environ.get("PANDA_ROOT", str(_Path(__file__).resolve().parents[2])))
ROOT = Path(str(PANDA_ROOT))
MIN_PER_GROUP = 15
def _M(genes, type_, citation, direction=None):
return {"genes": list(genes), "type": type_, "citation": citation,
"direction": direction}
# pan-skin: mouse symbols
SKIN_MODULES = {
"MITF_regulon": _M(["Mitf","Dct","Tyr","Pmel","Mlana","Tyrp1","Slc24a5",
"Slc45a2","Sox10","Pax3","Kit","Ednrb"],
"lineage", "Steingrimsson-2004"),
"Wnt_signaling": _M(["Wnt3","Wnt5a","Wnt7a","Wnt10b","Ctnnb1","Lef1",
"Tcf4","Tcf7","Axin2","Dkk1","Sfrp1","Fzd7","Lrp5"],
"signaling", "Nusse-2017"),
"BMP_signaling": _M(["Bmp2","Bmp4","Bmp5","Bmp7","Bmpr1a","Bmpr1b",
"Bmpr2","Smad1","Smad5","Id1","Id2","Id3"],
"signaling", "Botchkarev-2003"),
"TGFB_signaling": _M(["Tgfb1","Tgfb2","Tgfbr1","Tgfbr2","Smad3","Smad7"],
"signaling", "Massague-2012"),
"FGF_signaling": _M(["Fgf1","Fgf2","Fgf7","Fgf9","Fgf10","Fgfr1","Fgfr2",
"Etv1","Etv4","Etv5","Spry2","Dusp6"],
"signaling", "Ornitz-2015"),
"Notch_signaling": _M(["Notch1","Notch2","Notch3","Jag1","Dll1","Hes1",
"Hes5","Hey1","Hey2","Rbpj"],
"signaling", "Andersson-2011"),
"Hedgehog": _M(["Shh","Ptch1","Smo","Gli1","Gli2","Gli3"],
"signaling", "St-Jacques-1998"),
"Eda_ectodysplasin": _M(["Eda","Edar","Edaradd","Nfkb1","Nfkb2","Rela"],
"signaling", "Mikkola-2009"),
"EMT": _M(["Zeb1","Zeb2","Snai1","Snai2","Twist1","Twist2",
"Vim","Cdh2","Fn1","Prrx1"],
"lineage", "Thiery-2009"),
"Cell_cycle": _M(["Ccnd1","Ccne1","Ccna2","Ccnb1","Cdk1","Cdk2",
"Cdk4","Mki67","Top2a","Pcna","Mcm2","Mcm3"],
"cycle", "Whitfield-2002"),
"KC_differentiation":_M(["Krt1","Krt10","Ivl","Lor","Flg","Flg2","Klk5",
"Klk7","Cdsn"],
"lineage", "Fuchs-2007"),
"Basal_keratinocyte":_M(["Krt5","Krt14","Krt15","Trp63","Itga6","Itgb1",
"Itga3"],
"lineage", "Blanpain-2007"),
"Sweat_gland": _M(["Foxi3","Foxa1","En1","Krt8","Krt18","Krt19",
"Muc5b","Aqp5","Cutl1"],
"lineage", "Lu-2016"),
"Hair_placode": _M(["Wnt10b","Shh","Lef1","Foxi3","Edar","Bmp4","Msx2"],
"lineage", "Millar-2002"),
"Neural_crest": _M(["Sox10","Sox9","Sox2","Pax3","Foxd3","Nes","Tfap2a"],
"lineage", "Simoes-Costa-2015"),
"Apoptosis": _M(["Bax","Bak1","Bad","Bcl2","Casp3","Casp9","Trp53",
"Cdkn1a"],
"stress", "Youle-2008"),
"Melanogenesis_late":_M(["Tyrp1","Slc45a2","Oca2","Gpnmb","Pmel","Silv",
"Mlph","Rab27a","Melana"],
"lineage", "Raposo-2007"),
"Sebogenesis": _M(["Elovl3","Awat2","Scd1","Scd3","Adipoq","Fasn",
"Mgst1","Srebf1","Pparg"],
"metabolism", "Zouboulis-2016"),
"Immune_Th1_Th2_Th17":_M(["Tbx21","Gata3","Rorc","Ifng","Il4","Il13","Il17a",
"Il17f","Il22","Foxp3"],
"immune", "Zhu-2010"),
"DNA_damage": _M(["Trp53","Cdkn1a","Atm","Atr","Brca1","Chek1",
"Chek2","Rad51","Mre11a","H2ax","Nbn"],
"stress", "Ciccia-2010"),
"Autophagy": _M(["Atg5","Atg7","Atg12","Becn1","Map1lc3b","Sqstm1",
"Ulk1","Atg3","Atg16l1"],
"stress", "Mizushima-2011"),
"Senescence": _M(["Cdkn2a","Cdkn2b","Cdkn1a","Il6","Cxcl1","Serpine1",
"Glb1","Lmnb1"],
"stress", "Coppe-2010"),
"Epidermal_junction":_M(["Cdh1","Dsg1a","Dsg1b","Dsg2","Dsg3","Cldn1",
"Cldn4","Ocln","Tjp1","Cldn23","Dsp","Pkp1"],
"junction", "Green-2010"),
"ECM_collagen": _M(["Col1a1","Col1a2","Col3a1","Col4a1","Col6a1",
"Col17a1","Lum","Dcn","Fbn1","Postn","Fbln1"],
"ecm", "Ricard-Blum-2011"),
"Endothelial_tip_stalk":_M(["Dll4","Notch1","Hes1","Kdr","Kit","Cxcr4",
"Angpt2","Nrp1","Flt1","Cdh5","Pecam1"],
"lineage", "Blanco-2013"),
"Fibroblast_wound": _M(["Postn","Fap","Aspn","Tnc","Acta2","Prrx1","Pdgfra",
"Ly6a"],
"lineage", "Rinkevich-2015"),
"Fatty_acid_oxidation":_M(["Cpt1a","Acadm","Acadl","Acadvl","Hadha","Hadhb",
"Ppara","Ppargc1a","Ucp2"],
"metabolism", "Houten-2010"),
"Nrf2_oxidative_stress":_M(["Nfe2l2","Nqo1","Gclc","Hmox1","Slc7a11",
"Txnrd1","Gsta3","Gpx2","Keap1"],
"stress", "Ma-2013"),
"IFN_gamma": _M(["Ifng","Stat1","Ifit1","Ifit2","Ifit3","Isg15",
"Irf1","Cxcl9","Cxcl10","Gbp2"],
"immune", "Schoggins-2011"),
"IL6_JAK_STAT": _M(["Il6","Stat3","Socs3","Jak1","Jak2","Il6ra",
"Il6st","Osm"],
"signaling", "Heinrich-2003"),
"Pigment_regulation":_M(["Asip","Kitl","Kit","Bcl2","Mc1r","Pomc","Adcy8",
"Ednrb","Edn3"],
"signaling", "Slominski-2004"),
}
# hematopoiesis: mouse symbols
HSC_MODULES = {
"Kit_signaling": _M(["Kit","Kitl","Sox4","Gata2","Runx1","Meis1"],
"signaling", "Lennartsson-2012"),
"Kit_ligand": _M(["Kit","Kitl"], "signaling", "Broudy-1997"),
"MYC_targets": _M(["Myc","Nolc1","Nop58","Ncl","Npm1","Fbl","Eif4e",
"Nop56","Ldha","Odc1"],
"lineage", "Dang-2012"),
"Cell_cycle": _M(["Ccnd1","Ccne1","Ccna2","Ccnb1","Cdk1","Cdk2",
"Cdk4","Mki67","Top2a","Pcna","Mcm2","Mcm3","Mcm5"],
"cycle", "Whitfield-2002"),
"DNA_replication": _M(["Mcm2","Mcm3","Mcm4","Mcm5","Mcm6","Mcm7","Pcna",
"Rfc4","Pola1","Pole","Rpa1","Rpa2"],
"cycle", "Bell-2002"),
"Integrated_stress": _M(["Atf4","Ddit3","Ppp1r15a","Ppp1r15b","Eif2ak3",
"Eif2s1","Atf3","Gadd45a"],
"stress", "Pakos-Zebrucka-2016"),
"Apoptosis_pro": _M(["Bax","Bak1","Bid","Bad","Bim","Puma","Noxa",
"Casp3","Casp9"],
"stress", "Youle-2008"),
"Apoptosis_anti": _M(["Bcl2","Bcl2l1","Mcl1","Bcl2l2","Bcl2l10","Xiap"],
"stress", "Adams-2018"),
"Erythropoiesis_early":_M(["Gata1","Klf1","Epo","Epor","Tal1","Zfpm1",
"Gypa","Lmo2"],
"lineage", "Palis-2014"),
"Erythropoiesis_late":_M(["Alas2","Hba-a1","Hba-a2","Hbb-b1","Hbb-b2","Slc4a1",
"Ank1","Blvrb","Car1","Car2"],
"lineage", "Palis-2014"),
"Granulopoiesis": _M(["Cebpa","Cebpe","Elane","Mpo","Prtn3","Csf3r",
"S100a8","S100a9","Ctsg","Ltf","Lcn2","Mmp8"],
"lineage", "Rosenbauer-2007"),
"Lymphopoiesis_B": _M(["Rag1","Rag2","Dntt","Vpreb1","Vpreb3","Igll1",
"Cd19","Pax5","Ebf1"],
"lineage", "Nutt-2011"),
"Lymphopoiesis_T": _M(["Il7r","Cd3d","Cd3e","Cd3g","Lck","Zap70","Gata3",
"Tcf7","Runx3"],
"lineage", "Rothenberg-2014"),
"Megakaryopoiesis": _M(["Nfe2","Gata1","Fli1","Runx1","Itga2b","Pf4",
"Gp1bb","Gp9","Mpl","Vwf"],
"lineage", "Tijssen-2013"),
"Basophil_mast": _M(["Cpa3","Ms4a2","Gata2","Hdc","Mcpt8","Prss34",
"Fcer1a","Il4","Il6"],
"lineage", "Voehringer-2013"),
"Hemostasis": _M(["Vwf","F5","F13a1","Fga","Fgb","Fgg","Serpine1",
"Plat","Plau","Plg"],
"signaling", "Furie-2008"),
"OXPHOS_ETC": _M(["Ndufa1","Ndufa2","Ndufb1","Ndufb2","Sdha","Sdhb",
"Cox4i1","Cox5a","Cox6a1","Atp5a1","Atp5b","Uqcrq"],
"metabolism", "Mishra-2016"),
"Glycolysis": _M(["Hk1","Hk2","Pfkm","Pfkl","Aldoa","Gapdh","Pgk1",
"Pkm","Ldha","Eno1","Tpi1","Pgam1"],
"metabolism", "Vander-Heiden-2009"),
"TCA": _M(["Cs","Aco2","Idh2","Idh3a","Sdha","Fh1","Mdh2",
"Ogdh","Sucla2"],
"metabolism", "Chandel-2015"),
"Redox_glutathione": _M(["Gpx1","Gpx2","Gpx3","Gpx4","Gsr","Prdx1","Prdx2",
"Prdx3","Prdx4","Prdx5","Prdx6","Sod1","Sod2","Cat"],
"stress", "Ho-2007"),
"Wnt_hemato": _M(["Wnt3a","Wnt5a","Ctnnb1","Lef1","Tcf7","Axin2",
"Fzd4","Fzd7"],
"signaling", "Reya-2003"),
"Notch_hemato": _M(["Notch1","Notch2","Jag1","Hes1","Dll1","Dll4",
"Rbpj","Hey1"],
"signaling", "Bigas-2018"),
"TGFb_hemato": _M(["Tgfb1","Tgfb2","Tgfbr1","Tgfbr2","Smad2","Smad3",
"Smad4","Smad7"],
"signaling", "Blank-2015"),
"IFN_signaling": _M(["Ifnar1","Ifnar2","Stat1","Stat2","Ifit1","Ifit2",
"Ifit3","Isg15","Irf7","Mx1"],
"immune", "Essers-2009"),
"Complement": _M(["C1qa","C1qb","C1qc","C3","C4b","Cfp","Cfh","Cfd"],
"immune", "Ricklin-2016"),
"NK_cytotoxicity": _M(["Ncr1","Klrk1","Prf1","Gzmb","Gzmk","Nkg7","Klrd1",
"Klrb1c","Klra8"],
"immune", "Vivier-2011"),
"Mast_cell_degran": _M(["Ms4a2","Fcer1a","Cpa3","Kit","Hdc","Tpsb2",
"Prss34","Mcpt4"],
"immune", "Galli-2011"),
"Autophagy": _M(["Atg5","Atg7","Atg12","Becn1","Map1lc3b","Sqstm1",
"Ulk1","Atg3","Atg16l1"],
"stress", "Warr-2013"),
"Senescence": _M(["Cdkn2a","Cdkn2b","Cdkn1a","Il6","Cxcl1","Serpine1",
"Glb1","Lmnb1"],
"stress", "Chang-2016"),
"LT_HSC_quiescence": _M(["Hlf","Meis1","Mecom","Procr","Fgd5","Mllt3","Egr1",
"Rgs1","Cdkn1c","Ndn","Mpl"],
"lineage", "Cabezas-Wallscheid-2017"),
}
# pancreas: HUMAN symbols (Veres is hPSC)
PANCREAS_MODULES = {
"Insulin_secretion": _M(["INS","IAPP","CHGA","CHGB","SCG5","ERO1B","PCSK1",
"PCSK2","SLC30A8","G6PC2"],
"hormone", "Rorsman-2013"),
"Glucose_sensing": _M(["SLC2A2","GCK","KCNJ11","ABCC8","SIRT1","GLUT1",
"SLC2A1"],
"signaling", "Matschinsky-2013"),
"Alpha_master_TF": _M(["ARX","IRX1","IRX2","MAFB","POU3F4","GCG","TTR"],
"lineage", "Collombat-2003"),
"Beta_master_TF_embryonic":_M(["NKX6-1","MNX1","NEUROD1","PDX1","NKX2-2",
"HNF1B"],
"lineage", "Gu-2004"),
"Beta_master_TF_adult":_M(["MAFA","UCN3","SIX3","INS","IAPP","G6PC2"],
"lineage", "Blum-2012"),
"Neurog3_EP_cascade":_M(["NEUROG3","PAX4","FEV","INSM1","NEUROD1","SOX4",
"CBFA2T3","BTBD17"],
"lineage", "Gradwohl-2000"),
"Endocrine_maturation":_M(["RFX3","RFX6","ISL1","FOXA2","PAX6","NKX2-2"],
"lineage", "Piccand-2014"),
"Exocrine_acinar": _M(["PRSS1","PRSS2","CEL","CPA1","CTRB1","AMY2A",
"ELOVL5","PTF1A","CELA1"],
"lineage", "Kawaguchi-2002"),
"Ductal_epithelial": _M(["KRT19","KRT7","SOX9","MUC1","ONECUT1","HES1",
"HNF1B","CFTR"],
"lineage", "Solar-2009"),
"Foregut_endoderm": _M(["SOX17","FOXA1","FOXA2","ONECUT1","PROX1","HNF1A",
"HNF1B","GATA4","GATA6"],
"lineage", "Zorn-2009"),
"Cilium_Foxj1": _M(["FOXJ1","CFAP43","CFAP157","NPHP1","IFT88","DNAH5",
"TEKT1","SPAG6"],
"lineage", "Choksi-2014"),
"Delta_master": _M(["SST","HHEX","LEPR","GHSR"],
"hormone", "Rorsman-2018"),
"Gamma_master": _M(["PPY","PYY","SLC38A4"],
"hormone", "Wang-2016"),
"Epsilon_ghrelin": _M(["GHRL","ACSL1"],
"hormone", "Prado-2004"),
"ER_stress_pancreas":_M(["ATF6","XBP1","ERN1","DDIT3","HSPA5","HSPA1A",
"HSPA1B","EIF2AK3"],
"stress", "Back-2012"),
"Unfolded_protein_response":_M(["ATF4","ATF6","XBP1","HERPUD1","BAK1","BAX",
"EDEM1","DERL1"],
"stress", "Walter-2011"),
"Hormone_processing":_M(["PCSK1","PCSK2","CPE","CHGA","CHGB","SCG2","SCG5",
"PAM"],
"hormone", "Docherty-1997"),
"Insulin_receptor_signaling":_M(["INSR","IRS1","IRS2","AKT2","PDX1","FOXO1",
"GSK3B","MTOR"],
"signaling", "Kulkarni-1999"),
"Mesenchyme_pancreatic":_M(["NKX3-2","BMP4","SOX9","FGF10","COL1A1","COL3A1",
"DCN"],
"lineage", "Landsman-2011"),
"Fatty_acid_oxidation":_M(["CPT1A","ACADM","ACADL","HADHA","PPARA","PPARGC1A",
"ACOX1"],
"metabolism", "Houten-2010"),
"Glycolysis": _M(["HK1","HK2","PFKM","PFKL","ALDOA","GAPDH","PGK1",
"PKM","LDHA","ENO1","TPI1"],
"metabolism", "Vander-Heiden-2009"),
"TCA": _M(["CS","ACO2","IDH2","IDH3A","SDHA","FH","MDH2",
"OGDH","SUCLA2"],
"metabolism", "Chandel-2015"),
"OXPHOS_ETC": _M(["NDUFA1","NDUFA2","NDUFB1","SDHA","SDHB","COX4I1",
"COX5A","COX6A1","ATP5A1","ATP5B","UQCRQ"],
"metabolism", "Mishra-2016"),
"Redox_glutathione": _M(["GPX1","GPX2","GPX3","GPX4","GSR","PRDX1","PRDX2",
"PRDX3","PRDX4","PRDX5","PRDX6","SOD1","SOD2","CAT"],
"stress", "Ho-2007"),
"Wnt_pancreas": _M(["WNT3A","WNT5A","CTNNB1","LEF1","TCF7","AXIN2",
"FZD7"],
"signaling", "Murtaugh-2008"),
"Notch_pancreas": _M(["NOTCH1","NOTCH2","JAG1","HES1","DLL1","DLL4",
"RBPJ","HEY1"],
"signaling", "Apelqvist-1999"),
"TGFb_pancreas": _M(["TGFB1","TGFB2","TGFBR1","TGFBR2","SMAD2","SMAD3",
"SMAD4","SMAD7"],
"signaling", "Sanvito-1994"),
"Immune_pancreas": _M(["PTPRC","CD68","ADGRE1","CD3D","CD3E","CD4","CD8A",
"CD19"],
"immune", "Homo-2015"),
"Endothelial_pancreas":_M(["PECAM1","CDH5","KDR","VWF","PLVAP","FLT1",
"TEK","ENG"],
"lineage", "Cleaver-2019"),
"Cell_cycle": _M(["CCND1","CCNE1","CCNA2","CCNB1","CDK1","CDK2",
"CDK4","MKI67","TOP2A","PCNA","MCM2","MCM3"],
"cycle", "Whitfield-2002"),
}
def load_pan_skin():
RAW = ROOT / "data/raw/GSE220977_combined.h5ad"
PRED = ROOT / "discovery/pan_skin/marker/dingwall_predictions.csv"
CKO_GSMS = {"GSM6833482", "GSM6833483"} # 480/481 are rttaControl (WT), not cKO — per GEO metadata
WT_GSMS = {"GSM6833478", "GSM6833479", "GSM6833480", "GSM6833481"} # 4 Cre-neg controls per GEO metadata
a = ad.read_h5ad(RAW)
pred = pd.read_csv(PRED)
common = a.obs_names.intersection(pd.Index(pred["cell_id"].astype(str)))
a = a[list(common)].copy()
pred_map = dict(zip(pred["cell_id"].astype(str), pred["pred_label"]))
a.obs["pred_label"] = pd.Categorical([pred_map.get(c,"unknown")
for c in a.obs_names])
samp = a.obs["sample"].astype(str)
a.obs["group"] = np.where(samp.isin(list(CKO_GSMS)), "En1-cKO",
np.where(samp.isin(list(WT_GSMS)), "WT", "other"))
a = a[a.obs["group"].isin(["En1-cKO","WT"])].copy()
return a, "En1-cKO", "WT", SKIN_MODULES
def load_hematopoiesis():
D_DIR = ROOT / "data/corpus/hematopoiesis/held_out_unlabeled/dahlin_extract"
PRED = ROOT / "discovery/hematopoiesis/marker/nestorowa_anchor_predictions.csv"
if not PRED.exists():
PRED = ROOT / "discovery/hematopoiesis/marker/97_nestorowa_anchor_predictions.csv"
# Dahlin lacks a nestorowa-style anchor csv; fall back to its own predictions
DAHLIN_PRED_CANDIDATES = [
ROOT / "discovery/hematopoiesis/marker/dahlin_predictions.csv",
ROOT / "discovery/hematopoiesis/marker/92_dahlin_predictions.csv",
]
for p in DAHLIN_PRED_CANDIDATES:
if p.exists():
PRED = p
break
GT = {"SIGAB1":"WT","SIGAC1":"WT","SIGAD1":"WT","SIGAF1":"WT","SIGAG1":"WT",
"SIGAH1":"WT","SIGAG8":"Kit_W41","SIGAH8":"Kit_W41"}
parts = []
for f in sorted(D_DIR.glob("*.txt.gz")):
sample = f.name.split("_")[1].split(".")[0]
df = pd.read_csv(f, sep="\t", compression="gzip", index_col=0)
X = sp.csr_matrix(df.values.T.astype(np.float32))
obs = pd.DataFrame(index=[f"{sample}_{bc}" for bc in df.columns.astype(str)])
obs["sample"] = sample
obs["group"] = GT.get(sample, "unknown")
var = pd.DataFrame(index=df.index.astype(str))
parts.append(ad.AnnData(X=X, obs=obs, var=var))
a = ad.concat(parts, join="outer", label="_batch")
try:
import mygene
mg = mygene.MyGeneInfo()
res = mg.querymany(a.var_names.astype(str).tolist(), scopes="ensembl.gene",
fields="symbol", species="mouse", verbose=False)
id2sym = {r["query"]: r["symbol"] for r in res if "symbol" in r}
syms = pd.Series(a.var_names.astype(str)).map(id2sym).values
keep = pd.notna(syms)
a = a[:, keep].copy()
a.var_names = syms[keep]
a.var_names_make_unique()
except Exception as e:
print(f"[warn] mygene mapping failed: {e}")
a = a[a.obs["group"].isin(["Kit_W41","WT"])].copy()
if PRED.exists():
pred = pd.read_csv(PRED)
common = a.obs_names.intersection(pd.Index(pred["cell_id"].astype(str)))
a = a[list(common)].copy()
pred_map = dict(zip(pred["cell_id"].astype(str), pred["pred_label"]))
a.obs["pred_label"] = pd.Categorical([pred_map.get(c,"unknown")
for c in a.obs_names])
else:
# assign a single class so module scoring still runs
a.obs["pred_label"] = pd.Categorical(["all"] * a.n_obs)
print(f"[warn] no Dahlin prediction file found; using pred_label='all'")
return a, "Kit_W41", "WT", HSC_MODULES
def load_pancreas():
SHARON_DIR = ROOT / "data/corpus/pancreas/held_out_unlabeled/sharon_extract"
PRED = ROOT / "discovery/pancreas/marker/veres_predictions.csv"
parts = []
for meta_file in sorted(SHARON_DIR.glob("*.cell_metadata.tsv.gz")):
counts_file = str(meta_file).replace("cell_metadata", "processed_counts")
if not Path(counts_file).exists():
continue
meta = pd.read_csv(meta_file, sep="\t", compression="gzip")
counts = pd.read_csv(counts_file, sep="\t", compression="gzip", index_col=0)
obs = meta.set_index("library.barcode")
obs = obs.loc[obs.index.intersection(counts.index)]
counts_al = counts.loc[obs.index]
X = sp.csr_matrix(counts_al.values.astype(np.float32))
aa = ad.AnnData(X=X, obs=obs,
var=pd.DataFrame(index=counts_al.columns))
aa.var_names_make_unique()
parts.append(aa)
a = ad.concat(parts, join="outer")
pred = pd.read_csv(PRED)
# veres cell_ids are prefixed with "veres_" — strip to match sharon obs_names
pred["cell_id_stripped"] = pred["cell_id"].astype(str).str.replace(r"^veres_", "", regex=True)
common = a.obs_names.intersection(pd.Index(pred["cell_id_stripped"]))
a = a[list(common)].copy()
pred_map = dict(zip(pred["cell_id_stripped"], pred["pred_label"]))
a.obs["pred_label"] = pd.Categorical([pred_map.get(c,"unknown")
for c in a.obs_names])
# canonical Veres contrast: Stage 6 (mature) vs Stage 5 (immature)
stage = a.obs["Stage"].astype(str)
a.obs["group"] = np.where(stage == "6", "Stage6",
np.where(stage == "5", "Stage5", "other"))
a = a[a.obs["group"].isin(["Stage6","Stage5"])].copy()
return a, "Stage6", "Stage5", PANCREAS_MODULES
LOADERS = {
"pan_skin": load_pan_skin,
"hematopoiesis": load_hematopoiesis,
"pancreas": load_pancreas,
}
def score_modules(sub, modules):
var_set = set(sub.var_names.astype(str))
for name, spec in modules.items():
present = [g for g in spec["genes"] if g in var_set]
if not present:
sub.obs[f"pw_{name}"] = 0.0
continue
try:
sc.tl.score_genes(sub, gene_list=present, score_name=f"pw_{name}",
random_state=0, use_raw=False)
except Exception:
sub.obs[f"pw_{name}"] = 0.0
return sub
def run_system(system_name):
print(f"[load] {system_name}", flush=True)
a, g1, g2, modules = LOADERS[system_name]()
print(f"[load] {a.n_obs} cells, {sum(a.obs['group']==g1)} {g1}, "
f"{sum(a.obs['group']==g2)} {g2}, {len(modules)} modules", flush=True)
sc.pp.normalize_total(a, target_sum=1e4)
sc.pp.log1p(a)
OUT = ROOT / f"discovery/{system_name}/marker"
OUT.mkdir(parents=True, exist_ok=True)
classes = sorted(a.obs["pred_label"].astype(str).unique())
rows = []
for cls in classes:
mask = (a.obs["pred_label"].astype(str) == cls).values
n1 = int((mask & (a.obs["group"].values == g1)).sum())
n2 = int((mask & (a.obs["group"].values == g2)).sum())
if n1 < MIN_PER_GROUP or n2 < MIN_PER_GROUP:
print(f"[pw] {cls}: skip (n_{g1}={n1}, n_{g2}={n2})")
continue
sub = a[mask].copy()
sub = score_modules(sub, modules)
grp = sub.obs["group"].values
for mod_name, spec in modules.items():
s = sub.obs[f"pw_{mod_name}"].astype(float).values
v1 = s[grp == g1]; v2 = s[grp == g2]
try:
_, pval = mannwhitneyu(v1, v2, alternative="two-sided")
except Exception:
pval = 1.0
delta = float(v1.mean() - v2.mean())
rows.append({
"class": cls,
"module_name": mod_name,
"module_type": spec["type"],
"citation": spec["citation"],
"direction": spec["direction"],
"n_g1": n1,
"n_g2": n2,
"group_g1": g1,
"group_g2": g2,
"delta": round(delta, 4),
"mannu_p": float(pval),
})
print(f"[pw] {cls}: {n1} {g1}, {n2} {g2} — scored")
df = pd.DataFrame(rows)
if df.empty:
print("[pw] no eligible classes — done")
return
n_tests = len(df)
df["mannu_p_adj_bonferroni"] = np.minimum(df["mannu_p"] * n_tests, 1.0)
csv_path = OUT / "57_pathway_analysis.csv"
df.to_csv(csv_path, index=False)
print(f"[pw] wrote {csv_path} ({len(df)} rows, n_tests={n_tests})",
flush=True)
pivot_delta = df.pivot(index="module_name", columns="class", values="delta")
pivot_padj = df.pivot(index="module_name", columns="class",
values="mannu_p_adj_bonferroni")
pivot_delta.to_csv(OUT / "57_pathway_class_by_module_delta.tsv", sep="\t")
pivot_padj.to_csv(OUT / "57_pathway_class_by_module_padj.tsv", sep="\t")
print(f"[pw] wrote heatmap TSVs to {OUT}")
sig = df[df["mannu_p_adj_bonferroni"] < 0.01].sort_values(
"mannu_p_adj_bonferroni")
print(f"\n[pw] top Bonferroni-significant shifts (padj<0.01, "
f"n={len(sig)}):")
if len(sig):
print(sig[["class","module_name","module_type","delta",
"mannu_p_adj_bonferroni"]].head(30).to_string(index=False))
def main():
p = argparse.ArgumentParser()
p.add_argument("--system", required=True,
choices=list(LOADERS.keys()),
help="pan_skin | hematopoiesis | pancreas")
args = p.parse_args()
run_system(args.system)
if __name__ == "__main__":
main()