Files
RGC-ADOA/script/12_inflammation_modules.py
rain 092f39a662 chore: 初始化 RGC-ADOA 分析仓库
纳入 script/doc/ref/output 及配置;忽略 data/(26G 原始/中间数据)。
git 身份:rain <wjs_Rain@126.com>。
2026-09-18 18:21:19 +08:00

147 lines
7.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
12_inflammation_modules.py — P1-④ 炎症模块打分(胶质为主,决策记录 第二轮裁决 #3)
模块(小鼠基因符号,手工策展,以 Hallmark/Reactome 标准集为骨干):
IFN_alpha_ISG / IFN_gamma / IL6_JAK_STAT3 / TNFA_NFkB / Complement /
cGAS_STING / NLRP3_inflammasome / UPRmt / ISR /
Muller_reactive_gliosis / Microglia_DAM / Microglia_homeostatic
重点组:Muller、Microglia、RGC1-like、RGC2-likeRod/Cone 作参照。
Microglia WT 仅 8 核 → 仅描述性(每样本中位数 + 检出率),不做推断。
产出:output/12_inflammation/ + data/06_inflammation.h5ad
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scanpy as sc
OUT = Path("output/12_inflammation")
OUT.mkdir(parents=True, exist_ok=True)
plt.rcParams.update({"figure.dpi": 300, "savefig.dpi": 300, "font.size": 8})
GENESETS = {
"IFN_alpha_ISG": "Isg15/Ifit1/Ifit2/Ifit3/Ifit3b/Ifi27/Ifi27l2a/Ifi44/Ifi47/Ifitm1/Ifitm2/Ifitm3/Mx1/Mx2/Oas1a/Oas1b/Oas2/Oas3/Oasl1/Oasl2/Oas1g/Rsad2/Bst2/Usp18/Irf7/Irf9/Stat1/Stat2/Cxcl10/Cxcl9/Cxcl11/Ddx58/Ifih1/Ddx60/Cmpk2/Nlrc5/Psmb8/Psmb9/Psmb10/Tap1/Tap2/Tapbp/B2m/Gbp2/Gbp3/Gbp4/Gbp5/Gbp7/Iigp1/Irgm1/Trim21/Xaf1/Ifi35/Parp9/Parp14/Samd9l/Eif2ak2/Zbp1/Herc6/Rtp4/Lgals3bp/Ccl5",
"IFN_gamma": "Jak1/Jak2/Stat1/Irf1/Irf8/Ciita/Cd74/H2-Aa/H2-Ab1/H2-Eb1/H2-K1/H2-D1/Icam1/Gbp2/Gbp3/Gbp4/Gbp5/Gbp7/Ifi47/Socs1/Cxcl9/Cxcl10/Cxcl11/Psmb9/Tap1/Tap2",
"IL6_JAK_STAT3": "Il6/Il6ra/Il6st/Jak1/Jak2/Tyk2/Stat3/Socs3/Nfkbia/Nfkbiz/Tnfaip3/Cxcl1/Ccl2/Icam1/Timp1/Serpina3n/Lcn2/Osmr/Saa1/Saa2/Saa3",
"TNFA_NFkB": "Tnf/Tnfrsf1a/Tnfrsf1b/Nfkb1/Nfkb2/Rela/Relb/Nfkbia/Nfkbib/Nfkbiz/Tnfaip3/Tnfaip2/Cxcl1/Cxcl2/Cxcl10/Ccl2/Ccl5/Ccl20/Il1b/Il1a/Ptgs2/Icam1/Vcam1/Sod2/Jun/Fos",
"Complement": "C1qa/C1qb/C1qc/C2/C3/C4b/Cfb/Cfd/Cfh/Cfi/Serping1/Cd55/Cd59a/Cd59b/Cr1l/C3ar1/C5ar1/Itgam/Itgax/Itgb2/Masp1/Masp2/Mbl2/C1ra/C1s1/Clu/Vtn",
"cGAS_STING": "Mb21d1/Tmem173/Tbk1/Ikbke/Irf3/Ddx41/Ifnb1",
"NLRP3_inflammasome": "Nlrp3/Pycard/Casp1/Il1b/Il18/Gsdmd/Aim2/Nek7/Txnip",
"UPRmt": "Atf5/Hspa9/Hspe1/Hspd1/Dnaja3/Lonp1/Clpp/Yme1l1/Afg3l2",
"ISR": "Atf4/Ddit3/Atf3/Asns/Trib3/Ppp1r15a/Chac1",
"Muller_reactive_gliosis": "Gfap/Vim/Serpina3n/Apoe/Lcn2/S100a6/Cd44/Timp1/Nes/Clu/Osmr/Ccl2",
"Microglia_DAM": "Apoe/Spp1/Lpl/Trem2/Tyrobp/Axl/Clec7a/Cst7/Itgax/Cd9/Lgals3/Lilrb4a/Ccl6/Fabp5/Ctsb/Ctsd/Gpnmb",
"Microglia_homeostatic": "P2ry12/P2ry13/Tmem119/Hexb/Sall1/Cx3cr1/Siglech/Mafb/Selplg/Fcrls/Olfml3/Tgfbr1/Gpr34/Slc2a5",
}
adata = sc.read_h5ad("data/05_rgc2sig.h5ad")
raw = adata.raw.to_adata()
raw.obs = adata.obs.copy()
gs = {k: [g for g in v.split("/") if g in raw.var_names] for k, v in GENESETS.items()}
for k, v in gs.items():
print(f"{k}: {len(v)}/{len(GENESETS[k].split('/'))} genes in data")
miss = set(GENESETS[k].split("/")) - set(v)
if miss:
print(f" missing: {sorted(miss)}")
for k, v in gs.items():
sc.tl.score_genes(raw, gene_list=v, score_name=f"score_{k}", use_raw=False, random_state=0)
adata.obs[f"score_{k}"] = raw.obs[f"score_{k}"]
# ---- 分组定义 ----
obs = adata.obs
obs["cmp_group"] = obs["cell_type"].astype(str)
is_rgc = obs["cell_type"] == "RGC"
obs.loc[is_rgc, "cmp_group"] = obs.loc[is_rgc, "rgc2_sig_group"].astype(str)
FOCUS = ["RGC2-like", "RGC1-like", "Muller", "Microglia", "Astrocyte", "Rod", "Cone"]
# ---- 每样本中位数 + delta 表 ----
rows = []
for grp in FOCUS:
m = obs["cmp_group"] == grp
for k in gs:
col = f"score_{k}"
per_s = obs.loc[m].groupby("sample")[col].median()
wt = per_s.get("WT", np.nan)
rows.append({
"group": grp, "module": k,
"n_WT": int((m & (obs["genotype"] == "WT")).sum()),
"median_WT": round(wt, 4),
"median_S1": round(per_s.get("Opa1V291D_S1", np.nan), 4),
"median_S2": round(per_s.get("Opa1V291D_S2", np.nan), 4),
"delta_S1": round(per_s.get("Opa1V291D_S1", np.nan) - wt, 4),
"delta_S2": round(per_s.get("Opa1V291D_S2", np.nan) - wt, 4),
})
res = pd.DataFrame(rows)
res.to_csv(OUT / "inflammation_module_scores.csv", index=False)
print("\n", res[res["group"].isin(["Muller", "Microglia", "RGC2-like"])].to_string(index=False))
# ---- 关键基因检出率(每 组×基因型 检出比例) ----
KEY = ["Ifnb1", "Ifnar1", "Ifnar2", "Jak1", "Jak2", "Stat1", "Stat2", "Stat3", "Irf7", "Irf9",
"Isg15", "Ifit1", "Ifit2", "Ifit3", "Mx1", "Cxcl10", "Il6", "Tnf", "Il1b", "C3", "C1qa",
"Mb21d1", "Tmem173", "Nlrp3", "Gfap", "Serpina3n", "Apoe", "Lcn2", "Spp1", "Trem2"]
rawX = raw.X
vidx = {g: i for i, g in enumerate(raw.var_names)}
det_rows = []
for grp in FOCUS:
m = obs["cmp_group"] == grp
for g in KEY:
if g not in vidx:
continue
col = rawX[:, vidx[g]]
r = {"group": grp, "gene": g}
for geno in ["WT", "V291D"]:
mm = (m & (obs["genotype"] == geno)).values
r[f"det_{geno}"] = round((col[mm].toarray().ravel() > 0).mean(), 4) if mm.sum() else np.nan
r[f"n_{geno}"] = int(mm.sum())
det_rows.append(r)
det = pd.DataFrame(det_rows)
det.to_csv(OUT / "key_gene_detection_rates.csv", index=False)
# ---- 图 1:模块 delta 热图 ----
piv = res.pivot(index="module", columns="group", values="delta_S1")
piv2 = res.pivot(index="module", columns="group", values="delta_S2")
mean_delta = (piv + piv2) / 2
mean_delta = mean_delta[[c for c in FOCUS if c in mean_delta.columns]]
fig, ax = plt.subplots(figsize=(6.5, 4.2))
vmax = np.nanmax(np.abs(mean_delta.values))
im = ax.imshow(mean_delta.values, cmap="RdBu_r", vmin=-vmax, vmax=vmax, aspect="auto")
ax.set_xticks(range(len(mean_delta.columns)), mean_delta.columns, rotation=45, ha="right")
ax.set_yticks(range(len(mean_delta.index)), mean_delta.index, fontsize=7)
for i in range(mean_delta.shape[0]):
for j in range(mean_delta.shape[1]):
v = mean_delta.values[i, j]
ax.text(j, i, f"{v:+.2f}", ha="center", va="center", fontsize=6,
color="white" if abs(v) > 0.6 * vmax else "black")
fig.colorbar(im, label="mean delta module score (V291D - WT)")
ax.set_title("Inflammation module scores: mutant - WT (mean of S1,S2 deltas)")
fig.tight_layout()
fig.savefig(OUT / "inflammation_heatmap.png", bbox_inches="tight")
# ---- 图 2Muller 反应性胶质化 + 补体 分布(每样本) ----
fig, axes = plt.subplots(1, 3, figsize=(10, 3.2))
for ax, k in zip(axes, ["Muller_reactive_gliosis", "Complement", "IFN_alpha_ISG"]):
col = f"score_{k}"
for grp, color in [("Muller", "#1f77b4"), ("Microglia", "#2ca02c")]:
for s, ls in [("WT", "-"), ("Opa1V291D_S1", "--"), ("Opa1V291D_S2", ":")]:
mm = (obs["cmp_group"] == grp) & (obs["sample"] == s)
v = obs.loc[mm, col]
if len(v) > 5:
ax.hist(v, bins=40, histtype="step", density=True, color=color, ls=ls, lw=1.2,
label=f"{grp} {s.replace('Opa1V291D_', 'V291D_')}")
ax.set_title(k, fontsize=9); ax.set_xlabel("module score")
axes[0].set_ylabel("density")
axes[-1].legend(fontsize=5.5, frameon=False)
fig.tight_layout()
fig.savefig(OUT / "glia_module_distributions.png", bbox_inches="tight")
adata.write_h5ad("data/06_inflammation.h5ad")
print("Done ->", OUT, "+ data/06_inflammation.h5ad")