Repository: pandas-dev/pandas
Pull request #57647 (2024-02-27 17:33:32)

PERF: Return RangeIndex from RangeIndex.reindex when possible
Discovered in https://github.com/pandas-dev/pandas/pull/57441

datapythonista: I think something went wrong with git here, feels like this have unrelated changes, right?

Patch:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index fae7edba05..23ae0f3ea1 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -256,6 +256,7 @@ Performance improvements
 - Performance improvement in :meth:`RangeIndex.__getitem__` with a boolean mask returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57588`)
 - Performance improvement in :meth:`RangeIndex.append` when appending the same index (:issue:`57252`)
 - Performance improvement in :meth:`RangeIndex.join` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57651`)
+- Performance improvement in :meth:`RangeIndex.reindex` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57647`)
 - Performance improvement in :meth:`RangeIndex.take` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57445`)
 - Performance improvement in ``DataFrameGroupBy.__len__`` and ``SeriesGroupBy.__len__`` (:issue:`57595`)
 - Performance improvement in indexing operations for string dtypes (:issue:`56997`)
diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py
index d6a7509e60..09d635b53c 100644
--- a/pandas/core/indexes/range.py
+++ b/pandas/core/indexes/range.py
@@ -29,6 +29,7 @@ from pandas.util._decorators import (
     doc,
 )
 
+from pandas.core.dtypes import missing
 from pandas.core.dtypes.base import ExtensionDtype
 from pandas.core.dtypes.common import (
     ensure_platform_int,
@@ -475,7 +476,7 @@ class RangeIndex(Index):
             # GH 46675 & 43885: If values is equally spaced, return a
             # more memory-compact RangeIndex instead of Index with 64-bit dtype
             diff = values[1] - values[0]
-            if diff != 0:
+            if not missing.isna(diff) and diff != 0:
                 maybe_range_indexer, remainder = np.divmod(values - values[0], diff)
                 if (
                     lib.is_range_indexer(maybe_range_indexer, len(maybe_range_indexer))
@@ -490,6 +491,11 @@ class RangeIndex(Index):
         result._cache = self._cache
         return result
 
+    def _wrap_reindex_result(self, target, indexer, preserve_names: bool):
+        if not isinstance(target, type(self)) and target.dtype.kind == "i":
+            target = self._shallow_copy(target._values, name=target.name)
+        return super()._wrap_reindex_result(target, indexer, preserve_names)
+
     @doc(Index.copy)
     def copy(self, name: Hashable | None = None, deep: bool = False) -> Self:
         name = self._validate_names(name=name, deep=deep)[0]


Test patch:
diff --git a/pandas/tests/indexes/ranges/test_range.py b/pandas/tests/indexes/ranges/test_range.py
index 8b4b7a5d70..898548d1cc 100644
--- a/pandas/tests/indexes/ranges/test_range.py
+++ b/pandas/tests/indexes/ranges/test_range.py
@@ -608,6 +608,26 @@ class TestRangeIndex:
         tm.assert_index_equal(result, expected)
 
 
+def test_reindex_returns_rangeindex():
+    ri = RangeIndex(2, name="foo")
+    result, result_indexer = ri.reindex([1, 2, 3])
+    expected = RangeIndex(1, 4, name="foo")
+    tm.assert_index_equal(result, expected, exact=True)
+
+    expected_indexer = np.array([1, -1, -1], dtype=np.intp)
+    tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
+def test_reindex_returns_index():
+    ri = RangeIndex(4, name="foo")
+    result, result_indexer = ri.reindex([0, 1, 3])
+    expected = Index([0, 1, 3], name="foo")
+    tm.assert_index_equal(result, expected, exact=True)
+
+    expected_indexer = np.array([0, 1, 3], dtype=np.intp)
+    tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
 def test_take_return_rangeindex():
     ri = RangeIndex(5, name="foo")
     result = ri.take([])
diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py
index 7208c688bd..9c33d15c01 100644
--- a/pandas/tests/indexing/test_loc.py
+++ b/pandas/tests/indexing/test_loc.py
@@ -1204,10 +1204,7 @@ class TestLocBaseIndependent:
         data = [1, 2]
         df = DataFrame(columns=["x", "y"])
         df.index = df.index.astype(np.int64)
-        msg = (
-            rf"None of \[Index\(\[0, 1\], dtype='{np.dtype(int)}'\)\] "
-            r"are in the \[index\]"
-        )
+        msg = r"None of .*Index.* are in the \[index\]"
         with pytest.raises(KeyError, match=msg):
             df.loc[[0, 1], "x"] = data