| """per-class pathway scoring cKO vs WT on aldrich, MannU per (class, pathway).""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| import warnings | |
| warnings.filterwarnings("ignore") | |
| import numpy as np | |
| import pandas as pd | |
| import anndata as ad | |
| import scanpy as sc | |
| from scipy.stats import mannwhitneyu | |
| import os as _os | |
| from pathlib import Path as _Path | |
| PANDA_ROOT = _Path(_os.environ.get("PANDA_ROOT", str(_Path(__file__).resolve().parents[2]))) | |
| TARGET = Path(str(PANDA_ROOT / "data/processed/skin/adata_processed.h5ad")) | |
| PROJ = Path(str(PANDA_ROOT / "discovery/pan_skin/marker/50_aldrich_projections.h5ad")) | |
| OUT = Path(str(PANDA_ROOT / "discovery/pan_skin/marker")) | |
| CLASSES_OF_INTEREST = [ | |
| "basal-IFE", "spinous", "granular", | |
| "fibroblast-papillary", "fibroblast-reticular", | |
| "endothelial", "immune", "melanocyte", | |
| ] | |
| PATHWAYS = { | |
| "MITF_regulon": ["Mitf", "Dct", "Tyr", "Pmel", "Mlana", "Tyrp1", "Slc24a5", | |
| "Slc45a2", "Sox10", "Pax3", "Kit", "Ednrb"], | |
| "Wnt_signaling": ["Wnt3", "Wnt5a", "Wnt7a", "Wnt10b", "Ctnnb1", "Lef1", "Tcf4", | |
| "Tcf7", "Axin2", "Dkk1", "Sfrp1", "Fzd7", "Lrp5"], | |
| "BMP_signaling": ["Bmp2", "Bmp4", "Bmp5", "Bmp7", "Bmpr1a", "Bmpr1b", "Bmpr2", | |
| "Smad1", "Smad5", "Id1", "Id2", "Id3"], | |
| "TGFB_signaling": ["Tgfb1", "Tgfb2", "Tgfbr1", "Tgfbr2", "Smad3", "Smad7"], | |
| "FGF_signaling": ["Fgf1", "Fgf2", "Fgf7", "Fgf9", "Fgf10", "Fgfr1", "Fgfr2", | |
| "Etv1", "Etv4", "Etv5", "Spry2", "Dusp6"], | |
| "Notch_signaling": ["Notch1", "Notch2", "Notch3", "Jag1", "Dll1", "Hes1", "Hes5", | |
| "Hey1", "Hey2", "Rbpj"], | |
| "Hedgehog": ["Shh", "Ptch1", "Smo", "Gli1", "Gli2", "Gli3"], | |
| "Eda_ectodysplasin": ["Eda", "Edar", "Edaradd", "Nfkb1", "Nfkb2", "Rela"], | |
| "EMT": ["Zeb1", "Zeb2", "Snai1", "Snai2", "Twist1", "Twist2", "Vim", | |
| "Cdh2", "Fn1", "Prrx1"], | |
| "Cell_cycle": ["Ccnd1", "Ccne1", "Ccna2", "Ccnb1", "Cdk1", "Cdk2", "Cdk4", | |
| "Mki67", "Top2a", "Pcna", "Mcm2", "Mcm3"], | |
| "KC_differentiation": ["Krt1", "Krt10", "Ivl", "Lor", "Flg", "Flg2", "Klk5", "Klk7", | |
| "Cdsn"], | |
| "Basal_keratinocyte": ["Krt5", "Krt14", "Krt15", "Trp63", "Itga6", "Itgb1", "Itga3"], | |
| "Sweat_gland": ["Foxi3", "Foxa1", "En1", "Krt8", "Krt18", "Krt19", "Muc5b", | |
| "Aqp5", "Cutl1"], | |
| "Hair_placode": ["Wnt10b", "Shh", "Lef1", "Foxi3", "Edar", "Bmp4", "Msx2"], | |
| "Neural_crest": ["Sox10", "Sox9", "Sox2", "Pax3", "Foxd3", "Nes", "Tfap2a"], | |
| "Apoptosis": ["Bax", "Bak1", "Bad", "Bcl2", "Casp3", "Casp9", "Trp53", | |
| "Cdkn1a"], | |
| } | |
| def pathway_scoring(sub, pathway_dict): | |
| for name, genes in pathway_dict.items(): | |
| present = [g for g in genes if g in sub.var_names] | |
| if not present: | |
| sub.obs[f"pw_{name}"] = 0.0 | |
| continue | |
| sc.tl.score_genes(sub, gene_list=present, score_name=f"pw_{name}", | |
| random_state=0, use_raw=False) | |
| return sub | |
| def main(): | |
| a = ad.read_h5ad(TARGET) | |
| p = ad.read_h5ad(PROJ) | |
| a.obs["pred_label"] = p.obs["pred_bbse_label"].values | |
| print(f"[pw] classes in target: {a.obs['pred_label'].value_counts().to_dict()}", flush=True) | |
| rows = [] | |
| for cls in CLASSES_OF_INTEREST: | |
| mask = a.obs["pred_label"] == cls | |
| n_c = int((mask & (a.obs["genotype"]=="En1-cKO")).sum()) | |
| n_w = int((mask & (a.obs["genotype"]=="WT")).sum()) | |
| if n_c < 15 or n_w < 15: | |
| print(f"[pw] {cls}: skip (n_cKO={n_c}, n_WT={n_w})") | |
| continue | |
| sub = a[mask].copy() | |
| sub = pathway_scoring(sub, PATHWAYS) | |
| for pw in PATHWAYS.keys(): | |
| s = sub.obs[f"pw_{pw}"].astype(float).values | |
| g = sub.obs["genotype"].values | |
| cvals = s[g=="En1-cKO"]; wvals = s[g=="WT"] | |
| try: | |
| _, pval = mannwhitneyu(cvals, wvals, alternative="two-sided") | |
| except Exception: | |
| pval = 1.0 | |
| delta = cvals.mean() - wvals.mean() | |
| rows.append({"class": cls, "pathway": pw, | |
| "n_cKO": n_c, "n_WT": n_w, | |
| "delta_cKO_minus_WT": round(delta, 4), | |
| "MannU_p": pval}) | |
| print(f"[pw] {cls}: {n_c} cKO, {n_w} WT — scored") | |
| df = pd.DataFrame(rows) | |
| df.to_csv(OUT / "57_pathway_class_by_pathway.csv", index=False) | |
| pivot_delta = df.pivot(index="pathway", columns="class", values="delta_cKO_minus_WT") | |
| pivot_p = df.pivot(index="pathway", columns="class", values="MannU_p") | |
| def stars(p): return "***" if p<0.001 else "**" if p<0.01 else "*" if p<0.05 else "" | |
| disp = pivot_delta.copy().astype(object) | |
| for pw in disp.index: | |
| for c in disp.columns: | |
| d = pivot_delta.loc[pw, c]; p = pivot_p.loc[pw, c] | |
| if pd.isna(d): disp.loc[pw, c] = "" | |
| else: disp.loc[pw, c] = f"{d:+.3f}{stars(p)}" | |
| lines = ["# Pathway score contrasts by class — Aldrich En1-cKO vs WT", | |
| "", | |
| "Delta = cKO mean − WT mean of `sc.tl.score_genes` pathway score.", | |
| "Sig: * p<0.05, ** p<0.01, *** p<0.001 (MannU two-sided).\n", | |
| disp.to_markdown()] | |
| (OUT / "57_pathway_class_by_pathway.md").write_text("\n".join(lines)) | |
| print(f"[pw] wrote {OUT}/57_pathway_class_by_pathway.md") | |
| df_sig = df[df["MannU_p"] < 0.01].sort_values("MannU_p") | |
| print("\n[pw] Strongest cKO/WT pathway shifts (p<0.01):") | |
| print(df_sig[["class","pathway","delta_cKO_minus_WT","MannU_p"]].to_string(index=False)) | |
| df_sig.to_csv(OUT / "57_pathway_top_hits.csv", index=False) | |
| if __name__ == "__main__": | |
| main() | |