API Reference¶
This page documents all public classes and functions in scCS. Full source-linked documentation is also available in the autoapi section in the sidebar.
SingleScorer¶
Single-condition analysis. Wraps an AnnData object and exposes all scoring and plotting methods.
- class scCS.SingleScorer(adata, root: str, branches: List[str], obs_key: str = 'leiden', n_angle_bins: int = 36, sector_method: Literal['centroid', 'equal'] = 'centroid', copy: bool = False)[source]¶
Bases:
_BaseScorerRNA velocity commitment scorer with radial star embedding.
Computes commitment scores for a k-furcation defined by a single user-supplied bifurcation cluster and k terminal fate clusters.
The embedding places the bifurcation cluster at the origin and arranges each fate on its own radial arm, with cells ordered by differentiation level (pseudotime, CytoTRACE2, or custom score).
- Parameters:
adata (AnnData) – Single-cell dataset.
root (str) – Label of the progenitor/root cluster in adata.obs[obs_key]. Example: ‘17’ (leiden cluster 17)
branches (list of str) – Labels of the k terminal fate clusters. Example: [‘Monocyte’, ‘DC’, ‘Neutrophil’]
obs_key (str) – Column in adata.obs with cluster labels. Default: ‘leiden’.
n_angle_bins (int) – Number of angular bins for commitment scoring. Default: 36 (10° each).
sector_method ({'centroid', 'equal'}) –
How to define angular sectors: - ‘centroid’: anchor sectors to the direction from origin to each
fate centroid in the star embedding (recommended).
’equal’: divide [0°, 360°] into k equal sectors.
copy (bool) – Work on a copy of adata.
Examples
# k=2 bifurcation scorer = SingleScorer(
adata, root=’17’, branches=[‘homeostatic’, ‘activated’], obs_key=’leiden’,
) scorer.build_embedding(ordering_metric=’pseudotime’) scorer.fit() result = scorer.score() scorer.plot_star(result)
# k=3 with CytoTRACE2 scorer = SingleScorer(
adata, root=’5’, branches=[‘FateA’, ‘FateB’, ‘FateC’], obs_key=’cell_type’,
) scorer.build_embedding(ordering_metric=’cytotrace’) scorer.fit() result = scorer.score() scorer.plot_star(result)
- compute_velocity(mode: str = 'dynamical', n_top_genes: int = 2000, n_pcs: int = 30, n_neighbors: int = 30, min_shared_counts: int = 20, verbose: bool = True) SingleScorer[source]¶
Run the full scVelo RNA velocity pipeline.
Call this if adata does not yet have velocity vectors. Requires ‘spliced’ and ‘unspliced’ layers.
- Parameters:
mode ({'dynamical', 'stochastic', 'steady_state'})
n_top_genes (int)
n_pcs (int)
n_neighbors (int)
min_shared_counts (int)
verbose (bool)
- Return type:
self
- fit(verbose: bool = True) SingleScorer[source]¶
Build the FateMap from the user-supplied cluster labels.
Must be called after build_embedding().
This step: 1. Validates that root and branches
exist in adata.obs[obs_key].
Computes fate centroids in the X_sccs embedding.
Extracts velocity vectors if not already loaded.
- Return type:
self
- get_deg_drivers(n_top_genes: int = 50, pval_threshold: float = 0.05, logfc_threshold: float = 0.25) dict[source]¶
Find DEGs for each fate arm vs the bifurcation cluster (Wilcoxon).
- Parameters:
n_top_genes (int)
pval_threshold (float)
logfc_threshold (float)
- Returns:
dict
- Return type:
fate_name -> DataFrame[gene, logfoldchange, pval, pval_adj, significant]
- get_enrichment(deg_drivers: dict, gene_sets: List[str] | None = None, organism: str = 'mouse', pval_threshold: float = 0.05, logfc_threshold: float = 0.25, plot: bool = True, n_top_pathways: int = 15) dict[source]¶
Run pathway enrichment on DEG driver genes per fate arm.
- Parameters:
deg_drivers (dict) – Output of get_deg_drivers().
gene_sets (list of str, optional)
organism (str)
pval_threshold (float)
logfc_threshold (float)
plot (bool)
n_top_pathways (int)
- Returns:
dict
- Return type:
fate_name -> {‘up’: DataFrame, ‘down’: DataFrame}
- get_velocity_drivers(n_top_genes: int = 50) dict[source]¶
Rank genes by mean scVelo velocity in each fate arm.
- Parameters:
n_top_genes (int) – Number of top driver genes to print per fate.
- Returns:
dict
- Return type:
fate_name -> DataFrame[gene, mean_velocity, rank]
- get_velocity_fate_drivers(result: CommitmentScoreResult, n_top_genes: int = 50, pval_threshold: float = 0.05) dict[source]¶
Identify driver genes by correlating gene velocity with fate affinity.
- Parameters:
result (CommitmentScoreResult) – Output of scorer.score(cell_level=True).
n_top_genes (int)
pval_threshold (float)
- Returns:
dict – mean_velocity, delta_velocity, significant]
- Return type:
fate_name -> DataFrame[gene, spearman_r, pval, pval_adj,
- classmethod load(path: str, adata) SingleScorer[source]¶
Load a scorer from a pickle file.
- Parameters:
path (str) – Path to a file saved by
save().adata (AnnData) – The full dataset (same object originally passed to
SingleScorer.__init__). Not stored in the pickle.
- Return type:
- plot_commitment_bar(result: CommitmentScoreResult, **kwargs)[source]¶
Bar chart of unCS vs nCS per fate pair.
- plot_commitment_heatmap(result: CommitmentScoreResult, **kwargs)[source]¶
Per-cell fate affinity heatmap.
- plot_nn_entropy_elbow(**kwargs)[source]¶
Elbow plots for choosing k_nn for NN-smoothed entropy.
- Parameters:
k_nn_range (list or range, optional) – k_nn values to sweep. Default: range(5, 51, 5).
**kwargs – Passed to
scCS.plot.plot_nn_entropy_elbow().
- Returns:
fig
- Return type:
matplotlib Figure
- plot_pairwise_cs(result: CommitmentScoreResult, **kwargs)[source]¶
Heatmap of pairwise normalized commitment scores.
- plot_rose(result: CommitmentScoreResult, **kwargs)[source]¶
Rose/polar plot of cumulative magnitudes per angular bin.
- plot_star(result: CommitmentScoreResult, **kwargs)[source]¶
Radial star embedding plot — primary visualization.
- plot_subset_comparison(subset_results: dict, **kwargs)[source]¶
Compare commitment scores across subsets.
- save(path: str) None[source]¶
Serialize scorer state to a pickle file.
Saves the embedding, FateMap, velocity vectors, and configuration. The full
adatais NOT saved (too large); pass it again toload().- Parameters:
path (str) – Destination file path (e.g.,
'scorer.pkl').
- score(cell_mask: ndarray | None = None, cell_level: bool = True, k_nn: int | None = None, n_bootstrap: int = 0, bootstrap_ci: float = 0.95, bootstrap_seed: int = 42, verbose: bool = True, write_to_obs: bool = True) CommitmentScoreResult[source]¶
Compute commitment scores for the full population or a subset.
- Parameters:
cell_mask (np.ndarray of bool, shape (n_sub_cells,), optional) – Boolean mask over
adata_subcells (NOT the full adata). If provided, only cells where mask=True contribute to the population-level score (M_bin, M_sector, unCS, nCS). Per-cell scores are still computed for all cells.cell_level (bool) – Whether to compute per-cell fate affinity scores.
k_nn (int, optional) – If set, compute NN-smoothed per-cell entropy using this many nearest neighbors in the scCS embedding (X_sccs).
n_bootstrap (int) – Number of bootstrap replicates for CS confidence intervals. 0 (default) disables bootstrapping. Recommended: 500.
bootstrap_ci (float) – Confidence interval level for bootstrap. Default 0.95 (95% CI).
bootstrap_seed (int) – Random seed for bootstrap resampling.
verbose (bool)
write_to_obs (bool) – If True (default), write per-cell scores to
adata_sub.obs.
- Return type:
- score_per_subset(split_by: str, cell_level: bool = False, n_bootstrap: int = 0, verbose: bool = False) dict[source]¶
Compute commitment scores separately for each value of split_by.
Useful for comparing commitment across conditions, time points, or trajectory directions.
- Parameters:
split_by (str) – Column in adata_sub.obs to split by.
cell_level (bool)
n_bootstrap (int) – Bootstrap replicates for CI. 0 = disabled.
verbose (bool)
- Return type:
dict mapping subset_value -> CommitmentScoreResult
- transfer_labels(adata, result: CommitmentScoreResult, prefix: str = 'cs_') None[source]¶
Write per-cell commitment scores back to the full adata.
After scoring, per-cell fate affinities, dominant fate, and entropy are stored in
adata_sub.obs. This method transfers those columns to the full adata so they can be used in downstream analyses.Cells not in the embedding subset receive NaN for numeric columns and ‘unassigned’ for categorical columns.
- Parameters:
adata (AnnData) – The full dataset (same object passed to SingleScorer.__init__).
result (CommitmentScoreResult) – Output of scorer.score(cell_level=True).
prefix (str) – Column prefix. Default: ‘cs_’.
PairScorer¶
Pairwise comparison (exactly 2 conditions). Builds a shared star embedding on pooled data, then scores each condition separately. Provides statistical comparison, mixed-effects modeling, and trajectory shift analysis.
- class scCS.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]¶
Bases:
objectRNA 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)
- property adata_sub¶
The embedding subset (from the internal SingleScorer).
- build_embedding(ordering_metric: str | 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
- compare_conditions(results: Dict[str, CommitmentScoreResult], test: Literal['permutation', 'kruskal'] = 'permutation', n_permutations: int = 1000, pval_threshold: float = 0.05, seed: int = 42, verbose: bool = True) 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
- 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
- fit(verbose: bool = True) PairScorer[source]¶
Fit the shared FateMap and project velocity.
Must be called after build_embedding().
- Return type:
self
- fit_mixed_model(results: Dict[str, CommitmentScoreResult], replicate_key: str | None = None, ref_condition: str | None = None, verbose: bool = True) 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
- property is_fitted: bool¶
- plot_affinity_distributions(results: Dict[str, 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) 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
- plot_commitment_vector_radar(results: Dict[str, CommitmentScoreResult], **kwargs) 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
- plot_compare_conditions_bar(results: Dict[str, CommitmentScoreResult], **kwargs) 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_delta_cs_heatmap(delta_result: dict, **kwargs) 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_rose_grid(results: Dict[str, 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) 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_star(result: CommitmentScoreResult, **kwargs)[source]¶
Radial star embedding plot.
- plot_star_grid(results: Dict[str, CommitmentScoreResult], color_map: Dict[str, str] | None = None, figsize_per_panel: Tuple[float, float] = (6, 6), save_path: str | None = None) 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_trajectory_shift(shift_df: 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) 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
- 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().
- 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, 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
- property scorer: SingleScorer | None¶
The internal SingleScorer used for embedding and scoring.
- trajectory_shift(results: Dict[str, CommitmentScoreResult], pseudotime_key: str = 'sccs_pseudotime', n_bootstrap: int = 500, seed: int = 42, verbose: bool = True) 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
- transfer_labels(results: Dict[str, 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_’.
MultiScorer¶
Multi-condition comparison (3+ conditions). Same shared-embedding approach as PairScorer, plus tiered statistical testing: omnibus tests followed by post-hoc pairwise comparisons, all-pairs delta-CS, and mixed-model contrasts.
- class scCS.MultiScorer(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]¶
Bases:
objectRNA velocity commitment scorer for experiments with 3+ conditions.
Builds a SHARED star embedding on the pooled data from all conditions, then scores each condition separately. This ensures arm geometry is identical across conditions, making CS values directly comparable.
Provides tiered statistical testing: - Tier 2: Omnibus tests (Kruskal-Wallis / ANOVA) followed by
post-hoc pairwise comparisons (Dunn / Tukey / Conover).
Tier 3: Mixed-effects models with custom contrasts, trajectory shift analysis.
- Parameters:
adata (AnnData) – Full single-cell dataset containing all 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 at least 3 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 than 3 unique values. For 2 conditions, use PairScorer instead.
Examples
>>> mscorer = MultiScorer( ... adata, ... root='17', ... branches=['homeostatic', 'activated'], ... condition_obs_key='treatment', ... obs_key='leiden', ... ) >>> mscorer.build_embedding(ordering_metric='pseudotime') >>> mscorer.fit() >>> results = mscorer.score_all_conditions() >>> omnibus = mscorer.compare_omnibus(results) >>> posthoc = mscorer.compare_posthoc(results, omnibus_results=omnibus)
- property adata_sub¶
The embedding subset (from the internal SingleScorer).
- build_embedding(ordering_metric: str | 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) MultiScorer[source]¶
Build the shared star embedding on pooled data from all conditions.
The embedding is built on ALL cells (all 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
- compare_omnibus(results: Dict[str, CommitmentScoreResult], test: Literal['kruskal', 'anova'] = 'kruskal', pval_threshold: float = 0.05, verbose: bool = True) DataFrame[source]¶
Omnibus test across all conditions per fate.
For each fate arm, tests whether per-cell affinity scores differ across ALL conditions simultaneously.
‘kruskal’: Kruskal-Wallis H test (non-parametric, recommended default)
‘anova’: One-way ANOVA (parametric, assumes normality)
- Parameters:
results (dict) – Output of score_all_conditions() with cell_level=True.
test ({'kruskal', 'anova'}) – Statistical test to use. Default: ‘kruskal’.
pval_threshold (float) – Significance threshold for flagging. Default 0.05.
verbose (bool)
- Returns:
fate, test, statistic, pval, pval_adj, significant, n_conditions
- Return type:
pd.DataFrame with columns
- compare_posthoc(results: Dict[str, CommitmentScoreResult], omnibus_results: DataFrame | None = None, method: Literal['dunn', 'tukey', 'conover'] = 'dunn', pval_correction: Literal['fdr', 'bonferroni', 'holm'] = 'fdr', pval_threshold: float = 0.05, verbose: bool = True) DataFrame[source]¶
Post-hoc pairwise comparisons across conditions per fate.
Only meaningful after an omnibus test rejects H0. If omnibus_results is provided, post-hoc is only run for fates where omnibus p < threshold.
- - 'dunn': Dunn's test with rank-based comparisons (non-parametric,
recommended with Kruskal-Wallis). Uses scikit-posthocs.
- - 'tukey': Tukey HSD (parametric, for balanced designs, with ANOVA).
- - 'conover': Conover-Iman test (more powerful than Dunn, non-parametric).
Uses scikit-posthocs.
- Multiple testing correction applied across all pairwise comparisons
- within each fate arm.
- Parameters:
results (dict) – Output of score_all_conditions() with cell_level=True.
omnibus_results (pd.DataFrame, optional) – Output of compare_omnibus(). If provided, post-hoc is only run for fates where omnibus pval_adj < pval_threshold.
method ({'dunn', 'tukey', 'conover'}) – Post-hoc test method. Default: ‘dunn’.
pval_correction ({'fdr', 'bonferroni', 'holm'}) – Multiple testing correction method. Default: ‘fdr’.
pval_threshold (float) – Significance threshold. Default 0.05.
verbose (bool)
- Returns:
fate, comparison, method, statistic, pval, pval_adj, significant, mean_A, mean_B, delta_mean
- Return type:
pd.DataFrame with columns
- compute_pairwise_deltas(n_bootstrap: int = 500, ci: float = 0.95, seed: int = 42, verbose: bool = True) Dict[Tuple[str, str], Dict][source]¶
Compute ΔCS for ALL condition pairs with bootstrap CI.
Unlike PairScorer.compute_delta_CS() which takes two specific conditions, this computes delta for every pair in the condition set.
- Parameters:
n_bootstrap (int) – Number of bootstrap replicates. Default 500.
ci (float) – Confidence interval level. Default 0.95.
seed (int)
verbose (bool)
- Returns:
(same structure as PairScorer.compute_delta_CS() output).
- Return type:
dict mapping (cond_a, cond_b) -> delta_result dict
- fit(verbose: bool = True) MultiScorer[source]¶
Fit the shared FateMap and project velocity.
Must be called after build_embedding().
- Return type:
self
- fit_mixed_model(results: Dict[str, CommitmentScoreResult], replicate_key: str | None = None, ref_condition: str | None = None, verbose: bool = True) 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
- fit_mixed_model_contrasts(results: Dict[str, CommitmentScoreResult], contrasts: List[Tuple[str, str]] | None = None, replicate_key: str | None = None, ref_condition: str | None = None, pval_threshold: float = 0.05, verbose: bool = True) DataFrame[source]¶
Linear mixed-effects model with custom condition contrasts.
Extends fit_mixed_model() to test specific condition comparisons within the LMM framework (more powerful than separate models).
If contrasts is None, tests each condition vs ref_condition. If contrasts is provided, tests each specified pair, e.g.:
[(‘drug_A’, ‘control’), (‘drug_B’, ‘control’), (‘drug_A’, ‘drug_B’)]
Uses statsmodels MixedLM with Wald tests on contrast coefficients.
- Parameters:
results (dict) – Output of score_all_conditions(cell_level=True).
contrasts (list of (str, str), optional) – Pairs of conditions to compare. If None, all conditions vs ref_condition are tested.
replicate_key (str, optional) – Column in adata_sub.obs with sample/replicate IDs.
ref_condition (str, optional) – Reference condition. Required when contrasts is None.
pval_threshold (float) – Significance threshold. Default 0.05.
verbose (bool)
- Returns:
fate, contrast, coef, std_err, z_score, pval, pval_adj, significant
- Return type:
pd.DataFrame with columns
- property is_fitted: bool¶
- plot_affinity_distributions(results: Dict[str, 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) Figure[source]¶
Violin/box plots of per-cell fate affinity scores by condition.
- plot_commitment_vector_radar(results: Dict[str, CommitmentScoreResult], **kwargs) Figure[source]¶
Radar / spider chart of commitment vectors per condition.
- plot_compare_conditions_bar(results: Dict[str, CommitmentScoreResult], **kwargs) Figure[source]¶
Grouped bar chart of nCS per condition.
- plot_delta_cs_heatmap(delta_result: dict, **kwargs) Figure[source]¶
Heatmap of ΔCS = nCS_A − nCS_B with CI annotation.
- plot_omnibus_summary(omnibus_df: DataFrame, results: Dict[str, CommitmentScoreResult], posthoc_df: DataFrame | None = None, figsize: Tuple[float, float] | None = None, save_path: str | None = None, vmin: float | None = None, vmax: float | None = None) Figure[source]¶
Summary heatmap: fates × conditions showing omnibus significance.
Left panel: heatmap of mean per-cell affinity per fate per condition, annotated with omnibus p-value stars. Right panel (if posthoc provided): significant pairwise comparisons.
- Parameters:
omnibus_df (pd.DataFrame) – Output of compare_omnibus().
results (dict) – Output of score_all_conditions().
posthoc_df (pd.DataFrame, optional) – Output of compare_posthoc().
figsize (tuple, optional)
save_path (str, optional)
vmin (float, optional) – Color limits for the mean-affinity heatmap. If both are
None(default), they are derived from the finite values of the affinity matrix so the colormap spans the actual data range. Set explicitly to pin a fixed scale across figures.vmax (float, optional) – Color limits for the mean-affinity heatmap. If both are
None(default), they are derived from the finite values of the affinity matrix so the colormap spans the actual data range. Set explicitly to pin a fixed scale across figures.
- Returns:
fig
- Return type:
matplotlib Figure
- plot_pairwise_delta_grid(delta_results: Dict[Tuple[str, str], Dict], figsize_per_panel: Tuple[float, float] = (4, 4), cmap: str = 'RdBu_r', save_path: str | None = None) Figure[source]¶
Grid of ΔCS heatmaps for all condition pairs.
Each panel shows ΔnCS = nCS_A − nCS_B for one condition pair, with bootstrap CI half-width annotated below each entry. Inherits the same layout as
scCS.plot.plot_delta_cs_heatmap()but renders all pairs on a single shared figure.- Parameters:
delta_results (dict) – Output of compute_pairwise_deltas().
figsize_per_panel (tuple)
cmap (str) – Diverging colormap. Default ‘RdBu_r’.
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- plot_posthoc_heatmap(posthoc_df: DataFrame, fate: str | None = None, figsize: Tuple[float, float] | None = None, save_path: str | None = None) Figure[source]¶
Condition × condition heatmap of post-hoc p-values for a given fate.
Lower triangle: p-values. Upper triangle: delta mean affinity. Annotated with significance stars.
- Parameters:
posthoc_df (pd.DataFrame) – Output of compare_posthoc().
fate (str, optional) – Which fate to plot. If None, uses the first fate with significant results.
figsize (tuple, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- plot_rose_grid(results: Dict[str, 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) Figure[source]¶
Grid of polar rose plots — one per condition.
- plot_star(result: CommitmentScoreResult, **kwargs)[source]¶
Radial star embedding plot.
- plot_star_grid(results: Dict[str, CommitmentScoreResult], color_map: Dict[str, str] | None = None, figsize_per_panel: Tuple[float, float] = (6, 6), save_path: str | None = None) Figure[source]¶
Side-by-side star embedding plots, one per condition.
- plot_trajectory_shift(shift_df: 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) Figure[source]¶
Visualize pseudotime distributions per condition per fate arm.
Produces a grid of KDE plots: one row per fate arm, one column per pairwise comparison. 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
- 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) MultiScorer[source]¶
Rebuild the shared embedding using subset-local pseudotime.
See SingleScorer.refit_pseudotime().
- 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, 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
- property scorer: SingleScorer | None¶
The internal SingleScorer used for embedding and scoring.
- trajectory_shift(results: Dict[str, CommitmentScoreResult], pseudotime_key: str = 'sccs_pseudotime', n_bootstrap: int = 500, seed: int = 42, verbose: bool = True) DataFrame[source]¶
Test whether pseudotime distributions differ across conditions per fate arm.
For each fate arm and each pair of conditions, 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
- transfer_labels(results: Dict[str, 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_’.
CommitmentScoreResult¶
Dataclass returned by scorer.score(). Contains all computed scores,
matrices, and metadata.
- class scCS.CommitmentScoreResult(fate_names: List[str], M_bin: ndarray, bin_edges: ndarray, sectors: List[List[int]], M_sector: ndarray, n_cells_per_fate: ndarray, commitment_vector: ndarray, population_entropy: float, mean_cell_entropy: float, per_fate_entropy: ndarray, pairwise_unCS: ndarray, pairwise_nCS: ndarray, cell_scores: ndarray | None = None, fate_angles: ndarray | None = None, cell_obs_names: ndarray | None = None, nn_cell_entropy: ndarray | None = None, nn_k: int | None = None, bootstrap_ci: Dict[str, Any] | None = None)[source]¶
Bases:
objectContainer for all commitment score outputs.
- fate_names¶
- Type:
list of str
- M_bin¶
Cumulative magnitude per angular bin.
- Type:
np.ndarray, shape (n_bins,)
- bin_edges¶
- Type:
np.ndarray, shape (n_bins + 1,)
- sectors¶
- Type:
list of k lists of bin indices
- population_entropy¶
Normalized Shannon entropy of the aggregate commitment vector in [0, 1]. Single scalar. See
compute_population_entropy().- Type:
float
- mean_cell_entropy¶
Mean normalized per-cell Shannon entropy in [0, 1]. See
compute_mean_cell_entropy(). NaN when cell_level=False.- Type:
float
- per_fate_entropy¶
Mean binary cell entropy for each fate individually. per_fate_entropy[j] = mean over cells of H_bin(s_ij, 1-s_ij). See
compute_per_fate_cell_entropy(). All-NaN array when cell_level=False.- Type:
np.ndarray, shape (k,)
- fate_angles¶
Angle (degrees) of each fate axis in the radial embedding.
- Type:
np.ndarray, shape (k,), optional
- nn_cell_entropy¶
NN-smoothed per-cell entropy. Set when k_nn > 0 in score(). Also written to adata_sub.obs[‘cs_nn_entropy’].
- Type:
np.ndarray, shape (n_cells,), optional
- nn_k¶
The k_nn value used to compute nn_cell_entropy.
- Type:
int, optional
- dominant_fate¶
Fate with highest M_sector.
- Type:
str
- M_bin: ndarray¶
- M_sector: ndarray¶
- bin_edges: ndarray¶
- bootstrap_ci: Dict[str, Any] | None = None¶
- cell_obs_names: ndarray | None = None¶
- cell_scores: ndarray | None = None¶
- property commitment_entropy: float¶
Alias for
population_entropy(deprecated, usemean_cell_entropy).
- commitment_vector: ndarray¶
- property dominant_fate: str¶
- fate_angles: ndarray | None = None¶
- fate_names: List[str]¶
- property k: int¶
- mean_cell_entropy: float¶
- n_cells_per_fate: ndarray¶
- nn_cell_entropy: ndarray | None = None¶
- nn_k: int | None = None¶
- pairwise_nCS: ndarray¶
- pairwise_to_dataframe(normalized: bool = True) DataFrame[source]¶
Pairwise CS matrix as a labeled DataFrame.
- pairwise_unCS: ndarray¶
- per_fate_entropy: ndarray¶
- population_entropy: float¶
- sectors: List[List[int]]¶
FateMap¶
Stores the fate topology: which cells belong to which arm, centroids, and arm angles.
- class scCS.FateMap(root: str, fate_names: List[str], fate_centroids: ndarray, root_centroid: ndarray, root_cells: ndarray, fate_cell_indices: List[ndarray], arm_angles_deg: ndarray, obs_key: str)[source]¶
Bases:
objectStandardized description of k cell fates for commitment scoring.
- root¶
Label of the progenitor/root cluster supplied by the user.
- Type:
str
- fate_names¶
Human-readable labels for each terminal fate (length k).
- Type:
list of str
- fate_centroids¶
Mean 2D position of each fate’s cells in the scCS embedding.
- Type:
np.ndarray, shape (k, 2)
- root_centroid¶
Mean 2D position of the bifurcation cluster cells. In the scCS star embedding this is always near (0, 0).
- Type:
np.ndarray, shape (2,)
- root_cells¶
Indices of bifurcation cluster cells in adata.
- Type:
np.ndarray of int
- fate_cell_indices¶
Per-fate arrays of cell indices.
- Type:
list of np.ndarray
- arm_angles_deg¶
Angle (degrees) of each fate’s radial arm in the star embedding.
- Type:
np.ndarray, shape (k,)
- obs_key¶
The obs column used for cluster labels.
- Type:
str
- k¶
Number of fates (read-only property).
- Type:
int
- arm_angles_deg: ndarray¶
- fate_cell_indices: List[ndarray]¶
- fate_centroids: ndarray¶
- fate_names: List[str]¶
- property k: int¶
- obs_key: str¶
- root: str¶
- root_cells: ndarray¶
- root_centroid: ndarray¶
Plotting Functions¶
All plotting functions accept a color_map dict (fate name → hex color)
to preserve your original scanpy/Seurat cluster colors.
Single-condition plots:
- scCS.plot_star_embedding(adata, result: CommitmentScoreResult, color_by: str = 'fate', figsize: Tuple[float, float] = (8, 8), point_size: float = 8.0, alpha: float = 0.75, arm_color: str = '#CCCCCC', arm_linewidth: float = 1.5, arm_linestyle: str = '--', show_arm_labels: bool = True, show_velocity: bool = False, velocity_scale: float = 1.0, color_map: Dict[str, str] | None = None, title: str | None = None, vmin: float | None = None, vmax: float | None = None, cmap: str | None = None, ax: Axes | None = None, save_path: str | None = None) Figure[source]¶
Radial star embedding plot — the primary scCS visualization.
Draws the X_sccs embedding with: - Radial arm axes (dashed lines from origin to each fate tip) - Fate labels at the arm tips - Cells colored by fate, pseudotime, entropy, or per-fate affinity - Optional velocity arrows
- Parameters:
adata (AnnData) – Must have X_sccs in obsm.
result (CommitmentScoreResult)
color_by (str) –
What to color cells by: -
"fate"— cluster/arm assignment (default) -"pseudotime"— readssccs_pseudotimethenvelocity_pseudotime-"entropy"— per-cell commitment entropy (cs_entropy) -"nn_entropy"— NN-smoothed entropy (cs_nn_entropy;requires
score(k_nn=...))a fate name — per-cell affinity (
cs_{fate}; requiresscore(cell_level=True))any other str — auto-detected numeric or categorical column in
adata.obs
figsize (tuple)
point_size (float)
alpha (float)
arm_color (str) – Color of the radial arm guide lines.
arm_linewidth (float)
arm_linestyle (str)
show_arm_labels (bool) – Draw fate name labels at arm tips.
show_velocity (bool) – Overlay velocity arrows (requires velocity_sccs in obsm).
velocity_scale (float) – Scale factor for velocity arrows.
title (str, optional)
vmin (float, optional) – Color-scale limits for numeric
color_bymodes. Defaults to the finite data range, so structure is always visible regardless of the absolute entropy/affinity scale. Pass explicit values to pin limits for cross-figure comparison.vmax (float, optional) – Color-scale limits for numeric
color_bymodes. Defaults to the finite data range, so structure is always visible regardless of the absolute entropy/affinity scale. Pass explicit values to pin limits for cross-figure comparison.cmap (str, optional) – Matplotlib colormap name. Defaults:
"RdYlBu_r"for entropy,"viridis"for pseudotime/generic numeric,"Blues"for per-fate affinity.ax (matplotlib Axes, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_star_panels(adata, result: CommitmentScoreResult, panels: List[str] | None = None, figsize_per_panel: Tuple[float, float] = (6, 6), point_size: float = 6.0, alpha: float = 0.75, color_map: Dict[str, str] | None = None, save_path: str | None = None) Figure[source]¶
Multi-panel star embedding: one panel per coloring scheme.
Default panels: fate assignment, pseudotime, entropy, + one per fate.
- Parameters:
adata (AnnData)
result (CommitmentScoreResult)
panels (list of str, optional) – List of color_by values. Defaults to [‘fate’, ‘pseudotime’, ‘entropy’] + fate_names.
figsize_per_panel (tuple)
point_size (float)
alpha (float)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_commitment_bar(result: CommitmentScoreResult, ref_fate: str | None = None, mode: str = 'auto', color_map: Dict[str, str] | None = None, title: str | None = None, figsize: Tuple[float, float] | None = None, save_path: str | None = None) Figure[source]¶
Bar chart of unCS and nCS for all k populations.
For a k-furcation, produces k subplots — one per reference fate. Each subplot shows unCS (solid) and nCS (hatched) for all other k-1 fates relative to that reference. This way every population is shown as both a query and a reference, and nothing is hidden.
For k=2 a single subplot is produced (equivalent to the old behaviour).
- Parameters:
result (CommitmentScoreResult)
ref_fate (str, optional) – If given, produce only a single subplot using this fate as reference. Useful when you want a focused comparison.
mode (str) – Kept for backward compatibility; ignored.
color_map (dict, optional) – Mapping of fate name → hex color.
title (str, optional) – Overall figure title.
figsize (tuple, optional) – Per-subplot size
(w, h). Total figure width scales with k.save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_expression_trends(adata, result: CommitmentScoreResult, genes: List[str], fate: str | None = None, x_axis: str = 'affinity', n_bins: int = 10, layer: str | None = None, smooth: bool = True, smooth_frac: float = 0.4, color_map: Dict[str, str] | None = None, figsize: Tuple[float, float] | None = None, ncols: int = 3, save_path: str | None = None) Figure[source]¶
Plot gene expression trends along a chosen commitment axis.
Cells are binned along the x-axis and mean expression per bin is plotted with a LOWESS smooth.
- Parameters:
adata (AnnData) – Must contain the same cells as
result.result (CommitmentScoreResult)
genes (list of str) – Gene names to plot. Must be present in
adata.var_names.fate (str, optional) – Which fate to use as the reference. Defaults to the fate with the highest M_sector.
x_axis (str) –
What to use as the x-axis for binning: -
'affinity': per-cell fate affinity score forfate(0 → 1, from compute_cell_scores).
'pseudotime'velocity_pseudotime from adata.obs(or sccs_pseudotime if available via compute_local_pseudotime()).
'radial_distance': Euclidean distance from origin in X_sccs(arm position, 0 = progenitor, arm_scale = tip).
Default:
'affinity'.n_bins (int) – Number of bins along the x-axis.
layer (str, optional) – AnnData layer to use for expression. Defaults to
adata.X.smooth (bool) – Whether to overlay a LOWESS smoothed curve.
smooth_frac (float) – LOWESS smoothing fraction (0–1).
color_map (dict, optional) – Fate name → hex color. Used to color the smoothed line.
figsize (tuple, optional)
ncols (int) – Number of columns in the subplot grid.
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_rose(result: CommitmentScoreResult, title: str = 'Cumulative Velocity Magnitude by Direction', figsize: Tuple[float, float] = (7, 7), show_sectors: bool = True, color_map: Dict[str, str] | None = None, ax: Axes | None = None, save_path: str | None = None) Figure[source]¶
Polar rose plot of cumulative velocity magnitudes per angular bin.
Each bin shows the total velocity magnitude pointing in that direction. Fate sectors are shaded with distinct colors.
- Parameters:
result (CommitmentScoreResult)
title (str)
figsize (tuple)
show_sectors (bool)
ax (matplotlib Axes (polar), optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_pairwise_cs(result: CommitmentScoreResult, normalized: bool = True, title: str | None = None, figsize: Tuple[float, float] | None = None, cmap: str = 'RdBu_r', save_path: str | None = None) Figure[source]¶
Heatmap of pairwise commitment scores.
Entry [i, j] = CS(fate_i relative to fate_j). Values > 1 indicate stronger commitment to fate_i than fate_j. Color scale is log2-transformed for readability.
- Parameters:
result (CommitmentScoreResult)
normalized (bool) – Use nCS (True) or unCS (False).
title (str, optional)
figsize (tuple, optional)
cmap (str)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_commitment_heatmap(result: CommitmentScoreResult, cell_scores: ndarray | None = None, max_cells: int = 500, title: str = 'Per-Cell Fate Affinity', figsize: Tuple[float, float] | None = None, save_path: str | None = None) Figure[source]¶
Heatmap of per-cell fate affinity scores (cells × fates).
- Parameters:
result (CommitmentScoreResult)
cell_scores (np.ndarray, shape (n_cells, k), optional) – If None, uses result.cell_scores.
max_cells (int) – Subsample to this many cells for readability.
title (str)
figsize (tuple, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_subset_comparison(subset_results: dict, ref_fate: str | None = None, normalized: bool = True, title: str = 'Commitment Score by Subset', figsize: Tuple[float, float] = (8, 4), save_path: str | None = None) Figure[source]¶
Compare commitment scores across multiple subsets.
Subsets whose chosen reference pair yields
inf(e.g. progenitor-only subsets with no fate-arm cells, sopairwise_nCSis undefined) are rendered as gray hatched placeholders at zero height with an"inf"annotation, instead of silently producing empty bars.- Parameters:
subset_results (dict) – Mapping of subset_name -> CommitmentScoreResult (from
SingleScorer.score_per_subset).ref_fate (str, optional) – Reference fate for the CS column. If None, use the fate with smallest sector magnitude (most likely to be present in all subsets).
normalized (bool) – If True use
pairwise_nCS, elsepairwise_unCS.title (str)
figsize (tuple)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_nn_entropy_elbow(scorer, k_nn_range: List[int] | range = range(5, 51, 5), color_map: Dict[str, str] | None = None, figsize: Tuple[float, float] = (12, 5), title: str | None = None, save_path: str | None = None) Figure[source]¶
Elbow plots for choosing the optimal number of nearest neighbors (k_nn).
Sweeps over
k_nn_range, computing NN-smoothed cell entropy at each k, and produces two side-by-side subplots:Left: mean NN entropy across all cells vs k_nn.
Right: mean NN entropy per fate arm vs k_nn (one line per fate).
Use these plots to identify the elbow — the k_nn where entropy stabilizes, indicating that additional smoothing no longer changes the signal.
- Parameters:
scorer (SingleScorer) – A fitted scorer with
build_embedding()andfit()already called. No priorscore()call is needed — cell scores are recomputed internally from the velocity vectors.k_nn_range (list or range) – k_nn values to sweep. Default: 5, 10, 15, …, 50.
color_map (dict, optional) – Fate name -> hex color. Falls back to the default FATE_PALETTE.
figsize (tuple)
title (str, optional) – Overall figure title. Defaults to “NN Entropy Elbow”.
save_path (str, optional) – If provided, save figure to this path.
- Returns:
fig
- Return type:
matplotlib Figure
Examples
>>> scorer.build_embedding(differentiation_metric='pseudotime') >>> scorer.fit() >>> result = scorer.score(compute_cell_level=True) >>> fig = scorer.plot_nn_entropy_elbow()
Multi-condition plots (PairScorer + MultiScorer):
- scCS.plot_rose_grid(results: Dict[str, 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) Figure[source]¶
Grid of polar rose plots — one per condition.
All panels share the same radial scale (max of all M_bin.max() across conditions), making magnitudes directly comparable. Fate sectors are shaded with FATE_PALETTE colors (consistent with single-condition plot_rose).
- Parameters:
results (dict) – Mapping of condition_label -> CommitmentScoreResult (output of PairScorer.score_all_conditions()).
color_map (dict, optional) – fate_name -> hex color. Falls back to FATE_PALETTE.
figsize_per_panel (tuple) – Size of each polar subplot.
title (str, optional) – Overall figure title.
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_delta_cs_heatmap(delta_result: dict, title: str | None = None, figsize: Tuple[float, float] | None = None, cmap: str = 'RdBu_r', save_path: str | None = None) Figure[source]¶
Heatmap of ΔCS = nCS_A − nCS_B with CI annotation.
Entry [i, j] = nCS_A(i÷j) − nCS_B(i÷j). Positive values (red) mean condition A has stronger commitment of fate i relative to fate j. Cells are annotated with Δ ± CI_half.
- Parameters:
delta_result (dict) – Output of PairScorer.compute_delta_CS().
title (str, optional)
figsize (tuple, optional)
cmap (str) – Diverging colormap. Default: ‘RdBu_r’.
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_compare_conditions_bar(results: Dict[str, CommitmentScoreResult], ref_fate: str | None = None, color_map: Dict[str, str] | None = None, title: str | None = None, figsize: Tuple[float, float] | None = None, save_path: str | None = None) Figure[source]¶
Grouped bar chart of nCS per condition.
For each fate pair (query ÷ reference), one group of bars — one bar per condition, colored by CONDITION_PALETTE. A horizontal dashed line at CS = 1 marks the neutral point.
- Parameters:
results (dict) – Mapping of condition_label -> CommitmentScoreResult (output of PairScorer.score_all_conditions()).
ref_fate (str, optional) – Reference fate for the denominator. If None, uses the fate with the lowest mean M_sector across conditions.
color_map (dict, optional) – condition_label -> hex color. Falls back to CONDITION_PALETTE.
title (str, optional)
figsize (tuple, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_commitment_vector_radar(results: Dict[str, CommitmentScoreResult], color_map: Dict[str, str] | None = None, title: str | None = None, figsize: Tuple[float, float] = (6, 6), save_path: str | None = None) Figure[source]¶
Radar / spider chart of commitment vectors per condition.
Each condition is one closed polygon. Axes = fate names (k spokes). Values = commitment_vector (sums to 1). Conditions colored by CONDITION_PALETTE.
For k < 3, falls back to a grouped bar chart with a warning.
- Parameters:
results (dict) – Mapping of condition_label -> CommitmentScoreResult (output of PairScorer.score_all_conditions()).
color_map (dict, optional) – condition_label -> hex color. Falls back to CONDITION_PALETTE.
title (str, optional)
figsize (tuple)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
MultiScorer-specific plots:
- scCS.plot_omnibus_summary(omnibus_df, results: Dict[str, CommitmentScoreResult], posthoc_df=None, figsize: Tuple[float, float] | None = None, save_path: str | None = None) Figure[source]¶
Summary heatmap: fates × conditions showing omnibus significance.
Left panel: heatmap of mean per-cell affinity per fate per condition, annotated with omnibus p-value stars. Right panel (if posthoc_df provided): significant pairwise comparisons as a connectivity grid.
- Parameters:
omnibus_df (pd.DataFrame) – Output of MultiScorer.compare_omnibus(). Columns: fate, test, statistic, pval, pval_adj, significant.
results (dict) – Mapping of condition_label -> CommitmentScoreResult (output of MultiScorer.score_all_conditions()).
posthoc_df (pd.DataFrame, optional) – Output of MultiScorer.compare_posthoc(). If provided, right panel shows post-hoc significance grid.
figsize (tuple, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_posthoc_heatmap(posthoc_df, fate: str | None = None, figsize: Tuple[float, float] | None = None, save_path: str | None = None) Figure[source]¶
Condition × condition heatmap of post-hoc p-values for a given fate.
Lower triangle: p-values (color intensity). Upper triangle: delta mean affinity. Annotated with significance stars.
- Parameters:
posthoc_df (pd.DataFrame) –
Output of MultiScorer.compare_posthoc(). Columns: fate, comparison, method, statistic, pval, pval_adj,
significant, mean_A, mean_B, delta_mean.
fate (str, optional) – Which fate to plot. If None, uses the first fate in posthoc_df.
figsize (tuple, optional)
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
- scCS.plot_pairwise_delta_grid(delta_results: Dict[Tuple[str, str], dict], figsize_per_panel: Tuple[float, float] = (4, 4), save_path: str | None = None) Figure[source]¶
Grid of ΔCS heatmaps for all condition pairs.
Each panel shows the ΔnCS heatmap for one condition pair, using the same layout as plot_delta_cs_heatmap().
- Parameters:
delta_results (dict) – Output of MultiScorer.compute_pairwise_deltas(). Mapping of (cond_a, cond_b) -> delta_result dict.
figsize_per_panel (tuple) – Size of each subplot.
save_path (str, optional)
- Returns:
fig
- Return type:
matplotlib Figure
Embedding¶
- scCS.build_star_embedding(adata, root: str, branches: List[str], obs_key: str = 'leiden', ordering_metric: str | ndarray = 'pseudotime', invert_ordering: bool = False, arm_scale: float = 10.0, jitter: float = 0.3, seed: int = 42, arm_norm: str = 'global') AnnData[source]¶
Build the radial star embedding on a subset of adata.
Only cells belonging to the bifurcation cluster or a terminal fate cluster are included. All other populations are excluded entirely.
- Parameters:
adata (AnnData) – Full dataset. Will NOT be modified.
root (str) – Label of the progenitor/bifurcation cluster in adata.obs[obs_key]. These cells are placed at the origin.
branches (list of str) – Labels of the k terminal fate populations. Each gets one radial arm.
obs_key (str) – Column in adata.obs with cluster labels.
ordering_metric (str or np.ndarray) – How to order cells along each arm: - ‘pseudotime’ : uses adata.obs[‘velocity_pseudotime’] (computed if absent) - ‘cytotrace’ : uses adata.obs[‘cytotrace2_score’] (must be pre-computed) - any str : uses adata.obs[ordering_metric] directly - np.ndarray : per-cell scores, shape (n_cells,) for the FULL adata Higher value = more differentiated = farther from center.
invert_ordering (bool) – If True, invert the metric so that high values map to the center (use for metrics where high = less differentiated, e.g. raw CytoTRACE2).
arm_scale (float) – Maximum radial distance (length of each arm).
jitter (float) – Gaussian noise added perpendicular to each arm to avoid overplotting.
seed (int) – Random seed for jitter.
arm_norm ({"global", "per_arm"}, default "global") –
How to normalize the ordering metric onto the radial arms. The rescale formula
(s - s_min) / (s_max - s_min) * arm_scaleis only applied to fate cells (bifurcation cells sit at the origin);s_minands_maxare computed from fate cells only in both modes since v0.7.4, so the closest fate cell always maps tor ≈ 0and the furthest tor ≈ arm_scale."global"(default, v0.7.3+): compute one(s_min, s_max) = (fate_scores.min(), fate_scores.max())over all fate cells and apply uniformly to every arm. Arms whose cells span shorter pseudotime intervals stay visibly shorter. Preserves the relative ordering of cells across arms — if Alpha cells span a wider pseudotime range than Delta cells, the Alpha arm extends further. Biologically meaningful: arm length reflects how far each fate has differentiated from the progenitor on a shared scale."per_arm"(legacy, pre-v0.7.3 default): each arm gets its own(s_min, s_max) = (fate_mask_scores.min(), fate_mask_scores.max())and is mapped to[0, arm_scale]independently. All arms reach the fullarm_scaleregardless of how compressed/extended their pseudotime range is. Provided for reproducibility of older plots.
Changed in version 0.7.4: Both modes now compute
s_min/s_maxfrom fate cells only, instead of including bifurcation cells. This removes a visible gap between the origin and the start of each arm in v0.7.3"global"mode.
- Returns:
adata_sub – Subset containing ONLY bifurcation + terminal fate cells. Star embedding stored in adata_sub.obsm[‘X_sccs’]. Metadata stored in adata_sub.uns[‘sccs’].
- Return type:
AnnData
- scCS.compute_local_pseudotime(adata_sub, adata_full, scale_01: bool = True, verbose: bool = True) ndarray[source]¶
Recompute velocity pseudotime on the subset’s induced subgraph.
When
build_star_embeddingusesordering_metric='pseudotime', the pseudotime is resolved on the full adata before subsetting. This means the pseudotime range within the bifurcation+fate subset is compressed and non-uniform: cells that span the full differentiation axis in the subset may all cluster near 0 or 1 on the arm, leaving large empty stretches.This function extracts the velocity_graph submatrix for the subset cells, recomputes pseudotime locally, and optionally scales it to [0, 1]. The result is stored in
adata_sub.obs['sccs_pseudotime']and returned as an array.Call this after
build_embedding()and before (or instead of) using the full-adata pseudotime for arm ordering. To rebuild the embedding with the corrected pseudotime, pass the returned array as a custom metric:scorer.build_embedding(ordering_metric='pseudotime') pt_sub = compute_local_pseudotime(scorer.adata_sub, adata) scorer.build_embedding(ordering_metric=pt_sub_full) # where pt_sub_full is the subset scores mapped back to full adata indices
Alternatively, use the convenience method
SingleScorer.refit_pseudotime().- Parameters:
adata_sub (AnnData) – Subset returned by
build_star_embedding(). Must haveuns['sccs']['parent_indices']set (done automatically).adata_full (AnnData) – Full dataset with intact
uns['velocity_graph'].scale_01 (bool) – If True (default), min-max scale the recomputed pseudotime to [0, 1] within the subset. This ensures cells span the full arm length regardless of where the subset sits in the global pseudotime range. If False, the raw pseudotime values are returned (useful when you want to compare absolute pseudotime across conditions).
verbose (bool)
- Returns:
pt_sub – Subset-local pseudotime, stored in
adata_sub.obs['sccs_pseudotime'].- Return type:
np.ndarray, shape (n_sub_cells,)
- scCS.scale_metric_01(scores: ndarray) ndarray[source]¶
Min-max scale a per-cell metric to [0, 1].
Useful for normalizing any differentiation metric (pseudotime, CytoTRACE2, pathway score, etc.) before passing it to
build_star_embeddingso that cells span the full arm length uniformly.- Parameters:
scores (np.ndarray, shape (n_cells,)) – Per-cell metric values. NaN values are preserved.
- Returns:
scaled – Values in [0, 1]. Returns zeros if all values are identical.
- Return type:
np.ndarray, shape (n_cells,)
Fate Detection¶
- scCS.build_fate_map(adata, root: str, branches: List[str], obs_key: str = 'leiden', verbose: bool = True) FateMap[source]¶
Build a FateMap from user-supplied cluster labels.
This is the only fate-detection strategy in scCS. The user explicitly names the bifurcation cluster and all terminal fate clusters.
- Parameters:
adata (AnnData) – Must have X_sccs in obsm (built by build_star_embedding).
root (str) – Label of the progenitor cluster in adata.obs[obs_key]. Example: ‘17’ (leiden cluster 17)
branches (list of str) – Labels of the k terminal fate clusters. Example: [‘Monocyte’, ‘DC’, ‘Neutrophil’]
obs_key (str) – Column in adata.obs with cluster labels.
verbose (bool)
- Return type:
Driver Genes¶
Three complementary approaches to identify fate-driving genes:
- scCS.get_velocity_drivers(adata_sub, fate_names: List[str], obs_key: str, root: str, n_top_genes: int = 50) Dict[str, DataFrame][source]¶
Rank genes by mean scVelo velocity in each fate arm’s cells.
- Parameters:
adata_sub (AnnData) – Subset containing only bifurcation + terminal fate cells. Must have the ‘velocity’ layer (from scVelo).
fate_names (list of str) – Terminal fate cluster labels.
obs_key (str) – Column in adata_sub.obs with cluster labels.
root (str) – Label of the progenitor cluster (used for context only).
n_top_genes (int) – Number of top driver genes to print per fate.
- Returns:
dict – Sorted by mean_velocity descending (most upregulated first).
- Return type:
fate_name -> DataFrame with columns [gene, mean_velocity, rank]
- scCS.get_deg_drivers(adata_sub, fate_names: List[str], obs_key: str, root: str, n_top_genes: int = 50, pval_threshold: float = 0.05, logfc_threshold: float = 0.25) Dict[str, DataFrame][source]¶
Find DEGs for each fate arm vs the bifurcation cluster (Wilcoxon).
For each fate arm, compares arm cells against progenitor (bifurcation) cells using a Wilcoxon rank-sum test via scanpy.
- Parameters:
adata_sub (AnnData) – Subset containing only bifurcation + terminal fate cells.
fate_names (list of str) – Terminal fate cluster labels.
obs_key (str) – Column in adata_sub.obs with cluster labels.
root (str) – Label of the progenitor cluster (reference group).
n_top_genes (int) – Number of top significant DEGs to print per fate.
pval_threshold (float) – Adjusted p-value threshold for significance.
logfc_threshold (float) – Minimum absolute log fold-change for significance.
- Returns:
dict – [gene, logfoldchange, pval, pval_adj, significant] Sorted by logfoldchange descending.
- Return type:
fate_name -> DataFrame with columns:
- scCS.get_velocity_fate_drivers(adata_sub, cell_scores: ndarray, fate_names: List[str], obs_key: str, root: str, n_top_genes: int = 50, pval_threshold: float = 0.05, min_cells: int = 10) Dict[str, DataFrame][source]¶
Identify driver genes by correlating gene velocity with fate affinity.
For each fate arm, computes the Spearman correlation between each gene’s velocity (from the ‘velocity’ layer) and the cell’s fate affinity score (from cell_scores[:, j]). Genes with high positive Spearman correlation are being upregulated specifically as cells commit to that fate — a stronger signal than mean velocity alone, because it filters out genes that are fast everywhere.
Algorithm¶
For each fate j, extract velocity matrix V (n_cells × n_genes).
Extract fate affinity vector a (n_cells,) = cell_scores[:, j].
Compute Spearman correlation between a and each gene’s velocity column.
Compute FDR-corrected p-values (Benjamini-Hochberg via statsmodels).
Return DataFrame sorted by spearman_r descending.
- param adata_sub:
Subset containing only bifurcation + terminal fate cells. Must have the ‘velocity’ layer (from scVelo).
- type adata_sub:
AnnData
- param cell_scores:
Per-cell fate affinity scores from CommitmentScoreResult.cell_scores.
- type cell_scores:
np.ndarray, shape (n_cells, k)
- param fate_names:
Terminal fate cluster labels (length k).
- type fate_names:
list of str
- param obs_key:
Column in adata_sub.obs with cluster labels.
- type obs_key:
str
- param root:
Label of the progenitor cluster.
- type root:
str
- param n_top_genes:
Number of top driver genes to print per fate.
- type n_top_genes:
int
- param pval_threshold:
FDR-adjusted p-value threshold for significance.
- type pval_threshold:
float
- param min_cells:
Minimum number of cells required to compute correlations.
- type min_cells:
int
- returns:
dict –
- [gene, spearman_r, pval, pval_adj, mean_velocity, delta_velocity,
significant]
Sorted by spearman_r descending.
- rtype:
fate_name -> DataFrame with columns:
Pathway Enrichment¶
- scCS.run_enrichment_per_fate(deg_drivers: Dict[str, DataFrame], fate_names: List[str] | None = None, gene_sets: List[str] | None = None, organism: str = 'mouse', pval_threshold: float = 0.05, logfc_threshold: float = 0.25, plot: bool = True, n_top_pathways: int = 15) Dict[str, Dict[str, DataFrame]][source]¶
Run Enrichr ORA on DEG driver genes for each fate arm.
Runs separately for up-regulated and down-regulated genes. Requires gseapy >= 1.0.
- Parameters:
deg_drivers (dict) – Output of get_deg_drivers(). fate_name -> DataFrame[gene, logfoldchange, pval, pval_adj, significant]
fate_names (list of str, optional) – Terminal fate cluster labels (determines iteration order). If omitted (default
None), the fate names are inferred fromdeg_drivers.keys()in their natural insertion order. If provided but missing entries that appear indeg_drivers, a warning is emitted and only the intersection is used.gene_sets (list of str, optional) – Enrichr gene set library names. Defaults to KEGG + GO BP + Reactome for the specified organism.
organism (str) – ‘mouse’ or ‘human’. Used for default gene sets and Enrichr organism.
pval_threshold (float) – Adjusted p-value threshold for reporting enriched terms.
logfc_threshold (float) – Minimum absolute logFC used to split up/down gene lists.
plot (bool) – If True, generate dot plots per fate per direction.
n_top_pathways (int) – Number of top enriched terms to show in dot plots.
- Returns:
dict – Each DataFrame has columns: [Gene_set, Term, Overlap, P-value, Adjusted P-value, Genes] Sorted by Adjusted P-value ascending. Empty DataFrame if no significant terms found.
- Return type:
fate_name -> {‘up’: DataFrame, ‘down’: DataFrame}
- scCS.export_enrichment_tables(enrichment_results: Dict[str, Dict[str, DataFrame]], output_dir: str = '.', prefix: str = 'enrichment') List[str][source]¶
Save enrichment result DataFrames to CSV files.
- Parameters:
enrichment_results (dict) – Output of run_enrichment_per_fate().
output_dir (str) – Directory to save files.
prefix (str) – Filename prefix.
- Returns:
list of str
- Return type:
paths of saved files.
Core Math — Entropy¶
- scCS.compute_population_entropy(p_vec: ndarray) float[source]¶
Shannon entropy of the aggregate commitment vector, normalized to [0, 1].
Operates on the population-level commitment vector
p_vec = M_sector / sum(M_sector), which reflects how total velocity mass is distributed across fate sectors.H_pop = 0 => all velocity mass concentrated in one sector. H_pop = 1 => velocity mass uniformly spread across all sectors.
Warning
This metric can be misleading when cells are split between fates. A population where 50 % of cells strongly commit to fate A and 50 % strongly commit to fate B will yield H_pop ≈ 1 (maximum uncertainty), even though every individual cell is decisive. Use
compute_mean_cell_entropy()as the primary commitment metric.- Parameters:
p_vec (np.ndarray, shape (k,)) – Normalized commitment vector (sums to 1).
- Return type:
float in [0, 1]
- scCS.compute_mean_cell_entropy(cell_scores: ndarray) float[source]¶
Mean per-cell Shannon entropy of fate-affinity scores, normalized to [0, 1].
For each cell i, computes the normalized Shannon entropy of its row-normalized fate-affinity vector
s_i = cell_scores[i, :]:h_i = -sum_j( s_ij * log(s_ij) ) / log(k)
and returns the mean over all cells:
H_cell = mean_i( h_i )
This is the recommended primary entropy metric because it measures individual cell commitment uncertainty rather than population-level velocity-mass balance.
Interpretation¶
- H_cell ≈ 0 => cells are individually decisive (each cell strongly
favors one fate). Occurs in committed populations regardless of whether cells split between fates.
- H_cell ≈ 1 => cells are individually undecided (each cell’s velocity
points equally toward all fates). Occurs in genuinely uncommitted / progenitor-like populations.
Contrast with
compute_population_entropy()¶A population split 50/50 between two strongly committed sub-groups gives: - H_pop ≈ 1.0 (misleadingly high — velocity mass is balanced) - H_cell ≈ 0.0 (correctly low — each cell is individually committed)
- param cell_scores:
Per-cell fate-affinity matrix, row-normalized to sum to 1. Typically the output of
compute_cell_scores().- type cell_scores:
np.ndarray, shape (n_cells, k)
- returns:
Mean normalized per-cell entropy. Returns 0.0 for a single cell or single fate.
- rtype:
float in [0, 1]
- scCS.compute_per_fate_cell_entropy(cell_scores: ndarray) ndarray[source]¶
Per-fate mean binary cell entropy of fate-affinity scores.
For each fate j, treats each cell’s affinity score
s_ijas a binary distribution[s_ij, 1 - s_ij]and computes the normalized binary Shannon entropy, then averages over all cells:h_j = mean_i[ H_bin(s_ij) ] = mean_i[ -(s_ij * log(s_ij) + (1-s_ij) * log(1-s_ij)) / log(2) ]
Interpretation¶
- h_j ≈ 0 => cells are sharply decisive about fate j (either strongly
committed or strongly not committed).
h_j ≈ 1 => cells are ambiguous about fate j (scores cluster near 0.5).
This is the per-fate analogue of
compute_mean_cell_entropy().- param cell_scores:
Per-cell fate-affinity matrix, row-normalized to sum to 1. Typically the output of
compute_cell_scores().- type cell_scores:
np.ndarray, shape (n_cells, k)
- returns:
per_fate_entropy – Mean binary entropy for each fate. Returns zeros for k=0 or n=0.
- rtype:
np.ndarray, shape (k,)
- scCS.compute_nn_cell_entropy(cell_scores: ndarray, coords: ndarray, k_nn: int) ndarray[source]¶
NN-smoothed per-cell commitment entropy in the scCS embedding.
For each cell i: 1. Find its
k_nnnearest neighbors incoords(X_sccs, 2D). 2. Averagecell_scoresover those neighbors (including cell i itself). 3. Compute normalized k-way Shannon entropy on the smoothed scores.This removes single-cell velocity noise while preserving local commitment structure. Use
scCS.plot.plot_nn_entropy_elbow()to choose k_nn.- Parameters:
cell_scores (np.ndarray, shape (n_cells, k)) – Per-cell fate-affinity matrix from
compute_cell_scores().coords (np.ndarray, shape (n_cells, 2)) – 2D scCS embedding coordinates (
adata_sub.obsm['X_sccs']).k_nn (int) – Number of nearest neighbors to average over (excluding self). Self is always included, so the effective window is k_nn + 1 cells.
- Returns:
nn_entropy – Normalized Shannon entropy of the NN-smoothed fate scores per cell, in [0, 1].
- Return type:
np.ndarray, shape (n_cells,)
Core Math — Scores¶
- scCS.compute_unCS(M_sector_i: float, M_sector_j: float) float[source]¶
Unnormalized commitment score of fate i relative to fate j (Eq. 8).
unCS > 1 => population is more committed to fate i than fate j.
- Parameters:
M_sector_i (float) – Cumulative magnitudes for fates i and j.
M_sector_j (float) – Cumulative magnitudes for fates i and j.
- Return type:
float (inf if M_sector_j == 0)
- scCS.compute_nCS(M_sector_i: float, M_sector_j: float, n_cells_i: int, n_cells_j: int) float[source]¶
Cell-number-normalized commitment score (Eq. 9).
nCS = (M_sector_i / M_sector_j) * (n_cells_j / n_cells_i)
- Parameters:
M_sector_i (float)
M_sector_j (float)
n_cells_i (int) – Number of cells in each population / trajectory arm.
n_cells_j (int) – Number of cells in each population / trajectory arm.
- Return type:
float
- scCS.compute_commitment_vector(M_sector: ndarray) ndarray[source]¶
Normalize sector magnitudes to a probability-like commitment vector.
- scCS.compute_pairwise_cs_matrix(M_sector: ndarray, n_cells_per_fate: ndarray | None = None, normalized: bool = True) ndarray[source]¶
Compute full k x k pairwise commitment score matrix.
Entry [i, j] = CS(i relative to j). Diagonal is 1.0.
- scCS.compute_cell_scores(vx: ndarray, vy: ndarray, fate_centroids: ndarray, root_centroid: ndarray, mag_weight: bool = True, mag_threshold_pct: float = 5.0) ndarray[source]¶
Per-cell fate affinity: magnitude-weighted cosine similarity to fate direction.
- For each cell i and fate j, computes:
raw_score(i, j) = dot(unit_v_i, unit_d_j)
where unit_d_j is the unit vector from root_centroid to fate_centroid_j.
Scores are shifted to [0, 1] via (score + 1) / 2, then optionally weighted by the cell’s velocity magnitude (normalized to [0, 1]). Cells with velocity magnitude below
mag_threshold_pctpercentile are down-weighted toward the uniform distribution (1/k), reducing noise from near-zero-velocity cells (typically progenitors at the origin).- Parameters:
vx (np.ndarray, shape (n_cells,))
vy (np.ndarray, shape (n_cells,))
fate_centroids (np.ndarray, shape (k, 2))
root_centroid (np.ndarray, shape (2,))
mag_weight (bool) – If True (default), weight each cell’s score by its normalized velocity magnitude. Low-magnitude cells are pulled toward the uniform distribution (1/k), reducing noise from near-stationary cells. If False, all cells contribute equally (original behavior).
mag_threshold_pct (float) – Percentile of velocity magnitudes below which cells are considered near-stationary and receive zero weight (replaced by 1/k). Default: 5th percentile. Only used when mag_weight=True.
- Returns:
cell_scores – Per-cell affinity for each fate, row-normalized to sum to 1. Low-magnitude cells have scores close to 1/k (uniform).
- Return type:
np.ndarray, shape (n_cells, k)
- scCS.compute_magnitudes(vx: ndarray, vy: ndarray) ndarray[source]¶
Euclidean norm of 2D velocity vectors (Eq. 1).
- Parameters:
vx (array-like, shape (n_cells,)) – x and y components of velocity vectors.
vy (array-like, shape (n_cells,)) – x and y components of velocity vectors.
- Returns:
magnitudes – Non-negative magnitudes; NaN inputs yield NaN.
- Return type:
np.ndarray, shape (n_cells,)
- scCS.compute_angles(vx: ndarray, vy: ndarray) ndarray[source]¶
Angle of each velocity vector in [0, 360) degrees (Eq. 2-3).
- Parameters:
vx (array-like, shape (n_cells,))
vy (array-like, shape (n_cells,))
- Returns:
angles_deg – Angles in degrees, range [0, 360). NaN for zero-magnitude vectors.
- Return type:
np.ndarray, shape (n_cells,)
- scCS.bin_angles(angles_deg: ndarray, magnitudes: ndarray, n_bins: int = 36) Tuple[ndarray, ndarray][source]¶
Discretize angles and accumulate magnitudes per bin (Eq. 4-6).
- Parameters:
angles_deg (np.ndarray, shape (n_cells,)) – Angles in [0, 360). NaN values are ignored.
magnitudes (np.ndarray, shape (n_cells,)) – Per-cell magnitudes. NaN values are ignored.
n_bins (int) – Number of angular bins. Default 36 (10° each, as in manuscript).
- Returns:
bin_edges (np.ndarray, shape (n_bins + 1,)) – Bin edge angles in degrees.
M_bin (np.ndarray, shape (n_bins,)) – Cumulative magnitude per bin.
- scCS.equal_sectors(k: int, n_bins: int = 36) List[List[int]][source]¶
Divide n_bins into k equal contiguous sectors.
- Parameters:
k (int) – Number of fates / sectors.
n_bins (int) – Total number of angular bins.
- Returns:
sectors – Each inner list contains the bin indices belonging to that sector.
- Return type:
list of lists
- scCS.centroid_sectors(fate_centroids: ndarray, root_centroid: ndarray, n_bins: int = 36) Tuple[List[List[int]], ndarray][source]¶
Define sectors anchored to fate centroid directions from the root.
Each sector is centered on the angle from the root to the corresponding fate centroid. Sector boundaries are placed at the midpoints between adjacent fate angles.
- Parameters:
fate_centroids (np.ndarray, shape (k, 2)) – 2D embedding coordinates of each fate centroid.
root_centroid (np.ndarray, shape (2,)) – 2D embedding coordinate of the root / progenitor centroid.
n_bins (int) – Number of angular bins.
- Returns:
sectors (list of k lists of bin indices)
fate_angles (np.ndarray, shape (k,)) – Central angle (degrees) for each fate.
- scCS.compute_sector_magnitudes(M_bin: ndarray, sectors: List[List[int]]) ndarray[source]¶
Sum M_bin values within each sector (Eq. 7).
- Parameters:
M_bin (np.ndarray, shape (n_bins,))
sectors (list of k lists of bin indices)
- Returns:
M_sector
- Return type:
np.ndarray, shape (k,)
- scCS.bootstrap_cs(vx: ndarray, vy: ndarray, sectors: List[List[int]], n_cells_per_fate: ndarray, n_bins: int = 36, n_bootstrap: int = 500, ci: float = 0.95, seed: int = 42, normalized: bool = True, stratified: bool = False, fate_cell_indices: Sequence | None = None) Dict[source]¶
Bootstrap confidence intervals for pairwise commitment scores.
Resamples cells with replacement
n_bootstraptimes, recomputes unCS and nCS for each bootstrap replicate, and returns the empirical CI bounds.- Parameters:
vx (np.ndarray, shape (n_cells,)) – Velocity components in scCS space.
vy (np.ndarray, shape (n_cells,)) – Velocity components in scCS space.
sectors (list of k lists of bin indices) – Sector definition from centroid_sectors() or equal_sectors().
n_cells_per_fate (np.ndarray, shape (k,)) – Number of cells per fate arm (used for nCS normalization).
n_bins (int) – Number of angular bins.
n_bootstrap (int) – Number of bootstrap replicates. Default 500.
ci (float) – Confidence interval level. Default 0.95 (95% CI).
seed (int)
normalized (bool) – If True, return CI for nCS; if False, for unCS.
stratified (bool) – If True, resample cells within each fate arm separately (preserving arm cell counts), then concatenate. Prevents bootstrap replicates with very few cells in one arm. Requires
fate_cell_indices. Default False (uniform resampling, original behavior).fate_cell_indices (sequence of array-like, optional) – List of k arrays, each containing the integer indices of cells belonging to that fate arm. Required when
stratified=True. Typicallyfate_map.fate_cell_indices.
- Returns:
‘mean’ : np.ndarray (k, k) — mean CS across replicates ‘ci_low’ : np.ndarray (k, k) — lower CI bound ‘ci_high’: np.ndarray (k, k) — upper CI bound ‘std’ : np.ndarray (k, k) — standard deviation across replicates ‘n_bootstrap’: int ‘ci_level’: float
- Return type:
dict with keys