File size: 6,052 Bytes
925ee3b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | """Shared utilities for DeepPTR benchmark scripts.
All scripts in analyses/deep/ import from here for reproducibility.
"""
from __future__ import annotations
import os
os.environ["OMP_NUM_THREADS"] = "4"
os.environ["MKL_NUM_THREADS"] = "4"
os.environ["OPENBLAS_NUM_THREADS"] = "4"
os.environ["NUMEXPR_NUM_THREADS"] = "4"
import json
import sys
import time
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
import torch
torch.set_num_threads(4)
# Inline figure style (avoids name collision with parent _common.py)
def set_figure_style():
plt.rcParams.update({
"figure.dpi": 150, "savefig.dpi": 300, "savefig.bbox": "tight",
"font.size": 10, "axes.titlesize": 12, "axes.labelsize": 11,
"xtick.labelsize": 9, "ytick.labelsize": 9, "legend.fontsize": 9,
"figure.figsize": (6, 5), "axes.spines.top": False, "axes.spines.right": False,
})
import scptr
# ββ Paths ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PROJECT_ROOT = Path(__file__).parent.parent.parent
OUTPUT_ROOT = PROJECT_ROOT / "output" / "deep_benchmarks"
DATA_DIR = Path(scptr.benchmark.__file__).parent / "data"
DEEP_HP = dict(
d_T=8, d_PT=8, d_hidden=48, n_enc_layers=2,
batch_size=512, max_epochs=100, kl_warmup_epochs=20,
patience=15, n_posterior_samples=15,
device="cpu", seed=0,
)
def output_dir(script_name: str) -> Path:
"""Return output directory for a given script, e.g. '01_fair_comparison'."""
d = OUTPUT_ROOT / script_name
(d / "figures").mkdir(parents=True, exist_ok=True)
(d / "results").mkdir(parents=True, exist_ok=True)
return d
def save_fig(fig, name: str, out: Path, subdir: str = "figures"):
if fig is None:
return
path = out / subdir / f"{name}.png"
fig.savefig(path, dpi=150, bbox_inches="tight")
plt.close(fig)
print(f" Saved: {path}")
def save_json(data, name: str, out: Path):
path = out / "results" / f"{name}.json"
with open(path, "w") as f:
json.dump(data, f, indent=2, default=str)
print(f" Saved: {path}")
# ββ Data loading βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def load_halflife_refs():
return scptr.datasets.herzog2017_halflives(), scptr.datasets.schofield2018_halflives()
def select_top_genes(adata, n_top=300):
"""Select top genes by unspliced signal for DeepPTR."""
from scipy.sparse import issparse
u = adata.layers["unspliced"]
if issparse(u):
u = np.asarray(u.todense())
u = np.asarray(u, dtype=np.float32)
score = u.sum(axis=0) * (u > 0).mean(axis=0)
top_idx = np.sort(np.argsort(score)[::-1][:n_top])
adata_sub = adata[:, adata.var_names[top_idx]].copy()
from scipy.sparse import issparse as _iss
for key in ("spliced", "unspliced"):
if key in adata_sub.layers and _iss(adata_sub.layers[key]):
adata_sub.layers[key] = np.asarray(adata_sub.layers[key].todense())
return adata_sub
def run_analytical(adata_loader):
"""Run full analytical scPTR pipeline, return adata."""
adata = adata_loader()
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)
return adata
def run_deep(adata_loader, n_top=300, verbose=True):
"""Run preprocessing + DeepPTR, return (adata_deep, model, history)."""
adata = adata_loader()
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)
adata = select_top_genes(adata, n_top=n_top)
torch.set_num_threads(4)
model, history = scptr.deep.fit_deepptr(adata, verbose=verbose, **DEEP_HP)
return adata, model, history
def run_both(adata_loader, n_top=300):
"""Return (adata_analytical, adata_deep, model, history)."""
adata_an = run_analytical(adata_loader)
adata_dp, model, history = run_deep(adata_loader, n_top=n_top)
return adata_an, adata_dp, model, history
# ββ Half-life matching βββββββββββββββββββββββββββββββββββββββββββββββββββββ
def match_halflife(adata, hl_df, gene_col="gene_symbol", hl_col="half_life_hours"):
"""Match genes case-insensitively, return (gamma_vals, hl_vals, gene_names)."""
gamma_med = np.median(adata.layers["gamma"], axis=0)
hl_s = hl_df.set_index(gene_col)[hl_col]
gamma_upper = {g.upper(): i for i, g in enumerate(adata.var_names)}
hl_upper = {g.upper(): g for g in hl_s.index if isinstance(g, str)}
shared = set(gamma_upper.keys()) & set(hl_upper.keys())
idx = [gamma_upper[u] for u in shared]
g = gamma_med[idx].astype(float)
h = np.array([hl_s[hl_upper[u]] for u in shared], dtype=float)
names = [adata.var_names[gamma_upper[u]] for u in shared]
valid = np.isfinite(g) & np.isfinite(h) & (g > 0) & (h > 0)
return g[valid], h[valid], [n for n, v in zip(names, valid) if v]
def halflife_spearman(adata, hl_df):
"""Quick Spearman r with half-life reference."""
g, h, _ = match_halflife(adata, hl_df)
if len(g) < 3:
return np.nan, 0
r, _ = stats.spearmanr(g, h)
return float(r), len(g)
# ββ Dataset registry βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DATASETS = [
("pancreas", scptr.datasets.pancreas, "clusters"),
("dentate_gyrus", scptr.datasets.dentate_gyrus, "clusters"),
]
|