From 0418ee2a9ffcda28de3d20b5c2ea6732859357ab Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Fri, 12 Aug 2022 16:39:37 +0200 Subject: [PATCH] REGR: fix reset_index (Index.insert) regression with custom Index subclasses --- doc/source/whatsnew/v1.4.4.rst | 1 + pandas/core/indexes/base.py | 10 ++++--- pandas/tests/indexes/test_subclass.py | 38 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 pandas/tests/indexes/test_subclass.py diff --git a/doc/source/whatsnew/v1.4.4.rst b/doc/source/whatsnew/v1.4.4.rst index 57b8fdee5888a..19434aefa0afb 100644 --- a/doc/source/whatsnew/v1.4.4.rst +++ b/doc/source/whatsnew/v1.4.4.rst @@ -19,6 +19,7 @@ Fixed regressions - Fixed regression in :meth:`DataFrame.loc` not updating the cache correctly after values were set (:issue:`47867`) - Fixed regression in :meth:`DataFrame.loc` not aligning index in some cases when setting a :class:`DataFrame` (:issue:`47578`) - Fixed regression in setting ``None`` or non-string value into a ``string``-dtype Series using a mask (:issue:`47628`) +- Fixed regression using custom Index subclasses (for example, use in xarray) with :meth:`~DataFrame.reset_index` or :meth:`Index.insert` (:issue:`47071`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 7e2a9184f04d9..0d29aa49bb4c2 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6902,10 +6902,12 @@ def insert(self, loc: int, item) -> Index: loc = loc if loc >= 0 else loc - 1 new_values[loc] = item - # Use self._constructor instead of Index to retain NumericIndex GH#43921 - # TODO(2.0) can use Index instead of self._constructor - # Check if doing so fixes GH#47071 - return self._constructor._with_infer(new_values, name=self.name) + if self._typ == "numericindex": + # Use self._constructor instead of Index to retain NumericIndex GH#43921 + # TODO(2.0) can use Index instead of self._constructor + return self._constructor._with_infer(new_values, name=self.name) + else: + return Index._with_infer(new_values, name=self.name) def drop( self, diff --git a/pandas/tests/indexes/test_subclass.py b/pandas/tests/indexes/test_subclass.py new file mode 100644 index 0000000000000..2ddf3baabbec0 --- /dev/null +++ b/pandas/tests/indexes/test_subclass.py @@ -0,0 +1,38 @@ +""" +Tests involving custom Index subclasses +""" +import numpy as np + +from pandas import ( + DataFrame, + Index, +) +import pandas._testing as tm + + +class CustomIndex(Index): + def __new__(cls, data, name=None): + # assert that this index class cannot hold strings + if any(isinstance(val, str) for val in data): + raise TypeError("CustomIndex cannot hold strings") + + if name is None and hasattr(data, "name"): + name = data.name + data = np.array(data, dtype="O") + + return cls._simple_new(data, name) + + +def test_insert_fallback_to_base_index(): + # https://github.com/pandas-dev/pandas/issues/47071 + + idx = CustomIndex([1, 2, 3]) + result = idx.insert(0, "string") + expected = Index(["string", 1, 2, 3], dtype=object) + tm.assert_index_equal(result, expected) + + df = DataFrame( + np.random.randn(2, 3), columns=idx, index=Index([1, 2], name="string") + ) + result = df.reset_index() + tm.assert_index_equal(result.columns, expected)