From 2adbd92c3132adf5511e492ea034657506a0e975 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 22 Aug 2022 21:46:20 +0200 Subject: [PATCH] Backport PR #48058: REGR: fix reset_index (Index.insert) regression with custom Index subclasses --- doc/source/whatsnew/v1.4.4.rst | 1 + pandas/core/indexes/base.py | 9 ++++--- pandas/tests/indexes/test_subclass.py | 38 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 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 25db2ef7253d9..b09d14010545c 100644 --- a/doc/source/whatsnew/v1.4.4.rst +++ b/doc/source/whatsnew/v1.4.4.rst @@ -22,6 +22,7 @@ Fixed regressions - Fixed regression in :meth:`DataFrame.loc` not aligning index in some cases when setting a :class:`DataFrame` (:issue:`47578`) - Fixed regression in :meth:`DataFrame.loc` setting a length-1 array like value to a single value in the DataFrame (:issue:`46268`) - 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, used in xarray) with :meth:`~DataFrame.reset_index` or :meth:`Index.insert` (:issue:`47071`) - Fixed regression in :meth:`DatetimeIndex.intersection` when the :class:`DatetimeIndex` has dates crossing daylight savings time (:issue:`46702`) - Fixed regression in :func:`merge` throwing an error when passing a :class:`Series` with a multi-level name (:issue:`47946`) - Fixed regression in :meth:`DataFrame.eval` creating a copy when updating inplace (:issue:`47449`) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 5707ba1e025a4..85b9b48110602 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6614,9 +6614,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 - 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, labels, errors: str_t = "raise") -> Index: """ 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)