scCS.single =========== .. py:module:: scCS.single .. autoapi-nested-parse:: 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 ------- .. autoapisummary:: scCS.single.SingleScorer Module Contents --------------- .. py:class:: 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) Bases: :py:obj:`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). :param adata: Single-cell dataset. :type adata: AnnData :param root: Label of the progenitor/root cluster in adata.obs[obs_key]. Example: '17' (leiden cluster 17) :type root: str :param branches: Labels of the k terminal fate clusters. Example: ['Monocyte', 'DC', 'Neutrophil'] :type branches: list of str :param obs_key: Column in adata.obs with cluster labels. Default: 'leiden'. :type obs_key: str :param n_angle_bins: Number of angular bins for commitment scoring. Default: 36 (10° each). :type n_angle_bins: int :param sector_method: 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. :type sector_method: {'centroid', 'equal'} :param copy: Work on a copy of adata. :type copy: bool .. rubric:: 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) .. py:method:: 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 Run the full scVelo RNA velocity pipeline. Call this if adata does not yet have velocity vectors. Requires 'spliced' and 'unspliced' layers. :param mode: :type mode: {'dynamical', 'stochastic', 'steady_state'} :param n_top_genes: :type n_top_genes: int :param n_pcs: :type n_pcs: int :param n_neighbors: :type n_neighbors: int :param min_shared_counts: :type min_shared_counts: int :param verbose: :type verbose: bool :rtype: self .. py:method:: fit(verbose: bool = True) -> SingleScorer 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]. 2. Computes fate centroids in the X_sccs embedding. 3. Extracts velocity vectors if not already loaded. :rtype: self .. py:method:: score(cell_mask: Optional[numpy.ndarray] = None, cell_level: bool = True, k_nn: Optional[int] = 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 Compute commitment scores for the full population or a subset. :param cell_mask: 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. :type cell_mask: np.ndarray of bool, shape (n_sub_cells,), optional :param cell_level: Whether to compute per-cell fate affinity scores. :type cell_level: bool :param k_nn: If set, compute NN-smoothed per-cell entropy using this many nearest neighbors in the scCS embedding (X_sccs). :type k_nn: int, optional :param n_bootstrap: Number of bootstrap replicates for CS confidence intervals. 0 (default) disables bootstrapping. Recommended: 500. :type n_bootstrap: int :param bootstrap_ci: Confidence interval level for bootstrap. Default 0.95 (95% CI). :type bootstrap_ci: float :param bootstrap_seed: Random seed for bootstrap resampling. :type bootstrap_seed: int :param verbose: :type verbose: bool :param write_to_obs: If True (default), write per-cell scores to ``adata_sub.obs``. :type write_to_obs: bool :rtype: CommitmentScoreResult .. py:method:: score_per_subset(split_by: str, cell_level: bool = False, n_bootstrap: int = 0, verbose: bool = False) -> dict Compute commitment scores separately for each value of split_by. Useful for comparing commitment across conditions, time points, or trajectory directions. :param split_by: Column in adata_sub.obs to split by. :type split_by: str :param cell_level: :type cell_level: bool :param n_bootstrap: Bootstrap replicates for CI. 0 = disabled. :type n_bootstrap: int :param verbose: :type verbose: bool :rtype: dict mapping subset_value -> CommitmentScoreResult .. py:method:: transfer_labels(adata, result: scCS.scores.CommitmentScoreResult, prefix: str = 'cs_') -> None 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. :param adata: The full dataset (same object passed to SingleScorer.__init__). :type adata: AnnData :param result: Output of scorer.score(cell_level=True). :type result: CommitmentScoreResult :param prefix: Column prefix. Default: 'cs_'. :type prefix: str .. py:method:: save(path: str) -> None 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 :meth:`load`. :param path: Destination file path (e.g., ``'scorer.pkl'``). :type path: str .. py:method:: load(path: str, adata) -> SingleScorer :classmethod: Load a scorer from a pickle file. :param path: Path to a file saved by :meth:`save`. :type path: str :param adata: The full dataset (same object originally passed to ``SingleScorer.__init__``). Not stored in the pickle. :type adata: AnnData :rtype: SingleScorer .. py:method:: plot_star(result: scCS.scores.CommitmentScoreResult, **kwargs) Radial star embedding plot — primary visualization. .. py:method:: plot_rose(result: scCS.scores.CommitmentScoreResult, **kwargs) Rose/polar plot of cumulative magnitudes per angular bin. .. py:method:: plot_pairwise_cs(result: scCS.scores.CommitmentScoreResult, **kwargs) Heatmap of pairwise normalized commitment scores. .. py:method:: plot_commitment_bar(result: scCS.scores.CommitmentScoreResult, **kwargs) Bar chart of unCS vs nCS per fate pair. .. py:method:: plot_commitment_heatmap(result: scCS.scores.CommitmentScoreResult, **kwargs) Per-cell fate affinity heatmap. .. py:method:: plot_subset_comparison(subset_results: dict, **kwargs) Compare commitment scores across subsets. .. py:method:: plot_nn_entropy_elbow(**kwargs) Elbow plots for choosing k_nn for NN-smoothed entropy. :param k_nn_range: k_nn values to sweep. Default: range(5, 51, 5). :type k_nn_range: list or range, optional :param \*\*kwargs: Passed to :func:`scCS.plot.plot_nn_entropy_elbow`. :returns: **fig** :rtype: matplotlib Figure .. py:method:: get_velocity_drivers(n_top_genes: int = 50) -> dict Rank genes by mean scVelo velocity in each fate arm. :param n_top_genes: Number of top driver genes to print per fate. :type n_top_genes: int :returns: **dict** :rtype: fate_name -> DataFrame[gene, mean_velocity, rank] .. py:method:: get_deg_drivers(n_top_genes: int = 50, pval_threshold: float = 0.05, logfc_threshold: float = 0.25) -> dict Find DEGs for each fate arm vs the bifurcation cluster (Wilcoxon). :param n_top_genes: :type n_top_genes: int :param pval_threshold: :type pval_threshold: float :param logfc_threshold: :type logfc_threshold: float :returns: **dict** :rtype: fate_name -> DataFrame[gene, logfoldchange, pval, pval_adj, significant] .. py:method:: get_velocity_fate_drivers(result: scCS.scores.CommitmentScoreResult, n_top_genes: int = 50, pval_threshold: float = 0.05) -> dict Identify driver genes by correlating gene velocity with fate affinity. :param result: Output of scorer.score(cell_level=True). :type result: CommitmentScoreResult :param n_top_genes: :type n_top_genes: int :param pval_threshold: :type pval_threshold: float :returns: **dict** -- mean_velocity, delta_velocity, significant] :rtype: fate_name -> DataFrame[gene, spearman_r, pval, pval_adj, .. py:method:: get_enrichment(deg_drivers: dict, gene_sets: Optional[List[str]] = None, organism: str = 'mouse', pval_threshold: float = 0.05, logfc_threshold: float = 0.25, plot: bool = True, n_top_pathways: int = 15) -> dict Run pathway enrichment on DEG driver genes per fate arm. :param deg_drivers: Output of get_deg_drivers(). :type deg_drivers: dict :param gene_sets: :type gene_sets: list of str, optional :param organism: :type organism: str :param pval_threshold: :type pval_threshold: float :param logfc_threshold: :type logfc_threshold: float :param plot: :type plot: bool :param n_top_pathways: :type n_top_pathways: int :returns: **dict** :rtype: fate_name -> {'up': DataFrame, 'down': DataFrame}