scPTR / analyses /run_cross_platform.py
bryan7264's picture
Add files using upload-large-folder tool
9d901ad verified
Raw
History Blame Contribute Delete
10.1 kB
#!/usr/bin/env python
"""Cross-platform benchmarking: compare scPTR gamma estimates across
different sequencing platforms and datasets.
Compares gamma estimates between:
1. 10x Chromium datasets (pancreas, dentate gyrus)
2. sci (combinatorial indexing) dataset (sci-fate A549)
3. Assesses whether gene-level gamma rankings are consistent across platforms
This addresses the cross-platform benchmarking component of the research plan.
"""
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
from scipy import stats
sys.path.insert(0, str(Path(__file__).parent))
from _common import set_figure_style
import scptr
OUTPUT_DIR = Path(__file__).parent.parent / "output" / "cross_platform"
def save_fig(fig, name, subdir="figures"):
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 run_pipeline(adata, name):
"""Run full scPTR pipeline and return per-gene median gamma."""
import copy
adata = copy.deepcopy(adata)
scptr.pp.filter_genes(adata)
scptr.pp.normalize_layers(adata)
scptr.pp.neighbors(adata, n_neighbors=30)
scptr.pp.smooth_layers(adata)
scptr.tl.estimate_beta(adata)
scptr.tl.estimate_gamma(adata)
gamma = np.median(adata.layers["gamma"], axis=0)
expr = np.mean(adata.layers["spliced"], axis=0) if "spliced" in adata.layers else np.mean(adata.X, axis=0)
if hasattr(expr, 'A1'):
expr = np.asarray(expr).flatten()
return pd.DataFrame({
"gene": adata.var_names,
"gamma": gamma,
"expression": expr,
"nonzero_frac": (adata.layers["gamma"] > 0).mean(axis=0),
}).set_index("gene"), adata
def compare_datasets(df_a, df_b, name_a, name_b):
"""Compare gamma estimates between two datasets."""
print(f"\n {name_a} vs {name_b}:")
# Find shared genes (case-insensitive)
genes_a = {g.upper(): g for g in df_a.index}
genes_b = {g.upper(): g for g in df_b.index}
shared = set(genes_a.keys()) & set(genes_b.keys())
print(f" Shared genes: {len(shared)}")
if len(shared) < 50:
print(f" Too few shared genes for comparison.")
return None
gamma_a = np.array([df_a.loc[genes_a[g], "gamma"] for g in shared])
gamma_b = np.array([df_b.loc[genes_b[g], "gamma"] for g in shared])
expr_a = np.array([df_a.loc[genes_a[g], "expression"] for g in shared])
expr_b = np.array([df_b.loc[genes_b[g], "expression"] for g in shared])
nonzero_a = np.array([df_a.loc[genes_a[g], "nonzero_frac"] for g in shared])
nonzero_b = np.array([df_b.loc[genes_b[g], "nonzero_frac"] for g in shared])
# Overall correlation
valid = (gamma_a > 0) & (gamma_b > 0)
if valid.sum() < 20:
print(f" Too few valid genes (both gamma>0): {valid.sum()}")
return None
r_gamma, p_gamma = stats.spearmanr(gamma_a[valid], gamma_b[valid])
r_expr, p_expr = stats.spearmanr(expr_a[valid], expr_b[valid])
print(f" Gamma Spearman r = {r_gamma:.4f} (n={valid.sum()})")
print(f" Expression Spearman r = {r_expr:.4f}")
# Stratify by expression level
expr_combined = expr_a + expr_b
quartiles = np.percentile(expr_combined[valid], [25, 50, 75])
labels = ["Q1 (low)", "Q2", "Q3", "Q4 (high)"]
bounds = [(-np.inf, quartiles[0]), (quartiles[0], quartiles[1]),
(quartiles[1], quartiles[2]), (quartiles[2], np.inf)]
print(f"\n Stratified by expression level:")
stratified = []
for label, (lo, hi) in zip(labels, bounds):
mask = valid & (expr_combined >= lo) & (expr_combined < hi)
if mask.sum() < 10:
continue
r_q, p_q = stats.spearmanr(gamma_a[mask], gamma_b[mask])
r_e, _ = stats.spearmanr(expr_a[mask], expr_b[mask])
print(f" {label}: gamma r={r_q:.3f}, expr r={r_e:.3f} (n={mask.sum()})")
stratified.append({
"quartile": label,
"gamma_r": float(r_q),
"expr_r": float(r_e),
"n_genes": int(mask.sum()),
})
# Informative genes only (>10% nonzero in both)
informative = valid & (nonzero_a >= 0.1) & (nonzero_b >= 0.1)
if informative.sum() >= 20:
r_inf, _ = stats.spearmanr(gamma_a[informative], gamma_b[informative])
print(f"\n Informative genes only (>10% nonzero both): "
f"r={r_inf:.4f} (n={informative.sum()})")
return {
"dataset_a": name_a,
"dataset_b": name_b,
"shared_genes": len(shared),
"valid_genes": int(valid.sum()),
"gamma_r": float(r_gamma),
"expr_r": float(r_expr),
"informative_gamma_r": float(r_inf) if informative.sum() >= 20 else None,
"stratified": stratified,
}
def main():
set_figure_style()
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Load datasets
print("=" * 60)
print("CROSS-PLATFORM BENCHMARKING")
print("=" * 60)
datasets = {}
# 10x Chromium datasets
print("\nLoading pancreas (10x Chromium)...")
df_pan, adata_pan = run_pipeline(scptr.datasets.pancreas(), "pancreas")
datasets["pancreas_10x"] = df_pan
print(f" {len(df_pan)} genes, {(df_pan['gamma'] > 0).sum()} with gamma>0")
print("\nLoading dentate gyrus (10x Chromium)...")
df_dg, adata_dg = run_pipeline(scptr.datasets.dentate_gyrus(), "dentate_gyrus")
datasets["dg_10x"] = df_dg
print(f" {len(df_dg)} genes, {(df_dg['gamma'] > 0).sum()} with gamma>0")
# sci-fate (combinatorial indexing)
try:
print("\nLoading sci-fate (sci)...")
df_sci, adata_sci = run_pipeline(scptr.datasets.sci_fate(), "sci_fate")
datasets["scifate_sci"] = df_sci
print(f" {len(df_sci)} genes, {(df_sci['gamma'] > 0).sum()} with gamma>0")
except Exception as e:
print(f" sci-fate not available: {e}")
# Pairwise comparisons
print("\n" + "=" * 60)
print("PAIRWISE COMPARISONS")
print("=" * 60)
pairs = []
dataset_names = list(datasets.keys())
all_comparisons = []
for i in range(len(dataset_names)):
for j in range(i + 1, len(dataset_names)):
name_a, name_b = dataset_names[i], dataset_names[j]
result = compare_datasets(datasets[name_a], datasets[name_b],
name_a, name_b)
if result:
all_comparisons.append(result)
# Platform comparison summary
print(f"\n{'='*60}")
print("PLATFORM COMPARISON SUMMARY")
print(f"{'='*60}")
# Categorize comparisons
same_platform = []
cross_platform = []
for comp in all_comparisons:
a, b = comp["dataset_a"], comp["dataset_b"]
a_platform = "10x" if "10x" in a else "sci" if "sci" in a else "other"
b_platform = "10x" if "10x" in b else "sci" if "sci" in b else "other"
if a_platform == b_platform:
same_platform.append(comp)
else:
cross_platform.append(comp)
print(f"\n Same platform comparisons:")
for comp in same_platform:
print(f" {comp['dataset_a']} vs {comp['dataset_b']}: "
f"gamma r={comp['gamma_r']:.3f}")
print(f"\n Cross-platform comparisons:")
for comp in cross_platform:
print(f" {comp['dataset_a']} vs {comp['dataset_b']}: "
f"gamma r={comp['gamma_r']:.3f}")
# Half-life validation per platform
print(f"\n Half-life validation per platform:")
halflife_dir = Path(__file__).parent.parent / "src" / "scptr" / "datasets" / "data"
for hl_file, hl_name in [("schofield2018_halflives.csv", "Schofield 2018")]:
hl_path = halflife_dir / hl_file
if not hl_path.exists():
continue
hl = pd.read_csv(hl_path)
for ds_name, df in datasets.items():
gene_map = {g.upper(): g for g in df.index}
gamma_vals, hl_vals = [], []
for _, row in hl.iterrows():
g = str(row.iloc[0]).upper()
if g in gene_map and df.loc[gene_map[g], "gamma"] > 0:
gamma_vals.append(df.loc[gene_map[g], "gamma"])
hl_vals.append(float(row.iloc[1]))
if len(gamma_vals) >= 20:
r, p = stats.spearmanr(gamma_vals, hl_vals)
print(f" {ds_name}: r={r:.4f}, n={len(gamma_vals)} ({hl_name})")
# Save results
res_dir = OUTPUT_DIR / "results"
res_dir.mkdir(parents=True, exist_ok=True)
with open(res_dir / "cross_platform_results.json", "w") as f:
json.dump(all_comparisons, f, indent=2)
# Figure
n_comps = len(all_comparisons)
fig, axes = plt.subplots(1, max(n_comps, 1), figsize=(6 * max(n_comps, 1), 5))
if n_comps == 1:
axes = [axes]
for idx, comp in enumerate(all_comparisons):
name_a, name_b = comp["dataset_a"], comp["dataset_b"]
df_a, df_b = datasets[name_a], datasets[name_b]
genes_a = {g.upper(): g for g in df_a.index}
genes_b = {g.upper(): g for g in df_b.index}
shared = set(genes_a.keys()) & set(genes_b.keys())
ga = np.array([df_a.loc[genes_a[g], "gamma"] for g in shared])
gb = np.array([df_b.loc[genes_b[g], "gamma"] for g in shared])
valid = (ga > 0) & (gb > 0)
axes[idx].scatter(ga[valid], gb[valid], s=2, alpha=0.3, color="steelblue")
axes[idx].set_xlabel(f"Gamma ({name_a})")
axes[idx].set_ylabel(f"Gamma ({name_b})")
axes[idx].set_title(f"r={comp['gamma_r']:.3f} (n={comp['valid_genes']})")
lim = max(ga[valid].max(), gb[valid].max()) * 1.1
axes[idx].plot([0, lim], [0, lim], "r--", alpha=0.5)
fig.suptitle("Cross-Platform Gamma Comparison", fontsize=13, y=1.02)
fig.tight_layout()
save_fig(fig, "cross_platform_gamma")
print(f"\nResults saved to: {OUTPUT_DIR.resolve()}")
if __name__ == "__main__":
main()