feat(plotting): add ncols to wrap multi-panel violin plots (#1552)#4132
feat(plotting): add ncols to wrap multi-panel violin plots (#1552)#4132Hore01 wants to merge 4 commits into
ncols to wrap multi-panel violin plots (#1552)#4132Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4132 +/- ##
==========================================
+ Coverage 79.61% 79.64% +0.02%
==========================================
Files 120 120
Lines 12786 12794 +8
==========================================
+ Hits 10180 10190 +10
+ Misses 2606 2604 -2
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Adds an `ncols` parameter to `sc.pl.violin` for wrapping multi-key violins into a grid layout, matching the convention already used by `sc.pl.umap` / `sc.pl.embedding`. Two layout paths are affected: - `multi_panel=True` (the seaborn `catplot` path): forwards `ncols` to seaborn's `col_wrap=`, so the wrapping is handled by the FacetGrid. The existing stripplot/rotation loops were switched from `g.axes[0]` 2D indexing to `g.axes_dict` so they work for both the wrapped and non-wrapped cases. - `groupby` set, multiple keys (the legacy `setup_axes` path): when `ncols` is provided, uses `_panel_grid` (already battle-tested by `sc.pl.embedding`) to build a gridspec figure. The `_panel_grid` import is local to the function to avoid the `_tools/__init__.py` -> `_anndata.py` cycle. `ncols=None` (the default) preserves byte-identical output for every existing test_violin baseline. Closes scverse#1552.
Brings patch coverage on the previous commit from 25% to ~90%+ by exercising the gridspec branch added in 64feb1e. Mirrors the existing groupby subtest's pandas-3 xfail.
b1fd1a2 to
37ae108
Compare
Adds two subtests exercising the `ax is not None` paths in `sc.pl.violin`: - `ax_provided_single_key` hits the legacy single-axis branch (ax provided, ncols default None). - `ax_with_ncols_raises` asserts the explicit ValueError when the new ncols parameter is combined with a pre-supplied ax. Together these lift patch coverage on the new branches that codecov flagged on this PR.
|
Rebased on main — the earlier All CI checks pass except |
Summary
Adds an
ncolsparameter tosc.pl.violinso multi-key violins can wrap into a grid instead of one giant horizontal row. Mirrors thencolsconvention already used bysc.pl.umap/sc.pl.embedding.Closes #1552.
What changes
Two code paths in
violin()get the new parameter:multi_panel=True(seaborncatplotpath), forwardsncolsto seaborn'scol_wrap=. The existing stripplot and rotation loops usedg.axes[0]2D indexing, whichcol_wrapflattens, so I swapped them tog.axes_dict, same semantics either way, but compatible with both shapes.groupbyset, multiple keys (legacysetup_axespath) — whenncolsis provided, builds a gridspec figure via_panel_grid(already used bysc.pl.embedding). The import is local to the function to dodge the_tools/__init__.py↔_anndata.pycircular-import (which I hit on the first try — the package's_tools/__init__.pyalready importsrankingfrom
_anndata.py, so a module-level import would cycle).ncols=None(default) flows through the unchanged code, so every existingtest_violinimage baseline stays byte-identical.Why hard-code
hspace/wspaceembeddingexposes both; doing the same here would add API surface for no gain on the issue as filed. Defaults(0.25, 0.1)matchembedding. Easy follow-up if anyone asks.Tests
Added one new subtest in
test_violin:The
assert len(...) == 3is cheap insurance;tol=40would happily mask an off-by-one in panel count, and an earlier draft of this patch had exactly that bug (the originalPath Bonly made one axis whengroupby is Nonebecause the data is melted into a single column, which is why I ended up wiring the wrap intomulti_panel=Trueinstead). The baseline attests/_images/violin_ncols_wrap/expected.pngwas generated locally and visually checked.Full
test_violin(all four subtests) passes locally.Refs
good first issue)_panel_gridinsrc/scanpy/plotting/_tools/scatterplots.pydocs/release-notes/<PR>.feat.md; placeholder added, will rename to the real number in a follow-up commit