scCS.plot ========= .. py:module:: scCS.plot .. autoapi-nested-parse:: plot.py — Publication-quality visualizations for scCS. Primary visualization: plot_star_embedding() Radial star layout with one arm per fate, cells colored by cluster, pseudotime, fate affinity, or commitment entropy. Arm axes are drawn with fate labels at the tips. Additional plots: plot_rose() — polar rose of velocity magnitude by direction plot_pairwise_cs() — heatmap of pairwise nCS/unCS matrix plot_commitment_bar() — unCS/nCS bar chart per fate pair plot_commitment_heatmap() — per-cell fate affinity heatmap plot_expression_trends() — CellRank-style gene expression vs pseudotime plot_subset_comparison() — multi-subset CS comparison plot_nn_entropy_elbow() — elbow plot for k_nn selection Multi-condition plots (PairScorer + MultiScorer): plot_delta_cs_heatmap() — ΔCS heatmap with CI annotation plot_compare_conditions_bar() — grouped bar chart of nCS per condition plot_commitment_vector_radar() — radar chart of commitment vectors plot_omnibus_summary() — fates × conditions heatmap with omnibus significance plot_posthoc_heatmap() — condition × condition post-hoc p-value heatmap plot_pairwise_delta_grid() — grid of ΔCS heatmaps for all condition pairs Color maps ---------- All plot functions accept an optional ``color_map`` dict mapping fate name to a hex color string. Pass this to preserve your original cluster colors from scanpy/Seurat across all scCS plots. Progenitor cells always use PROGENITOR_COLOR (gray) regardless of color_map. Example:: # Extract colors from scanpy color_map = dict(zip( adata.obs['cell_type'].cat.categories, adata.uns['cell_type_colors'], )) scorer.plot_star(result, color_map=color_map) All plots use seaborn ticks theme. Figures are returned as matplotlib Figure objects. Attributes ---------- .. autoapisummary:: scCS.plot.FATE_PALETTE scCS.plot.PROGENITOR_COLOR scCS.plot.CONDITION_PALETTE Functions --------- .. autoapisummary:: scCS.plot.plot_star_embedding scCS.plot.plot_nn_entropy_elbow scCS.plot.plot_expression_trends scCS.plot.plot_star_panels scCS.plot.plot_rose scCS.plot.plot_rose_grid scCS.plot.plot_pairwise_cs scCS.plot.plot_commitment_bar scCS.plot.plot_commitment_heatmap scCS.plot.plot_subset_comparison scCS.plot.plot_delta_cs_heatmap scCS.plot.plot_compare_conditions_bar scCS.plot.plot_commitment_vector_radar scCS.plot.plot_omnibus_summary scCS.plot.plot_posthoc_heatmap scCS.plot.plot_pairwise_delta_grid Module Contents --------------- .. py:data:: FATE_PALETTE :value: ['#0072B2', '#D55E00', '#009E73', '#CC79A7', '#E69F00', '#56B4E9', '#F0E442', '#000000'] .. py:data:: PROGENITOR_COLOR :value: '#AAAAAA' .. py:data:: CONDITION_PALETTE :value: ['#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7', '#000000',... .. py:function:: plot_star_embedding(adata, result: scCS.scores.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: Optional[Dict[str, str]] = None, title: Optional[str] = None, vmin: Optional[float] = None, vmax: Optional[float] = None, cmap: Optional[str] = None, ax: Optional[matplotlib.pyplot.Axes] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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 :param adata: Must have X_sccs in obsm. :type adata: AnnData :param result: :type result: CommitmentScoreResult :param color_by: What to color cells by: - ``"fate"`` — cluster/arm assignment (default) - ``"pseudotime"`` — reads ``sccs_pseudotime`` then ``velocity_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}``; requires ``score(cell_level=True)``) - any other str — auto-detected numeric or categorical column in ``adata.obs`` :type color_by: str :param figsize: :type figsize: tuple :param point_size: :type point_size: float :param alpha: :type alpha: float :param arm_color: Color of the radial arm guide lines. :type arm_color: str :param arm_linewidth: :type arm_linewidth: float :param arm_linestyle: :type arm_linestyle: str :param show_arm_labels: Draw fate name labels at arm tips. :type show_arm_labels: bool :param show_velocity: Overlay velocity arrows (requires velocity_sccs in obsm). :type show_velocity: bool :param velocity_scale: Scale factor for velocity arrows. :type velocity_scale: float :param title: :type title: str, optional :param vmin: Color-scale limits for numeric ``color_by`` modes. 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. :type vmin: float, optional :param vmax: Color-scale limits for numeric ``color_by`` modes. 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. :type vmax: float, optional :param cmap: Matplotlib colormap name. Defaults: ``"RdYlBu_r"`` for entropy, ``"viridis"`` for pseudotime/generic numeric, ``"Blues"`` for per-fate affinity. :type cmap: str, optional :param ax: :type ax: matplotlib Axes, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_nn_entropy_elbow(scorer, k_nn_range: Union[List[int], range] = range(5, 51, 5), color_map: Optional[Dict[str, str]] = None, figsize: Tuple[float, float] = (12, 5), title: Optional[str] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param scorer: A fitted scorer with ``build_embedding()`` and ``fit()`` already called. No prior ``score()`` call is needed — cell scores are recomputed internally from the velocity vectors. :type scorer: SingleScorer :param k_nn_range: k_nn values to sweep. Default: 5, 10, 15, ..., 50. :type k_nn_range: list or range :param color_map: Fate name -> hex color. Falls back to the default FATE_PALETTE. :type color_map: dict, optional :param figsize: :type figsize: tuple :param title: Overall figure title. Defaults to "NN Entropy Elbow". :type title: str, optional :param save_path: If provided, save figure to this path. :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. rubric:: Examples >>> scorer.build_embedding(differentiation_metric='pseudotime') >>> scorer.fit() >>> result = scorer.score(compute_cell_level=True) >>> fig = scorer.plot_nn_entropy_elbow() .. py:function:: plot_expression_trends(adata, result: scCS.scores.CommitmentScoreResult, genes: List[str], fate: Optional[str] = None, x_axis: str = 'affinity', n_bins: int = 10, layer: Optional[str] = None, smooth: bool = True, smooth_frac: float = 0.4, color_map: Optional[Dict[str, str]] = None, figsize: Optional[Tuple[float, float]] = None, ncols: int = 3, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param adata: Must contain the same cells as ``result``. :type adata: AnnData :param result: :type result: CommitmentScoreResult :param genes: Gene names to plot. Must be present in ``adata.var_names``. :type genes: list of str :param fate: Which fate to use as the reference. Defaults to the fate with the highest M_sector. :type fate: str, optional :param x_axis: What to use as the x-axis for binning: - ``'affinity'`` : per-cell fate affinity score for ``fate`` (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'``. :type x_axis: str :param n_bins: Number of bins along the x-axis. :type n_bins: int :param layer: AnnData layer to use for expression. Defaults to ``adata.X``. :type layer: str, optional :param smooth: Whether to overlay a LOWESS smoothed curve. :type smooth: bool :param smooth_frac: LOWESS smoothing fraction (0–1). :type smooth_frac: float :param color_map: Fate name → hex color. Used to color the smoothed line. :type color_map: dict, optional :param figsize: :type figsize: tuple, optional :param ncols: Number of columns in the subplot grid. :type ncols: int :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_star_panels(adata, result: scCS.scores.CommitmentScoreResult, panels: Optional[List[str]] = None, figsize_per_panel: Tuple[float, float] = (6, 6), point_size: float = 6.0, alpha: float = 0.75, color_map: Optional[Dict[str, str]] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure Multi-panel star embedding: one panel per coloring scheme. Default panels: fate assignment, pseudotime, entropy, + one per fate. :param adata: :type adata: AnnData :param result: :type result: CommitmentScoreResult :param panels: List of color_by values. Defaults to ['fate', 'pseudotime', 'entropy'] + fate_names. :type panels: list of str, optional :param figsize_per_panel: :type figsize_per_panel: tuple :param point_size: :type point_size: float :param alpha: :type alpha: float :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_rose(result: scCS.scores.CommitmentScoreResult, title: str = 'Cumulative Velocity Magnitude by Direction', figsize: Tuple[float, float] = (7, 7), show_sectors: bool = True, color_map: Optional[Dict[str, str]] = None, ax: Optional[matplotlib.pyplot.Axes] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param result: :type result: CommitmentScoreResult :param title: :type title: str :param figsize: :type figsize: tuple :param show_sectors: :type show_sectors: bool :param ax: :type ax: matplotlib Axes (polar), optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_rose_grid(results: Dict[str, scCS.scores.CommitmentScoreResult], color_map: Optional[Dict[str, str]] = None, figsize_per_panel: Tuple[float, float] = (5, 5), title: Optional[str] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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). :param results: Mapping of condition_label -> CommitmentScoreResult (output of PairScorer.score_all_conditions()). :type results: dict :param color_map: fate_name -> hex color. Falls back to FATE_PALETTE. :type color_map: dict, optional :param figsize_per_panel: Size of each polar subplot. :type figsize_per_panel: tuple :param title: Overall figure title. :type title: str, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_pairwise_cs(result: scCS.scores.CommitmentScoreResult, normalized: bool = True, title: Optional[str] = None, figsize: Optional[Tuple[float, float]] = None, cmap: str = 'RdBu_r', save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param result: :type result: CommitmentScoreResult :param normalized: Use nCS (True) or unCS (False). :type normalized: bool :param title: :type title: str, optional :param figsize: :type figsize: tuple, optional :param cmap: :type cmap: str :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_commitment_bar(result: scCS.scores.CommitmentScoreResult, ref_fate: Optional[str] = None, mode: str = 'auto', color_map: Optional[Dict[str, str]] = None, title: Optional[str] = None, figsize: Optional[Tuple[float, float]] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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). :param result: :type result: CommitmentScoreResult :param ref_fate: If given, produce only a single subplot using this fate as reference. Useful when you want a focused comparison. :type ref_fate: str, optional :param mode: Kept for backward compatibility; ignored. :type mode: str :param color_map: Mapping of fate name → hex color. :type color_map: dict, optional :param title: Overall figure title. :type title: str, optional :param figsize: Per-subplot size ``(w, h)``. Total figure width scales with k. :type figsize: tuple, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_commitment_heatmap(result: scCS.scores.CommitmentScoreResult, cell_scores: Optional[numpy.ndarray] = None, max_cells: int = 500, title: str = 'Per-Cell Fate Affinity', figsize: Optional[Tuple[float, float]] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure Heatmap of per-cell fate affinity scores (cells × fates). :param result: :type result: CommitmentScoreResult :param cell_scores: If None, uses result.cell_scores. :type cell_scores: np.ndarray, shape (n_cells, k), optional :param max_cells: Subsample to this many cells for readability. :type max_cells: int :param title: :type title: str :param figsize: :type figsize: tuple, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_subset_comparison(subset_results: dict, ref_fate: Optional[str] = None, normalized: bool = True, title: str = 'Commitment Score by Subset', figsize: Tuple[float, float] = (8, 4), save_path: Optional[str] = None) -> matplotlib.pyplot.Figure Compare commitment scores across multiple subsets. Subsets whose chosen reference pair yields ``inf`` (e.g. progenitor-only subsets with no fate-arm cells, so ``pairwise_nCS`` is undefined) are rendered as gray hatched placeholders at zero height with an ``"inf"`` annotation, instead of silently producing empty bars. :param subset_results: Mapping of subset_name -> CommitmentScoreResult (from ``SingleScorer.score_per_subset``). :type subset_results: dict :param ref_fate: Reference fate for the CS column. If None, use the fate with smallest sector magnitude (most likely to be present in all subsets). :type ref_fate: str, optional :param normalized: If True use ``pairwise_nCS``, else ``pairwise_unCS``. :type normalized: bool :param title: :type title: str :param figsize: :type figsize: tuple :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_delta_cs_heatmap(delta_result: dict, title: Optional[str] = None, figsize: Optional[Tuple[float, float]] = None, cmap: str = 'RdBu_r', save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param delta_result: Output of PairScorer.compute_delta_CS(). :type delta_result: dict :param title: :type title: str, optional :param figsize: :type figsize: tuple, optional :param cmap: Diverging colormap. Default: 'RdBu_r'. :type cmap: str :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_compare_conditions_bar(results: Dict[str, scCS.scores.CommitmentScoreResult], ref_fate: Optional[str] = None, color_map: Optional[Dict[str, str]] = None, title: Optional[str] = None, figsize: Optional[Tuple[float, float]] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param results: Mapping of condition_label -> CommitmentScoreResult (output of PairScorer.score_all_conditions()). :type results: dict :param ref_fate: Reference fate for the denominator. If None, uses the fate with the lowest mean M_sector across conditions. :type ref_fate: str, optional :param color_map: condition_label -> hex color. Falls back to CONDITION_PALETTE. :type color_map: dict, optional :param title: :type title: str, optional :param figsize: :type figsize: tuple, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_commitment_vector_radar(results: Dict[str, scCS.scores.CommitmentScoreResult], color_map: Optional[Dict[str, str]] = None, title: Optional[str] = None, figsize: Tuple[float, float] = (6, 6), save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param results: Mapping of condition_label -> CommitmentScoreResult (output of PairScorer.score_all_conditions()). :type results: dict :param color_map: condition_label -> hex color. Falls back to CONDITION_PALETTE. :type color_map: dict, optional :param title: :type title: str, optional :param figsize: :type figsize: tuple :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_omnibus_summary(omnibus_df, results: Dict[str, scCS.scores.CommitmentScoreResult], posthoc_df=None, figsize: Optional[Tuple[float, float]] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param omnibus_df: Output of MultiScorer.compare_omnibus(). Columns: fate, test, statistic, pval, pval_adj, significant. :type omnibus_df: pd.DataFrame :param results: Mapping of condition_label -> CommitmentScoreResult (output of MultiScorer.score_all_conditions()). :type results: dict :param posthoc_df: Output of MultiScorer.compare_posthoc(). If provided, right panel shows post-hoc significance grid. :type posthoc_df: pd.DataFrame, optional :param figsize: :type figsize: tuple, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_posthoc_heatmap(posthoc_df, fate: Optional[str] = None, figsize: Optional[Tuple[float, float]] = None, save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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. :param posthoc_df: Output of MultiScorer.compare_posthoc(). Columns: fate, comparison, method, statistic, pval, pval_adj, significant, mean_A, mean_B, delta_mean. :type posthoc_df: pd.DataFrame :param fate: Which fate to plot. If None, uses the first fate in posthoc_df. :type fate: str, optional :param figsize: :type figsize: tuple, optional :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure .. py:function:: plot_pairwise_delta_grid(delta_results: Dict[Tuple[str, str], dict], figsize_per_panel: Tuple[float, float] = (4, 4), save_path: Optional[str] = None) -> matplotlib.pyplot.Figure 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(). :param delta_results: Output of MultiScorer.compute_pairwise_deltas(). Mapping of (cond_a, cond_b) -> delta_result dict. :type delta_results: dict :param figsize_per_panel: Size of each subplot. :type figsize_per_panel: tuple :param save_path: :type save_path: str, optional :returns: **fig** :rtype: matplotlib Figure