Skip to content

fix: support pandas 3 and anndata 0.13#16

Open
settylab-dotto-bot[bot] wants to merge 1 commit into
mainfrom
dominik/pandas3-anndata013-compat
Open

fix: support pandas 3 and anndata 0.13#16
settylab-dotto-bot[bot] wants to merge 1 commit into
mainfrom
dominik/pandas3-anndata013-compat

Conversation

@settylab-dotto-bot

Copy link
Copy Markdown
Contributor

Why

The test (3.12) CI leg is red on main and on every open PR (40 failed, 1981 passed). It is not caused by any recent change: pandas 3.0.3 and anndata 0.13.1 ship 3.12-only wheels, so the 3.12 matrix entry resolves them while 3.10/3.11 stay on pandas 2.3.3 / anndata 0.12.19. pyproject.toml declares pandas>=1.3.0 with no upper bound, so the new majors were picked up the moment they were released.

Verified by checking out main (a6702b2) into a clean tree and running the same tests against pandas 3.0.3 / anndata 0.13.1: identical 20/20 failures. This branch is the fix, not a workaround.

What actually broke

Four independent contract changes, all real incompatibilities in kompot:

  1. np.issubdtype cannot interpret pandas extension dtypes. pandas 3 stores plain string columns as StringDtype instead of object, so np.issubdtype(col.dtype, np.number) raises TypeError: Cannot interpret '<StringDtype(...)>' as a data type. This took out every dtype branch in group_utils — i.e. groups="cell_type", the single most common way to group. Now dispatched through pandas.api.types.

  2. Series.__getitem__ lost its positional fallback. group_value[0] raised KeyError: 0 for a pd.Series group with a non-integer index. Now indexed with .iloc.

  3. volcano_de misclassified categorical backgrounds. It tested dtype == "object" to decide categorical-vs-continuous; a pandas-3 string column is StringDtype, so it fell through to the continuous path and handed 'category_1' to mpl.colors.Normalize (ValueError: could not convert string to float). Any non-numeric dtype is now treated as categorical.

  4. anndata 0.13 exposes X as layers[None]. Iterating adata.layers therefore also yields None, which broke _detect_condition_label ('NoneType' object has no attribute 'startswith') and made len(adata.layers) count X. Added kompot.anndata.utils.layer_names() and used it wherever layer names are enumerated.

No suppression

No test was skipped, xfailed, or loosened, and no matrix entry was dropped. The two test edits swap bare adata.layers iteration for layer_names(adata) — that is the same set of keys on anndata < 0.13, and the correct set on 0.13, where X would otherwise be counted as a layer.

Verification

Affected modules, run locally against both dependency sets on Python 3.12:

stack result
pandas 3.0.3 / anndata 0.13.1 / scanpy 1.12.2 (the 3.12 CI leg) 241 passed, 3 skipped
pandas 2.3.3 / anndata 0.12.19 / scanpy 1.11.5 (the 3.10/3.11 legs) 241 passed, 3 skipped

Backward compatible; no new pin or lower bound is required.

Unblocks

PR #15 (tutorials) is red for exactly this reason and is not at fault. Once this lands on main, re-running #15's checks turns it green without touching that branch — GitHub tests the PR merged into main.

@settylab-dotto-bot settylab-dotto-bot Bot requested a review from katosh as a code owner July 12, 2026 04:12
@settylab-dotto-bot

Copy link
Copy Markdown
Contributor Author

CI green on all three legs

Run 29179372179:

leg before (on main / #15) after
test (3.10) success success
test (3.11) success success
test (3.12) 40 failed, 1981 passed, 34 skipped 0 failed, 2021 passed, 34 skipped
codecov patch/project success success

The 3.12 leg still resolves pandas 3.0.3 and anndata 0.13.1 — the exact versions that broke it — and now passes.

Worth stating explicitly, because it is the whole question with a CI fix: passed went 1981 → 2021, up by exactly the 40 that were failing, while skipped stayed at 34. Nothing was skipped, xfailed, or loosened, and no matrix entry was dropped — a suppression would have moved the skipped count or removed a leg. The 40 were fixed.

Also verified backward-compatible: the affected modules give an identical 241 passed, 3 skipped under pandas 2.3.3 / anndata 0.12.19 (the 3.10/3.11 resolution) and under pandas 3.0.3 / anndata 0.13.1.

The Python 3.12 CI leg resolves pandas 3.0 and anndata 0.13 (their wheels
are 3.12-only), which broke 40 tests. Both releases changed contracts that
kompot relied on:

- pandas 3 stores plain string columns as StringDtype rather than object.
  np.issubdtype() cannot interpret pandas extension dtypes and raises
  TypeError, so every dtype branch in group_utils went down. Detect dtypes
  with pandas.api.types instead.
- pandas 3 dropped the positional fallback in Series.__getitem__, so
  group_value[0] raised KeyError on a Series with a non-integer index.
  Index positionally with .iloc.
- volcano_de classified a background colour column as categorical by
  testing dtype == "object", which no longer holds for string columns; it
  then fed strings to a continuous Normalize. Treat any non-numeric dtype
  as categorical.
- anndata 0.13 exposes X as layers[None], so iterating adata.layers now
  also yields None. Add kompot.anndata.utils.layer_names() and use it
  wherever layer names are enumerated.

Verified against both pandas 3.0.3/anndata 0.13.1 and pandas
2.3.3/anndata 0.12.19: 241 passed, 3 skipped in the affected modules on
each.
@settylab-dotto-bot settylab-dotto-bot Bot force-pushed the dominik/pandas3-anndata013-compat branch from 4de3cef to 761af00 Compare July 12, 2026 06:06
@settylab-dotto-bot

Copy link
Copy Markdown
Contributor Author

Independent skeptic validation: check.

  • Reproduced 41 failures on plain main (a6702b2) against pandas 3.0.3 / anndata 0.13.1 with zero PR content applied — the CI break is pre-existing, not introduced here.
  • All four fixes are genuine, and there are zero regressions in parse_groups / apply_cell_filter on either dependency stack (pandas 3.0.3 / anndata 0.13.1 and pandas 2.3.3 / anndata 0.12.19).

It found one real defect, now corrected: the CHANGELOG claimed "Behaviour on pandas 2 / anndata 0.12 is unchanged." That is not strictly true. Treating any non-numeric dtype as categorical also changes the old stack: a volcano_de background column of nullable boolean dtype previously fell through to a continuous colormap and is now coloured categorically. Verified on pandas 2.3.3 — numpy bool was already categorical under the old dtype == "bool" test, nullable boolean was not.

The change is intentional (it aligns nullable booleans with numpy booleans), but it is user-visible, so the CHANGELOG now names it as a deliberate exception rather than denying it. Amended onto the branch as 761af00.

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.

1 participant