Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction.
The 40 matches
- [1] § Methods › Attention extraction and edge score computation ↔ src/05_causal_ablation/21_scgpt_forward_ablation.py, lines 1–89 · score 0.99 · F.multi_head_attention_forward, need_weights, bypasses hooks, nn.MultiheadAttention, column slice, proj weight
- [2] § Results › Causal ablation reveals distributed redundancy ↔ src/05_causal_ablation/20_scgpt_ablation.py, lines 1–63 · score 0.99 · Tabula Sapiens tissue, Cross architecture replication, HVG vocabulary, Geneformer head mask, distributed redundancy, forward pass intervention
- [3] § Results › Causal ablation reveals distributed redundancy ↔ src/05_causal_ablation/21_scgpt_forward_ablation.py, lines 1–89 · score 0.98 · Cross architecture replication, proj weight, column slice, TRRUST ranked heads, zeroing head, residual stream
- [4] § Results › Positive control: the pipeline detects planted regulatory signal ↔ src/13_synthetic_validation/20_positive_control_pipeline.py, lines 498–601 · score 0.96 · minimal indirect propagation, strong indirect propagation, weak direct edges, shuffled ground truth, stress variants, planted ground truth
- [5] § Results › Causal ablation reveals distributed redundancy ↔ src/05_causal_ablation/05_intervention_fidelity.py, lines 1–51 · score 0.92 · hidden state cosine, materially perturb internal, ineffective interventions, Intervention fidelity, orthogonal interventions, uniform attention
- [6] § Results › Context-dependent attention–correlation relationship ↔ src/06_cross_context/08_non_k562_replication.py, lines 1–67 · score 0.91 · RPE1 retinal epithelial, derived neurons, K562 replication, iPSC, Cross context replication, evaluable perturbations
- [7] § Results › Causal ablation reveals distributed redundancy ↔ src/05_causal_ablation/04_orthogonal_interventions.py, lines 1–45 · score 0.91 · orthogonal interventions, destroying attention patterns, uniform attention replacement, zeroing FFN, preserving head, MLP ablation
- [8] § Methods › Model coverage by analysis family ↔ src/05_causal_ablation/20_scgpt_ablation.py, lines 1–63 · score 0.91 · Causal ablation evidence, scGPT ablation, forward pass intervention, distributed redundancy, cross architecture, head_mask
- [9] § Results › Context-dependent attention–correlation relationship ↔ src/15_biological/05_context_dependence.py, lines 47–122 · score 0.86 · derived neurons, iPSC, K562 replication, perturbation genes forced, Cross context, retinal
- [10] § Methods › Causal ablation ↔ src/05_causal_ablation/05_intervention_fidelity.py, lines 1–51 · score 0.86 · logit cosine distances, Intervention fidelity, Orthogonal interventions, uniform attention, hidden state, ablation
- [11] § Methods › Causal ablation ↔ src/05_causal_ablation/04_orthogonal_interventions.py, lines 1–45 · score 0.84 · Orthogonal interventions, uniform attention replacement, zeroing FFN, MLP ablation, Head masking, Causal
- [12] § Results › Causal ablation reveals distributed redundancy ↔ src/05_causal_ablation/03_expanded_ablation.py, lines 1–47 · score 0.75 · matched random controls, alternative ranking, layer ablation, dose response, Geneformer V2, composite
- [13] § Results › Positive control: the pipeline detects planted regulatory signal ↔ src/02_cssi_method/cssi_real_data/20_positive_control_tfs.py, lines 127–210 · score 0.75 · HVG evaluable targets, TRRUST direction known, TRRUST entries, best TF, TF AUROC, HVG genes
- [14] § Methods › Confound decomposition ↔ src/13_synthetic_validation/20_positive_control_pipeline.py, lines 370–417 · score 0.70 · logistic regression, GroupKFold, cross validation, target gene, fitted, splits
- [15] § Results › No incremental pairwise value on the perturbation-target prediction task ↔ src/13_synthetic_validation/20_positive_control_pipeline.py, lines 419–495 · score 0.68 · plus correlation, logistic regression, GroupKFold, correlation edge, dropout, variance
- [16] § Results › Attention-specific scaling failure and CSSI remedy ↔ src/01_scaling_failure/10_paper_bundle.py, lines 103–143 · score 0.67 · attention derived GRN, model tiers, archived, medium, degrades, degradation
- [17] § Results › Cross-cell-type generalisation of confound pattern ↔ src/06_cross_context/09_rpe1_confound_battery.py, lines 1140–1207 · score 0.64 · full K562 confound, evaluable TFs, TRRUST direct target, battery, GBDT, RPE1
- [18] § Results › Cross-cell-type generalisation of confound pattern ↔ src/06_cross_context/09_rpe1_confound_battery.py, lines 1140–1207 · score 0.64 · full K562 confound, evaluable TFs, TRRUST direct target, battery, GBDT, RPE1
- [19] § Methods › Scaling and CSSI ↔ src/15_biological/04_cssi_biology.py, lines 1–30 · score 0.63 · stratum edge scores, Spearman correlation, Cell State, strata, CSSI, Stratified
- [20] § Methods › Confound decomposition ↔ src/04_confound_decomposition/05_residualization_v2.py, lines 49–99 · score 0.63 · Cross fitted residualisation, residual AUROC, OLS, GBDT, regression, splits
- [21] § Methods › Attention extraction and edge score computation ↔ src/05_causal_ablation/04_orthogonal_interventions.py, lines 586–651 · score 0.62 · BertSelfAttention, forward hooks, softmax, PyTorch, module, Batch
- [22] § Results › Evaluation framework: two objectives, five test families ↔ src/13_synthetic_validation/20_positive_control_pipeline.py, lines 1–55 · score 0.62 · planted pairwise signal, Trivial baseline, pipeline, synthetic, diagnostics, edge scores
- [23] § Methods › Scaling and CSSI ↔ src/15_biological/09_harden_cssi_biology.py, lines 1–31 · score 0.62 · CSSI max, Cell State, Leiden, resolution, stratum, cluster
- [24] § Methods › Models and data ↔ src/14_multi_model/07_investigate_models.py, lines 42–120 · score 0.61 · scFoundation, single cell foundation, routinely, UCE, BERT, transformers
- [25] § Methods › Statistical framework ↔ src/15_biological/07_fix_expression.py, lines 138–200 · score 0.60 · Bootstrap confidence intervals, master regulator, TF characterization, biological, family, AUROC
- [26] § Methods › Attention extraction and edge score computation ↔ src/03_perturbation_validation/02_attention_first.py, lines 265–317 · score 0.60 · output_attentions, evaluation layer, Geneformer V2, PyTorch, hooks, heads
- [27] § Methods › Perturbation-first validation ↔ src/02_cssi_method/crispri_validation/scripts/01_pipeline.py, lines 436–508 · score 0.58 · Mann Whitney, perturbation genes, control cell, threshold, K562, validation
- [28] § Methods › Perturbation-first validation ↔ src/02_cssi_method/10_perturbation_validation.py, lines 81–200 · score 0.58 · Mann Whitney, perturbation genes, control cell, LFC, validation, edge
- [29] § Results › Cross-cell-type generalisation of confound pattern ↔ src/06_cross_context/09_rpe1_confound_battery.py, lines 1326–1369 · score 0.58 · RPE1 confound battery, propensity matching, Trivial baselines, Residualisation, incremental, Cross
- [30] § Results › Cross-cell-type generalisation of confound pattern ↔ src/06_cross_context/09_rpe1_confound_battery.py, lines 1326–1369 · score 0.58 · RPE1 confound battery, propensity matching, Trivial baselines, Residualisation, incremental, Cross
- [31] § Results › Perturbation-first validation reveals gene-level dominance ↔ src/02_cssi_method/crispri_validation/scripts/03_cssi_pipeline.py, lines 872–925 · score 0.57 · regulatory information, achieves AUROC, regulatory signal, detectable, interactions, validation
- [32] § Results › Perturbation-first validation reveals gene-level dominance ↔ src/16_statistical_framework/02_multiplicity_sensitivity.py, lines 192–259 · score 0.56 · layer perturbation, cross gene, nested, Wilcoxon, L15, CRISPRa
- [33] § Results › Perturbation-first validation reveals gene-level dominance ↔ src/02_cssi_method/crispri_validation/scripts/03_cssi_pipeline.py, lines 872–925 · score 0.56 · layers achieve AUROC, best layer, CRISPRi, interactions, L13, validation
- [34] § Methods › Models and data ↔ src/15_biological/05_context_dependence.py, lines 47–122 · score 0.56 · iPSC, Tian, Shifrut, Adamson, neurons, Replogle
- [35] § Results › Attention-specific scaling failure and CSSI remedy ↔ src/02_cssi_method/cssi_real_data/20_positive_control_tfs.py, lines 1–52 · score 0.55 · late Geneformer layers, motivated, exploratory, NN, TFs, CSSI
- [36] § Results › No incremental pairwise value on the perturbation-target prediction task ↔ src/06_cross_context/09_rpe1_confound_battery.py, lines 1277–1324 · score 0.55 · Cross fitted residualisation, propensity matched, OLS, covariate, GBDT, raw
- [37] § Results › No incremental pairwise value on the perturbation-target prediction task ↔ src/06_cross_context/09_rpe1_confound_battery.py, lines 1277–1324 · score 0.55 · Cross fitted residualisation, propensity matched, OLS, covariate, GBDT, raw
- [38] § Methods › Models and data ↔ src/14_multi_model/06_v1_analysis_summary.py, lines 294–387 · score 0.54 · single cell foundation, scGPT, cited, architectures, transformers, rank
- [39] § Results › No incremental pairwise value on the perturbation-target prediction task ↔ src/04_confound_decomposition/08_metric_robust_incremental.py, lines 444–497 · score 0.52 · joint cross gene, cross perturbation, harder, AUPRC, GBDT, splits
- [40] § Results › No incremental pairwise value on the perturbation-target prediction task ↔ src/04_confound_decomposition/08_metric_robust_incremental.py, lines 444–497 · score 0.52 · joint cross gene, cross perturbation, harder, AUPRC, GBDT, splits
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 605 lines · 23 KB · no license · 4 matches
- #!/usr/bin/env python3
- """
- Positive control for the perturbation-first evaluation pipeline.
- Motivation (reviewer R1.3): the paper reports that correlation and attention
- edge scores add no incremental value beyond trivial gene-level baselines for
- perturbation-target prediction on Replogle K562 CRISPRi. Reviewer 1 flagged
- that without a decisive positive control this null is hard to interpret —
- it could reflect limitations of the pipeline rather than absence of signal.
- This script builds a synthetic single-cell Perturb-seq dataset with planted
- ground-truth regulatory edges, applies the full trivial-baseline +
- incremental-value pipeline end-to-end, and checks whether the pipeline
- recovers the planted pairwise signal. Three stress variants are run:
- Variant A (strong): strong direct edges, minimal indirect effects.
- Pipeline should recover edges strongly. Establishes ceiling.
- Variant B (weak + indirect): weak direct edges, strong indirect
- propagation. Tests whether indirect effects mask pairwise signal.
- Variant C (realistic Replogle-like): gene-level AUROC tuned to match
- K562 (~0.88). Diagnoses whether the K562 null could persist even
- when ground truth exists at a detectable level.
- A shuffled-null control is run within each variant: ground-truth is
- randomly permuted and the full pipeline is re-run. The shuffled result
- must be near-chance — this confirms pipeline gains come from planted
- structure, not bookkeeping artefacts.
- Deliverable (for paper): Supplementary Note 21, new figure
- fig_positive_control.png, and a summary paragraph in Results referencing
- the pipeline sensitivity established here.
- Usage:
- python 20_positive_control_pipeline.py [--variant A|B|C|all]
- Output:
- results/positive_control/variant_{A,B,C}.json
- results/positive_control/shuffled_null_{A,B,C}.json
- results/positive_control/summary.json
- """
- from __future__ import annotations
- import argparse
- import json
- import os
- import sys
- from dataclasses import dataclass, field, asdict
- from pathlib import Path
- import numpy as np
- from scipy import stats
- from sklearn.linear_model import LogisticRegression
- from sklearn.metrics import roc_auc_score
- from sklearn.model_selection import GroupKFold
- # Line-buffered stdout so long runs emit progress
- sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 1)
- REPO_ROOT = Path(__file__).resolve().parents[2]
- RESULTS_DIR = REPO_ROOT / "results" / "positive_control"
- RESULTS_DIR.mkdir(parents=True, exist_ok=True)
- # ---------------------------------------------------------------------------
- # Synthetic GRN generator
- # ---------------------------------------------------------------------------
- @dataclass
- class SyntheticGRNConfig:
- n_genes: int = 300
- n_tfs: int = 20
- direct_targets_per_tf: int = 6 # direct TF -> target edges
- indirect_targets_per_tf: int = 8 # TF -> target (via 1 regulator hop)
- n_ctrl_cells: int = 5000
- n_pert_cells_per_condition: int = 150
- n_perturbations: int = 20 # knockdowns; each TF is perturbed once
- edge_strength: float = 1.5 # direct edge coefficient magnitude
- indirect_strength: float = 0.8 # indirect propagation coefficient
- noise_scale: float = 0.8
- dropout_p: float = 0.1
- # If True, z-score each gene post-hoc so variance is uninformative.
- # Used in positive-control variants to isolate pairwise signal.
- z_score_genes: bool = False
- # If True, restrict the positive class to direct targets only (rather
- # than all DE hits). This tests whether correlation edges predict
- # *direct* regulation vs. indirect propagation.
- direct_only_positives: bool = False
- # Variant tag for reproducibility of the stress suite.
- variant: str = "A"
- seed: int = 2026
- def build_grn(cfg: SyntheticGRNConfig, rng: np.random.Generator):
- """Build a sparse hierarchical GRN.
- Returns:
- tf_idx: indices of TFs (shape [n_tfs])
- direct: dict TF_idx -> list of direct target gene indices
- indirect: dict TF_idx -> list of indirect target gene indices
- weight_mat: (n_genes, n_genes) direct TF->target weights
- indirect_weight_mat: (n_genes, n_genes) indirect effect weights
- """
- n = cfg.n_genes
- gene_pool = np.arange(n)
- tf_idx = rng.choice(gene_pool, size=cfg.n_tfs, replace=False)
- non_tf = np.setdiff1d(gene_pool, tf_idx)
- direct = {}
- indirect = {}
- weight_mat = np.zeros((n, n), dtype=np.float32)
- indirect_weight_mat = np.zeros((n, n), dtype=np.float32)
- for tf in tf_idx:
- # Direct targets — TFs regulate non-TF genes
- dtargets = rng.choice(
- non_tf, size=cfg.direct_targets_per_tf, replace=False
- )
- # Mix of activation / repression
- signs = rng.choice([-1.0, 1.0], size=len(dtargets))
- magnitudes = rng.uniform(0.7, 1.3, size=len(dtargets)) * cfg.edge_strength
- for tgt, s, m in zip(dtargets, signs, magnitudes):
- weight_mat[tf, tgt] = s * m
- direct[int(tf)] = dtargets.tolist()
- # Indirect targets: not in direct set, reached via a single-hop
- # regulator. These contribute to DE on perturbation but are not
- # direct regulatory edges.
- candidate_pool = np.setdiff1d(non_tf, dtargets)
- itargets = rng.choice(
- candidate_pool, size=cfg.indirect_targets_per_tf, replace=False
- )
- signs_i = rng.choice([-1.0, 1.0], size=len(itargets))
- mags_i = rng.uniform(0.6, 1.2, size=len(itargets)) * cfg.indirect_strength
- for tgt, s, m in zip(itargets, signs_i, mags_i):
- indirect_weight_mat[tf, tgt] = s * m
- indirect[int(tf)] = itargets.tolist()
- return tf_idx, direct, indirect, weight_mat, indirect_weight_mat
- def simulate_control_cells(
- cfg: SyntheticGRNConfig,
- rng: np.random.Generator,
- weight_mat: np.ndarray,
- indirect_weight_mat: np.ndarray,
- ) -> np.ndarray:
- """Simulate baseline control expression.
- Model: baseline TF activity is drawn from a heavy-tailed distribution,
- target expression is a linear combination of regulator activities plus
- Gaussian noise plus dropout. TF expression equals TF activity directly.
- We optionally z-score each gene post-hoc to control whether gene-level
- variance carries information about direct-target status. This is
- essential for the positive-control design: variant A z-scores to make
- variance uninformative, so any incremental value from correlation
- edges reflects genuine pairwise signal detection.
- """
- n = cfg.n_genes
- n_cells = cfg.n_ctrl_cells
- # Heavy-tailed baseline TF activities for each gene slot
- tf_activity = rng.standard_t(df=3, size=(n_cells, n)).astype(np.float32) * 0.8
- # Target expression comes from regulator activity
- expr = tf_activity @ (weight_mat + indirect_weight_mat)
- # TFs' own expression is their activity (direct readout)
- tf_mask = weight_mat.sum(axis=1) != 0 # rows with outgoing edges are TFs
- expr[:, tf_mask] = tf_activity[:, tf_mask]
- # Add small random mean shift (identifies the gene but does not correlate
- # with direct-target status)
- gene_means = rng.normal(0, 0.3, size=n).astype(np.float32)
- expr = expr + gene_means
- # Gaussian noise (additive)
- expr = expr + rng.normal(
- 0, cfg.noise_scale, size=expr.shape
- ).astype(np.float32)
- # Dropout
- mask = rng.uniform(size=expr.shape) < cfg.dropout_p
- expr[mask] = 0.0
- # Optionally z-score per gene so variance is uninformative.
- if getattr(cfg, "z_score_genes", False):
- std = expr.std(axis=0)
- std[std == 0] = 1.0
- expr = (expr - expr.mean(axis=0)) / std
- return expr.astype(np.float32)
- def simulate_perturbation(
- cfg: SyntheticGRNConfig,
- rng: np.random.Generator,
- weight_mat: np.ndarray,
- indirect_weight_mat: np.ndarray,
- pert_tf: int,
- ) -> np.ndarray:
- """Simulate cells where the given TF is knocked down.
- Knockdown: set pert_tf activity to near zero and re-simulate expression.
- """
- n = cfg.n_genes
- n_cells = cfg.n_pert_cells_per_condition
- tf_activity = rng.standard_t(df=3, size=(n_cells, n)) * 0.5
- # Knockdown: zero out the perturbed TF's activity
- tf_activity[:, pert_tf] *= 0.05
- expr = tf_activity @ (weight_mat + indirect_weight_mat)
- gene_means = 0.0 # means are cell-invariant, already in baseline
- expr = expr + gene_means
- expr = expr + rng.normal(0, cfg.noise_scale, size=expr.shape).astype(np.float32)
- mask = rng.uniform(size=expr.shape) < cfg.dropout_p
- expr[mask] = 0.0
- return expr.astype(np.float32)
- # ---------------------------------------------------------------------------
- # Pipeline components: DE calling, gene-level features, correlation edges
- # ---------------------------------------------------------------------------
- def compute_gene_features(ctrl_expr: np.ndarray) -> np.ndarray:
- """Compute trivial gene-level features from control cells.
- Returns array (n_genes, 3): variance, mean, dropout rate.
- """
- var = ctrl_expr.var(axis=0)
- mean = ctrl_expr.mean(axis=0)
- dropout = (ctrl_expr == 0).mean(axis=0)
- return np.stack([var, mean, dropout], axis=1)
- def compute_correlation_edges(ctrl_expr: np.ndarray) -> np.ndarray:
- """Compute gene-by-gene Spearman correlation edge scores on control cells.
- Uses fast rank-based Pearson as an approximation of Spearman.
- Returns (n_genes, n_genes) symmetric matrix.
- """
- # Rank each gene independently
- ranks = stats.rankdata(ctrl_expr, axis=0)
- ranks = ranks - ranks.mean(axis=0)
- std = ranks.std(axis=0)
- std[std == 0] = 1.0
- ranks = ranks / std
- corr = (ranks.T @ ranks) / ranks.shape[0]
- # Use absolute correlation as edge score (magnitude of association)
- return np.abs(corr).astype(np.float32)
- def call_de(
- ctrl_expr: np.ndarray,
- pert_expr: np.ndarray,
- lfc_threshold: float = 0.5,
- alpha: float = 0.05,
- ) -> np.ndarray:
- """Return boolean array (n_genes,) of DE-hit targets.
- Uses Welch's t-test + LFC threshold (crude, but matches paper pipeline).
- """
- n_genes = ctrl_expr.shape[1]
- de_hits = np.zeros(n_genes, dtype=bool)
- # Simple LFC in log1p-space
- mean_c = np.log1p(np.abs(ctrl_expr).mean(axis=0) + 1e-3)
- mean_p = np.log1p(np.abs(pert_expr).mean(axis=0) + 1e-3)
- lfc = mean_p - mean_c
- # Welch's t across genes
- t, p = stats.ttest_ind(pert_expr, ctrl_expr, axis=0, equal_var=False)
- de_hits = (np.abs(lfc) > lfc_threshold) & (p < alpha)
- return de_hits
- # ---------------------------------------------------------------------------
- # Main experiment
- # ---------------------------------------------------------------------------
- @dataclass
- class VariantResult:
- variant: str
- config: dict
- n_perturbations: int = 0
- n_evaluable: int = 0
- gene_only_auroc: float = float("nan")
- gene_plus_corr_auroc: float = float("nan")
- delta_auroc_corr: float = float("nan")
- delta_auroc_ci: tuple = field(default_factory=lambda: (float("nan"), float("nan")))
- per_tf_corr_auroc: dict = field(default_factory=dict)
- variance_baseline_auroc: float = float("nan")
- mean_baseline_auroc: float = float("nan")
- dropout_baseline_auroc: float = float("nan")
- positive_rate: float = float("nan")
- notes: str = ""
- def run_variant(cfg: SyntheticGRNConfig, shuffle_ground_truth: bool = False) -> VariantResult:
- """Run one stress variant and return summary metrics.
- If shuffle_ground_truth, the TF->target mapping is randomly permuted
- after simulation so that simulated DE still occurs but correlation
- edges no longer map to ``true'' direct targets. This provides the
- negative control: pipeline should produce zero incremental value.
- """
- rng = np.random.default_rng(cfg.seed + (1_000_000 if shuffle_ground_truth else 0))
- tf_idx, direct, indirect, weight_mat, indirect_weight_mat = build_grn(cfg, rng)
- print(f"[{cfg.variant}] Built GRN: {cfg.n_genes} genes, {cfg.n_tfs} TFs, "
- f"{sum(len(v) for v in direct.values())} direct edges")
- # Simulate control cells
- ctrl_expr = simulate_control_cells(cfg, rng, weight_mat, indirect_weight_mat)
- print(f"[{cfg.variant}] Simulated {ctrl_expr.shape[0]} control cells")
- # Gene-level features + correlation edges from control cells
- gene_feats = compute_gene_features(ctrl_expr)
- corr_edges = compute_correlation_edges(ctrl_expr)
- # Optionally shuffle ground truth: permute target labels per TF
- # so DE positives no longer match the edges that contributed to them.
- if shuffle_ground_truth:
- all_non_tf = [g for g in range(cfg.n_genes) if g not in set(tf_idx.tolist())]
- shuffled_direct = {}
- for tf in direct:
- shuffled_direct[tf] = list(
- rng.choice(all_non_tf, size=len(direct[tf]), replace=False)
- )
- # Note: we keep the actual simulation edges in place (so DE still
- # happens biologically), but we treat shuffled_direct as the
- # "ground truth direct set". Any incremental value on this shuffled
- # truth is spurious.
- ground_truth_direct = shuffled_direct
- else:
- ground_truth_direct = {int(tf): list(direct[int(tf)]) for tf in direct}
- # Simulate each perturbation
- pert_results = [] # (tf, de_mask, per_gene_pair_rows)
- rows_X = []
- rows_y = []
- rows_group = []
- tfs_to_perturb = tf_idx[: cfg.n_perturbations]
- for k, tf in enumerate(tfs_to_perturb):
- tf_int = int(tf)
- pert_expr = simulate_perturbation(
- cfg, rng, weight_mat, indirect_weight_mat, pert_tf=tf_int
- )
- # Compute DE hits empirically (used for descriptive stats)
- de_hits_empirical = call_de(ctrl_expr, pert_expr)
- if cfg.direct_only_positives:
- # Positive class = direct ground-truth targets only.
- # This is the cleanest test of whether the pipeline can
- # detect direct pairwise regulation.
- positives = np.zeros(cfg.n_genes, dtype=bool)
- for tgt in ground_truth_direct.get(tf_int, []):
- positives[tgt] = True
- label_mask = positives
- else:
- label_mask = de_hits_empirical
- n_pos = int(label_mask.sum())
- if n_pos < 3 or n_pos > cfg.n_genes - 3:
- continue
- pert_results.append((tf_int, label_mask, n_pos))
- # Build per-perturbation rows: for each non-self target gene,
- # features = [tf_var, tf_mean, tf_dropout, tgt_var, tgt_mean, tgt_dropout, corr_edge]
- for tgt in range(cfg.n_genes):
- if tgt == tf_int:
- continue
- X_row = [
- gene_feats[tf_int, 0], gene_feats[tf_int, 1], gene_feats[tf_int, 2],
- gene_feats[tgt, 0], gene_feats[tgt, 1], gene_feats[tgt, 2],
- corr_edges[tf_int, tgt],
- ]
- rows_X.append(X_row)
- rows_y.append(int(label_mask[tgt]))
- rows_group.append(tf_int)
- X = np.array(rows_X, dtype=np.float32)
- y = np.array(rows_y, dtype=np.int32)
- groups = np.array(rows_group, dtype=np.int32)
- if len(pert_results) < 3 or y.sum() == 0:
- return VariantResult(
- variant=cfg.variant,
- config=asdict(cfg),
- notes="Too few evaluable perturbations or no DE hits",
- )
- positive_rate = float(y.mean())
- print(f"[{cfg.variant}] {len(pert_results)} perturbations, "
- f"{len(y)} gene-pair rows, positive rate {positive_rate:.3f}")
- # 5-fold GroupKFold by source TF
- n_splits = min(5, len(set(groups)))
- gkf = GroupKFold(n_splits=n_splits)
- def eval_model(feat_cols):
- """Cross-validated mean AUROC across folds on the given feature cols."""
- aurocs = []
- for tr, te in gkf.split(X, y, groups):
- X_tr = X[tr][:, feat_cols]
- X_te = X[te][:, feat_cols]
- y_tr = y[tr]
- y_te = y[te]
- if y_te.sum() == 0 or y_te.sum() == len(y_te):
- continue
- clf = LogisticRegression(max_iter=1000, solver="liblinear")
- clf.fit(X_tr, y_tr)
- p = clf.predict_proba(X_te)[:, 1]
- aurocs.append(roc_auc_score(y_te, p))
- return float(np.mean(aurocs)) if aurocs else float("nan")
- # Feature column indices
- GENE_COLS = [0, 1, 2, 3, 4, 5]
- CORR_COL = [6]
- gene_only = eval_model(GENE_COLS)
- gene_plus_corr = eval_model(GENE_COLS + CORR_COL)
- delta = gene_plus_corr - gene_only
- # Bootstrap delta-AUROC CI (100 resamples over perturbations)
- rng_boot = np.random.default_rng(cfg.seed + 7777)
- deltas = []
- unique_tfs = sorted(set(groups.tolist()))
- for _ in range(100):
- boot_tfs = rng_boot.choice(unique_tfs, size=len(unique_tfs), replace=True)
- mask = np.isin(groups, boot_tfs)
- if y[mask].sum() == 0:
- continue
- Xb = X[mask]
- yb = y[mask]
- gb = groups[mask]
- try:
- gkf_b = GroupKFold(n_splits=min(3, len(set(gb))))
- aur_g, aur_gc = [], []
- for tr, te in gkf_b.split(Xb, yb, gb):
- if yb[te].sum() == 0 or yb[te].sum() == len(yb[te]):
- continue
- c1 = LogisticRegression(max_iter=1000, solver="liblinear")
- c1.fit(Xb[tr][:, GENE_COLS], yb[tr])
- aur_g.append(roc_auc_score(yb[te], c1.predict_proba(Xb[te][:, GENE_COLS])[:, 1]))
- c2 = LogisticRegression(max_iter=1000, solver="liblinear")
- c2.fit(Xb[tr][:, GENE_COLS + CORR_COL], yb[tr])
- aur_gc.append(roc_auc_score(yb[te], c2.predict_proba(Xb[te][:, GENE_COLS + CORR_COL])[:, 1]))
- if aur_g and aur_gc:
- deltas.append(np.mean(aur_gc) - np.mean(aur_g))
- except Exception:
- continue
- if deltas:
- ci_low, ci_high = float(np.percentile(deltas, 2.5)), float(np.percentile(deltas, 97.5))
- else:
- ci_low, ci_high = float("nan"), float("nan")
- # Trivial baselines: each gene-level feature alone
- def single_feature_auroc(col):
- scores = X[:, col]
- return float(roc_auc_score(y, scores))
- variance_auroc = single_feature_auroc(3) # target variance
- mean_auroc = single_feature_auroc(4)
- dropout_auroc = 1.0 - single_feature_auroc(5) # low dropout = more detectable
- # Per-TF correlation-edge AUROC (marginal, no gene features)
- per_tf_corr_auroc = {}
- for tf_int_inner, label_mask_inner, _ in pert_results:
- # Use corr_edges[tf, :] as scores for each target
- scores = corr_edges[tf_int_inner]
- try:
- aur = roc_auc_score(label_mask_inner.astype(int), scores)
- except ValueError:
- aur = float("nan")
- per_tf_corr_auroc[int(tf_int_inner)] = float(aur)
- res = VariantResult(
- variant=cfg.variant,
- config=asdict(cfg),
- n_perturbations=cfg.n_perturbations,
- n_evaluable=len(pert_results),
- gene_only_auroc=gene_only,
- gene_plus_corr_auroc=gene_plus_corr,
- delta_auroc_corr=delta,
- delta_auroc_ci=(ci_low, ci_high),
- per_tf_corr_auroc=per_tf_corr_auroc,
- variance_baseline_auroc=variance_auroc,
- mean_baseline_auroc=mean_auroc,
- dropout_baseline_auroc=dropout_auroc,
- positive_rate=positive_rate,
- notes="shuffled ground truth" if shuffle_ground_truth else "planted ground truth",
- )
- return res
- def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--variant", default="all", choices=["A", "B", "C", "all"])
- args = parser.parse_args()
- # Three stress variants.
- # All three z-score gene expression so variance is uninformative —
- # this is the positive-control design: any delta_AUROC > 0 must come
- # from pairwise (correlation) information, not univariate baselines.
- # The variants then stress different aspects:
- # A: strong, clean direct regulation with minimal indirect.
- # Pipeline ceiling test.
- # B: weak direct edges + strong indirect propagation.
- # Tests whether indirect effects mask pairwise signal.
- # C: realistic balance of direct + indirect + noise.
- # Closest to real K562-like regime.
- variants = {
- "A": SyntheticGRNConfig(
- variant="A",
- edge_strength=2.2,
- indirect_strength=0.1,
- noise_scale=0.5,
- direct_targets_per_tf=8,
- indirect_targets_per_tf=2,
- z_score_genes=True,
- direct_only_positives=True, # positive class = direct targets only
- seed=2026,
- ),
- "B": SyntheticGRNConfig(
- variant="B",
- edge_strength=0.8,
- indirect_strength=1.6,
- noise_scale=0.9,
- direct_targets_per_tf=5,
- indirect_targets_per_tf=15,
- z_score_genes=True,
- direct_only_positives=True,
- seed=2027,
- ),
- "C": SyntheticGRNConfig(
- variant="C",
- edge_strength=1.2,
- indirect_strength=0.8,
- noise_scale=0.8,
- direct_targets_per_tf=6,
- indirect_targets_per_tf=8,
- z_score_genes=True,
- direct_only_positives=True,
- seed=2028,
- ),
- }
- if args.variant != "all":
- variants = {args.variant: variants[args.variant]}
- summary = {}
- for vname, cfg in variants.items():
- print(f"\n===== Variant {vname}: {cfg.notes if hasattr(cfg, 'notes') else ''} =====")
- # Planted ground-truth run
- res = run_variant(cfg, shuffle_ground_truth=False)
- out_path = RESULTS_DIR / f"variant_{vname}.json"
- with open(out_path, "w") as f:
- # Convert tuple to list for JSON
- d = asdict(res)
- d["delta_auroc_ci"] = list(d["delta_auroc_ci"])
- json.dump(d, f, indent=2, default=float)
- print(f" gene_only AUROC: {res.gene_only_auroc:.4f}")
- print(f" gene+corr AUROC: {res.gene_plus_corr_auroc:.4f}")
- print(f" delta_auroc: {res.delta_auroc_corr:+.4f} "
- f"[{res.delta_auroc_ci[0]:+.4f}, {res.delta_auroc_ci[1]:+.4f}]")
- print(f" variance baseline: {res.variance_baseline_auroc:.4f}")
- print(f" mean ctrl baseline: {res.mean_baseline_auroc:.4f}")
- # Shuffled-null control
- print(f"\n --- shuffled null for variant {vname} ---")
- res_null = run_variant(cfg, shuffle_ground_truth=True)
- null_path = RESULTS_DIR / f"shuffled_null_{vname}.json"
- with open(null_path, "w") as f:
- d = asdict(res_null)
- d["delta_auroc_ci"] = list(d["delta_auroc_ci"])
- json.dump(d, f, indent=2, default=float)
- print(f" shuffled gene_only: {res_null.gene_only_auroc:.4f}")
- print(f" shuffled gene+corr: {res_null.gene_plus_corr_auroc:.4f}")
- print(f" shuffled delta: {res_null.delta_auroc_corr:+.4f}")
- summary[vname] = {
- "planted": {
- "gene_only": res.gene_only_auroc,
- "gene_plus_corr": res.gene_plus_corr_auroc,
- "delta": res.delta_auroc_corr,
- "delta_ci": list(res.delta_auroc_ci),
- "variance_baseline": res.variance_baseline_auroc,
- "n_evaluable": res.n_evaluable,
- "positive_rate": res.positive_rate,
- },
- "shuffled_null": {
- "gene_only": res_null.gene_only_auroc,
- "gene_plus_corr": res_null.gene_plus_corr_auroc,
- "delta": res_null.delta_auroc_corr,
- },
- }
- with open(RESULTS_DIR / "summary.json", "w") as f:
- json.dump(summary, f, indent=2, default=float)
- print(f"\nDone. Results in {RESULTS_DIR}")
- if __name__ == "__main__":
- main()
20_positive_control_pipeline.py at commit 40e6da0, no license · at the source
Overview
Abstract
Background: Single-cell foundation models such as scGPT and Geneformer are increasingly used for gene regulatory network (GRN) inference, with attention-derived edge scores routinely interpreted as regulatory proxies. Prior benchmarks have evaluated curated-reference recovery but have not systematically tested whether attention adds information beyond expression statistics for predicting the outcomes of genetic perturbations, nor whether attention-identified “regulatory” components are causally required for such predictions. This gap matters because the NLP interpretability literature has established that attention weights do not reliably indicate feature importance, and biological foundation models are being deployed without analogous scrutiny.
Methods: We present an evaluation framework comprising thirty-seven analyses and 153 statistical tests under Benjamini-Hochberg FDR correction, spanning two architectures (scGPT, Geneformer V2-316M), four cell types (K562, RPE1, primary T cells, iPSC neurons), and two perturbation modalities (CRISPRi, CRISPRa). The framework separates two objectives: (A) mechanistic interpretability / GRN recovery against curated references, and (B) perturbation-target prediction, i.e. classifying which genes show differential expression after a CRISPR perturbation. Five test families—trivial-baselin
Results: Attention patterns encode layer-specific biological structure—protein–protei
Conclusions: Attention patterns in single-cell foundation models encode biologically structured information, including layer-specific regulatory signals recoverable via CSSI, but provide no unique predictive information beyond simple gene-level statistics for the perturbation-target prediction task. The paper thus offers both a cautionary finding for Objective B and a constructive method (CSSI) for Objective A. Practitioners should apply trivial-baseline and incremental-value tests before claiming pairwise regulatory signal, and should stratify by cell state when extracting attention-derived GRNs.
Supplementary Information: The online version contains supplementary material available at 10.1186/
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 40 matches between paragraphs and lines of code.
Zenodo 18701417
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
188 files
- src/
01_scaling_failure/ , Python, 641 lines01_scaling_characterizat ion.py - src/
01_scaling_failure/ , Python, 409 lines02_controlled_compositio n.py - src/
01_scaling_failure/ , Python, 301 lines03_k_sensitivity.py - src/
01_scaling_failure/ , Python, 277 lines04_heterogeneity_entropy .py - src/
01_scaling_failure/ , Python, 575 lines05_saturation_analysis.p y - src/
01_scaling_failure/ , Python, 371 lines06_saturation_analysis_f ast.py - src/
01_scaling_failure/ , Python, 855 lines07_extended_evidence.py - src/
01_scaling_failure/ , Python, 328 lines08_snapshot_artifacts.py - src/
01_scaling_failure/ , Python, 504 lines09_submission_grade_evid ence.py - src/
01_scaling_failure/ , Python, 405 lines10_paper_bundle.py - src/
02_cssi_method/ , Python, 44 lines01_data_exploration.py - src/
02_cssi_method/ , Python, 670 lines02_full_pipeline.py - src/
02_cssi_method/ , Python, 517 lines03_optimized_pipeline.py - src/
02_cssi_method/ , Python, 389 lines04_revised_statistics.py - src/
02_cssi_method/ , Python, 424 lines05_figures.py - src/
02_cssi_method/ , Python, 90 lines06_fig1_heatmap.py - src/
02_cssi_method/ , Python, 440 lines07_expanded_null_and_bas eline.py - src/
02_cssi_method/ , Python, 178 lines08_realdata_validation.p y - src/
02_cssi_method/ , Python, 555 lines09_multi_tissue.py - src/
02_cssi_method/ , Python, 373 lines10_perturbation_validati on.py - src/
02_cssi_method/ , Python, 609 lines11_ceiling_effect.py - src/
02_cssi_method/ , Python, 424 lines12_temporal_dynamics.py - src/
02_cssi_method/ , Python, 796 linescrispri_validation/ scripts/ 01_pipeline.py - src/
02_cssi_method/ , Python, 785 linescrispri_validation/ scripts/ 02_pipeline_v2.py - src/
02_cssi_method/ , Python, 925 linescrispri_validation/ scripts/ 03_cssi_pipeline.py - src/
02_cssi_method/ , Python, 579 linescssi_real_data/ 01_research.py - src/
02_cssi_method/ , Python, 451 linescssi_real_data/ 02_research_v2.py - src/
02_cssi_method/ , Python, 439 linescssi_real_data/ 03_research_v3.py - src/
02_cssi_method/ , Python, 228 linescssi_real_data/ 04_experiment.py - src/
02_cssi_method/ , Python, 450 linescssi_real_data/ 05_final.py - src/
02_cssi_method/ , Python, 359 linescssi_real_data/ 06_geneformer.py - src/
02_cssi_method/ , Python, 360 linescssi_real_data/ 07_geneformer_v2.py - src/
02_cssi_method/ , Python, 358 linescssi_real_data/ 08_geneformer_v3.py - src/
02_cssi_method/ , Python, 501 linescssi_real_data/ 09_cross_dataset_validat ion.py - src/
02_cssi_method/ , Python, 407 linescssi_real_data/ 10_cross_tissue_validati on.py - src/
02_cssi_method/ , Python, 631 linescssi_real_data/ 11_held_out_validation.p y - src/
02_cssi_method/ , Python, 144 linescssi_real_data/ 12_run_validation.py - src/
02_cssi_method/ , Shell, 7 linescssi_real_data/ run_sequential.sh - src/
02_cssi_method/ , Python, 235 linescssi_realdata/ 01_realdata_validation.p y - src/
02_cssi_method/ , Python, 166 linesnovel_method/ 01_experiment.py - src/
02_cssi_method/ , Python, 166 linesnovel_method/ 02_experiment_v2.py - src/
02_cssi_method/ , Python, 255 linesnovel_method/ 03_null_inflation.py - src/
02_cssi_method/ , Python, 36 linesnovel_method/ 04_figure.py - src/
03_perturbation_validati , Python, 393 lineson/ 01_first_validation.py - src/
03_perturbation_validati , Python, 759 lineson/ 02_attention_first.py - src/
03_perturbation_validati , Python, 366 lineson/ 03_sensitivity.py - src/
03_perturbation_validati , Python, 340 lineson/ 04_sensitivity_fast.py - src/
03_perturbation_validati , Python, 371 lineson/ 05_sensitivity_mp.py - src/
03_perturbation_validati , Python, 465 lineson/ 06_trivial_baselines.py - src/
03_perturbation_validati , Python, 518 lineson/ 07_degree_preserving_nul ls.py - src/
03_perturbation_validati , Python, 284 lineson/ 08_trrust_circularity.py - src/
03_perturbation_validati , Python, 287 lineson/ 09_full_layer_analysis.p y - src/
03_perturbation_validati , Python, 218 lineson/ 10_nested_layer_selectio n.py - src/
03_perturbation_validati , Python, 369 lineson/ analysis/ 01_bridge_benchmark.py - src/
03_perturbation_validati , Python, 520 lineson/ analysis/ 02_independent_perturbat ion_panel.py - src/
03_perturbation_validati , Python, 288 lineson/ analysis/ 03_bridge_controls.py - src/
03_perturbation_validati , Python, 193 lineson/ analysis/ 04_cross_regime_rank_shi ft.py - src/
03_perturbation_validati , Python, 165 lineson/ analysis/ 05_perturbation_concentr ation.py - src/
03_perturbation_validati , Python, 255 lineson/ baseline_comparison/ 01_simple_baseline.py - src/
03_perturbation_validati , Python, 453 lineson/ baseline_comparison/ 02_fast_baseline.py - src/
03_perturbation_validati , Python, 376 lineson/ baseline_comparison/ 03_working_baseline.py - src/
03_perturbation_validati , Python, 354 lineson/ baseline_comparison/ 04_baseline_comparison.p y - src/
03_perturbation_validati , Python, 415 lineson/ baseline_comparison/ 05_demo_baseline.py - src/
03_perturbation_validati , Python, 156 lineson/ perturbation_validation/ 01_explore_crispri_data. py - src/
03_perturbation_validati , Python, 467 lineson/ perturbation_validation/ 02_validation_experiment .py - src/
03_perturbation_validati , Python, 288 lineson/ perturbation_validation/ 03_fast_validation.py - src/
03_perturbation_validati , Python, 229 lineson/ perturbation_validation/ 04_demo_validation.py - src/
04_confound_decompositio , Python, 393 linesn/ 01_incremental_value.py - src/
04_confound_decompositio , Python, 496 linesn/ 02_confound_decompositio n.py - src/
04_confound_decompositio , Python, 245 linesn/ 03_correlation_mapping.p y - src/
04_confound_decompositio , Python, 360 linesn/ 04_residualization.py - src/
04_confound_decompositio , Python, 533 linesn/ 05_residualization_v2.py - src/
04_confound_decompositio , Python, 568 linesn/ 06_bootstrap_per_tf.py - src/
04_confound_decompositio , Python, 371 linesn/ 07_hard_generalization.p y - src/
04_confound_decompositio , Python, 589 lines, 1 matchn/ 08_metric_robust_increme ntal.py - src/
04_confound_decompositio , Python, 278 linesn/ 09_bootstrap_only.py - src/
04_confound_decompositio , Python, 1,056 linesn/ 10_propensity_matched.py - src/
04_confound_decompositio , Python, 277 linesn/ 11_propensity_matched_fi xup.py - src/
05_causal_ablation/ , Python, 872 lines01_head_ablation.py - src/
05_causal_ablation/ , Python, 394 lines02_fix_per_head_ranking. py - src/
05_causal_ablation/ , Python, 893 lines03_expanded_ablation.py - src/
05_causal_ablation/ , Python, 872 lines04_orthogonal_interventi ons.py - src/
05_causal_ablation/ , Python, 865 lines05_intervention_fidelity .py - src/
06_cross_context/ , Python, 431 lines01_confound_uncertainty. py - src/
06_cross_context/ , Python, 494 lines02_transfer_artifacts.py - src/
06_cross_context/ , Python, 438 lines03_iterator_stress_tests .py - src/
06_cross_context/ , Python, 603 lines04_paper_figures.py - src/
06_cross_context/ , Python, 693 lines05_adamson_cross_context .py - src/
06_cross_context/ , Python, 763 lines06_shifrut_cross_context .py - src/
06_cross_context/ , Python, 861 lines07_adamson_L15.py - src/
06_cross_context/ , Python, 1,195 lines08_non_k562_replication. py - src/
06_cross_context/ , Python, 1,426 lines, 3 matches09_rpe1_confound_battery .py - src/
07_mediation_bias/ , Python, 1,626 lines01_full_paper_pipeline.p y - src/
07_mediation_bias/ , Python, 745 lines02_proposal5_analysis.py - src/
07_mediation_bias/ , Python, 647 lines03_targeted_pairwise_pat ches.py - src/
07_mediation_bias/ , Python, 502 lines04_targeted_triplet_patc hes.py - src/
07_mediation_bias/ , Python, 273 lines05_iterator_stress_tests .py - src/
07_mediation_bias/ , Python, 219 lines06_refresh_figure_qualit y.py - src/
08_detectability/ , Python, 663 lines01_phase_diagram.py - src/
08_detectability/ , Python, 1,054 lines02_full_study.py - src/
08_detectability/ , Python, 845 lines03_real_data_calibration .py - src/
09_ortholog_transfer/ , Python, 83 lines01_load_and_explore.py - src/
09_ortholog_transfer/ , Python, 110 lines02_ortholog_mapping.py - src/
09_ortholog_transfer/ , Python, 226 lines03_edge_scoring.py - src/
09_ortholog_transfer/ , Python, 290 lines04_cross_species_analysi s.py - src/
09_ortholog_transfer/ , Python, 446 lines05_figures.py - src/
09_ortholog_transfer/ , Python, 401 lines06_multi_tissue_extensio n.py - src/
10_pseudotime/ , Python, 60 lines01_explore_data.py - src/
10_pseudotime/ , Python, 28 lines02_check_genes.py - src/
10_pseudotime/ , Python, 729 lines03_pseudotime_and_analys is.py - src/
10_pseudotime/ , Python, 112 lines04_pseudotime.py - src/
10_pseudotime/ , Python, 439 lines05_directionality_analys is.py - src/
10_pseudotime/ , Python, 419 lines06_figures.py - src/
10_pseudotime/ , Python, 754 lines07_multi_tissue.py - src/
10_pseudotime/ , Python, 558 lines08_perturbation_validati on.py - src/
10_pseudotime/ , Python, 611 lines09_topology_analysis.py - src/
10_pseudotime/ , Python, 612 lines10_velocity_proxy.py - src/
11_batch_leakage/ , Python, 51 lines01_explore_metadata.py - src/
11_batch_leakage/ , Python, 855 lines02_full_pipeline.py - src/
11_batch_leakage/ , Python, 466 lines03_robust_splits.py - src/
11_batch_leakage/ , Python, 461 lines04_kidney_3tissue.py - src/
11_batch_leakage/ , Python, 465 lines05_composition_decomposi tion.py - src/
11_batch_leakage/ , Python, 459 lines06_perturbation_validati on.py - src/
11_batch_leakage/ , Python, 504 lines07_leakage_correction.py - src/
12_calibration/ , Python, 394 lines01_compute_edge_scores.p y - src/
12_calibration/ , Python, 468 lines02_calibration_analysis. py - src/
12_calibration/ , Python, 323 lines03_figures.py - src/
12_calibration/ , Python, 477 lines04_tissue_transfer.py - src/
12_calibration/ , Python, 365 lines05_adaptive_conformal.py - src/
12_calibration/ , Python, 526 lines06_shifrut_validation.py - src/
12_calibration/ , Python, 338 lines07_calibration_stability .py - src/
13_synthetic_validation/ , Python, 201 lines01_reproduce_analysis.py - src/
13_synthetic_validation/ , Python, 757 lines02_synthetic_validation. py - src/
13_synthetic_validation/ , Python, 167 lines03_sergio_validation.py - src/
14_multi_model/ , Python, 297 lines01_grn_pipeline.py - src/
14_multi_model/ , Python, 825 lines02_grn_validation.py - src/
14_multi_model/ , Python, 228 lines03_v1_attention_extracti on.py - src/
14_multi_model/ , Python, 270 lines04_v1_experiment.py - src/
14_multi_model/ , Python, 264 lines05_cross_context_experim ent.py - src/
14_multi_model/ , Python, 437 lines06_v1_analysis_summary.p y - src/
14_multi_model/ , Python, 123 lines07_investigate_models.py - src/
14_multi_model/ , Python, 504 lines08_analyze_extended_resu lts.py - src/
15_biological/ , Python, 1,157 lines01_value_weighted.py - src/
15_biological/ , Python, 888 lines02_biological_characteri zation.py - src/
15_biological/ , Python, 870 lines03_characterization_corr ections.py - src/
15_biological/ , Python, 885 lines04_cssi_biology.py - src/
15_biological/ , Python, 875 lines05_context_dependence.py - src/
15_biological/ , Python, 646 lines06_per_tf.py - src/
15_biological/ , Python, 252 lines07_fix_expression.py - src/
15_biological/ , Python, 310 lines08_tf_enrichment.py - src/
15_biological/ , Python, 493 lines09_harden_cssi_biology.p y - src/
15_biological/ , Python, 522 lines10_harden_context_depend ence.py - src/
15_biological/ , Python, 437 lines11_harden_per_tf.py - src/
16_statistical_framework , Python, 657 lines/ 01_statistical_audit.py - src/
16_statistical_framework , Python, 461 lines/ 02_multiplicity_sensitiv ity.py - src/
_debug/ , Python, 6 linescheck1.py - src/
_debug/ , Python, 4 linescheck2.py - src/
_debug/ , Python, 20 linescheck2b.py - src/
_debug/ , Python, 24 linescheck3.py - src/
_debug/ , Python, 20 linescheck3b.py - src/
_debug/ , Python, 40 linescheck_env.py - src/
_debug/ , Python, 24 linescheck_vocab.py - src/
_debug/ , Python, 18 linescheck_vocab2.py - src/
_debug/ , Python, 47 linesdebug_genes.py - src/
_debug/ , Python, 44 linesdebug_mapping.py - src/
_debug/ , Python, 59 linesdebug_vocab.py - src/
_debug/ , Python, 165 linesminimal_saturation_test. py - src/
_debug/ , Python, 150 linesminimal_test.py - src/
_debug/ , Python, 246 linesrobust_saturation_test.p y - src/
_debug/ , Python, 434 linestest_c2s_pythia.py - src/
_debug/ , Python, 32 linestest_deps.py - src/
_debug/ , Python, 45 linestest_geneformer_basic.py - src/
_debug/ , Python, 39 linestest_huggingface_genefor mer.py - src/
_debug/ , Python, 64 linestest_imports.py - src/
_debug/ , Python, 5 linestest_path.py - src/
_debug/ , Python, 286 linestest_scvi_basic.py - src/
_debug/ , Python, 375 linestest_uce_basic.py - src/
_debug/ , Python, 48 linesupdate_tracking.py - src/
plotting/ , Python, 431 lines01_main_figures.py - src/
plotting/ , Python, 727 lines02_supp_figures.py - src/
plotting/ , Python, 583 lines03_supp_batch2.py - src/
plotting/ , Python, 911 lines04_supp_batch3.py - src/
plotting/ , Python, 349 lines05_supp_batch4.py - src/
plotting/ , Python, 479 lines06_supp_batch5.py - src/
plotting/ , Python, 238 lines07_supp_batch6.py - src/
plotting/ , Python, 158 lines08_supp_batch7.py - src/
shared/ , Python, 790 lines01_unified_extraction.py - README.md, Text, 283 lines
Biodyn-AI/biomechinterp-framework
40e6da0ae21a768dbbcb2bfa62539845f0ef7c88, 14 April 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
195 files
- src/
01_scaling_failure/ , Python, 641 lines01_scaling_characterizat ion.py - src/
01_scaling_failure/ , Python, 409 lines02_controlled_compositio n.py - src/
01_scaling_failure/ , Python, 301 lines03_k_sensitivity.py - src/
01_scaling_failure/ , Python, 277 lines04_heterogeneity_entropy .py - src/
01_scaling_failure/ , Python, 575 lines05_saturation_analysis.p y - src/
01_scaling_failure/ , Python, 371 lines06_saturation_analysis_f ast.py - src/
01_scaling_failure/ , Python, 855 lines07_extended_evidence.py - src/
01_scaling_failure/ , Python, 328 lines08_snapshot_artifacts.py - src/
01_scaling_failure/ , Python, 504 lines09_submission_grade_evid ence.py - src/
01_scaling_failure/ , Python, 405 lines, 1 match10_paper_bundle.py - src/
02_cssi_method/ , Python, 44 lines01_data_exploration.py - src/
02_cssi_method/ , Python, 670 lines02_full_pipeline.py - src/
02_cssi_method/ , Python, 517 lines03_optimized_pipeline.py - src/
02_cssi_method/ , Python, 389 lines04_revised_statistics.py - src/
02_cssi_method/ , Python, 424 lines05_figures.py - src/
02_cssi_method/ , Python, 90 lines06_fig1_heatmap.py - src/
02_cssi_method/ , Python, 440 lines07_expanded_null_and_bas eline.py - src/
02_cssi_method/ , Python, 178 lines08_realdata_validation.p y - src/
02_cssi_method/ , Python, 555 lines09_multi_tissue.py - src/
02_cssi_method/ , Python, 373 lines, 1 match10_perturbation_validati on.py - src/
02_cssi_method/ , Python, 609 lines11_ceiling_effect.py - src/
02_cssi_method/ , Python, 424 lines12_temporal_dynamics.py - src/
02_cssi_method/ , Python, 796 lines, 1 matchcrispri_validation/ scripts/ 01_pipeline.py - src/
02_cssi_method/ , Python, 785 linescrispri_validation/ scripts/ 02_pipeline_v2.py - src/
02_cssi_method/ , Python, 925 lines, 2 matchescrispri_validation/ scripts/ 03_cssi_pipeline.py - src/
02_cssi_method/ , Python, 579 linescssi_real_data/ 01_research.py - src/
02_cssi_method/ , Python, 451 linescssi_real_data/ 02_research_v2.py - src/
02_cssi_method/ , Python, 439 linescssi_real_data/ 03_research_v3.py - src/
02_cssi_method/ , Python, 228 linescssi_real_data/ 04_experiment.py - src/
02_cssi_method/ , Python, 450 linescssi_real_data/ 05_final.py - src/
02_cssi_method/ , Python, 359 linescssi_real_data/ 06_geneformer.py - src/
02_cssi_method/ , Python, 360 linescssi_real_data/ 07_geneformer_v2.py - src/
02_cssi_method/ , Python, 358 linescssi_real_data/ 08_geneformer_v3.py - src/
02_cssi_method/ , Python, 501 linescssi_real_data/ 09_cross_dataset_validat ion.py - src/
02_cssi_method/ , Python, 407 linescssi_real_data/ 10_cross_tissue_validati on.py - src/
02_cssi_method/ , Python, 631 linescssi_real_data/ 11_held_out_validation.p y - src/
02_cssi_method/ , Python, 144 linescssi_real_data/ 12_run_validation.py - src/
02_cssi_method/ , Python, 214 lines, 2 matchescssi_real_data/ 20_positive_control_tfs. py - src/
02_cssi_method/ , Shell, 7 linescssi_real_data/ run_sequential.sh - src/
02_cssi_method/ , Python, 235 linescssi_realdata/ 01_realdata_validation.p y - src/
02_cssi_method/ , Python, 166 linesnovel_method/ 01_experiment.py - src/
02_cssi_method/ , Python, 166 linesnovel_method/ 02_experiment_v2.py - src/
02_cssi_method/ , Python, 255 linesnovel_method/ 03_null_inflation.py - src/
02_cssi_method/ , Python, 36 linesnovel_method/ 04_figure.py - src/
03_perturbation_validati , Python, 393 lineson/ 01_first_validation.py - src/
03_perturbation_validati , Python, 759 lines, 1 matchon/ 02_attention_first.py - src/
03_perturbation_validati , Python, 366 lineson/ 03_sensitivity.py - src/
03_perturbation_validati , Python, 340 lineson/ 04_sensitivity_fast.py - src/
03_perturbation_validati , Python, 371 lineson/ 05_sensitivity_mp.py - src/
03_perturbation_validati , Python, 465 lineson/ 06_trivial_baselines.py - src/
03_perturbation_validati , Python, 518 lineson/ 07_degree_preserving_nul ls.py - src/
03_perturbation_validati , Python, 284 lineson/ 08_trrust_circularity.py - src/
03_perturbation_validati , Python, 287 lineson/ 09_full_layer_analysis.p y - src/
03_perturbation_validati , Python, 218 lineson/ 10_nested_layer_selectio n.py - src/
03_perturbation_validati , Python, 369 lineson/ analysis/ 01_bridge_benchmark.py - src/
03_perturbation_validati , Python, 520 lineson/ analysis/ 02_independent_perturbat ion_panel.py - src/
03_perturbation_validati , Python, 288 lineson/ analysis/ 03_bridge_controls.py - src/
03_perturbation_validati , Python, 193 lineson/ analysis/ 04_cross_regime_rank_shi ft.py - src/
03_perturbation_validati , Python, 165 lineson/ analysis/ 05_perturbation_concentr ation.py - src/
03_perturbation_validati , Python, 255 lineson/ baseline_comparison/ 01_simple_baseline.py - src/
03_perturbation_validati , Python, 453 lineson/ baseline_comparison/ 02_fast_baseline.py - src/
03_perturbation_validati , Python, 376 lineson/ baseline_comparison/ 03_working_baseline.py - src/
03_perturbation_validati , Python, 354 lineson/ baseline_comparison/ 04_baseline_comparison.p y - src/
03_perturbation_validati , Python, 415 lineson/ baseline_comparison/ 05_demo_baseline.py - src/
03_perturbation_validati , Python, 156 lineson/ perturbation_validation/ 01_explore_crispri_data. py - src/
03_perturbation_validati , Python, 467 lineson/ perturbation_validation/ 02_validation_experiment .py - src/
03_perturbation_validati , Python, 288 lineson/ perturbation_validation/ 03_fast_validation.py - src/
03_perturbation_validati , Python, 229 lineson/ perturbation_validation/ 04_demo_validation.py - src/
04_confound_decompositio , Python, 393 linesn/ 01_incremental_value.py - src/
04_confound_decompositio , Python, 496 linesn/ 02_confound_decompositio n.py - src/
04_confound_decompositio , Python, 245 linesn/ 03_correlation_mapping.p y - src/
04_confound_decompositio , Python, 360 linesn/ 04_residualization.py - src/
04_confound_decompositio , Python, 533 lines, 1 matchn/ 05_residualization_v2.py - src/
04_confound_decompositio , Python, 568 linesn/ 06_bootstrap_per_tf.py - src/
04_confound_decompositio , Python, 371 linesn/ 07_hard_generalization.p y - src/
04_confound_decompositio , Python, 589 lines, 1 matchn/ 08_metric_robust_increme ntal.py - src/
04_confound_decompositio , Python, 278 linesn/ 09_bootstrap_only.py - src/
04_confound_decompositio , Python, 1,056 linesn/ 10_propensity_matched.py - src/
04_confound_decompositio , Python, 277 linesn/ 11_propensity_matched_fi xup.py - src/
05_causal_ablation/ , Python, 872 lines01_head_ablation.py - src/
05_causal_ablation/ , Python, 394 lines02_fix_per_head_ranking. py - src/
05_causal_ablation/ , Python, 893 lines, 1 match03_expanded_ablation.py - src/
05_causal_ablation/ , Python, 872 lines, 3 matches04_orthogonal_interventi ons.py - src/
05_causal_ablation/ , Python, 865 lines, 2 matches05_intervention_fidelity .py - src/
05_causal_ablation/ , Python, 360 lines, 2 matches20_scgpt_ablation.py - src/
05_causal_ablation/ , Python, 719 lines, 2 matches21_scgpt_forward_ablatio n.py - src/
06_cross_context/ , Python, 431 lines01_confound_uncertainty. py - src/
06_cross_context/ , Python, 494 lines02_transfer_artifacts.py - src/
06_cross_context/ , Python, 438 lines03_iterator_stress_tests .py - src/
06_cross_context/ , Python, 603 lines04_paper_figures.py - src/
06_cross_context/ , Python, 693 lines05_adamson_cross_context .py - src/
06_cross_context/ , Python, 763 lines06_shifrut_cross_context .py - src/
06_cross_context/ , Python, 861 lines07_adamson_L15.py - src/
06_cross_context/ , Python, 1,195 lines, 1 match08_non_k562_replication. py - src/
06_cross_context/ , Python, 1,426 lines, 3 matches09_rpe1_confound_battery .py - src/
07_mediation_bias/ , Python, 1,626 lines01_full_paper_pipeline.p y - src/
07_mediation_bias/ , Python, 745 lines02_proposal5_analysis.py - src/
07_mediation_bias/ , Python, 647 lines03_targeted_pairwise_pat ches.py - src/
07_mediation_bias/ , Python, 502 lines04_targeted_triplet_patc hes.py - src/
07_mediation_bias/ , Python, 273 lines05_iterator_stress_tests .py - src/
07_mediation_bias/ , Python, 219 lines06_refresh_figure_qualit y.py - src/
08_detectability/ , Python, 663 lines01_phase_diagram.py - src/
08_detectability/ , Python, 1,054 lines02_full_study.py - src/
08_detectability/ , Python, 845 lines03_real_data_calibration .py - src/
09_ortholog_transfer/ , Python, 83 lines01_load_and_explore.py - src/
09_ortholog_transfer/ , Python, 110 lines02_ortholog_mapping.py - src/
09_ortholog_transfer/ , Python, 226 lines03_edge_scoring.py - src/
09_ortholog_transfer/ , Python, 290 lines04_cross_species_analysi s.py - src/
09_ortholog_transfer/ , Python, 446 lines05_figures.py - src/
09_ortholog_transfer/ , Python, 401 lines06_multi_tissue_extensio n.py - src/
10_pseudotime/ , Python, 60 lines01_explore_data.py - src/
10_pseudotime/ , Python, 28 lines02_check_genes.py - src/
10_pseudotime/ , Python, 729 lines03_pseudotime_and_analys is.py - src/
10_pseudotime/ , Python, 112 lines04_pseudotime.py - src/
10_pseudotime/ , Python, 439 lines05_directionality_analys is.py - src/
10_pseudotime/ , Python, 419 lines06_figures.py - src/
10_pseudotime/ , Python, 754 lines07_multi_tissue.py - src/
10_pseudotime/ , Python, 558 lines08_perturbation_validati on.py - src/
10_pseudotime/ , Python, 611 lines09_topology_analysis.py - src/
10_pseudotime/ , Python, 612 lines10_velocity_proxy.py - src/
11_batch_leakage/ , Python, 51 lines01_explore_metadata.py - src/
11_batch_leakage/ , Python, 855 lines02_full_pipeline.py - src/
11_batch_leakage/ , Python, 466 lines03_robust_splits.py - src/
11_batch_leakage/ , Python, 461 lines04_kidney_3tissue.py - src/
11_batch_leakage/ , Python, 465 lines05_composition_decomposi tion.py - src/
11_batch_leakage/ , Python, 459 lines06_perturbation_validati on.py - src/
11_batch_leakage/ , Python, 504 lines07_leakage_correction.py - src/
12_calibration/ , Python, 394 lines01_compute_edge_scores.p y - src/
12_calibration/ , Python, 468 lines02_calibration_analysis. py - src/
12_calibration/ , Python, 323 lines03_figures.py - src/
12_calibration/ , Python, 477 lines04_tissue_transfer.py - src/
12_calibration/ , Python, 365 lines05_adaptive_conformal.py - src/
12_calibration/ , Python, 526 lines06_shifrut_validation.py - src/
12_calibration/ , Python, 338 lines07_calibration_stability .py - src/
13_synthetic_validation/ , Python, 201 lines01_reproduce_analysis.py - src/
13_synthetic_validation/ , Python, 757 lines02_synthetic_validation. py - src/
13_synthetic_validation/ , Python, 167 lines03_sergio_validation.py - src/
13_synthetic_validation/ , Python, 605 lines, 4 matches20_positive_control_pipe line.py - src/
14_multi_model/ , Python, 297 lines01_grn_pipeline.py - src/
14_multi_model/ , Python, 825 lines02_grn_validation.py - src/
14_multi_model/ , Python, 228 lines03_v1_attention_extracti on.py - src/
14_multi_model/ , Python, 270 lines04_v1_experiment.py - src/
14_multi_model/ , Python, 264 lines05_cross_context_experim ent.py - src/
14_multi_model/ , Python, 437 lines, 1 match06_v1_analysis_summary.p y - src/
14_multi_model/ , Python, 123 lines, 1 match07_investigate_models.py - src/
14_multi_model/ , Python, 504 lines08_analyze_extended_resu lts.py - src/
15_biological/ , Python, 1,157 lines01_value_weighted.py - src/
15_biological/ , Python, 888 lines02_biological_characteri zation.py - src/
15_biological/ , Python, 870 lines03_characterization_corr ections.py - src/
15_biological/ , Python, 885 lines, 1 match04_cssi_biology.py - src/
15_biological/ , Python, 875 lines, 2 matches05_context_dependence.py - src/
15_biological/ , Python, 646 lines06_per_tf.py - src/
15_biological/ , Python, 252 lines, 1 match07_fix_expression.py - src/
15_biological/ , Python, 310 lines08_tf_enrichment.py - src/
15_biological/ , Python, 493 lines, 1 match09_harden_cssi_biology.p y - src/
15_biological/ , Python, 522 lines10_harden_context_depend ence.py - src/
15_biological/ , Python, 437 lines11_harden_per_tf.py - src/
16_statistical_framework , Python, 657 lines/ 01_statistical_audit.py - src/
16_statistical_framework , Python, 461 lines, 1 match/ 02_multiplicity_sensitiv ity.py - src/
_debug/ , Python, 6 linescheck1.py - src/
_debug/ , Python, 4 linescheck2.py - src/
_debug/ , Python, 20 linescheck2b.py - src/
_debug/ , Python, 24 linescheck3.py - src/
_debug/ , Python, 20 linescheck3b.py - src/
_debug/ , Python, 40 linescheck_env.py - src/
_debug/ , Python, 24 linescheck_vocab.py - src/
_debug/ , Python, 18 linescheck_vocab2.py - src/
_debug/ , Python, 47 linesdebug_genes.py - src/
_debug/ , Python, 44 linesdebug_mapping.py - src/
_debug/ , Python, 59 linesdebug_vocab.py - src/
_debug/ , Python, 165 linesminimal_saturation_test. py - src/
_debug/ , Python, 150 linesminimal_test.py - src/
_debug/ , Python, 246 linesrobust_saturation_test.p y - src/
_debug/ , Python, 434 linestest_c2s_pythia.py - src/
_debug/ , Python, 32 linestest_deps.py - src/
_debug/ , Python, 45 linestest_geneformer_basic.py - src/
_debug/ , Python, 39 linestest_huggingface_genefor mer.py - src/
_debug/ , Python, 64 linestest_imports.py - src/
_debug/ , Python, 5 linestest_path.py - src/
_debug/ , Python, 286 linestest_scvi_basic.py - src/
_debug/ , Python, 375 linestest_uce_basic.py - src/
_debug/ , Python, 48 linesupdate_tracking.py - src/
plotting/ , Python, 431 lines01_main_figures.py - src/
plotting/ , Python, 727 lines02_supp_figures.py - src/
plotting/ , Python, 583 lines03_supp_batch2.py - src/
plotting/ , Python, 911 lines04_supp_batch3.py - src/
plotting/ , Python, 349 lines05_supp_batch4.py - src/
plotting/ , Python, 487 lines06_supp_batch5.py - src/
plotting/ , Python, 238 lines07_supp_batch6.py - src/
plotting/ , Python, 158 lines08_supp_batch7.py - src/
plotting/ , Python, 195 lines20_framework_schematic.p y - src/
plotting/ , Python, 218 lines21_positive_control.py - src/
plotting/ , Python, 58 linespalette.py - src/
shared/ , Python, 790 lines01_unified_extraction.py - README.md, Text, 283 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 381 scripts, each with its path and the digest of its content;
- 40 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability
All analysis scripts, data processing pipelines, source data for all figures and tables, and reproducibility instructions are deposited at Zenodo (https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 1 author, 9 keywords, 4 MeSH terms, 1 funder, 19 references.
Cite
This paper
Kendiukhov, I. (2026). Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction. BMC genomics, 27(1), 634. https://
BibTeX
@article{kendiukhov2026s
author = {Kendiukhov, Ihor},
title = {{Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction}},
journal = {BMC genomics},
year = {2026},
month = jul,
volume = {27},
number = {1},
pages = {634},
publisher = {BMC},
issn = {1471-2164},
doi = {10.1186/
url = {https://
pmid = {42482180},
pmcid = {PMC13390336}
}
RIS
TY - JOUR
AU - Kendiukhov, Ihor
TI - Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction
T2 - BMC genomics
J2 - BMC Genomics
PY - 2026
DA - 2026/
VL - 27
IS - 1
SP - 634
SN - 1471-2164
PB - BMC
DO - 10.1186/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1186/
"type": "article-journal",
"title": "Systematic evaluation of single-cell foundation model interpretability: attention-derived edge scores add no incremental value over gene-level features for perturbation-target prediction",
"container-title": "BMC genomics",
"author": [
{
"family": "Kendiukhov",
"given": "Ihor"
}
],
"container-title-short":
"volume": "27",
"issue": "1",
"page": "634",
"DOI": "10.1186/
"PMID": "42482180",
"PMCID": "PMC13390336",
"ISSN": "1471-2164",
"publisher": "BMC",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
22
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1093/nar/gkag706 [code]
- scDifformer: diffusion-based post-training for virtual cell modeling across large-scale single-cell data.Journal: Nucleic acids researchIn common: anndata, Scanpy, NetworkX, 9 other tools, 5 references
- [2] doi:10.64898/2026.03.30.714220 [code]
- An integrated single cell and spatial omics atlas of human prenatal developmentJournal: bioRxiv (preprint)In common: Hugging Face Transformers, anndata, Scanpy, 10 other tools, 2 references
- [3] doi:10.1016/j.isci.2026.116055 [code]
- Mapping the transcriptional diversity of calcium signaling in the mouse and human brain.Journal: iScienceIn common: anndata, igraph, Scanpy, 10 other tools, 1 reference
- [4] doi:10.1016/j.crmeth.2026.101342 [code]
- Interpretable learning of temporal cellular dynamics from single-cell data.Journal: Cell reports methodsIn common: anndata, igraph, Scanpy, 9 other tools, cellular / molecular, 1 reference
- [5] doi:10.1038/s41593-026-02267-3 [code]
- Spatial proteomic analysis in human Alzheimer's disease brains enables identification of microenvironment-depende
nt microglial cell states. Journal: Nature neuroscienceIn common: anndata, igraph, Scanpy, 9 other tools, cellular / molecular, 1 reference - [6] doi:10.1093/bib/bbag259 [code]
- PVAED: prior-guided variational autoencoders with diffusion denoising for interpretable single-cell representation learning.Journal: Briefings in bioinformaticsIn common: anndata, Scanpy, NetworkX, 6 other tools, 4 references
- [7] doi:10.1038/s44320-026-00208-7 [code]
- Interpretable deep generative ensemble learning for single-cell omics with Hydra.Journal: Molecular systems biologyIn common: anndata, igraph, Scanpy, 8 other tools, cellular / molecular, 2 references
- [8] doi:10.1016/j.xgen.2026.101217 [code]
- ProtoCloud: A prototypical self-explaining model for single-cell analysis.Journal: Cell genomicsIn common: anndata, Scanpy, h5py, 7 other tools, cellular / molecular, 3 references
- [9] doi:10.1038/s41467-026-71759-4 [code]
- CellNiche represents cellular microenvironments in atlas-scale spatial omics data with contrastive learning.Journal: Nature communicationsIn common: anndata, Scanpy, NetworkX, 7 other tools, cellular / molecular, 3 references
- [10] doi:10.1038/s41592-026-03194-8 [code]
- Beyond benchmarking: an expert-guided consensus approach to spatially aware clustering.Journal: Nature methodsIn common: anndata, igraph, Scanpy, 7 other tools, 3 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 381 scripts, and 40 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:8633053f88f0bcc7…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
