Skip to content

Commit 131c41f

Browse files
committed
Issue 60869
1 parent f8d5a16 commit 131c41f

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Other enhancements
7070
- :meth:`.DataFrameGroupBy.transform`, :meth:`.SeriesGroupBy.transform`, :meth:`.DataFrameGroupBy.agg`, :meth:`.SeriesGroupBy.agg`, :meth:`.SeriesGroupBy.apply`, :meth:`.DataFrameGroupBy.apply` now support ``kurt`` (:issue:`40139`)
7171
- :meth:`DataFrame.apply` supports using third-party execution engines like the Bodo.ai JIT compiler (:issue:`60668`)
7272
- :meth:`DataFrame.iloc` and :meth:`Series.iloc` now support boolean masks in ``__getitem__`` for more consistent indexing behavior (:issue:`60994`)
73+
- :meth:`DataFrame.unset_index` is a new method that resets the index of the DataFrame to the default integer index (:issue:`60869`)
7374
- :meth:`DataFrameGroupBy.transform`, :meth:`SeriesGroupBy.transform`, :meth:`DataFrameGroupBy.agg`, :meth:`SeriesGroupBy.agg`, :meth:`RollingGroupby.apply`, :meth:`ExpandingGroupby.apply`, :meth:`Rolling.apply`, :meth:`Expanding.apply`, :meth:`DataFrame.apply` with ``engine="numba"`` now supports positional arguments passed as kwargs (:issue:`58995`)
7475
- :meth:`Rolling.agg`, :meth:`Expanding.agg` and :meth:`ExponentialMovingWindow.agg` now accept :class:`NamedAgg` aggregations through ``**kwargs`` (:issue:`28333`)
7576
- :meth:`Series.map` can now accept kwargs to pass on to func (:issue:`59814`)

pandas/core/frame.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
DatetimeIndex,
151151
Index,
152152
PeriodIndex,
153+
RangeIndex,
153154
default_index,
154155
ensure_index,
155156
ensure_index_from_sequences,
@@ -6367,6 +6368,90 @@ class max type
63676368

63686369
return None
63696370

6371+
def unset_index(
6372+
self,
6373+
*,
6374+
inplace: bool = False,
6375+
) -> DataFrame | None:
6376+
"""
6377+
Remove the index and restore the default RangeIndex.
6378+
6379+
This method resets the index of the DataFrame to the default integer index
6380+
(RangeIndex), always dropping the old index and never inserting it as a column.
6381+
If the DataFrame already has the default index, this is a no-op.
6382+
6383+
Parameters
6384+
----------
6385+
inplace : bool, default False
6386+
Whether to modify the DataFrame in place or return a new DataFrame.
6387+
6388+
Returns
6389+
-------
6390+
DataFrame or None
6391+
DataFrame with the default integer index or None if ``inplace=True``.
6392+
6393+
See Also
6394+
--------
6395+
DataFrame.reset_index : Reset the index, with options to insert as columns.
6396+
DataFrame.set_index : Set the DataFrame index using existing columns.
6397+
6398+
Examples
6399+
--------
6400+
>>> df = pd.DataFrame(
6401+
... [("bird", 389.0), ("bird", 24.0), ("mammal", 80.5), ("mammal", np.nan)],
6402+
... index=["falcon", "parrot", "lion", "monkey"],
6403+
... columns=("class", "max_speed"),
6404+
... )
6405+
>>> df
6406+
class max_speed
6407+
falcon bird 389.0
6408+
parrot bird 24.0
6409+
lion mammal 80.5
6410+
monkey mammal NaN
6411+
6412+
Remove the index and use the default integer index:
6413+
6414+
>>> df.unset_index()
6415+
class max_speed
6416+
0 bird 389.0
6417+
1 bird 24.0
6418+
2 mammal 80.5
6419+
3 mammal NaN
6420+
6421+
If the DataFrame already has the default index, nothing changes:
6422+
6423+
>>> df2 = df.unset_index()
6424+
>>> df2.unset_index()
6425+
class max_speed
6426+
0 bird 389.0
6427+
1 bird 24.0
6428+
2 mammal 80.5
6429+
3 mammal NaN
6430+
6431+
Modify the DataFrame in place:
6432+
6433+
>>> df.unset_index(inplace=True)
6434+
>>> df
6435+
class max_speed
6436+
0 bird 389.0
6437+
1 bird 24.0
6438+
2 mammal 80.5
6439+
3 mammal NaN
6440+
"""
6441+
6442+
if (
6443+
isinstance(self.index, RangeIndex)
6444+
and self.index.start == 0
6445+
and self.index.step == 1
6446+
):
6447+
# Already default index, no change needed
6448+
if inplace:
6449+
return None
6450+
else:
6451+
return self.copy()
6452+
else:
6453+
return self.reset_index(drop=True, inplace=inplace)
6454+
63706455
# ----------------------------------------------------------------------
63716456
# Reindex-based selection methods
63726457

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import numpy as np
2+
import pytest
3+
4+
import pandas as pd
5+
from pandas.testing import assert_frame_equal
6+
7+
8+
class TestUnsetIndex:
9+
# GH: 60869
10+
@pytest.fixture
11+
def df(self):
12+
# Fixture with custom string index
13+
return pd.DataFrame(
14+
[("bird", 389.0), ("bird", 24.0), ("mammal", 80.5), ("mammal", np.nan)],
15+
index=["falcon", "parrot", "lion", "monkey"],
16+
columns=("class", "max_speed"),
17+
)
18+
19+
def test_unset_index_returns_default_index(self, df):
20+
result = df.unset_index()
21+
expected = pd.DataFrame(
22+
{
23+
"class": ["bird", "bird", "mammal", "mammal"],
24+
"max_speed": [389.0, 24.0, 80.5, np.nan],
25+
},
26+
index=pd.RangeIndex(0, 4),
27+
)
28+
assert_frame_equal(result, expected)
29+
30+
def test_unset_index_inplace(self, df):
31+
out = df.unset_index(inplace=True)
32+
assert out is None
33+
assert isinstance(df.index, pd.RangeIndex)
34+
assert df.index.equals(pd.RangeIndex(0, 4))
35+
36+
def test_unset_index_on_default_index(self):
37+
df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
38+
original = df.copy()
39+
result = df.unset_index()
40+
assert_frame_equal(result, original)
41+
assert result is not df # Should return copy
42+
43+
def test_unset_index_on_non_default_rangeindex(self):
44+
# RangeIndex with start=2, step=1
45+
df = pd.DataFrame({"A": [1, 2, 3]}, index=pd.RangeIndex(start=2, stop=5))
46+
result = df.unset_index()
47+
assert result.index.equals(pd.RangeIndex(0, 3))
48+
49+
def test_unset_index_on_rangeindex_with_step_2(self):
50+
# RangeIndex with non-default step
51+
df = pd.DataFrame(
52+
{"A": [1, 2, 3]}, index=pd.RangeIndex(start=0, step=2, stop=6)
53+
)
54+
result = df.unset_index()
55+
assert result.index.equals(pd.RangeIndex(0, 3))
56+
57+
def test_unset_index_on_empty_dataframe(self):
58+
df = pd.DataFrame(columns=["A", "B"])
59+
result = df.unset_index()
60+
assert_frame_equal(result, df)
61+
assert result.index.equals(pd.RangeIndex(0, 0))
62+
63+
def test_unset_index_on_multiindex(self):
64+
index = pd.MultiIndex.from_tuples(
65+
[("a", 1), ("a", 2)], names=["letter", "number"]
66+
)
67+
df = pd.DataFrame({"data": [10, 20]}, index=index)
68+
result = df.unset_index()
69+
assert result.index.equals(pd.RangeIndex(0, 2))
70+
71+
def test_idempotent_on_default_index(self):
72+
df = pd.DataFrame({"A": [1, 2]})
73+
result = df.unset_index().unset_index()
74+
assert_frame_equal(result, df.unset_index())

0 commit comments

Comments
 (0)