Changelog¶
v0.7.4 (2026-06-08)¶
Fixed
scCS.embedding.build_star_embedding— the(s_min, s_max)range used to map the ordering metric onto each arm is now computed from fate cells only, excluding the bifurcation/progenitor cells. Previously the range was taken acrossfate_mask | bif_maskin per-arm mode (and across the whole subset in global mode), which let bifurcation cells — typically clustered at the low end of the pseudotime — push the lower edge of the rescale interval down and shift the closest fate cell off the origin. After this fix, the earliest fate cell on each arm (per-arm mode) or the earliest fate cell anywhere in the subset (global mode) sits at radius ≈ 0, which matches the published star-plot semantics. Bifurcation cells continue to be clustered tightly around the origin via the separate jitter-around-origin logic and are unaffected by this change.Visible effect on real data: on the pancreas tutorial subset (Pre-endocrine → Alpha/Beta/Delta/Epsilon), v0.7.3 placed every fate cluster at least 2.7 radial units away from the origin even with
arm_norm="per_arm"; v0.7.4 places the earliest fate cell on each arm within ~0.5 units of origin in per-arm mode and within ~0.05 units of origin for the global-min fate (Delta) in global mode.Multi-condition embeddings (
PairScorer/MultiScorer) share one rescale across the full subset, then each condition panel inplot_star_gridshows only that condition’s cells. If a particular condition does not contain the cell with the global-min pseudotime, its panel will show all arms starting slightly off origin — this is the intended behavior for global rescale and reflects the biological fact that the condition is enriched in later-pseudotime cells. Usearm_norm="per_arm"if you instead want every condition’s earliest cells to land at origin (at the cost of losing inter-condition timing comparison).All three tutorials — the
sc.pp.highly_variable_genesfallback in the scvelo HVG path now usesflavor="cell_ranger"instead of the defaultflavor="seurat". The seurat flavor passes an integern_binstopandas.cuton the log-dispersion vector, which rejects integer bin counts when the input contains±inffrom pandas 2.2 onward. Genes with zero mean expression produce-inflog-dispersions and trigger this error on Python 3.12 + pandas ≥ 2.2 environments. Thecell_rangerflavor uses explicit bin edges that already include±infand is robust across pandas versions. The IF-branch (scv.pp.filter_and_normalizewithn_top_genes=2000) is unchanged and still preferred when the installed scvelo supports it.
Tests
Added
tests/test_v074_fate_only_rescale.py(+6 tests):TestPerArmFateOnly— every fate’s closest cell touches origin inarm_norm="per_arm"mode; bifurcation cells remain near origin (regression test for the inner cluster).TestGlobalFateOnly— at least one fate touches origin inarm_norm="global"mode (the fate containing the global-min pseudotime cell); the longest-range fate reachesarm_scale; explicit regression check against the v0.7.3 fate+bif rescale behavior on a fixture where bifurcation cells are pseudotime-disjoint from fate cells.TestScveloHvgFallbackCellRanger— synthetic adata with all-zero genes (the failure mode for the seurat flavor on pandas ≥ 2.2) successfully runs throughflavor="cell_ranger".
Test count: 183 passed, 1 skipped (was 177 + 1 in v0.7.3).
v0.7.3 (2026-06-08)¶
Added
arm_normkeyword onscCS.embedding.build_star_embeddingand onSingleScorer.build_embedding,PairScorer.build_embedding,MultiScorer.build_embedding, plus the matchingrefit_pseudotimewrappers. Accepts"global"(new default) or"per_arm"(legacy behavior). See Changed below for the rationale.vmin/vmaxkeyword arguments onMultiScorer.plot_omnibus_summary. When both areNone(default), the colormap limits are derived from the finite values of the mean-affinity matrix; pass explicit floats to pin a fixed scale across figures.
Changed
build_star_embeddingnow rescales the ordering metric globally across the entire subset by default (arm_norm="global"). Previously each arm received its own(s_min, s_max)fromfate_mask | bif_maskand was mapped to[0, arm_scale]independently, so all arms reached the full radial cap regardless of the underlying pseudotime range. The new default uses one(s_min, s_max)from all subset cells and applies it uniformly, so arms whose cells span shorter pseudotime intervals stay visibly shorter. This preserves the relative ordering of cells across arms and matches the biological intuition that arm length reflects how far each fate has differentiated from the progenitor on a shared scale. Passarm_norm="per_arm"to reproduce pre-v0.7.3 plots.MultiScorer.plot_omnibus_summaryno longer pins the mean-affinity heatmap to[0, 1]. The colormap now spans the realized data range by default, so per-condition contrast is visible on datasets where affinities cluster well below 1.0 (the previous behavior rendered nearly uniform pale yellow on real data). The colorbar label includes the realized[vmin, vmax]so the scale stays explicit.scCS.enrichment.run_enrichment_per_fate—fate_namesis now an optional second argument. If omitted, fate names are inferred fromdeg_drivers.keys()in their natural insertion order. If provided but mismatched withdeg_drivers.keys(), aUserWarningis emitted and only the intersection is used. The previous positional contract is preserved for callers (includingSingleScorer.get_enrichment) that pass it explicitly.
Fixed
scCS_tutorial_pairwise.ipynbandscCS_tutorial_multi.ipynb—run_enrichment_per_fatecalls now passfate_names=fate_namesexplicitly. Previously the missing positional argument raisedTypeErrorwhich was silently swallowed by the surroundingexcept Exceptionclause, leaving the enrichment table empty with no clear error message.All three tutorials —
scv.pp.filter_and_normalizeis now invoked through aninspect.signature-based guard that usesn_top_genes=2000when the installed scvelo supports the keyword, and falls back tosc.pp.highly_variable_genes(adata, n_top_genes=2000, subset=True)otherwise. This keeps the notebooks runnable across scvelo releases where the keyword has been removed.MultiScorer.plot_omnibus_summarylayout cleanup — the default figsize is widened so the colorbar label (which now carries the auto-derived range) and the fate row labels render without overlap or truncation, and y-tick labels are explicitly set torotation=0on both panels.
Tutorial hygiene
Uniform warning filters added to
scCS_tutorial_pairwise.ipynbandscCS_tutorial_single.ipynb:DeprecationWarning,FutureWarning, andstatsmodels.ConvergenceWarningare suppressed. The previous blanketwarnings.filterwarnings("ignore")inscCS_tutorial_multi.ipynbis narrowed to the same category set, soUserWarningemitted by scCS itself (e.g. theplot_expression_trendsslice notice introduced in v0.7.2) stays visible.
Tests
Added
tests/test_arm_norm_and_enrichment.pycovering the newarm_normbranches inbuild_star_embedding, thefate_namesinference path inrun_enrichment_per_fate, and the auto-scale defaults ofplot_omnibus_summary.
v0.7.2 (2026-06-07)¶
Bug fixes — correctness
MultiScorer.compare_omnibus,MultiScorer.compare_posthoc, andMultiScorer.fit_mixed_model_contrastspreviously compared identical full-embeddingcell_scoresarrays across all conditions becauseSingleScorer.score(cell_mask=...)returnscell_scoressized to the fulladata_subregardless of the mask (a semantic thetransfer_labelspipeline depends on). The downstream multi-condition consumers wrongly assumed condition-only sizing. They now correctly slicecell_scoresper condition via the newMultiScorer._per_condition_cell_scoreshelper, so omnibus, posthoc, and LMM analyses operate on the actual per-condition cell distributions. Same fix applied toplot_omnibus_summary’s mean-affinity matrix and toplot_rose_grid’s long-form table.MultiScorer.plot_pairwise_delta_grid— previously calledplot_delta_cs_heatmap(delta_result, ax=ax)butplot_delta_cs_heatmapdoes not acceptax(it constructs its ownFigure). Heatmap rendering is now inlined in the grid loop so the grid renders correctly. Added acmapkeyword (default"RdBu_r") on the grid signature.embedding._fallback_dpt— previously calledsc.tl.dptwithout first runningsc.tl.diffmap, causing scanpy to silently use a default-parameter diffmap and producinginfpseudotime values on disconnected subgraph components. Now explicitly runssc.tl.diffmap(n_comps=15)(refitting neighbors onX_sccsif needed) before DPT and clips any remaining non-finite values to the finite range. This makesMultiScorer.refit_pseudotime()safe on velocity-tertile and other split datasets.embedding._fill_nan— generalized to handle±infin addition toNaN(positive infinities clip to the finite max, negative infinities to the finite min, and NaNs to the finite median).plot_expression_trends— now gracefully handles a common usage pattern where the caller passes a condition-masked subset of the scoredadatatogether with a full-embeddingCommitmentScoreResult. Previously this raisedKeyError: '... are not valid obs/var names or indices'becauseresult.cell_obs_namesreferenced cells outside the passedadata. The function now intersectsresult.cell_obs_nameswithadata.obs_names, slicescell_scoresto keep only the overlapping rows, and emits aUserWarningindicating how many cells were kept. Calls using the legacy patterns (fulladatapaired with fullresult, or a manually pre-slicedresult) are unchanged and produce no warning.
Tutorial fixes
scCS_tutorial_multi.ipynb— corrected several API calls that broke under v0.7.1:MultiScorer.build_embeddingnow usesordering_metric="velocity_pseudotime"(the value"pseudotime"is no longer accepted by the embedding builder when velocity is available).compare_posthocnow passespval_correction=...(notcorrection=...).plot_expression_trendsis now called with a per-conditionCommitmentScoreResultconstructed viadataclasses.replacethat slicescell_scores/cell_obs_names/nn_cell_entropyto the condition’s mask. This works around the full-embeddingcell_scoressizing without breakingtransfer_labels.MultiScorer.transfer_labels(results, prefix=...)no longer takes anadatapositional argument.MultiScorer.compute_pairwise_deltas()no longer takes aresultsargument (it reads cached scores internally).Final UMAP visualization uses
mscorer.adata(the scorer re-exposes its working AnnData).Added a markdown caveat that the velocity-tertile split is illustrative, not a real biological perturbation; fate prevalence is imbalanced across tertiles.
scCS_tutorial_pairwise.ipynb—_driver_overlaphelper made defensive against asymmetric fate sets across conditions: usesdict.get(fate)with empty-set fallback and unions fates across all conditions instead of intersecting on the first one (which raisedKeyErrorwhen one condition had no top drivers for a given fate).scCS_tutorial_single.ipynb— removed an unsupportedverbose=Falsekeyword argument from thescorer.get_deg_drivers(...)call (the method does not acceptverbose; previously raisedTypeErroron first run).All three tutorial notebooks now start with
%matplotlib inlineso that figures render correctly when the notebooks are executed headlessly (jupyter nbconvert --execute).
Tests
Updated 6 stale tests under
TestPairScorerPipeline/TestMultiScorerPipelineto match the v0.7.x APIs:test_compare_conditions_kruskal_pathskipped —PairScorernow enforces exactly 2 conditions.test_single_condition_raisesregex updated to"exactly 2".test_validation_rejects_2_conditionsregex updated to"at least 3".test_omnibus_anovausesdf["test"].str.contains("anova")instead of equality ondf["test"].values.Three plot tests now import
matplotlib.pyplot as pltlocally.
Suite size: 168 passed, 1 skipped.
v0.7.1 (2026-06-07)¶
Bug fixes
plot_star(color_by="entropy"|"cs_entropy")— colorbar limits now auto-scale to the data range instead of being hardcoded to[0, 1], so plots dominated by high entropy no longer appear uniformly red. Added newvmin,vmax, andcmapkeyword arguments toplot_star_embedding()(and forwarded throughSingleScorer.plot_star/PairScorer.plot_star/MultiScorer.plot_star) for users who want to pin limits across figures or change the colormap.plot_star(color_by="nn_entropy"|"cs_nn_entropy")— previously fell through to the generic numeric branch and looked up the missing columnnn_entropy(actual column iscs_nn_entropy), producing a fully gray scatter. Now uses a dedicated branch that reads the correct column and emits a clear warning whenscore(k_nn=...)was not run.plot_star(color_by="pseudotime")— added explicit support (readssccs_pseudotimewith fallback tovelocity_pseudotime). Removed unsupported"cytotrace"claim from the docstring.plot_subset_comparison()— subsets containing only progenitor cells producepairwise_nCS = inffor cross-fate pairs; matplotlib was silently dropping these bars, leaving an empty plot. The bars are now rendered as gray-hatched placeholders at a small fraction of the finite maximum with an “inf” annotation, and aUserWarninglisting the affected subsets is emitted.
Tutorial improvements
Tutorial notebooks (single, pairwise, multi) now use long-form tidy DataFrames for driver and enrichment displays. New helpers
_stack_drivers,_stack_enrichment,_stack_drivers_by_condition,_stack_enrichment_by_condition, and_driver_overlapare defined inline in each tutorial’s setup cell. One ~20-row table replaces the previous per-fate / per-condition print loops.scCS_tutorial_pairwise.ipynbextended with full downstream sections matching the single-condition tutorial:§6 Driver genes per condition (velocity + DEG drivers; per-condition masking of
adata_sub; driver overlap table across conditions)§7 Pathway enrichment per condition (offline-safe via try/except)
§8 Expression trends along fate arms — per condition (
plot_expression_trendsside by side)
scCS_tutorial_multi.ipynbrebuilt on the scVelo pancreas dataset split into three RNA-velocity-magnitude tertiles (low_velocity/med_velocity/high_velocity) instead of synthetic random data. Includes all three tiers of statistical comparison plus per-condition drivers (§10), enrichment (§11), expression trends (§12), andtransfer_labels+ UMAP (§13).
Tests
Added
TestPlotStarAutoScaletotests/test_scores.pycovering:test_plot_star_entropy_autoscale— asserts that the colorbarnorm.vmin/vmaxtrack the per-cell entropy data range.test_plot_star_entropy_explicit_range— asserts that explicitvmin/vmaxkwargs are honored verbatim.test_plot_star_nn_entropy_renders— asserts a real norm (not the gray-fallback) is built forcolor_by="nn_entropy".test_plot_subset_comparison_inf_handling— asserts theUserWarningis emitted, at least one bar is hatched, and the “inf” annotation is added.
v0.7.0 (2026-06-06)¶
Breaking changes — class rename
CommitmentScorer→SingleScorer(hard rename, no alias)MultiConditionScorer→PairScorer(hard rename, no alias)All references to old names removed from code, docs, and notebooks
New module: multicomparison.py
MultiScorer— new top-level class for experiments with 3+ conditions. Validates >= 3 conditions at init; suggests PairScorer for 2 conditions.Tier 2 — Omnibus + post-hoc statistical comparison
compare_omnibus(results, test='kruskal')— omnibus test across all conditions per fate arm. Supports Kruskal-Wallis (non-parametric) and one-way ANOVA (parametric). Returns tidy DataFrame with per-fate statistics and adjusted p-values.compare_posthoc(results, method='dunn', pval_correction='fdr')— post-hoc pairwise comparisons per fate arm. Supports Dunn’s test, Tukey HSD, and Conover-Iman test. Multiple testing correction via FDR (Benjamini-Hochberg), Bonferroni, or Holm. Optionally filters to fates where omnibus test was significant.compute_pairwise_deltas(n_bootstrap=500)— ΔCS with bootstrap CI for ALL condition pairs (not just one pair like PairScorer).fit_mixed_model_contrasts(results, contrasts=None)— LMM with custom condition contrasts via Wald tests.
New visualizations
plot_omnibus_summary()— fates × conditions heatmap with omnibus p-value annotation.plot_posthoc_heatmap()— condition × condition post-hoc p-value heatmap per fate arm.plot_pairwise_delta_grid()— grid of ΔCS heatmaps for all pairs.
New documentation
mathematical_framework.rst— dedicated page with full LaTeX derivations of the scCS scoring framework, entropy metrics, and statistical tests.scCS_tutorial_multi.ipynb— new tutorial notebook for MultiScorer with 3+ conditions.Expanded
introduction.rstwith three-scorer decision flowchart.Restructured
api.rstorganized by scorer class.
New dependency
scikit-posthocs>=0.8— required for Dunn’s test and Conover-Iman post-hoc comparisons in MultiScorer.
Internal
_base.py— new module with_BaseScorerabstract class extracting shared initialization and embedding logic from SingleScorer.single.py— renamed fromtrajectory.py.pairwise.py— renamed frommulticonditional.py.CONDITION_PALETTEextended to 12 colors for 3+ condition support.conf.py— addedsphinx.ext.mathjaxfor LaTeX rendering.
v0.6.2 (2026-05-25)¶
Bug fixes
Bug 1 —
plot_nn_entropy_elbow()raisedAttributeError: 'CommitmentScorer' object has no attribute 'cluster_key'. Fixed:scorer.cluster_key→scorer.obs_keyinplot.py(the v0.6.1 rename was not propagated to this call site).Bug 2 —
score_per_subset()printed each subset result twice whenverbose=True: once from the internalscore()call and once fromscore_per_subsetitself. Fixed:score()is now always called withverbose=Falseinsidescore_per_subset; the subset header and summary are printed only byscore_per_subset.Bug 3 —
score_per_subset()producedinfinpairwise_nCSfor progenitor-only subsets (e.g., “Pre-endocrine”) with no explanation. This is mathematically correct (nCS is undefined when a fate arm has 0 cells), but was confusing. Fixed:score_per_subset()now emits aUserWarningwhen all off-diagonal nCS entries areinf, explaining that the subset contains no cells from any fate arm.CommitmentScoreResult.summary()now appends a footnote line when anypairwise_nCSentry isinf:"(inf = fate arm has 0 cells in this subset; expected for progenitor-only subsets)".
v0.6.1 (2026-05-24)¶
New features
``get_velocity_fate_drivers()`` — velocity-fate correlation driver method. Computes Spearman correlation between each gene’s velocity and per-cell fate affinity scores (CellRank-style). Returns FDR-corrected p-values via Benjamini-Hochberg. Available as
scorer.get_velocity_fate_drivers(result)and as standalonescCS.get_velocity_fate_drivers().``plot_rose_grid()`` — per-condition rose plot grid. One polar subplot per condition, all panels sharing the same radial scale for direct magnitude comparison. Available as
mscorer.plot_rose_grid(results)and as standalonescCS.plot_rose_grid().``plot_delta_cs_heatmap()`` — ΔCS heatmap with CI annotation. Visualizes
compute_delta_CS()output as a diverging heatmap annotated with Δ ± CI_half per cell. Available asmscorer.plot_delta_cs_heatmap(delta)and as standalonescCS.plot_delta_cs_heatmap().``plot_compare_conditions_bar()`` — grouped bar chart of nCS per condition. One bar group per fate pair, one bar per condition, colored by
CONDITION_PALETTE. Available asmscorer.plot_compare_conditions_bar(results)and standalone.``plot_commitment_vector_radar()`` — radar/spider chart of commitment vectors. Each condition is one closed polygon; axes = fate names; values = commitment vector (sums to 1). Falls back to bar chart for k < 3. Available as
mscorer.plot_commitment_vector_radar(results)and standalone.``CONDITION_PALETTE`` — new colorblind-safe palette for condition coloring (distinct from
FATE_PALETTE). Used automatically in all multi-condition plots.``_condition_colors()`` — helper mirroring
_fate_colors()but drawing fromCONDITION_PALETTE. Used inplot_affinity_distributions(),plot_trajectory_shift(),plot_rose_grid(), and the three new plots.
API renames (hard rename — no deprecation shims)
All renames are breaking changes. Update call sites accordingly.
Old name |
New name |
Scope |
|---|---|---|
|
|
All classes and functions |
|
|
All classes and functions |
|
|
All classes and functions |
|
|
|
|
|
Both scorers |
|
|
|
|
|
|
|
|
|
|
|
Both scorers |
|
|
drivers, enrichment, |
|
|
drivers, enrichment |
|
|
|
|
|
enrichment functions |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
multiple files |
|
|
multiple files |
|
|
|
|
|
|
|
|
Both scorers |
|
|
|
|
|
|
|
|
|
Removed
MultiConditionScorer.score_per_condition()— was a thin alias forscore_all_conditions(). Usescore_all_conditions()directly.
Bug fixes
Bug E —
plot_affinity_distributions()andplot_trajectory_shift()now useCONDITION_PALETTEfor condition colors instead ofFATE_PALETTE.Bug F —
plot_expression_trends()error message now correctly referencescompute_local_pseudotime()(wasrecompute_subset_pseudotime()).
v0.6.0 (2026-05-23)¶
Bug fixes (13 total)
Fix #1 —
plot_nn_entropy_elbowdocstring: removed false prerequisite claimingscore()must be called before the elbow plot.Fix #2 —
write_to_obs=Falseinscore(),score_per_subset(),score_all_conditions(): prevents obs column clobbering when called in loops.Fix #3 — f-string bug in
compare_conditions()verbose path: condition label was not interpolated correctly in the “no significant differences” message.Fix #4 — Removed dead
PROGENITOR_COLORimport inmulticonditional.py(was imported but never used, causing a linting warning).Fix #5 —
try/except/finallyinembedding.pyStrategy 1 cleanup: ensures temporary obs columns are removed even if an exception is raised.Fix #6 —
_needs_refitflag + improved_check_fitted()error message: raises a clear error ifscore()is called afterrefit_pseudotime()without callingfit()again.Fix #7 —
pct_fate/pct_progenitorcolumns fromptsinget_deg_drivers(): correctly extracts percent-expressed values from scanpy’srank_genes_groupsoutput.Fix #8 —
__repr__onCommitmentScorerandMultiConditionScorer: now shows root, branches, conditions, and status.Fix #9 —
statsmodelsImportError guard inplot_expression_trends(): raises a clear error with install instructions when statsmodels is absent.Fix #10 —
save()/load()serialization onCommitmentScorer: correctly round-trips all scorer state including_needs_refit.Fix #11 — Stratified
bootstrap_cs(): addedstratified=andfate_cell_indices=parameters for stratified resampling within fate arms.Fix #12 —
_resolve_gene_sets()fuzzy year-suffix matching inenrichment.py: handles Enrichr library names with year suffixes (e.g.,KEGG_2021_HumanvsKEGG_2019_Mouse).Fix #13 —
TestMultiConditionScorertest class: 26 tests covering allMultiConditionScorermethods, bringing the total to 130 passing tests.
v0.5.0 (2026-03-27)¶
New module: multiconditional.py
MultiConditionScorer— new top-level class for multi-condition experiments. Builds a shared star embedding on pooled data from all conditions, ensuring arm geometry is identical across conditions and CS values are directly comparable. WrapsCommitmentScorerinternally.Tier 1 — Core multi-condition API
build_embedding()/fit()— same interface asCommitmentScorer, operates on pooled data.score_all_conditions()— scores each condition separately using cell masks on the shared embedding. Returnsdict[condition -> CommitmentScoreResult].score_per_condition()— alias with pseudotime-aware documentation.rebuild_embedding_with_subset_pseudotime()— delegates to the shared scorer.plot_condition_star()— side-by-side star embedding panels, one per condition, with identical arm geometry and color scale.transfer_labels()— writes per-condition commitment scores to full adata.
Tier 2 — Statistical comparison
compute_delta_CS(condition_a, condition_b, n_bootstrap=500)— computes ΔCS = nCS_A − nCS_B with bootstrap confidence intervals (cell resampling within each condition). Returns full k×k delta matrix with CI bounds.compare_conditions(results, test='auto')— statistical comparison of per-cell fate affinity scores across conditions. Permutation test for k=2 conditions; Kruskal-Wallis + pairwise Mann-Whitney with Bonferroni correction for k>2. Returns tidy DataFrame with p-values and significance flags.plot_condition_comparison(results, plot_type='violin')— violin/box/strip plots of per-cell fate affinity distributions split by condition, one panel per fate.
Tier 3 — Advanced
fit_mixed_model(results, sample_key=None)— linear mixed-effects model on per-cell fate affinity scores (condition as fixed effect, sample/replicate as optional random effect) viastatsmodels MixedLM. Correct approach for datasets with multiple biological replicates per condition.trajectory_shift(results, pseudotime_col='velocity_pseudotime_sub')— tests whether pseudotime distributions differ across conditions per fate arm. Computes KS statistic + p-value and Wasserstein distance with bootstrap CI. Answers: “do cells commit earlier/later under condition B?”plot_trajectory_shift(shift_df)— KDE plots of pseudotime distributions per condition per fate arm, annotated with Wasserstein distance and KS p-value.
Bug fixes
CommitmentScorer.score_per_subset(): fixed cell mask misalignment. The mask was previously applied toself.adata.obs(full adata) but_vx/_vyare indexed toadata_sub. Now correctly usesself.adata_sub.obs[subset_key].get_velocity_drivers(): now computes delta velocity (fate arm mean minus progenitor mean) instead of raw arm mean. This removes genes constitutively active in the progenitor, highlighting fate-specific upregulation. New columndelta_velocityadded to output DataFrames; results are sorted bydelta_velocity(descending).plot_expression_trends(): addedx_axisparameter ('affinity','pseudotime','radial_distance'). Previously the x-axis was always per-cell fate affinity but was misleadingly labeled. Now supports ordering cells by pseudotime or radial distance from origin in X_sccs.compute_cell_scores(): addedmag_weight=Trueandmag_threshold_pct=5.0parameters. Cells with near-zero velocity magnitude (typically progenitors at the origin) are now down-weighted toward the uniform distribution (1/k), reducing noise from near-stationary cells. Setmag_weight=Falseto restore original behavior.
New features
CommitmentScorer.score(n_bootstrap=0, bootstrap_ci=0.95)— optional bootstrap confidence intervals on pairwise CS values. Resamples cells with replacementn_bootstraptimes and returns empirical CI bounds stored inresult.bootstrap_ci. Shown inresult.summary()when computed.bootstrap_cs(vx, vy, sectors, ...)— standalone bootstrap function exported fromscores.pyfor advanced users.CommitmentScorer.transfer_labels(adata, result)— writes per-cell fate affinities, dominant fate, entropy, NN entropy, and subset pseudotime fromadata_sub.obsback to the full adata. Cells outside the embedding subset receive NaN / ‘unassigned’.CommitmentScorer.build_embedding(scale_metric=False)— new parameter. WhenTrue, min-max scales the metric array to [0, 1] before embedding. For pseudotime, preferrebuild_embedding_with_subset_pseudotime()instead.CommitmentScoreResult.bootstrap_ci— new optional field storing the bootstrap CI dict (keys:mean,ci_low,ci_high,std,n_bootstrap,ci_level).
Pseudotime recomputation (from v0.4.x preview)
recompute_subset_pseudotime(adata_sub, adata_full, scale_01=True)— recomputes velocity pseudotime on the subset’s induced velocity subgraph. Corrects the arm-coverage problem where full-adata pseudotime is compressed within the subset. Falls back to scanpy DPT, then radial distance.scale_metric_01(scores)— standalone min-max scaler for any metric.CommitmentScorer.recompute_subset_pseudotime(scale_01=True)— convenience wrapper.CommitmentScorer.rebuild_embedding_with_subset_pseudotime()— full pipeline: recompute → map back to full-adata indices → rebuild embedding. Resets_fitted=False; callfit()again after.
API changes
score_per_subset()now acceptsn_bootstrapparameter.plot_expression_trends()x_axisparameter added (default'affinity'preserves backward compatibility).get_velocity_drivers()output DataFrames now includedelta_velocityandprogenitor_velocitycolumns in addition tomean_velocity.Version bumped to
0.5.0.
v0.3.2 (2026-03-12)¶
New features
compute_per_fate_cell_entropy(cell_scores)→ndarray shape (k,). For each fate j: mean binary Shannon entropy of each cell’s affinity scores_ijtreated as a Bernoulli distribution[s_ij, 1−s_ij], averaged over all cells. Low = cells are sharply decisive about that fate; high = cells are ambiguous (scores cluster near 0.5).compute_nn_cell_entropy(cell_scores, coords, k_nn)→ndarray shape (n_cells,). For each cell: averagecell_scoresover itsk_nnnearest neighbors in the scCS embedding (X_sccs), then compute normalized k-way Shannon entropy on the smoothed scores. Removes single-cell velocity noise while preserving local commitment structure.CommitmentScorer.score(k_nn=...)— new optional parameter. When set, computes NN-smoothed per-cell entropy and stores it inresult.nn_cell_entropyandadata_sub.obs['cs_nn_entropy'].plot_nn_entropy_elbow(scorer, k_nn_range)— two-panel figure for choosingk_nn: mean NN entropy across all cells (left) and per fate arm (right) vs k. Also accessible asscorer.plot_nn_entropy_elbow().
Changed
CommitmentScoreResultgains three new fields:per_fate_entropy(shape(k,)),nn_cell_entropy(shape(n_cells,)orNone),nn_k(intorNone).summary()now prints per-fate entropy and NN entropy (when computed).Version bumped to
0.3.2.
v0.3.1 (2026-03-12)¶
Fixed — entropy quantification redesign
The previous commitment_entropy metric operated on the aggregate
commitment vector p_vec = M_sector / sum(M_sector). A population split
50/50 between two strongly committed sub-groups yielded H ≈ 1 (maximum
uncertainty) even though every individual cell was decisive, making the
metric uninformative for real bifurcations.
compute_population_entropy(p_vec)→float. Renamed fromcompute_commitment_entropy. Same math, clarified semantics: measures how evenly total velocity mass is distributed across fate sectors.compute_mean_cell_entropy(cell_scores)→float. New primary metric. Computes normalized Shannon entropy independently for each cell’s fate-affinity vector, then averages. Correctly distinguishes a split-committed bifurcation (H_cell ≈ 0) from a genuinely uncommitted population (H_cell ≈ 1).CommitmentScoreResult: fieldcommitment_entropyrenamed topopulation_entropy; new fieldmean_cell_entropyadded.commitment_entropyretained as a deprecated property that returnspopulation_entropywith aDeprecationWarning.adata_sub.obs['cs_entropy']now stores per-cell normalized Shannon entropy (formula unchanged, now consistent withmean_cell_entropy).Version bumped to
0.3.1.
v0.2.2 (2025-03-11)¶
Bug fixes
plot_expression_trends: fixedIndexErrorwhenadatacontains more cells than the scored subset.CommitmentScoreResultnow storescell_obs_namesso expression extraction is always correctly aligned.plot_commitment_bar: fixed all--1values for k ≥ 3 furcations. Now produces k subplots (one per reference fate) so every population is shown as both query and reference. Nothing is hidden.
v0.2.1 (2025-03-10)¶
New features
plot_expression_trends(): CellRank-style gene expression vs commitment axis plot. Cells binned by per-cell fate affinity score; mean expression per bin plotted with LOWESS smooth. Supports any gene inadata.var_names, any AnnData layer, and custom fate selection.color_mapparameter added to all plot functions. Pass a dict of{fate_name: hex_color}to preserve your original scanpy/Seurat cluster colors across all scCS plots. Progenitor cells always remain gray.plot_commitment_barrewritten: now shows unCS (solid bars) and nCS (hatched bars, same fate color) side by side. CS = 1 reference line included.
Internal
_fate_colors()updated to accept optionalcolor_mapoverride.CommitmentScoreResult.cell_obs_namesfield added.
v0.2.0 (2025-03-07)¶
New features
Generalized k-furcation support (k ≥ 2).
plot_pairwise_cs(): heatmap of full k×k unCS/nCS matrix.plot_commitment_heatmap(): per-cell fate affinity heatmap.plot_subset_comparison(): compare CS across experimental subsets viascorer.score_per_subset().get_velocity_drivers(): rank genes by mean scVelo velocity per fate arm.get_deg_drivers(): Wilcoxon rank-sum DEG analysis per fate arm.run_enrichment_per_fate(): Enrichr ORA (KEGG, GO BP, Reactome).Fate detection backends: GMM, PAGA, CellRank, supervised.
v0.1.0 (2025-03-01)¶
Initial release
2-state (homeostatic/activated) commitment score framework.
unCS and nCS for bifurcation (k=2).
Radial star embedding (
X_sccsinobsm).plot_star_embedding(),plot_rose().Based on: Kriukov et al. (2025).