PANDA / scripts /analysis /49_melanocyte_deep_dive.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
6.21 kB
"""melanocyte deep dive on aldrich: within-melanocyte cKO vs WT DE and pathway scoring."""
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"))
PATHWAYS = {
"MITF_regulon": ["Mitf", "Dct", "Tyr", "Pmel", "Mlana", "Tyrp1", "Slc24a5",
"Slc45a2", "Silv", "Sox10", "Pax3", "Kit", "Ednrb"],
"Wnt_signaling": ["Wnt3", "Wnt5a", "Wnt7a", "Wnt10b", "Ctnnb1", "Lef1", "Tcf4",
"Tcf7", "Axin2", "Dkk1", "Sfrp1", "Fzd7", "Lrp5", "Lrp6"],
"BMP_TGF_signaling": ["Bmp2", "Bmp4", "Bmp5", "Bmp7", "Bmpr1a", "Bmpr1b", "Bmpr2",
"Smad1", "Smad3", "Smad5", "Tgfb1", "Tgfb2", "Tgfbr1",
"Tgfbr2", "Id1", "Id2", "Id3"],
"FGF_signaling": ["Fgf1", "Fgf2", "Fgf7", "Fgf9", "Fgf10", "Fgf20", "Fgfr1",
"Fgfr2", "Fgfr3", "Fgfr4", "Etv1", "Etv4", "Etv5", "Spry2",
"Spry4", "Dusp6"],
"Notch_signaling": ["Notch1", "Notch2", "Notch3", "Jag1", "Jag2", "Dll1", "Dll3",
"Dll4", "Hes1", "Hes5", "Hey1", "Hey2", "Rbpj", "Maml1"],
"Hedgehog": ["Shh", "Ihh", "Ptch1", "Ptch2", "Smo", "Gli1", "Gli2", "Gli3",
"Hhip", "Sufu"],
"EMT": ["Zeb1", "Zeb2", "Snai1", "Snai2", "Twist1", "Twist2", "Vim",
"Cdh2", "Fn1", "Mmp2", "Mmp9", "Prrx1"],
"Cell_cycle": ["Ccnd1", "Ccnd2", "Ccne1", "Ccna2", "Ccnb1", "Cdk1", "Cdk2",
"Cdk4", "Cdk6", "Mki67", "Top2a", "Pcna", "Mcm2", "Mcm3"],
"Neural_crest": ["Sox10", "Sox9", "Sox2", "Pax3", "Foxd3", "Nes", "Ngfr",
"Ednrb", "Kit", "Tfap2a"],
"Apoptosis": ["Bax", "Bak1", "Bad", "Bid", "Bcl2", "Bcl2l1", "Casp3",
"Casp9", "Casp8", "Fas", "Fasl", "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"{name}"] = 0.0
continue
sc.tl.score_genes(sub, gene_list=present, score_name=f"{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
mask = a.obs["pred_label"] == "melanocyte"
print(f"[mel] melanocyte cells: {int(mask.sum())} "
f"({int((mask & (a.obs['genotype']=='En1-cKO')).sum())} cKO, "
f"{int((mask & (a.obs['genotype']=='WT')).sum())} WT)", flush=True)
if mask.sum() < 30:
print("[mel] too few melanocyte cells")
return
sub = a[mask].copy()
sub = pathway_scoring(sub, PATHWAYS)
print("\n[mel] pathway score cKO vs WT (positive = up in cKO):")
rows = []
for name in PATHWAYS.keys():
s = sub.obs[name].astype(float).values
g = sub.obs["genotype"].values
c_scores = s[g == "En1-cKO"]
w_scores = s[g == "WT"]
stat, pval = mannwhitneyu(c_scores, w_scores, alternative="two-sided")
delta = c_scores.mean() - w_scores.mean()
rows.append({"pathway": name, "delta_cKO_minus_WT": round(delta, 4),
"MannU_p": pval,
"cKO_mean": round(c_scores.mean(), 4),
"WT_mean": round(w_scores.mean(), 4)})
star = "***" if pval < 0.001 else "**" if pval < 0.01 else "*" if pval < 0.05 else ""
print(f" {name:20s} delta={delta:+.4f} p={pval:.2e} {star}")
pd.DataFrame(rows).sort_values("MannU_p").to_csv(OUT / "56_melanocyte_pathways.csv", index=False)
print("\n[mel] within-melanocyte cKO vs WT DE:")
sc.tl.rank_genes_groups(sub, "genotype", method="wilcoxon", n_genes=50, use_raw=False)
de = pd.DataFrame({
"cKO_up_gene": sub.uns["rank_genes_groups"]["names"]["En1-cKO"][:20],
"cKO_up_lfc": sub.uns["rank_genes_groups"]["logfoldchanges"]["En1-cKO"][:20],
"WT_up_gene": sub.uns["rank_genes_groups"]["names"]["WT"][:20],
"WT_up_lfc": sub.uns["rank_genes_groups"]["logfoldchanges"]["WT"][:20],
})
print(de.to_string(index=False))
de.to_csv(OUT / "56_melanocyte_wilcoxon_cko_vs_wt.csv", index=False)
lines = ["# Melanocyte deep dive — Aldrich En1-cKO vs WT",
"",
f"PANDA-MLP-predicted melanocyte cells: **{int(mask.sum())} total** "
f"({int((mask & (a.obs['genotype']=='En1-cKO')).sum())} cKO, "
f"{int((mask & (a.obs['genotype']=='WT')).sum())} WT).",
"",
"The v3b+v3c cross-model replication showed ~2× melanocyte enrichment in cKO "
"(log2fc +1.04, Fisher p ≈ 4×10⁻⁶). Here we probe what's happening WITHIN the "
"melanocyte compartment.",
"",
"## Pathway score comparisons (MannU cKO vs WT)",
""]
dfp = pd.DataFrame(rows).sort_values("MannU_p")
lines.append(dfp.to_markdown(index=False))
lines += ["",
"## Within-melanocyte Wilcoxon DE (top 20 each direction)",
""]
de_show = de.copy()
de_show["cKO_up_lfc"] = de_show["cKO_up_lfc"].round(3)
de_show["WT_up_lfc"] = de_show["WT_up_lfc"].round(3)
lines.append(de_show.to_markdown(index=False))
(OUT / "56_melanocyte_deep_dive.md").write_text("\n".join(lines))
print(f"[mel] wrote {OUT}/56_melanocyte_deep_dive.md")
if __name__ == "__main__":
main()