scCS.pairwise¶
pairwise.py — PairScorer: pairwise condition comparison for scCS.
Extends the single-condition SingleScorer to handle exactly 2 experimental conditions (e.g., treatment vs. control, mutant vs. wild-type).
Architecture¶
- PairScorer
Wraps SingleScorer. Pools both conditions for embedding, then scores each condition separately using cell masks on the shared embedding.
- Tier 1 — Core pairwise API
score_all_conditions() : dict[condition -> CommitmentScoreResult]
- Tier 2 — Statistical comparison
compare_conditions() : permutation test on per-cell fate affinity compute_delta_CS() : ΔCS = nCS_A − nCS_B with bootstrap CI plot_affinity_distributions() : violin/box plots of per-cell affinities
- Tier 3 — Advanced
- fit_mixed_model()linear mixed-effects model on per-cell
fate affinity scores via statsmodels MixedLM
- trajectory_shift()KS test + Wasserstein distance on
pseudotime distributions per fate arm
plot_trajectory_shift() : visualization of pseudotime distributions
Usage¶
>>> pscorer = scCS.PairScorer(
... adata,
... root='17',
... branches=['homeostatic', 'activated'],
... condition_obs_key='treatment',
... obs_key='leiden',
... )
>>> pscorer.build_embedding(ordering_metric='pseudotime')
>>> pscorer.fit()
>>> results = pscorer.score_all_conditions()
>>> delta = pscorer.compute_delta_CS('control', 'treated')
>>> stats = pscorer.compare_conditions(results)
>>> pscorer.plot_affinity_distributions(results)
>>> shift = pscorer.trajectory_shift(results)
>>> pscorer.plot_trajectory_shift(shift)
Classes¶
RNA velocity commitment scorer for pairwise (2-condition) experiments. |
Module Contents¶
- class scCS.pairwise.PairScorer(adata, root: str, branches: List[str], condition_obs_key: str, obs_key: str = 'leiden', n_angle_bins: int = 36, sector_method: Literal['centroid', 'equal'] = 'centroid', copy: bool = False)[source]¶
RNA velocity commitment scorer for pairwise (2-condition) experiments.
Builds a SHARED star embedding on the pooled data from both conditions, then scores each condition separately. This ensures arm geometry is identical across conditions, making CS values directly comparable.
- Parameters:
adata (AnnData) – Full single-cell dataset containing both conditions.
root (str) – Label of the progenitor/root cluster in adata.obs[obs_key].
branches (list of str) – Labels of the k terminal fate clusters.
condition_obs_key (str) – Column in adata.obs with condition labels (e.g., ‘treatment’). Must contain exactly 2 unique values.
obs_key (str) – Column in adata.obs with cluster labels. Default: ‘leiden’.
n_angle_bins (int) – Number of angular bins. Default: 36.
sector_method ({'centroid', 'equal'}) – Sector definition strategy.
copy (bool) – Work on a copy of adata.
- Raises:
ValueError – If condition_obs_key has fewer or more than 2 unique values. For 3+ conditions, use MultiScorer instead.
Examples
>>> pscorer = PairScorer( ... adata, ... root='17', ... branches=['homeostatic', 'activated'], ... condition_obs_key='treatment', ... obs_key='leiden', ... ) >>> pscorer.build_embedding(ordering_metric='pseudotime') >>> pscorer.fit() >>> results = pscorer.score_all_conditions() >>> delta = pscorer.compute_delta_CS('control', 'treated') >>> stats = pscorer.compare_conditions(results)
- build_embedding(ordering_metric: str | numpy.ndarray = 'pseudotime', invert_ordering: bool = False, scale_ordering: bool = False, arm_scale: float = 10.0, jitter: float = 0.3, seed: int = 42, arm_norm: str = 'global', verbose: bool = True) PairScorer[source]¶
Build the shared star embedding on pooled data from both conditions.
The embedding is built on ALL cells (both conditions pooled), ensuring that arm geometry is identical across conditions.
- Parameters:
ordering_metric (str or np.ndarray) – See SingleScorer.build_embedding().
invert_ordering (bool)
scale_ordering (bool)
arm_scale (float)
jitter (float)
seed (int)
verbose (bool)
- Return type:
self
- refit_pseudotime(scale_01: bool = True, arm_scale: float = 10.0, jitter: float = 0.3, seed: int = 42, arm_norm: str = 'global', verbose: bool = True) PairScorer[source]¶
Rebuild the shared embedding using subset-local pseudotime.
See SingleScorer.refit_pseudotime().
- fit(verbose: bool = True) PairScorer[source]¶
Fit the shared FateMap and project velocity.
Must be called after build_embedding().
- Return type:
self
- score_all_conditions(cell_level: bool = True, k_nn: int | None = None, n_bootstrap: int = 0, bootstrap_ci: float = 0.95, verbose: bool = True) Dict[str, scCS.scores.CommitmentScoreResult][source]¶
Compute commitment scores separately for each condition.
Uses the shared embedding and FateMap. Each condition’s cells are masked from the shared adata_sub, so arm geometry is identical.
- Parameters:
cell_level (bool) – Compute per-cell fate affinity scores.
k_nn (int, optional) – NN-smoothed entropy neighbors.
n_bootstrap (int) – Bootstrap replicates for CI. 0 = disabled.
bootstrap_ci (float) – CI level for bootstrap.
verbose (bool)
- Returns:
dict
- Return type:
condition_label -> CommitmentScoreResult
- compute_delta_CS(condition_a: str, condition_b: str, n_bootstrap: int = 500, ci: float = 0.95, seed: int = 42, verbose: bool = True) Dict[source]¶
Compute ΔCS = nCS_A − nCS_B with bootstrap confidence intervals.
For each pair of fates (i, j), computes the difference in normalized commitment score between condition A and condition B, with a bootstrap CI obtained by resampling cells within each condition.
- Parameters:
condition_a (str) – Condition labels (must be in self.conditions).
condition_b (str) – Condition labels (must be in self.conditions).
n_bootstrap (int) – Number of bootstrap replicates. Default 500.
ci (float) – Confidence interval level. Default 0.95.
seed (int)
verbose (bool)
- Returns:
‘delta_nCS’ : np.ndarray (k, k) — nCS_A − nCS_B ‘ci_low’ : np.ndarray (k, k) — lower CI bound on delta ‘ci_high’ : np.ndarray (k, k) — upper CI bound on delta ‘nCS_A’ : np.ndarray (k, k) — nCS for condition A ‘nCS_B’ : np.ndarray (k, k) — nCS for condition B ‘fate_names’ : list of str ‘condition_a’: str ‘condition_b’: str ‘n_bootstrap’: int ‘ci_level’ : float
- Return type:
dict with keys
- compare_conditions(results: Dict[str, scCS.scores.CommitmentScoreResult], test: Literal['permutation', 'kruskal'] = 'permutation', n_permutations: int = 1000, pval_threshold: float = 0.05, seed: int = 42, verbose: bool = True) pandas.DataFrame[source]¶
Statistical comparison of per-cell fate affinity scores across conditions.
For PairScorer (k=2 conditions), the default test is a permutation test: shuffle condition labels, recompute mean per-cell affinity difference, and get an empirical null distribution.
- Parameters:
results (dict) – Output of score_all_conditions() with cell_level=True.
test ({'permutation', 'kruskal'}) – Statistical test to use. Default: ‘permutation’ (recommended for k=2).
n_permutations (int) – Number of permutations for the permutation test. Default 1000.
pval_threshold (float) – Significance threshold. Default 0.05.
seed (int)
verbose (bool)
- Returns:
fate, test, statistic, pval, pval_adj, significant [+ comparison column for pairwise tests]
- Return type:
pd.DataFrame with columns
- plot_affinity_distributions(results: Dict[str, scCS.scores.CommitmentScoreResult], plot_type: Literal['violin', 'box', 'strip'] = 'violin', color_map: Dict[str, str] | None = None, figsize: Tuple[float, float] | None = None, title: str | None = None, save_path: str | None = None) matplotlib.figure.Figure[source]¶
Violin/box plots of per-cell fate affinity scores by condition.
One panel per fate, showing the distribution of per-cell affinity scores split by condition.
- Parameters:
results (dict) – Output of score_all_conditions(cell_level=True).
plot_type ({'violin', 'box', 'strip'})
color_map (dict, optional) – condition_label -> hex color.
figsize (tuple, optional)
title (str, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- fit_mixed_model(results: Dict[str, scCS.scores.CommitmentScoreResult], replicate_key: str | None = None, ref_condition: str | None = None, verbose: bool = True) pandas.DataFrame[source]¶
Linear mixed-effects model on per-cell fate affinity scores.
Models per-cell fate affinity as a function of condition (fixed effect) with optional sample/replicate as a random effect.
- Model (per fate j):
affinity_ij ~ condition_i + (1 | sample_id_i)
Uses statsmodels MixedLM.
- Parameters:
results (dict) – Output of score_all_conditions(cell_level=True).
replicate_key (str, optional) – Column in adata_sub.obs with sample/replicate IDs.
ref_condition (str, optional) – Reference condition for the fixed effect.
verbose (bool)
- Returns:
fate, condition, coef, std_err, z_score, pval, pval_adj, ci_low, ci_high, significant
- Return type:
pd.DataFrame with columns
- trajectory_shift(results: Dict[str, scCS.scores.CommitmentScoreResult], pseudotime_key: str = 'sccs_pseudotime', n_bootstrap: int = 500, seed: int = 42, verbose: bool = True) pandas.DataFrame[source]¶
Test whether pseudotime distributions differ across conditions per fate arm.
For each fate arm, computes: - Kolmogorov-Smirnov (KS) statistic and p-value - Wasserstein distance (Earth Mover’s Distance) - Bootstrap CI on the Wasserstein distance
- Parameters:
results (dict) – Output of score_all_conditions().
pseudotime_key (str) – Column in adata_sub.obs with pseudotime values.
n_bootstrap (int) – Bootstrap replicates for Wasserstein CI. Default 500.
seed (int)
verbose (bool)
- Returns:
fate, comparison, ks_stat, ks_pval, wasserstein, wasserstein_ci_low, wasserstein_ci_high, mean_pt_A, mean_pt_B, delta_mean_pt, significant
- Return type:
pd.DataFrame with columns
- plot_trajectory_shift(shift_df: pandas.DataFrame, pseudotime_key: str = 'sccs_pseudotime', color_map: Dict[str, str] | None = None, figsize: Tuple[float, float] | None = None, title: str | None = None, save_path: str | None = None) matplotlib.figure.Figure[source]¶
Visualize pseudotime distributions per condition per fate arm.
Produces a grid of KDE plots: one row per fate arm. Overlaid KDEs show how pseudotime distributions shift between conditions.
- Parameters:
shift_df (pd.DataFrame) – Output of trajectory_shift().
pseudotime_key (str)
color_map (dict, optional)
figsize (tuple, optional)
title (str, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- transfer_labels(results: Dict[str, scCS.scores.CommitmentScoreResult], prefix: str = 'cs_') None[source]¶
Transfer per-cell commitment scores to the full adata for all conditions.
Calls SingleScorer.transfer_labels() for each condition’s result, writing condition-specific columns to adata.obs.
- Parameters:
results (dict) – Output of score_all_conditions(cell_level=True).
prefix (str) – Column prefix. Default: ‘cs_’.
- plot_star(result: scCS.scores.CommitmentScoreResult, **kwargs)[source]¶
Radial star embedding plot.
- plot_star_grid(results: Dict[str, scCS.scores.CommitmentScoreResult], color_map: Dict[str, str] | None = None, figsize_per_panel: Tuple[float, float] = (6, 6), save_path: str | None = None) matplotlib.figure.Figure[source]¶
Side-by-side star embedding plots, one per condition.
All panels share the same arm geometry and color scale.
- Parameters:
results (dict)
color_map (dict, optional)
figsize_per_panel (tuple)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- plot_rose_grid(results: Dict[str, scCS.scores.CommitmentScoreResult], color_map: Dict[str, str] | None = None, figsize_per_panel: Tuple[float, float] = (5, 5), title: str | None = None, save_path: str | None = None) matplotlib.figure.Figure[source]¶
Grid of polar rose plots — one per condition.
All panels share the same radial scale, making magnitudes directly comparable across conditions.
- Parameters:
results (dict)
color_map (dict, optional)
figsize_per_panel (tuple)
title (str, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- plot_delta_cs_heatmap(delta_result: dict, **kwargs) matplotlib.figure.Figure[source]¶
Heatmap of ΔCS = nCS_A − nCS_B with CI annotation.
- Parameters:
delta_result (dict) – Output of compute_delta_CS().
**kwargs – Passed to
scCS.plot.plot_delta_cs_heatmap().
- Returns:
fig
- Return type:
matplotlib Figure
- plot_compare_conditions_bar(results: Dict[str, scCS.scores.CommitmentScoreResult], **kwargs) matplotlib.figure.Figure[source]¶
Grouped bar chart of nCS per condition.
- Parameters:
results (dict) – Output of score_all_conditions().
**kwargs – Passed to
scCS.plot.plot_compare_conditions_bar().
- Returns:
fig
- Return type:
matplotlib Figure
- plot_commitment_vector_radar(results: Dict[str, scCS.scores.CommitmentScoreResult], **kwargs) matplotlib.figure.Figure[source]¶
Radar / spider chart of commitment vectors per condition.
- Parameters:
results (dict) – Output of score_all_conditions().
**kwargs – Passed to
scCS.plot.plot_commitment_vector_radar().
- Returns:
fig
- Return type:
matplotlib Figure
- property scorer: scCS.single.SingleScorer | None[source]¶
The internal SingleScorer used for embedding and scoring.