MouseBrain Downstream Tutorial
This notebook expands the downstream analysis after the Microglia stGP fit: spatial proximity tests, cell-type shell enrichment, regression, and pathway/signature enrichment.
[ ]:
import json
import os
import sys
import time
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import numpy as np
import pandas as pd
import scanpy as sc
from IPython.display import display
PROJECT_DIR = Path.cwd()
if PROJECT_DIR.name != "RealData_MouseBrainMERFISH":
PROJECT_DIR = Path("/home/byual/stGP/RealData_MouseBrainMERFISH")
os.chdir(PROJECT_DIR)
sys.path.insert(0, str(PROJECT_DIR))
import utils as u
from plots import set_nature_style
set_nature_style()
print(f"Working directory: {PROJECT_DIR}")
print(f"QC data: {u.DATA_QC}")
print(f"Proximity results: {u.RESULTS_PROXIMITY}")
print(f"Enrichment results: {u.RESULTS_ENRICHMENT}")
Working directory: /import/home4/byual/stGP-0529/RealData_MouseBrainMERFISH
QC data: data/qc/aging_coronal_qc.h5ad
Proximity results: Results/proximity
Enrichment results: Results/enrichment
[2]:
CELLTYPE = "Microglia"
PROGRAMS = [2] # Fig. 5 focuses on stGP2. Use None to run every program.
N_PERM = 100
SEED = 0
SKIP_EXISTING = True
GENE_SET_COLLECTIONS = None # None = all canonical GO + cell-type signature sets.
PADJ_THRESHOLD = 0.1
N_TOP = 6
TOP_PER_PROGRAM = 5
safe_ct = u.safe_name(CELLTYPE)
prox_csv_dir = u.RESULTS_PROXIMITY / safe_ct
prox_fig_dir = u.FIGURES_ROOT / safe_ct / "proximity"
enrich_csv_dir = u.RESULTS_ENRICHMENT / safe_ct
enrich_fig_dir = u.FIGURES_ROOT / safe_ct / "enrichment"
for path in [prox_csv_dir, prox_fig_dir, enrich_csv_dir, enrich_fig_dir]:
path.mkdir(parents=True, exist_ok=True)
display(pd.DataFrame([{
"celltype": CELLTYPE,
"programs": "all" if PROGRAMS is None else PROGRAMS,
"n_perm": N_PERM,
"skip_existing": SKIP_EXISTING,
"proximity_csv_dir": str(prox_csv_dir),
"proximity_fig_dir": str(prox_fig_dir),
"enrichment_csv_dir": str(enrich_csv_dir),
"enrichment_fig_dir": str(enrich_fig_dir),
}]))
| celltype | programs | n_perm | skip_existing | proximity_csv_dir | proximity_fig_dir | enrichment_csv_dir | enrichment_fig_dir | |
|---|---|---|---|---|---|---|---|---|
| 0 | Microglia | [2] | 100 | True | Results/proximity/Microglia | Figures/Microglia/proximity | Results/enrichment/Microglia | Figures/Microglia/enrichment |
1. Load target stGP scores and all-cell QC coordinates
Proximity tests use the target cell type’s stGP spatial residual b and all cell coordinates/cell-type labels from the QC AnnData.
[3]:
adata_target = u.load_target_data(CELLTYPE, u.RESULTS_STGP)
target = u.extract_target_arrays(adata_target)
regions = sorted(set(target["region"].tolist()))
print(f"Target: {len(target['age']):,} {CELLTYPE} cells")
print(f"Programs: {target['program_labels']}")
print(f"Regions: {regions}")
adata_all = sc.read_h5ad(str(u.DATA_QC))
glob = u.extract_global_arrays(adata_all)
print(f"All QC cells: {adata_all.n_obs:,}")
display(pd.DataFrame({
"program": target["program_labels"],
"b_mean": target["B"].mean(axis=0),
"b_sd": target["B"].std(axis=0),
}))
Target: 52,417 Microglia cells
Programs: ['stGP1', 'stGP2', 'stGP3', 'stGP4']
Regions: ['CC/ACO', 'CTX', 'STR', 'VEN']
All QC cells: 981,750
| program | b_mean | b_sd | |
|---|---|---|---|
| 0 | stGP1 | -0.000025 | 2.349388 |
| 1 | stGP2 | 0.109022 | 2.114654 |
| 2 | stGP3 | -0.133021 | 2.165237 |
| 3 | stGP4 | 0.887728 | 2.202204 |
2. Abundance over age
Before proximity testing, check whether target/effectors change strongly in abundance across age.
[4]:
df_counts, df_abund = u.compute_abundance_check(CELLTYPE, glob)
df_counts.to_csv(prox_csv_dir / "abundance_per_slice.csv", index=False)
df_abund.to_csv(prox_csv_dir / "abundance_summary.csv", index=False)
u.render_abundance(CELLTYPE, df_counts, df_abund, prox_fig_dir)
display(df_abund.sort_values("spearman_rho", ascending=False))
print(f"Wrote abundance figure: {prox_fig_dir / 'abundance_check.png'}")
| celltype | total | n_slices | spearman_rho | spearman_p | |
|---|---|---|---|---|---|
| 0 | T cell | 1008 | 20 | 0.645113 | 2.130693e-03 |
| 1 | Microglia | 52417 | 20 | 0.373825 | 1.044513e-01 |
| 2 | Neuroblast | 3260 | 20 | -0.939850 | 7.860443e-10 |
| 3 | NSC | 2448 | 20 | -0.964635 | 7.227150e-12 |
Wrote abundance figure: Figures/Microglia/proximity/abundance_check.png
3. Matched near-vs-far proximity and permutation null
For each program, compare target cells near an effector cell type against far target cells within the same age slice. A spatial permutation null keeps effector counts fixed per slice.
[5]:
if PROGRAMS is None:
program_indices = list(range(target["n_programs"]))
else:
program_indices = [p - 1 for p in PROGRAMS if 0 < p <= target["n_programs"]]
effectors = [c for c in u.ALL_CELLTYPES if c != CELLTYPE]
program_context = {}
for k in program_indices:
k_label = target["program_labels"][k]
sub_csv = prox_csv_dir / k_label
sub_fig = prox_fig_dir / k_label
sub_csv.mkdir(parents=True, exist_ok=True)
sub_fig.mkdir(parents=True, exist_ok=True)
match_path = sub_csv / "matched_effects.csv"
perm_path = sub_csv / "permutation_null.csv"
print(f"\n--- {CELLTYPE} {k_label}: matched proximity ---")
if SKIP_EXISTING and match_path.exists() and perm_path.exists():
df_match = pd.read_csv(match_path)
df_perm = pd.read_csv(perm_path)
print(f"loaded cached matched/permutation tables from {sub_csv}")
else:
t0 = time.perf_counter()
df_match = u.compute_matched_effect_table(target, glob, regions, effectors)
df_match.to_csv(match_path, index=False)
u.make_standalone_matched_heatmap(df_match, CELLTYPE, k_label, sub_fig / "matched_heatmap.png")
print(f"matched table done in {time.perf_counter() - t0:.1f}s")
t0 = time.perf_counter()
df_perm, _, _ = u.compute_permutation_null(
target, glob, regions, effectors, k, n_perm=N_PERM, seed=SEED,
)
df_perm.to_csv(perm_path, index=False)
u.make_standalone_effector_ranking(df_match, df_perm, CELLTYPE, k_label, sub_fig / "effector_ranking.png")
print(f"permutation null done in {time.perf_counter() - t0:.1f}s")
sig_effectors = u._select_significant_effectors(df_match, k_label, q_threshold=u.SIG_Q_THRESHOLD)
if not sig_effectors:
sig_effectors = [e for e in u.DEFAULT_PERM_EFFECTORS if e != CELLTYPE][:4]
print(f"selected effectors: {sig_effectors}")
program_context[k] = dict(
label=k_label, sub_csv=sub_csv, sub_fig=sub_fig,
df_match=df_match, df_perm=df_perm, sig_effectors=sig_effectors,
)
display(pd.concat([
ctx["df_match"].assign(focus_program=ctx["label"])
for ctx in program_context.values()
], ignore_index=True).head())
--- Microglia stGP2: matched proximity ---
loaded cached matched/permutation tables from Results/proximity/Microglia/stGP2
selected effectors: ['Endothelial', 'NSC', 'Astrocyte', 'Neuroblast', 'Ependymal', 'T cell', 'Neuron-Excitatory', 'Oligodendrocyte', 'Neuron-MSN', 'Pericyte', 'Macrophage', 'OPC']
| effector | program | k | effect | se | z | p | n_near | n_far | n_blocks | q_bh | focus_program | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Astrocyte | stGP1 | 0 | -0.333182 | 0.201157 | -1.656328 | 9.765550e-02 | 1122 | 130 | 2 | 1.103932e-01 | stGP2 |
| 1 | Astrocyte | stGP2 | 1 | 2.640462 | 0.134043 | 19.698552 | 2.218769e-86 | 1122 | 130 | 2 | 5.244364e-86 | stGP2 |
| 2 | Astrocyte | stGP3 | 2 | 1.079732 | 0.089434 | 12.072961 | 1.467561e-33 | 1122 | 130 | 2 | 2.312520e-33 | stGP2 |
| 3 | Astrocyte | stGP4 | 3 | -1.990231 | 0.182373 | -10.913000 | 9.990450e-28 | 1122 | 130 | 2 | 1.484295e-27 | stGP2 |
| 4 | Endothelial | stGP1 | 0 | -3.665859 | 0.245810 | -14.913354 | 2.698483e-50 | 5482 | 86 | 6 | 5.197079e-50 | stGP2 |
4. Distance decay, age stratification, shell enrichment, and regression
These analyses characterize whether proximity effects are distance-dependent, age-dependent, enriched/depleted in a local shell, and explainable by age/region/cell-type density predictors.
[6]:
program_summaries = []
for k, ctx in program_context.items():
k_label = ctx["label"]
sub_csv = ctx["sub_csv"]
sub_fig = ctx["sub_fig"]
sig_effectors = ctx["sig_effectors"]
df_match = ctx["df_match"]
df_perm = ctx["df_perm"]
print(f"\n--- {CELLTYPE} {k_label}: secondary proximity analyses ---")
secondary_paths = {
"decay": sub_csv / "distance_decay.csv",
"age": sub_csv / "age_stratification.csv",
"enrich": sub_csv / "proximity_enrichment.csv",
"coefs": sub_csv / "variance_decomposition_coefs_full.csv",
"meta": sub_csv / "variance_decomposition_meta.json",
}
if SKIP_EXISTING and all(p.exists() for p in secondary_paths.values()):
df_decay = pd.read_csv(secondary_paths["decay"])
df_age = pd.read_csv(secondary_paths["age"])
df_enrich = pd.read_csv(secondary_paths["enrich"])
df_coefs = pd.read_csv(secondary_paths["coefs"])
var_meta = json.loads(secondary_paths["meta"].read_text())
demo_age = u.pick_demo_slice(target, k, glob)
print(f"loaded cached secondary tables from {sub_csv}")
else:
df_decay = u.compute_distance_decay(target, glob, regions, sig_effectors, k)
df_decay.to_csv(secondary_paths["decay"], index=False)
u.make_standalone_distance_decay(df_decay, CELLTYPE, k_label, sub_fig / "distance_decay.png")
df_age = u.compute_age_stratification(target, glob, regions, sig_effectors, k)
df_age.to_csv(secondary_paths["age"], index=False)
u.make_standalone_age_stratification(df_age, CELLTYPE, k_label, sub_fig / "age_stratification.png")
df_enrich, _, _, _ = u.compute_proximity_enrichment(target, glob, regions, k)
df_enrich.to_csv(secondary_paths["enrich"], index=False)
u.make_standalone_enrichment(df_enrich, CELLTYPE, k_label, sub_fig / "proximity_enrichment.png")
df_coefs, var_meta = u.compute_variance_decomposition(target, glob, regions, k, CELLTYPE)
df_coefs.to_csv(secondary_paths["coefs"], index=False)
secondary_paths["meta"].write_text(json.dumps(var_meta, indent=2))
u.make_standalone_variance(df_coefs, var_meta, CELLTYPE, k_label, sub_fig / "regression.png")
demo_age = u.pick_demo_slice(target, k, glob)
for eff_name, eff_tag in [("T cell", "Tcell"), ("NSC", "NSC")]:
if eff_name == CELLTYPE:
continue
eff_demo_age = u.pick_demo_slice(target, k, glob, prefer_old_with_effector=eff_name)
u.make_standalone_spatial(
target, glob, k, eff_demo_age, CELLTYPE, k_label,
sub_fig / f"spatial_example_{eff_tag}.png", effector=eff_name,
)
u.make_standalone_near_far_violins(
target, glob, k, CELLTYPE, k_label, sig_effectors, sub_fig / "near_far_violins.png",
)
summary = u._summarise_program(df_match, df_perm, CELLTYPE, k_label, demo_age, target, sig_effectors, var_meta)
program_summaries.append(summary)
print(f"secondary outputs are available in {sub_csv} and {sub_fig}")
display(pd.DataFrame(program_summaries))
--- Microglia stGP2: secondary proximity analyses ---
loaded cached secondary tables from Results/proximity/Microglia/stGP2
secondary outputs are available in Results/proximity/Microglia/stGP2 and Figures/Microglia/proximity/stGP2
| target_celltype | program | demo_age | n_target_cells | n_predictors | R2_full | sig_effectors | top_pro_aging_effector | top_pro_aging_effect | top_pro_rejuv_effector | top_pro_rejuv_effect | n_perm_pass | perm_pass_effectors | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Microglia | stGP2 | 34.5 | 52417 | 17 | 0.4008 | [Endothelial, NSC, Astrocyte, Neuroblast, Epen... | NSC | 3.390692 | Endothelial | -4.832034 | 7 | Astrocyte|Endothelial|Ependymal|Neuroblast|NSC... |
5. Downstream high-b vs low-b cell-type enrichment
This section asks which surrounding cell types are enriched around high spatial residual b cells, both across all slices and within each slice.
[7]:
for k, ctx in program_context.items():
k_label = ctx["label"]
out_csv = ctx["sub_csv"] / "downstream"
out_fig = ctx["sub_fig"] / "downstream"
out_csv.mkdir(parents=True, exist_ok=True)
out_fig.mkdir(parents=True, exist_ok=True)
df_match_prog = ctx["df_match"][ctx["df_match"]["program"] == k_label].copy()
print(f"\n--- {CELLTYPE} {k_label}: downstream high-b/low-b enrichment ---")
downstream_paths = {
"all": out_csv / "all_slices_enrichment.csv",
"slice_enrich": out_csv / "per_slice_enrichment.csv",
"slice_match": out_csv / "per_slice_matched_effects.csv",
"summary": out_csv / "downstream_summary.json",
}
if SKIP_EXISTING and all(p.exists() for p in downstream_paths.values()):
df_all = pd.read_csv(downstream_paths["all"])
df_slice_enrich = pd.read_csv(downstream_paths["slice_enrich"])
df_slice_match = pd.read_csv(downstream_paths["slice_match"])
downstream_summary = json.loads(downstream_paths["summary"].read_text())
print(f"loaded cached downstream tables from {out_csv}")
else:
counts_by_eff = u._shell_counts_by_effector(target, glob, effectors)
df_all = u.compute_downstream_all_slices_enrichment(
target, glob, effectors, k, counts_by_eff=counts_by_eff,
)
df_slice_enrich = u.compute_downstream_per_slice_enrichment(
target, glob, effectors, k, counts_by_eff=counts_by_eff,
)
df_slice_match = u.compute_downstream_per_slice_matched_effects(target, glob, effectors, k)
df_all.to_csv(downstream_paths["all"], index=False)
df_slice_enrich.to_csv(downstream_paths["slice_enrich"], index=False)
df_slice_match.to_csv(downstream_paths["slice_match"], index=False)
downstream_summary = u.summarise_downstream(
df_all, df_slice_enrich, df_slice_match, df_match_prog, CELLTYPE, k_label,
)
downstream_paths["summary"].write_text(json.dumps(downstream_summary, indent=2))
u.make_downstream_all_slices_figure(
df_all, df_match_prog, CELLTYPE, k_label, out_fig / "all_slices_celltype_enrichment.png",
)
u.make_downstream_heatmap(
df_slice_enrich, value_col="log2fc", q_col="q_bh_by_slice",
title=f"{CELLTYPE} {k_label}: per-slice high-b shell enrichment",
cbar_label="log2 shell enrichment",
savepath=out_fig / "per_slice_enrichment_heatmap.png",
)
u.make_downstream_heatmap(
df_slice_match, value_col="effect", q_col="q_bh_by_slice",
title=f"{CELLTYPE} {k_label}: per-slice matched proximity effect",
cbar_label="Delta median b (near - far)",
savepath=out_fig / "per_slice_matched_effect_heatmap.png",
)
by_ct_dir = out_fig / "by_celltype"
for eff in effectors:
eff_dir = by_ct_dir / u.safe_name(eff)
eff_dir.mkdir(parents=True, exist_ok=True)
u.make_downstream_effector_trend(
df_slice_enrich, df_slice_match, eff, CELLTYPE, k_label, eff_dir / "slice_trend.png",
)
demo_age = u.pick_demo_slice(target, k, glob, prefer_old_with_effector=eff)
u.make_downstream_effector_spatial(
target, glob, k, demo_age, CELLTYPE, k_label, eff, eff_dir / "spatial_overlay.png",
)
ctx["downstream_summary"] = downstream_summary
print(downstream_summary)
--- Microglia stGP2: downstream high-b/low-b enrichment ---
loaded cached downstream tables from Results/proximity/Microglia/stGP2/downstream
{'target_celltype': 'Microglia', 'program': 'stGP2', 'n_effectors_tested': 13, 'n_slices_tested': 20, 'n_all_slice_enrichment_sig': 12, 'top_all_slice_enriched_effector': 'Oligodendrocyte', 'top_all_slice_depleted_effector': 'Neuron-Excitatory', 'n_per_slice_enrichment_sig': 156, 'n_per_slice_matched_sig': 4, 'n_global_matched_sig': 12, 'global_matched_sig_effectors': 'T cell|NSC|Neuroblast|Macrophage|Ependymal|Pericyte|OPC|Endothelial|Astrocyte|Neuron-MSN|Oligodendrocyte|Neuron-Excitatory'}
6. Cell-type summary tables
Concatenate per-program downstream summaries into cell-type and all-cell-type overview tables.
[8]:
downstream_summary_df = u.compile_celltype_downstream_summary(
CELLTYPE,
csv_dir=prox_csv_dir,
fig_dir=prox_fig_dir,
program_labels=[program_context[k]["label"] for k in program_context],
)
summary = dict(
target_celltype=CELLTYPE,
n_target_cells=int(len(target["age"])),
n_programs=int(target["n_programs"]),
regions=regions,
n_downstream_summary_rows=int(len(downstream_summary_df)),
programs=program_summaries,
)
(prox_csv_dir / "summary.json").write_text(json.dumps(summary, indent=2))
master_rows = []
for ct in u.ALL_CELLTYPES:
sj = u.RESULTS_PROXIMITY / u.safe_name(ct) / "summary.json"
if sj.exists():
s = json.loads(sj.read_text())
master_rows.extend([p for p in s.get("programs", []) if "error" not in p])
if master_rows:
pd.DataFrame(master_rows).to_csv(u.RESULTS_PROXIMITY / "summary_all_celltypes.csv", index=False)
display(downstream_summary_df.head())
print(f"Wrote proximity summary: {prox_csv_dir / 'summary.json'}")
| program | k | effector | hi_threshold | lo_threshold | n_hi | n_lo | hi_mean | lo_mean | log2fc | p | q_bh | R_in | R_out | high_pct | low_pct | min_group | valid | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | stGP2 | 1 | T cell | 1.048398 | -1.247125 | 13105 | 13105 | 0.026326 | 0.006410 | 0.436226 | 1.136039e-34 | 2.461419e-34 | 20.0 | 50.0 | 75.0 | 25.0 | 30 | True |
| 1 | stGP2 | 1 | NSC | 1.048398 | -1.247125 | 13105 | 13105 | 0.047005 | 0.003129 | 0.868570 | 6.716685e-29 | 1.091461e-28 | 20.0 | 50.0 | 75.0 | 25.0 | 30 | True |
| 2 | stGP2 | 1 | Neuroblast | 1.048398 | -1.247125 | 13105 | 13105 | 0.077757 | 0.006791 | 1.169656 | 7.208538e-44 | 1.874220e-43 | 20.0 | 50.0 | 75.0 | 25.0 | 30 | True |
| 3 | stGP2 | 1 | Macrophage | 1.048398 | -1.247125 | 13105 | 13105 | 0.038306 | 0.051431 | -0.199912 | 3.426075e-07 | 4.453897e-07 | 20.0 | 50.0 | 75.0 | 25.0 | 30 | True |
| 4 | stGP2 | 1 | Ependymal | 1.048398 | -1.247125 | 13105 | 13105 | 0.074857 | 0.012514 | 0.998018 | 5.836289e-32 | 1.083882e-31 | 20.0 | 50.0 | 75.0 | 25.0 | 30 | True |
Wrote proximity summary: Results/proximity/Microglia/summary.json
7. Pathway and cell-type signature enrichment
Positive stGP gene weights are tested against the local GMT collections. Per-program bar charts and a combined program-by-term dotplot are written under Figures/<celltype>/enrichment.
[9]:
gene_sets = u._resolve_gene_sets(GENE_SET_COLLECTIONS, u.DEFAULT_GENESETS_DIR)
W = u._load_W(u.RESULTS_STGP, CELLTYPE)
programs = W.index.astype(str).tolist()
background_genes = W.columns.astype(str).tolist()
print(f"Loaded W: {len(programs)} programs x {len(background_genes)} genes")
print(f"Gene-set collections: {list(gene_sets.keys())}")
display(W.iloc[:, : min(10, W.shape[1])])
Loaded W: 4 programs x 220 genes
Gene-set collections: ['GO Biological process', 'GO Molecular Function', 'GO Cellular Component', 'Cell-type signatures']
| Th | Cd52 | S100a6 | Cd3g | Tgfb1 | Cd79a | Tpm4 | Ercc1 | Trp53 | Trh | |
|---|---|---|---|---|---|---|---|---|---|---|
| stGP1 | 0.0 | 0.00000 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| stGP2 | 0.0 | 0.03098 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| stGP3 | 0.0 | 0.00000 | 0.0 | 0.0 | 0.047638 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| stGP4 | 0.0 | 0.00000 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
[10]:
all_enrichment_results = []
enrichment_stats = []
for prog in programs:
stats, per_prog = u._enrich_one_program(
program=prog,
weights=W.loc[prog],
background_genes=background_genes,
gene_sets=gene_sets,
celltype=CELLTYPE,
safe=safe_ct,
fig_dir=enrich_fig_dir,
res_dir=enrich_csv_dir,
padj_threshold=PADJ_THRESHOLD,
n_top=N_TOP,
)
enrichment_stats.append(stats)
if per_prog is not None:
all_enrichment_results.append(per_prog)
combined_path = None
if all_enrichment_results:
combined = pd.concat(all_enrichment_results, ignore_index=True)
combined.insert(0, "celltype", CELLTYPE)
combined_path = enrich_csv_dir / f"{safe_ct}_combined_enrichment.csv"
combined.to_csv(combined_path, index=False)
u._plot_program_enrichment_dotplot(
combined,
out=enrich_fig_dir / f"{safe_ct}_enrichment_program_dotplot.png",
padj_threshold=PADJ_THRESHOLD,
)
display(combined.head())
else:
combined = pd.DataFrame()
enrich_summary = dict(
celltype=CELLTYPE,
safe_name=safe_ct,
n_programs=len(programs),
programs=enrichment_stats,
gene_sets=list(gene_sets.keys()),
padj_threshold=float(PADJ_THRESHOLD),
n_top_per_panel=int(N_TOP),
combined_csv=str(combined_path) if combined_path else None,
)
(enrich_csv_dir / "enrichment_summary.json").write_text(json.dumps(enrich_summary, indent=2))
u.compile_enrichment_master_summary([CELLTYPE], results_root=u.RESULTS_ENRICHMENT, top_per_program=TOP_PER_PROGRAM)
display(pd.DataFrame(enrichment_stats))
print(f"Wrote enrichment summary: {enrich_csv_dir / 'enrichment_summary.json'}")
[program] stGP1: 10 positive genes ...
[program] stGP2: 15 positive genes ...
[program] stGP3: 15 positive genes ...
[program] stGP4: 15 positive genes ...
| celltype | program | n_genes_input | Gene_set | Term | Overlap | P-value | Adjusted P-value | Odds Ratio | Combined Score | Genes | gene_set | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Microglia | stGP1 | 10 | m5.go.bp.v2026.1.Mm.symbols.gmt | Actin filament based process | 2/16 | 0.158439 | 0.460611 | 3.985801 | 7.343384 | Pik3r1;Prox1 | GO Biological process |
| 1 | Microglia | stGP1 | 10 | m5.go.bp.v2026.1.Mm.symbols.gmt | Actin filament bundle organization | 1/2 | 0.089041 | 0.356662 | 22.052632 | 53.337758 | Pik3r1 | GO Biological process |
| 2 | Microglia | stGP1 | 10 | m5.go.bp.v2026.1.Mm.symbols.gmt | Actin filament organization | 2/7 | 0.034656 | 0.346712 | 10.989305 | 36.949025 | Pik3r1;Prox1 | GO Biological process |
| 3 | Microglia | stGP1 | 10 | m5.go.bp.v2026.1.Mm.symbols.gmt | Activation of immune response | 2/29 | 0.388978 | 0.619581 | 1.962567 | 1.853120 | Pik3r1;Mog | GO Biological process |
| 4 | Microglia | stGP1 | 10 | m5.go.bp.v2026.1.Mm.symbols.gmt | Activation of innate immune response | 1/8 | 0.315020 | 0.575638 | 4.284211 | 4.948775 | Pik3r1 | GO Biological process |
[master] Wrote 80 rows -> Results/enrichment/summary_all_celltypes.csv
| program | n_pos_genes | n_terms_reported | runtime_sec | status | figure | |
|---|---|---|---|---|---|---|
| 0 | stGP1 | 10 | 882 | 0.84 | done | Figures/Microglia/enrichment/Microglia_stGP1_e... |
| 1 | stGP2 | 15 | 1033 | 1.41 | done | Figures/Microglia/enrichment/Microglia_stGP2_e... |
| 2 | stGP3 | 15 | 1741 | 0.60 | done | Figures/Microglia/enrichment/Microglia_stGP3_e... |
| 3 | stGP4 | 15 | 1196 | 0.59 | done | Figures/Microglia/enrichment/Microglia_stGP4_e... |
Wrote enrichment summary: Results/enrichment/Microglia/enrichment_summary.json