Skip to content

ENH/TST: unset_index method #60869 #61335

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Other enhancements
- :meth:`.DataFrameGroupBy.transform`, :meth:`.SeriesGroupBy.transform`, :meth:`.DataFrameGroupBy.agg`, :meth:`.SeriesGroupBy.agg`, :meth:`.SeriesGroupBy.apply`, :meth:`.DataFrameGroupBy.apply` now support ``kurt`` (:issue:`40139`)
- :meth:`DataFrame.apply` supports using third-party execution engines like the Bodo.ai JIT compiler (:issue:`60668`)
- :meth:`DataFrame.iloc` and :meth:`Series.iloc` now support boolean masks in ``__getitem__`` for more consistent indexing behavior (:issue:`60994`)
- :meth:`DataFrame.unset_index` is a new method that resets the index of the DataFrame to the default integer index (:issue:`60869`)
- :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`)
- :meth:`Rolling.agg`, :meth:`Expanding.agg` and :meth:`ExponentialMovingWindow.agg` now accept :class:`NamedAgg` aggregations through ``**kwargs`` (:issue:`28333`)
- :meth:`Series.map` can now accept kwargs to pass on to func (:issue:`59814`)
Expand Down
85 changes: 85 additions & 0 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
DatetimeIndex,
Index,
PeriodIndex,
RangeIndex,
default_index,
ensure_index,
ensure_index_from_sequences,
Expand Down Expand Up @@ -6367,6 +6368,90 @@ class max type

return None

def unset_index(
self,
*,
inplace: bool = False,
) -> DataFrame | None:
"""
Remove the index and restore the default RangeIndex.
This method resets the index of the DataFrame to the default integer index
(RangeIndex), always dropping the old index and never inserting it as a column.
If the DataFrame already has the default index, this is a no-op.
Parameters
----------
inplace : bool, default False
Whether to modify the DataFrame in place or return a new DataFrame.
Returns
-------
DataFrame or None
DataFrame with the default integer index or None if ``inplace=True``.
See Also
--------
DataFrame.reset_index : Reset the index, with options to insert as columns.
DataFrame.set_index : Set the DataFrame index using existing columns.
Examples
--------
>>> df = pd.DataFrame(
... [("bird", 389.0), ("bird", 24.0), ("mammal", 80.5), ("mammal", np.nan)],
... index=["falcon", "parrot", "lion", "monkey"],
... columns=("class", "max_speed"),
... )
>>> df
class max_speed
falcon bird 389.0
parrot bird 24.0
lion mammal 80.5
monkey mammal NaN
Remove the index and use the default integer index:
>>> df.unset_index()
class max_speed
0 bird 389.0
1 bird 24.0
2 mammal 80.5
3 mammal NaN
If the DataFrame already has the default index, nothing changes:
>>> df2 = df.unset_index()
>>> df2.unset_index()
class max_speed
0 bird 389.0
1 bird 24.0
2 mammal 80.5
3 mammal NaN
Modify the DataFrame in place:
>>> df.unset_index(inplace=True)
>>> df
class max_speed
0 bird 389.0
1 bird 24.0
2 mammal 80.5
3 mammal NaN
"""

if (
isinstance(self.index, RangeIndex)
and self.index.start == 0
and self.index.step == 1
):
# Already default index, no change needed
if inplace:
return None
else:
return self.copy()
else:
return self.reset_index(drop=True, inplace=inplace)

# ----------------------------------------------------------------------
# Reindex-based selection methods

Expand Down
74 changes: 74 additions & 0 deletions pandas/tests/frame/test_unset_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import numpy as np
import pytest

import pandas as pd
from pandas.testing import assert_frame_equal


class TestUnsetIndex:
# GH: 60869
@pytest.fixture
def df(self):
# Fixture with custom string index
return pd.DataFrame(
[("bird", 389.0), ("bird", 24.0), ("mammal", 80.5), ("mammal", np.nan)],
index=["falcon", "parrot", "lion", "monkey"],
columns=("class", "max_speed"),
)

def test_unset_index_returns_default_index(self, df):
result = df.unset_index()
expected = pd.DataFrame(
{
"class": ["bird", "bird", "mammal", "mammal"],
"max_speed": [389.0, 24.0, 80.5, np.nan],
},
index=pd.RangeIndex(0, 4),
)
assert_frame_equal(result, expected)

def test_unset_index_inplace(self, df):
out = df.unset_index(inplace=True)
assert out is None
assert isinstance(df.index, pd.RangeIndex)
assert df.index.equals(pd.RangeIndex(0, 4))

def test_unset_index_on_default_index(self):
df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
original = df.copy()
result = df.unset_index()
assert_frame_equal(result, original)
assert result is not df # Should return copy

def test_unset_index_on_non_default_rangeindex(self):
# RangeIndex with start=2, step=1
df = pd.DataFrame({"A": [1, 2, 3]}, index=pd.RangeIndex(start=2, stop=5))
result = df.unset_index()
assert result.index.equals(pd.RangeIndex(0, 3))

def test_unset_index_on_rangeindex_with_step_2(self):
# RangeIndex with non-default step
df = pd.DataFrame(
{"A": [1, 2, 3]}, index=pd.RangeIndex(start=0, step=2, stop=6)
)
result = df.unset_index()
assert result.index.equals(pd.RangeIndex(0, 3))

def test_unset_index_on_empty_dataframe(self):
df = pd.DataFrame(columns=["A", "B"])
result = df.unset_index()
assert_frame_equal(result, df)
assert result.index.equals(pd.RangeIndex(0, 0))

def test_unset_index_on_multiindex(self):
index = pd.MultiIndex.from_tuples(
[("a", 1), ("a", 2)], names=["letter", "number"]
)
df = pd.DataFrame({"data": [10, 20]}, index=index)
result = df.unset_index()
assert result.index.equals(pd.RangeIndex(0, 2))

def test_idempotent_on_default_index(self):
df = pd.DataFrame({"A": [1, 2]})
result = df.unset_index().unset_index()
assert_frame_equal(result, df.unset_index())
Loading