scCS.scores¶
scores.py — Core commitment score math engine for scCS.
Implements the generalized k-furcation commitment score framework, extending the 2-state (homeostatic/activated) formulation from:
Kriukov et al. (2025) “Single-cell transcriptome of myeloid cells in response to transplantation of human retinal neurons reveals reversibility of microglial activation”
Mathematical framework¶
Given per-cell RNA velocity vectors (vx_i, vy_i) in the scCS radial embedding:
magnitude_i = sqrt(vx_i^2 + vy_i^2) [Eq. 1]
theta_i = atan2(vy_i, vx_i) -> [0, 360) [Eq. 2-3]
Bin angles into N bins of width 360/N degrees [Eq. 4-5]
M_bin(b) = sum of magnitude_i for all cells in bin b [Eq. 6]
M_sector(j) = sum of M_bin(b) for b in sector j [Eq. 7]
unCS(i,j) = M_sector(i) / M_sector(j) [Eq. 8]
nCS(i,j) = unCS(i,j) * n_cells(j) / n_cells(i) [Eq. 9]
Generalization to k fates: - CS_vec = [M_sector(1), …, M_sector(k)] (raw) - p_vec = CS_vec / sum(CS_vec) (normalized) - H_pop = -sum(p_k * log(p_k)) / log(k) (population entropy) - H_cell_j = mean_i[ h_bin(s_ij) ] (per-fate cell entropy, k values) - H_nn_i = H( mean_{n in NN(i)}(cell_scores[n]) ) (NN-smoothed per-cell entropy) - cell_scores = dot(unit_velocity_i, unit_direction_to_fate_j) (per-cell)
Entropy notes¶
Three complementary entropy metrics are provided:
compute_population_entropy(p_vec)→ floatEntropy of the aggregate commitment vector (M_sector / sum(M_sector)). Single scalar. Measures how evenly total velocity mass is distributed. Limitation: high for any balanced split, even if every cell is decisive.
compute_per_fate_cell_entropy(cell_scores)→ ndarray shape (k,)For each fate j: binary entropy of each cell’s affinity toward j, averaged over all cells. h_j = mean_i[ H_bin(s_ij, 1-s_ij) ]. Tells you per-fate how individually decisive cells are toward that fate. Low h_j = cells are sharply committed (or sharply not committed) to fate j. High h_j = cells are ambiguous about fate j.
compute_nn_cell_entropy(cell_scores, coords, k_nn)→ ndarray shape (n_cells,)For each cell: average cell_scores over its k_nn nearest neighbors in the scCS embedding (X_sccs), then compute full k-way entropy on the smoothed scores. Spatially local smoothing removes single-cell noise. Use the elbow plots (plot_nn_entropy_elbow) to choose k_nn.
Attributes¶
Classes¶
Container for all commitment score outputs. |
Functions¶
|
Euclidean norm of 2D velocity vectors (Eq. 1). |
|
Angle of each velocity vector in [0, 360) degrees (Eq. 2-3). |
|
Discretize angles and accumulate magnitudes per bin (Eq. 4-6). |
|
Divide n_bins into k equal contiguous sectors. |
|
Define sectors anchored to fate centroid directions from the root. |
|
Sum M_bin values within each sector (Eq. 7). |
|
Unnormalized commitment score of fate i relative to fate j (Eq. 8). |
|
Cell-number-normalized commitment score (Eq. 9). |
|
Normalize sector magnitudes to a probability-like commitment vector. |
|
Shannon entropy of the aggregate commitment vector, normalized to [0, 1]. |
|
Mean per-cell Shannon entropy of fate-affinity scores, normalized to [0, 1]. |
|
Per-fate mean binary cell entropy of fate-affinity scores. |
|
NN-smoothed per-cell commitment entropy in the scCS embedding. |
|
Compute full k x k pairwise commitment score matrix. |
|
Per-cell fate affinity: magnitude-weighted cosine similarity to fate direction. |
|
Bootstrap confidence intervals for pairwise commitment scores. |
Module Contents¶
- scCS.scores.compute_magnitudes(vx: numpy.ndarray, vy: numpy.ndarray) numpy.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.scores.compute_angles(vx: numpy.ndarray, vy: numpy.ndarray) numpy.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.scores.bin_angles(angles_deg: numpy.ndarray, magnitudes: numpy.ndarray, n_bins: int = 36) Tuple[numpy.ndarray, numpy.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.scores.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.scores.centroid_sectors(fate_centroids: numpy.ndarray, root_centroid: numpy.ndarray, n_bins: int = 36) Tuple[List[List[int]], numpy.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.scores.compute_sector_magnitudes(M_bin: numpy.ndarray, sectors: List[List[int]]) numpy.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.scores.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.scores.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.scores.compute_commitment_vector(M_sector: numpy.ndarray) numpy.ndarray[source]¶
Normalize sector magnitudes to a probability-like commitment vector.
- scCS.scores.compute_population_entropy(p_vec: numpy.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.scores.compute_mean_cell_entropy(cell_scores: numpy.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.scores.compute_per_fate_cell_entropy(cell_scores: numpy.ndarray) numpy.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.scores.compute_nn_cell_entropy(cell_scores: numpy.ndarray, coords: numpy.ndarray, k_nn: int) numpy.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,)
- scCS.scores.compute_pairwise_cs_matrix(M_sector: numpy.ndarray, n_cells_per_fate: numpy.ndarray | None = None, normalized: bool = True) numpy.ndarray[source]¶
Compute full k x k pairwise commitment score matrix.
Entry [i, j] = CS(i relative to j). Diagonal is 1.0.
- scCS.scores.compute_cell_scores(vx: numpy.ndarray, vy: numpy.ndarray, fate_centroids: numpy.ndarray, root_centroid: numpy.ndarray, mag_weight: bool = True, mag_threshold_pct: float = 5.0) numpy.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.scores.bootstrap_cs(vx: numpy.ndarray, vy: numpy.ndarray, sectors: List[List[int]], n_cells_per_fate: numpy.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
- class scCS.scores.CommitmentScoreResult[source]¶
Container for all commitment score outputs.
- population_entropy[source]¶
Normalized Shannon entropy of the aggregate commitment vector in [0, 1]. Single scalar. See
compute_population_entropy().- Type:
float
- mean_cell_entropy[source]¶
Mean normalized per-cell Shannon entropy in [0, 1]. See
compute_mean_cell_entropy(). NaN when cell_level=False.- Type:
float
- per_fate_entropy[source]¶
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[source]¶
Angle (degrees) of each fate axis in the radial embedding.
- Type:
np.ndarray, shape (k,), optional
- nn_cell_entropy[source]¶
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
- property commitment_entropy: float[source]¶
Alias for
population_entropy(deprecated, usemean_cell_entropy).