scPTR / analyses /run_all.py
bryan7264's picture
Add files using upload-large-folder tool
9d901ad verified
Raw
History Blame Contribute Delete
12.4 kB
#!/usr/bin/env python
"""Run the full scPTR analysis pipeline on pancreas data and produce results.
This script runs Aims 1-3 end-to-end on the pancreas dataset:
- Aim 1: Benchmark gamma estimates against published half-lives, ARE/NMD enrichment
- Aim 2: PT state discovery and differential gamma analysis
- Aim 3: PT velocity computation
Results are saved to output/ directory.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent))
from _common import set_figure_style, setup_output_dirs
import scptr
OUTPUT_DIR = Path(__file__).parent.parent / "output"
def save_fig(fig, name, subdir="figures"):
"""Save a matplotlib figure to output dir."""
if fig is None:
print(f" [WARNING] {name}: plot returned None, skipping save")
return
out_dir = OUTPUT_DIR / subdir
out_dir.mkdir(parents=True, exist_ok=True)
path = out_dir / f"{name}.png"
fig.savefig(path, dpi=150, bbox_inches="tight")
plt.close(fig)
print(f" Saved: {path}")
def main():
set_figure_style()
OUTPUT_DIR.mkdir(exist_ok=True)
# =========================================================================
# LOAD DATA
# =========================================================================
print("=" * 60)
print("LOADING PANCREAS DATASET")
print("=" * 60)
adata = scptr.datasets.pancreas()
print(f" Shape: {adata.shape}")
print(f" Layers: {list(adata.layers.keys())}")
print(f" Cell types: {adata.obs['clusters'].value_counts().to_dict()}")
# =========================================================================
# PREPROCESSING
# =========================================================================
print("\n" + "=" * 60)
print("PREPROCESSING")
print("=" * 60)
scptr.pp.filter_genes(adata)
print(f" After filtering: {adata.shape}")
scptr.pp.normalize_layers(adata)
print(" Normalized layers")
scptr.pp.neighbors(adata, n_neighbors=30)
print(" Built kNN graph (k=30)")
scptr.pp.smooth_layers(adata)
print(" Smoothed layers (Mu, Ms)")
# =========================================================================
# CORE ANALYSIS
# =========================================================================
print("\n" + "=" * 60)
print("CORE ANALYSIS")
print("=" * 60)
# Beta estimation (global + per-cell-type)
scptr.tl.estimate_beta(adata)
beta = adata.var['beta'].values
print(f" Beta: median={np.median(beta):.4f}, max={np.max(beta):.4f}, "
f"nonzero={np.sum(beta > 0)}/{len(beta)}")
if "clusters" in adata.obs.columns:
scptr.tl.estimate_beta(adata, groupby="clusters")
print(f" Beta (per-cluster): {adata.varm['beta_groups'].shape}")
# Gamma estimation
scptr.tl.estimate_gamma(adata)
gamma_vals = adata.layers["gamma"]
gamma_med = np.median(gamma_vals, axis=0)
print(f" Gamma: shape={gamma_vals.shape}")
print(f" Median per-gene: median={np.median(gamma_med):.4f}, "
f"max={np.max(gamma_med):.4f}")
print(f" Global: max={np.max(gamma_vals):.4f}, "
f"99.5th pctl={np.percentile(gamma_vals[gamma_vals>0], 99.5):.4f}")
print(f" Genes with >0 median gamma: {np.sum(gamma_med > 0)}/{len(gamma_med)}")
# Variance decomposition
scptr.tl.variance_decomposition(adata)
tf = adata.var['tf_score'].values
ptf = adata.var['ptf_score'].values
print(f" TF score: median={np.median(tf):.4f}, mean={np.mean(tf):.4f}")
print(f" PTF score: median={np.median(ptf):.4f}, mean={np.mean(ptf):.4f}")
print(f" Genes with TF > 0.5: {np.sum(tf > 0.5)}/{len(tf)}")
# PT states
scptr.tl.pt_states(adata)
n_states = adata.obs["pt_state"].nunique()
print(f" PT states found: {n_states}")
# PT velocity
scptr.tl.pt_velocity(adata)
print(" PT velocity computed")
# =========================================================================
# AIM 1: BENCHMARKING
# =========================================================================
print("\n" + "=" * 60)
print("AIM 1: BENCHMARKING")
print("=" * 60)
fig_dir, res_dir = setup_output_dirs("figures/aim1", "results/aim1")
# 1a. Half-life correlation (mouse reference)
print("\n--- Half-life correlation (mouse reference) ---")
hl_mouse = scptr.datasets.herzog2017_halflives()
corr = scptr.benchmark.correlate_with_halflives(adata, hl_mouse)
print(f" n_genes matched: {corr['n_genes']} (unfiltered: {corr['n_genes_unfiltered']})")
print(f" Spearman r = {corr['spearman_r']:.4f} (p = {corr['spearman_p']:.2e})")
print(f" Pearson r = {corr['pearson_r']:.4f} (p = {corr['pearson_p']:.2e})")
# Also try human reference for cross-species comparison
print("\n--- Half-life correlation (human reference) ---")
hl_human = scptr.datasets.schofield2018_halflives()
corr_human = scptr.benchmark.correlate_with_halflives(adata, hl_human)
print(f" n_genes matched: {corr_human['n_genes']} (unfiltered: {corr_human['n_genes_unfiltered']})")
print(f" Spearman r = {corr_human['spearman_r']:.4f} (p = {corr_human['spearman_p']:.2e})")
# Save both correlation results
corr_save = {k: v for k, v in corr.items() if k != "matched_genes"}
corr_human_save = {k: v for k, v in corr_human.items() if k != "matched_genes"}
with open(res_dir / "halflife_correlation.json", "w") as f:
json.dump({"mouse_reference": corr_save, "human_reference": corr_human_save}, f, indent=2)
# Half-life scatter plot (log-log scale, filtered genes only)
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
gamma_med = np.median(adata.layers["gamma"], axis=0)
gamma_s = pd.Series(gamma_med, index=adata.var_names)
hl_s = hl_mouse.set_index("gene_symbol")["half_life_hours"]
shared = gamma_s.index.intersection(hl_s.index)
g = gamma_s[shared].values
h = hl_s[shared].values
# Left: all genes
axes[0].scatter(h, g, alpha=0.1, s=5, c="steelblue")
axes[0].set_xlabel("Published half-life (hours)")
axes[0].set_ylabel("scPTR median gamma")
axes[0].set_title(f"All genes (n={len(shared)})")
# Right: filtered genes (gamma > 0), log-log
mask = (g > 0) & (h > 0) & np.isfinite(g) & np.isfinite(h)
axes[1].scatter(h[mask], g[mask], alpha=0.15, s=8, c="steelblue")
axes[1].set_xscale("log")
axes[1].set_yscale("log")
axes[1].set_xlabel("Published half-life (hours)")
axes[1].set_ylabel("scPTR median gamma")
axes[1].set_title(
f"Filtered genes (Spearman r={corr['spearman_r']:.3f}, "
f"p={corr['spearman_p']:.1e}, n={corr['n_genes']})"
)
fig.suptitle("Gamma vs Published mRNA Half-lives", fontsize=13, y=1.02)
fig.tight_layout()
save_fig(fig, "halflife_scatter", "figures/aim1")
# 1b. ARE / NMD enrichment
print("\n--- ARE / NMD enrichment ---")
are_result = scptr.benchmark.are_enrichment(adata)
nmd_result = scptr.benchmark.nmd_enrichment(adata)
print(f" ARE: n_in={are_result['n_genes_in_set']}, "
f"median_gamma_in={are_result.get('median_gamma_in_set', 'N/A'):.4f}, "
f"median_gamma_bg={are_result.get('median_gamma_background', 'N/A'):.4f}, "
f"p={are_result['p_value']:.4f}")
print(f" NMD: n_in={nmd_result['n_genes_in_set']}, "
f"median_gamma_in={nmd_result.get('median_gamma_in_set', 'N/A'):.4f}, "
f"median_gamma_bg={nmd_result.get('median_gamma_background', 'N/A'):.4f}, "
f"p={nmd_result['p_value']:.4f}")
with open(res_dir / "enrichment_results.json", "w") as f:
json.dump({"ARE": are_result, "NMD": nmd_result}, f, indent=2)
fig = scptr.pl.enrichment_barplot([are_result, nmd_result])
save_fig(fig, "enrichment_barplot", "figures/aim1")
# 1c. Subsampling robustness
print("\n--- Subsampling robustness ---")
fractions = [0.2, 0.4, 0.6, 0.8, 0.9]
robust_df = scptr.benchmark.subsampling_robustness(
adata, fractions=fractions, n_repeats=5
)
robust_df.to_csv(res_dir / "subsampling_robustness.csv", index=False)
for frac in fractions:
sub = robust_df[robust_df["fraction"] == frac]
mean_r = sub["spearman_r"].mean()
print(f" fraction={frac:.1f}: mean Spearman r = {mean_r:.4f}")
# Robustness plot
fig, ax = plt.subplots(figsize=(6, 4))
for frac in fractions:
sub = robust_df[robust_df["fraction"] == frac]
ax.scatter([frac] * len(sub), sub["spearman_r"],
color="steelblue", alpha=0.6, s=25)
means = robust_df.groupby("fraction")["spearman_r"].mean()
ax.plot(means.index, means.values, "o-", color="darkblue", linewidth=2, markersize=6)
ax.set_xlabel("Fraction of cells")
ax.set_ylabel("Spearman r (vs full data)")
ax.set_title("Subsampling Robustness")
ax.set_ylim(0.5, 1.02)
save_fig(fig, "subsampling_robustness", "figures/aim1")
# =========================================================================
# AIM 2: HIDDEN PT STATES
# =========================================================================
print("\n" + "=" * 60)
print("AIM 2: PT STATE DISCOVERY")
print("=" * 60)
fig_dir, res_dir = setup_output_dirs("figures/aim2", "results/aim2")
# State composition
state_counts = adata.obs["pt_state"].value_counts()
state_counts.to_csv(res_dir / "pt_state_counts.csv")
print(f" PT states: {dict(state_counts)}")
# PT UMAP (use show=False to get fig back)
fig = scptr.pl.pt_umap(adata, show=False)
save_fig(fig, "pt_umap", "figures/aim2")
# TF vs PTF scatter
fig = scptr.pl.tf_ptf_scatter(adata, show=False)
save_fig(fig, "tf_ptf_scatter", "figures/aim2")
# Cross-tabulate PT states vs expression clusters
if "clusters" in adata.obs.columns:
ct = pd.crosstab(adata.obs["pt_state"], adata.obs["clusters"])
ct.to_csv(res_dir / "pt_state_vs_clusters.csv")
print(f"\n PT state vs expression cluster crosstab:")
print(ct.to_string())
# Rank genes by differential gamma
rank_df = scptr.tl.rank_pt_genes(adata, n_genes=50)
rank_df.to_csv(res_dir / "ranked_pt_genes.csv", index=False)
print(f"\n Top differentially degraded genes: {len(rank_df)} entries")
print(f" Top 10 gene names: {rank_df.head(10)['names'].tolist()}")
# Gamma heatmap
fig = scptr.pl.gamma_heatmap(adata, show=False)
save_fig(fig, "gamma_heatmap", "figures/aim2")
# =========================================================================
# AIM 3: PT VELOCITY
# =========================================================================
print("\n" + "=" * 60)
print("AIM 3: PT VELOCITY")
print("=" * 60)
fig_dir, res_dir = setup_output_dirs("figures/aim3", "results/aim3")
# Velocity embedding (show 30% of cells for cleaner arrows)
fig = scptr.pl.pt_velocity_embedding(adata, density=0.3, arrow_size=1.5, show=False)
save_fig(fig, "pt_velocity_embedding", "figures/aim3")
# =========================================================================
# SUMMARY
# =========================================================================
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
print(f" Dataset: pancreas ({adata.n_obs} cells, {adata.n_vars} genes)")
print(f" Beta: median={np.median(adata.var['beta']):.4f}, max={np.max(adata.var['beta']):.4f}")
print(f" Gamma max: {np.max(adata.layers['gamma']):.4f}")
print(f" PT states discovered: {n_states}")
print(f" TF score: median={np.median(adata.var['tf_score']):.4f}")
print(f" Half-life Spearman r (mouse): {corr['spearman_r']:.4f} (n={corr['n_genes']} genes)")
print(f" Half-life Spearman r (human): {corr_human['spearman_r']:.4f} (n={corr_human['n_genes']} genes)")
print(f" ARE enrichment p: {are_result['p_value']:.4f}")
print(f" NMD enrichment p: {nmd_result['p_value']:.4f}")
print(f" Robustness (90% cells): {robust_df[robust_df['fraction']==0.9]['spearman_r'].mean():.4f}")
print(f"\nAll results saved to: {OUTPUT_DIR.resolve()}")
print("Done!")
if __name__ == "__main__":
main()