| """within-class wilcoxon DE WT vs Kit_W41 + module scoring; checks dahlin paper claims (Myc/ISR).""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| import warnings, json, sys | |
| warnings.filterwarnings("ignore") | |
| import numpy as np, pandas as pd, anndata as ad, scanpy as sc, scipy.sparse as sp | |
| 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]))) | |
| DAHLIN = Path(str(PANDA_ROOT / "data/corpus/hematopoiesis/held_out_unlabeled/dahlin_extract")) | |
| PRED = Path(str(PANDA_ROOT / "discovery/hematopoiesis/marker/66_dahlin_predictions.csv")) | |
| OUT = Path(str(PANDA_ROOT / "discovery/hematopoiesis/marker")) | |
| def load_dahlin_all(): | |
| parts = [] | |
| for f in sorted(DAHLIN.glob("*.txt.gz")): | |
| gsm = f.name.split("_")[0] | |
| 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 | |
| var = pd.DataFrame(index=df.index.astype(str)) | |
| var["ensmusg"] = var.index.values | |
| a = ad.AnnData(X=X, obs=obs, var=var) | |
| parts.append(a) | |
| return ad.concat(parts, join="outer", label="_batch") | |
| def main(): | |
| print("[dahlin-mech] loading Dahlin …", flush=True) | |
| a = load_dahlin_all() | |
| import mygene | |
| mg = mygene.MyGeneInfo() | |
| ids = a.var_names.astype(str).tolist() | |
| print(f"[dahlin-mech] mapping {len(ids)} genes …", flush=True) | |
| res = mg.querymany(ids, 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() | |
| print(f"[dahlin-mech] converted, shape now {a.shape}", flush=True) | |
| preds = pd.read_csv(PRED, index_col=0) | |
| a.obs = a.obs.join(preds[["pred_label", "pred_conf", "genotype"]], how="left") | |
| print(f"[dahlin-mech] joined preds; genotype counts {a.obs['genotype'].value_counts().to_dict()}", | |
| flush=True) | |
| a_de = a.copy() | |
| sc.pp.normalize_total(a_de, target_sum=1e4) | |
| sc.pp.log1p(a_de) | |
| rows = [] | |
| lg_class_min = 300 | |
| class_counts = a.obs["pred_label"].value_counts() | |
| for cls in class_counts[class_counts >= lg_class_min].index: | |
| mask = a.obs["pred_label"] == cls | |
| sub = a_de[mask].copy() | |
| if len(sub.obs["genotype"].unique()) < 2: | |
| continue | |
| gc = sub.obs["genotype"].value_counts() | |
| if gc.min() < 30: | |
| continue | |
| try: | |
| sc.tl.rank_genes_groups(sub, "genotype", method="wilcoxon", n_genes=40, | |
| use_raw=False) | |
| for grp in ["Kit_W41", "WT"]: | |
| if grp not in sub.uns["rank_genes_groups"]["names"].dtype.names: | |
| continue | |
| names = list(sub.uns["rank_genes_groups"]["names"][grp][:15]) | |
| lfcs = list(sub.uns["rank_genes_groups"]["logfoldchanges"][grp][:15]) | |
| padjs = list(sub.uns["rank_genes_groups"]["pvals_adj"][grp][:15]) | |
| for g, l, p in zip(names, lfcs, padjs): | |
| rows.append({"class": cls, "up_in": grp, "gene": g, | |
| "logfc": round(float(l), 3), | |
| "padj": float(p)}) | |
| except Exception as exc: | |
| print(f"[dahlin-mech] DE failed for {cls}: {exc}") | |
| pd.DataFrame(rows).to_csv(OUT / "67_dahlin_within_class_de.csv", index=False) | |
| MODULES = { | |
| "MYC_targets": ["Myc", "Mycn", "Nme1", "Odc1", "Ncl", "Ppat", "Cad", | |
| "Ldha", "Nop58"], | |
| "Integrated_stress": ["Atf4", "Ddit3", "Chac1", "Trib3", "Asns", "Gdf15", | |
| "Sesn2", "Slc7a11"], | |
| "Apoptosis_pro": ["Bax", "Bak1", "Bad", "Bid", "Bbc3", "Pmaip1", "Casp3", | |
| "Casp9", "Trp53"], | |
| "Apoptosis_anti": ["Bcl2", "Bcl2l1", "Mcl1", "Bcl2l2"], | |
| "Erythroid_dev": ["Klf1", "Gata1", "Gata2", "Epor", "Hba-a1", "Hbb-b1"], | |
| "Cell_cycle": ["Ccnd1", "Ccnd2", "Ccne1", "Ccna2", "Ccnb1", "Cdk1", | |
| "Mki67", "Top2a", "Pcna", "Mcm2", "Mcm3", "Mcm4"], | |
| "Kit_signaling": ["Kit", "Kitl", "Sos1", "Rasgrf1", "Grb2", "Pik3ca"], | |
| } | |
| for name, genes in MODULES.items(): | |
| present = [g for g in genes if g in a_de.var_names] | |
| if not present: continue | |
| sc.tl.score_genes(a_de, gene_list=present, score_name=f"pw_{name}", | |
| random_state=0, use_raw=False) | |
| print("\n[dahlin-mech] Kit_W41 vs WT module score contrast per class:", flush=True) | |
| mod_rows = [] | |
| for cls in class_counts[class_counts >= lg_class_min].index: | |
| mask = a_de.obs["pred_label"] == cls | |
| for name in MODULES.keys(): | |
| col = f"pw_{name}" | |
| if col not in a_de.obs: continue | |
| s = a_de.obs.loc[mask, col].astype(float).values | |
| g = a_de.obs.loc[mask, "genotype"].astype(str).values | |
| cv = s[g == "Kit_W41"]; wv = s[g == "WT"] | |
| if len(cv) < 20 or len(wv) < 20: continue | |
| try: | |
| _, p = mannwhitneyu(cv, wv, alternative="two-sided") | |
| except Exception: p = 1.0 | |
| delta = cv.mean() - wv.mean() | |
| star = "***" if p < 1e-6 else "**" if p < 1e-3 else "*" if p < 0.05 else "" | |
| print(f" {cls:15s} {name:20s} delta={delta:+.4f} p={p:.2e} {star}") | |
| mod_rows.append({"class": cls, "module": name, | |
| "delta_Kit_minus_WT": round(delta, 4), | |
| "MannU_p": p}) | |
| pd.DataFrame(mod_rows).to_csv(OUT / "67_dahlin_module_scores.csv", index=False) | |
| print(f"\n[dahlin-mech] complete. Outputs in {OUT}/") | |
| if __name__ == "__main__": | |
| main() | |