PANDA / scripts /analysis /110_veres_polyhormonal_alpha.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
5 kB
"""test if veres alpha-pool polyhormonal (Ins+/Gcg+/Sst+) cells form a distinct sub-cluster vs graded."""
from __future__ import annotations
import json
import warnings
from pathlib import Path
import anndata as ad
import numpy as np
import pandas as pd
import scanpy as sc
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))
OUT = ROOT / "discovery/pancreas/marker"
OUT.mkdir(parents=True, exist_ok=True)
VERES = ROOT / "data/corpus/pancreas/held_out_labeled/veres_GSE114412_test.h5ad"
PRED = ROOT / "discovery/pancreas/marker/veres_predictions.csv"
def col(sub, g):
if g not in sub.var_names:
return np.zeros(sub.n_obs)
j = sub.var_names.get_loc(g)
x = sub.X[:, j]
if hasattr(x, "toarray"):
x = x.toarray()
return np.asarray(x).ravel()
def main():
print("[load]", flush=True)
a = ad.read_h5ad(VERES)
pred = pd.read_csv(PRED)
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])
sub = a[a.obs["pred_label"].astype(str).isin(
["alpha_progenitor", "alpha"])].copy()
print(f"[filter] {sub.n_obs} alpha-pool cells", flush=True)
ins1 = col(sub, "Ins1"); ins2 = col(sub, "Ins2")
gcg = col(sub, "Gcg"); sst = col(sub, "Sst"); iapp = col(sub, "Iapp")
ins_level = ins1 + ins2
q_ins = np.quantile(ins_level, 0.75)
q_gcg = np.quantile(gcg, 0.75)
q_sst = np.quantile(sst, 0.75)
n_pos = (
(ins_level >= q_ins).astype(int)
+ (gcg >= q_gcg).astype(int)
+ (sst >= q_sst).astype(int)
)
sub.obs["INS_level"] = ins_level
sub.obs["GCG_level"] = gcg
sub.obs["SST_level"] = sst
sub.obs["n_hormones_positive"] = n_pos
print("[cluster] PCA + Leiden", flush=True)
sc.pp.highly_variable_genes(sub, n_top_genes=2000, flavor="seurat_v3",
subset=False, batch_key=None)
sc.pp.pca(sub, n_comps=30)
sc.pp.neighbors(sub, n_neighbors=15, n_pcs=30)
sc.tl.leiden(sub, resolution=0.5, random_state=0, key_added="leiden_alpha")
df = sub.obs[[
"pred_label", "paper_label", "leiden_alpha",
"INS_level", "GCG_level", "SST_level", "n_hormones_positive"
]].copy()
df["Ins1"] = ins1; df["Ins2"] = ins2; df["Gcg"] = gcg
df["Sst"] = sst; df["Iapp"] = iapp
df.reset_index().rename(columns={"index": "cell_id"}).to_csv(
OUT / "110_veres_polyhormonal_alpha_scores.csv", index=False)
baseline_polyhormonal = float((df["n_hormones_positive"] >= 2).mean())
per_clus = df.groupby("leiden_alpha", observed=True).agg(
n_cells=("n_hormones_positive", "size"),
frac_polyhormonal=("n_hormones_positive",
lambda s: float((s >= 2).mean())),
frac_gcg_hi=("GCG_level",
lambda s: float((s >= q_gcg).mean())),
frac_ins_hi=("INS_level",
lambda s: float((s >= q_ins).mean())),
frac_sst_hi=("SST_level",
lambda s: float((s >= q_sst).mean())),
mean_gcg=("GCG_level", "mean"),
mean_ins=("INS_level", "mean"),
mean_sst=("SST_level", "mean"),
).sort_values("frac_polyhormonal", ascending=False).reset_index()
per_clus["enrichment_vs_baseline"] = per_clus["frac_polyhormonal"] \
/ max(baseline_polyhormonal, 1e-6)
per_clus.to_csv(OUT / "110_veres_polyhormonal_alpha_per_cluster.csv",
index=False)
n_2x_clusters = int((per_clus["enrichment_vs_baseline"] >= 2.0).sum())
verdict = ("distinct_polyhormonal_subcluster" if n_2x_clusters in (1, 2)
else "graded_phenotype" if n_2x_clusters == 0
else "diffuse_enrichment")
summary = {
"n_alpha_pool": int(sub.n_obs),
"baseline_polyhormonal_frac": round(baseline_polyhormonal, 4),
"q75_thresholds": {"Ins": float(q_ins),
"Gcg": float(q_gcg),
"Sst": float(q_sst)},
"leiden_resolution": 0.5,
"n_clusters": int(per_clus["leiden_alpha"].nunique()),
"n_clusters_enriched_2x": n_2x_clusters,
"verdict": verdict,
"per_cluster": per_clus.to_dict("records"),
}
with open(OUT / "110_veres_polyhormonal_alpha_summary.json", "w") as f:
json.dump(summary, f, indent=2)
print("\n[done] verdict:", verdict, flush=True)
print(per_clus.round(3).to_string(index=False))
if __name__ == "__main__":
main()