Repository navigation
Description
Tracking issue for migration to Pandas 3.0
Pandas released version 3.0 in Jan 2026 which introduces breaking changes for our codebase. It would be useful to address the places where it currently breaks, so we can remove the upperbound version we introduced in #3186. Even if we eventually drop pandas altogether this is probably a worthwhile effort.
Migration is prepared in separate PRs and will require pre-emptive regression testing before final removal of the upperbound.
Some examples of the required changes are listed here. For a full overview see the Pandas migration guide.
Copy on write (CoW)
This is arguably one of the most important ones as it removes the possibility of mutating parent dataframes via assignments in their subset views (e.g. chained .loc[..] assignment), for more predictable behavior. The worst-case to avoid is that data manipulations in our codebase might silently take no effect on the intended parent DataFrame.
String extension
Pandas 3.0 introduces a new default str datatype that is more efficient than object. Unfortunately this default datatype does not work well with our multi-index HDFs: by default the index/column levels become the new str extension dtype instead of object and PyTables (to_hdf) cannot serialize a MultiIndex whose levels use extension dtypes:
NotImplementedError: Saving a MultiIndex with an extension dtype is not supported.
This would require us to change all dataframes back to object before saving.
PRs
Activity
Guard CoW read-only .to_numpy()/.values mutations (#3362)
Guard remaining CoW read-only .to_numpy()/.values mutations
AxelNoun commentedon Oct 7, 2026
Notes on the pandas 3 migration, checked on 2026-10-07 against
devat 350917d.Canary cell
pandas>=3(ubuntu, Python 3.12, pandas 3.0.6). The most recent run of this cell (job on #3544, againstdev, 2026-10-06) reports5 failed, 3587 passed, 41 skipped, 6 xfailed, and the functional scripts pass. The three runs of 2026-10-01 on #3540, #3541 and #3542 (againstmain) show the same five failures. All five are intests/utils/test_pandas_cow.py, which @deruyter92 added to #3445. Each of these tests setsDLC_PANDAS_FUTURE=1, so the version gate indeeplabcut/utils/pandas_future_mode.py, which by design accepts only pandas 2.3.x, raises:These failures therefore come from the test setup, not from DeepLabCut behaviour under pandas 3.
Two call sites that the canary does not exercise. No test under
tests/references either module, and neither functional script uses them. I ran the exact source of the code involved (_format_gt_dataand its helpers;filter_low_proband thegroupbystatement), extracted fromdevat 350917d, on synthetic DLC-style frames:deeplabcut/gui/tracklet_toolbox.py:886,df.groupby(level="bodyparts", axis=1, group_keys=False).apply(filter_low_prob, prob=pcutoff): under pandas 3.0.6, it raisesTypeError: DataFrame.groupby() got an unexpected keyword argument 'axis'; under pandas 2.3.3, it runs with aFutureWarning.deeplabcut/benchmark/metrics.py:38,df.stack("individuals", dropna=False)in_format_gt_data: under pandas 3.0.6, it raisesValueError: dropna must be unspecified as the new implementation does not introduce rows of NA values; under pandas 2.3.3, it runs with aFutureWarning.Two observations that may be relevant to the fix:
metrics.py: withstack("individuals", future_stack=True)instead,_format_gt_dataruns on both versions, and under 2.3.3 it returns the same arrays and metadata as the current code on a synthetic two-animal file with missing values. This is a single synthetic case, not a proof of equivalence.tracklet_toolbox.py: under 2.3.3, eachbodypartsgroup contains the columns of every individual, andcols.iloc[:, 2]is the likelihood of the first individual only. On a synthetic two-animal frame, a low likelihood for animal1 also sets the keypoint of animal2 to NaN, whereas a low-likelihood keypoint of animal2 is kept. A port that preserves the grouping andiloc[:, 2]would preserve this behaviour; masking per individual would change what is exported. Is the current behaviour intended?HDF and the
strdtype (issue description). On pandas 3.0.6 with PyTables 3.11.1, synthetic DLC-style frames whose four column levels have the newstrdtype round-trip throughto_hdf/read_hdfunder a strictassert_frame_equal(dtypes included), with either the python or the pyarrow string storage. This holds infixedformat with aRangeIndexor alabeled-data/video/imageMultiIndexas row index, and intableformat with aRangeIndex. With a rowMultiIndex,tableformat already fails on pandas 2.3.3, with aValueErroraboutdata_columns. TheNotImplementedErrorquoted in the description is raised by pandas 2.3.3 infixedformat oncefuture.infer_stringis enabled, aspandas_future_mode.pydoes; in pandas 3.0.0 and 3.0.6,pandas/io/pytables.pyskips this check forStringDtypelevels. For these layouts, the conversion back toobjectbefore saving may therefore not be needed under pandas 3; I have not tested this on real project files.@deruyter92, as the assignee of this issue (the
#3362TODOs in the code also carry your handle): are these sites already covered in your migration work? If not, would separate small PRs againstdevbe useful, first fortest_pandas_cow.pyunder pandas >= 3, then for each call site with a regression test?