scCS.single

single.py — SingleScorer: single-condition commitment score analysis for scCS.

Orchestrates: 1. RNA velocity computation (optional, via scVelo) 2. Radial star embedding construction (embedding.py) 3. FateMap construction from user-supplied cluster labels (bifurcation.py) 4. Commitment score computation (scores.py) 5. Driver gene analysis (drivers.py) 6. Pathway enrichment (enrichment.py) 7. Plotting (plot.py)

Quick start

>>> import scCS
>>> scorer = scCS.SingleScorer(
...     adata,
...     root='17',
...     branches=['Monocyte', 'DC', 'Neutrophil'],
...     obs_key='leiden',
... )
>>> scorer.build_embedding(ordering_metric='pseudotime')
>>> scorer.fit()
>>> result = scorer.score()
>>> print(result.summary())
>>> scorer.plot_star(result)
>>> # Driver genes
>>> vel_drivers = scorer.get_velocity_drivers()
>>> deg_drivers = scorer.get_deg_drivers()
>>> # Pathway enrichment (mouse)
>>> enrichment = scorer.get_enrichment(deg_drivers)

Classes

SingleScorer

RNA velocity commitment scorer with radial star embedding.

Module Contents

class scCS.single.SingleScorer(adata, root: str, branches: List[str], obs_key: str = 'leiden', n_angle_bins: int = 36, sector_method: scCS._base.SectorMode = 'centroid', copy: bool = False)[source]

Bases: scCS._base._BaseScorer

RNA 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].

  1. Computes fate centroids in the X_sccs embedding.

  2. Extracts velocity vectors if not already loaded.

Return type:

self

score(cell_mask: numpy.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) scCS.scores.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_sub cells (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:

CommitmentScoreResult

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: scCS.scores.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_’.

save(path: str) None[source]

Serialize scorer state to a pickle file.

Saves the embedding, FateMap, velocity vectors, and configuration. The full adata is NOT saved (too large); pass it again to load().

Parameters:

path (str) – Destination file path (e.g., 'scorer.pkl').

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:

SingleScorer

plot_star(result: scCS.scores.CommitmentScoreResult, **kwargs)[source]

Radial star embedding plot — primary visualization.

plot_rose(result: scCS.scores.CommitmentScoreResult, **kwargs)[source]

Rose/polar plot of cumulative magnitudes per angular bin.

plot_pairwise_cs(result: scCS.scores.CommitmentScoreResult, **kwargs)[source]

Heatmap of pairwise normalized commitment scores.

plot_commitment_bar(result: scCS.scores.CommitmentScoreResult, **kwargs)[source]

Bar chart of unCS vs nCS per fate pair.

plot_commitment_heatmap(result: scCS.scores.CommitmentScoreResult, **kwargs)[source]

Per-cell fate affinity heatmap.

plot_subset_comparison(subset_results: dict, **kwargs)[source]

Compare commitment scores across subsets.

plot_nn_entropy_elbow(**kwargs)[source]

Elbow plots for choosing k_nn for NN-smoothed entropy.

Parameters:
Returns:

fig

Return type:

matplotlib Figure

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_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_velocity_fate_drivers(result: scCS.scores.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,

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}