Skip to content

fix: multi-column groupby crashing on non-string observations - #4385

Merged
flying-sheep merged 8 commits into
scverse:mainfrom
barlowa124:fix/multi-groupby-nonstr
Nov 12, 2026
Merged

flying-sheep merged 8 commits into
scverse:mainfrom
barlowa124:fix/multi-groupby-nonstr

Conversation

@barlowa124

@barlowa124 barlowa124 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4384

For multi-column groupby, _prepare_dataframe joins the key values
with "_".join over raw values and takes ordering levels from
.cat.categories. A non-string key column (integer categorical, plain
numeric) or a NaN row then fails with TypeError: sequence item 1: expected str instance. Single-key groupby handles the same columns
correctly. The breakage is specific to the multi-key join. The shared
helper is used by dotplot, matrixplot, stacked_violin,
tracksplot, heatmap and the dendrogram path, so they all gain the
fix.

  • Joined elements pass through str() individually.
  • Ordering levels come from .cat.categories on categorical columns
    and np.unique otherwise.
  • Rows missing any key value join to no category, matching the
    single-column path where NaN never forms one.

Tests

test_plotting.py gains parametrized test_multi_groupby_nonstring
and test_multi_groupby_partial_nan. Integer-categorical and numeric
second keys plus partially-NaN keys are pushed through sc.pl.dotplot
and sc.pl.matrixplot, with joined labels asserted on the returned
plot object. The pre-existing test_violin image-compare failures in
this file are seaborn drift, identical on main.

…#4384)

_prepare_dataframe joined multi-column groupby values with "_".join
directly and took ordering levels from .cat.categories, so integer
categoricals and plain numeric columns raised TypeError. Convert values
with astype(str), fall back to np.unique for non-categorical columns,
and order unmatched joined labels (NaN combinations) last.
@barlowa124 barlowa124 changed the title Fix multi-column groupby crashing on non-string observations fix: multi-column groupby crashing on non-string observations Sep 28, 2026
@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.11%. Comparing base (50bb2b0) to head (93ac9f0).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4385   +/-   ##
=======================================
  Coverage   85.11%   85.11%           
=======================================
  Files         139      139           
  Lines       12318    12318           
=======================================
  Hits        10484    10484           
  Misses       1834     1834           
Flag Coverage Δ
hatch-test.low-vers 78.68% <100.00%> (ø)
hatch-test.pre 84.89% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/scanpy/plotting/legacy/_anndata.py 85.72% <100.00%> (ø)

…e#4384)

_prepare_dataframe joined multi-column groupby values with "_".join
directly and took ordering levels from .cat.categories, so integer
categoricals and plain numeric columns raised TypeError. Join values
through per-element str(), take levels from np.unique for
non-categorical columns, and give rows missing a groupby value no
joined category, matching the single-column path.
@barlowa124
barlowa124 force-pushed the fix/multi-groupby-nonstr branch from ebffb76 to 682a0c3 Compare September 28, 2026 18:13
@barlowa124

barlowa124 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor Author

The only failing check is the title/milestone/labels metadata check, which needs maintainer-side labels.

@Zethson Zethson added this to the 1.12.5 milestone Oct 2, 2026
@Zethson

Zethson commented Oct 2, 2026

Copy link
Copy Markdown
Member

You're committing hundreds of times in weeks. Have you human reviewed your own PR? Otherwise, I'll close this as maintainers can guide an agent to exactly the needed solution much better.

@barlowa124

barlowa124 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Yes, this diff was human-reviewed before posting. Multi-column groupby joined obs values with "_".join, which raised TypeError on integer or float columns. The fix maps values through str, keeps the single-column NaN rule (a row missing any key gets no category), and falls back to np.unique for ordering when a column is not categorical. The added tests run under the hatch-test CI matrices (3.12 low-vers, 3.14 stable/pre/few-extras), which pass.

On commit volume, I can squash the branch if that is easier to review.

@flying-sheep flying-sheep modified the milestones: 1.12.5, 1.12.6, 1.12.7 Oct 29, 2026
Comment thread src/scanpy/plotting/legacy/_anndata.py Outdated
*(
obs_tidy[g].cat.categories
if isinstance(obs_tidy[g].dtype, CategoricalDtype)
else np.unique(obs_tidy[g])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

np.unique sorts object values, so this still raises for an ordinary non-categorical grouping column containing mixed strings and integers, or strings and missing values. For example, np.unique(pd.Series([1, 'control'], dtype=object)) raises TypeError: '<' not supported between instances of 'str' and 'int'; ['control', None] fails similarly. This PR is intended to support non-string observations. Could we use pd.unique/a categorical-safe unique operation (excluding missing values) and add a mixed-type and missing-value regression for multi-column grouping?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the correction, @barlowa124. I checked head b927638: the mixed int/string and string/missing cases now fall back to pd.unique with missing values dropped, while ordinary numeric values retain the sorted path. I independently exercised those three cases, and the new hosted test matrices pass. That resolves the correctness issue I raised. One small CI fix remains: the ruff check job reports D205 at tests/plotting/legacy/test_plotting.py:444 because the new multi-line test docstring needs a blank line between its summary and description. Appreciate the follow-through.

np.unique raises TypeError on object columns mixing ints and strings
or containing missing values. Fall back to pd.unique in appearance
order with missing values dropped, and cover the case with a
mixed-type/missing-value regression test. Addresses review on scverse#4385.
@barlowa124

Copy link
Copy Markdown
Contributor Author

Fixed in d31f6df. np.unique now sits behind a helper that falls back to pd.unique in appearance order with missing values dropped when sorting raises TypeError, covering mixed int/string and string/None object columns. test_multi_groupby_mixed_type exercises a mixed-type column with missing entries for dotplot and matrixplot. pytest tests/plotting/legacy/test_plotting.py -k multi_groupby passes locally, 8 tests.

@flying-sheep
flying-sheep merged commit e099d9b into scverse:main Nov 12, 2026
14 checks passed
flying-sheep pushed a commit that referenced this pull request Nov 13, 2026
…g on non-string observations) (#4517)

Co-authored-by: Angus Barlow <[email protected]>
@sylvesterkaczmarek

Copy link
Copy Markdown

Thanks for following up on the mixed-type case, @barlowa124, and for taking it through to merge. Glad the fallback now covers mixed string/integer and missing values, with the grouping regressions in place. Appreciate the careful correction.

@flying-sheep flying-sheep modified the milestones: 1.12.7, 1.12.6 Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-column groupby crashes on non-string observations in dotplot/matrixplot/heatmap

4 participants