Skip to content

DEPR: Deprecate inplace param in MultiIndex.set_codes and MultiIndex.set_levels #35626

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

Merged
merged 5 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
9 changes: 3 additions & 6 deletions doc/source/user_guide/indexing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1532,12 +1532,9 @@ Setting metadata
~~~~~~~~~~~~~~~~

Indexes are "mostly immutable", but it is possible to set and change their
metadata, like the index ``name`` (or, for ``MultiIndex``, ``levels`` and
``codes``).

You can use the ``rename``, ``set_names``, ``set_levels``, and ``set_codes``
to set these attributes directly. They default to returning a copy; however,
you can specify ``inplace=True`` to have the data change in place.
``name`` attribute. You can use the ``rename``, ``set_names`` to set these attributes
directly. They default to returning a copy; however, you can specify ``inplace=True`` to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove the inplace ref

have the data change in place.

See :ref:`Advanced Indexing <advanced>` for usage of MultiIndexes.

Expand Down
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Other enhancements

Deprecations
~~~~~~~~~~~~

- Deprecated parameter ``inplace`` in :meth:`MultiIndex.set_codes` and :meth:`MultiIndex.set_levels` (:issue:`xxxxx`)
-
-

Expand Down
2 changes: 1 addition & 1 deletion pandas/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ def multiindex_year_month_day_dataframe_random_data():
tdf = tm.makeTimeDataFrame(100)
ymd = tdf.groupby([lambda x: x.year, lambda x: x.month, lambda x: x.day]).sum()
# use Int64Index, to make sure things work
ymd.index.set_levels([lev.astype("i8") for lev in ymd.index.levels], inplace=True)
ymd.index = ymd.index.set_levels([lev.astype("i8") for lev in ymd.index.levels])
ymd.index.set_names(["year", "month", "day"], inplace=True)
return ymd

Expand Down
26 changes: 24 additions & 2 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ def _set_levels(
self._tuples = None
self._reset_cache()

def set_levels(self, levels, level=None, inplace=False, verify_integrity=True):
def set_levels(self, levels, level=None, inplace=None, verify_integrity=True):
"""
Set new levels on MultiIndex. Defaults to returning new index.

Expand All @@ -752,6 +752,8 @@ def set_levels(self, levels, level=None, inplace=False, verify_integrity=True):
Level(s) to set (None for all levels).
inplace : bool
If True, mutates in place.

.. deprecated:: 1.2.0
verify_integrity : bool, default True
If True, checks that levels and codes are compatible.

Expand Down Expand Up @@ -822,6 +824,15 @@ def set_levels(self, levels, level=None, inplace=False, verify_integrity=True):
>>> idx.set_levels([['a', 'b', 'c'], [1, 2, 3, 4]], level=[0, 1]).levels
FrozenList([['a', 'b', 'c'], [1, 2, 3, 4]])
"""
if inplace is not None:
warnings.warn(
"inplace is deprecated and will be removed in a future version.",
FutureWarning,
stacklevel=2,
)
else:
inplace = False

if is_list_like(levels) and not isinstance(levels, Index):
levels = list(levels)

Expand Down Expand Up @@ -898,7 +909,7 @@ def _set_codes(
self._tuples = None
self._reset_cache()

def set_codes(self, codes, level=None, inplace=False, verify_integrity=True):
def set_codes(self, codes, level=None, inplace=None, verify_integrity=True):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typing these would be good at some point

"""
Set new codes on MultiIndex. Defaults to returning new index.

Expand All @@ -914,6 +925,8 @@ def set_codes(self, codes, level=None, inplace=False, verify_integrity=True):
Level(s) to set (None for all levels).
inplace : bool
If True, mutates in place.

.. deprecated:: 1.2.0
verify_integrity : bool (default True)
If True, checks that levels and codes are compatible.

Expand Down Expand Up @@ -958,6 +971,15 @@ def set_codes(self, codes, level=None, inplace=False, verify_integrity=True):
(1, 'two')],
names=['foo', 'bar'])
"""
if inplace is not None:
warnings.warn(
"inplace is deprecated and will be removed in a future version.",
FutureWarning,
stacklevel=2,
)
else:
inplace = False

if level is not None and not is_list_like(level):
if not is_list_like(codes):
raise TypeError("Codes must be list-like")
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/frame/methods/test_sort_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,8 @@ def test_sort_index_and_reconstruction(self):
),
)

df.columns.set_levels(
pd.to_datetime(df.columns.levels[1]), level=1, inplace=True
df.columns = df.columns.set_levels(
pd.to_datetime(df.columns.levels[1]), level=1
)
assert not df.columns.is_lexsorted()
assert not df.columns.is_monotonic
Expand Down
6 changes: 4 additions & 2 deletions pandas/tests/indexes/multi/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ def test_inplace_mutation_resets_values():
tm.assert_almost_equal(mi1.values, vals)

# Inplace should kill _tuples
mi1.set_levels(levels2, inplace=True)
with tm.assert_produces_warning(FutureWarning):
mi1.set_levels(levels2, inplace=True)
tm.assert_almost_equal(mi1.values, vals2)

# Make sure label setting works too
Expand All @@ -103,7 +104,8 @@ def test_inplace_mutation_resets_values():
tm.assert_almost_equal(exp_values, new_values)

# ...and again setting inplace should kill _tuples, etc
mi2.set_codes(codes2, inplace=True)
with tm.assert_produces_warning(FutureWarning):
mi2.set_codes(codes2, inplace=True)
tm.assert_almost_equal(mi2.values, new_values)


Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/indexes/multi/test_duplicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ def test_duplicate_multiindex_codes():
mi = MultiIndex.from_arrays([["A", "A", "B", "B", "B"], [1, 2, 1, 2, 3]])
msg = r"Level values must be unique: \[[AB', ]+\] on level 0"
with pytest.raises(ValueError, match=msg):
mi.set_levels([["A", "B", "A", "A", "B"], [2, 1, 3, -2, 5]], inplace=True)
with tm.assert_produces_warning(FutureWarning):
mi.set_levels([["A", "B", "A", "A", "B"], [2, 1, 3, -2, 5]], inplace=True)


@pytest.mark.parametrize("names", [["a", "b", "a"], [1, 1, 2], [1, "a", 1]])
Expand Down
6 changes: 4 additions & 2 deletions pandas/tests/indexes/multi/test_equivalence.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,12 @@ def test_is_():
mi4 = mi3.view()

# GH 17464 - Remove duplicate MultiIndex levels
mi4.set_levels([list(range(10)), list(range(10))], inplace=True)
with tm.assert_produces_warning(FutureWarning):
mi4.set_levels([list(range(10)), list(range(10))], inplace=True)
assert not mi4.is_(mi3)
mi5 = mi.view()
mi5.set_levels(mi5.levels, inplace=True)
with tm.assert_produces_warning(FutureWarning):
mi5.set_levels(mi5.levels, inplace=True)
assert not mi5.is_(mi)


Expand Down
55 changes: 42 additions & 13 deletions pandas/tests/indexes/multi/test_get_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def test_set_levels(idx):

# level changing [w/ mutation]
ind2 = idx.copy()
inplace_return = ind2.set_levels(new_levels, inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_levels(new_levels, inplace=True)
assert inplace_return is None
assert_matching(ind2.levels, new_levels)

Expand All @@ -113,20 +114,23 @@ def test_set_levels(idx):

# level changing specific level [w/ mutation]
ind2 = idx.copy()
inplace_return = ind2.set_levels(new_levels[0], level=0, inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_levels(new_levels[0], level=0, inplace=True)
assert inplace_return is None
assert_matching(ind2.levels, [new_levels[0], levels[1]])
assert_matching(idx.levels, levels)

ind2 = idx.copy()
inplace_return = ind2.set_levels(new_levels[1], level=1, inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_levels(new_levels[1], level=1, inplace=True)
assert inplace_return is None
assert_matching(ind2.levels, [levels[0], new_levels[1]])
assert_matching(idx.levels, levels)

# level changing multiple levels [w/ mutation]
ind2 = idx.copy()
inplace_return = ind2.set_levels(new_levels, level=[0, 1], inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_levels(new_levels, level=[0, 1], inplace=True)
assert inplace_return is None
assert_matching(ind2.levels, new_levels)
assert_matching(idx.levels, levels)
Expand All @@ -136,19 +140,23 @@ def test_set_levels(idx):
original_index = idx.copy()
for inplace in [True, False]:
with pytest.raises(ValueError, match="^On"):
idx.set_levels(["c"], level=0, inplace=inplace)
with tm.assert_produces_warning(FutureWarning):
idx.set_levels(["c"], level=0, inplace=inplace)
assert_matching(idx.levels, original_index.levels, check_dtype=True)

with pytest.raises(ValueError, match="^On"):
idx.set_codes([0, 1, 2, 3, 4, 5], level=0, inplace=inplace)
with tm.assert_produces_warning(FutureWarning):
idx.set_codes([0, 1, 2, 3, 4, 5], level=0, inplace=inplace)
assert_matching(idx.codes, original_index.codes, check_dtype=True)

with pytest.raises(TypeError, match="^Levels"):
idx.set_levels("c", level=0, inplace=inplace)
with tm.assert_produces_warning(FutureWarning):
idx.set_levels("c", level=0, inplace=inplace)
assert_matching(idx.levels, original_index.levels, check_dtype=True)

with pytest.raises(TypeError, match="^Codes"):
idx.set_codes(1, level=0, inplace=inplace)
with tm.assert_produces_warning(FutureWarning):
idx.set_codes(1, level=0, inplace=inplace)
assert_matching(idx.codes, original_index.codes, check_dtype=True)


Expand All @@ -168,7 +176,8 @@ def test_set_codes(idx):

# changing label w/ mutation
ind2 = idx.copy()
inplace_return = ind2.set_codes(new_codes, inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_codes(new_codes, inplace=True)
assert inplace_return is None
assert_matching(ind2.codes, new_codes)

Expand All @@ -188,20 +197,23 @@ def test_set_codes(idx):

# label changing specific level w/ mutation
ind2 = idx.copy()
inplace_return = ind2.set_codes(new_codes[0], level=0, inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_codes(new_codes[0], level=0, inplace=True)
assert inplace_return is None
assert_matching(ind2.codes, [new_codes[0], codes[1]])
assert_matching(idx.codes, codes)

ind2 = idx.copy()
inplace_return = ind2.set_codes(new_codes[1], level=1, inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_codes(new_codes[1], level=1, inplace=True)
assert inplace_return is None
assert_matching(ind2.codes, [codes[0], new_codes[1]])
assert_matching(idx.codes, codes)

# codes changing multiple levels [w/ mutation]
ind2 = idx.copy()
inplace_return = ind2.set_codes(new_codes, level=[0, 1], inplace=True)
with tm.assert_produces_warning(FutureWarning):
inplace_return = ind2.set_codes(new_codes, level=[0, 1], inplace=True)
assert inplace_return is None
assert_matching(ind2.codes, new_codes)
assert_matching(idx.codes, codes)
Expand All @@ -217,7 +229,8 @@ def test_set_codes(idx):

# [w/ mutation]
result = ind.copy()
result.set_codes(codes=new_codes, level=1, inplace=True)
with tm.assert_produces_warning(FutureWarning):
result.set_codes(codes=new_codes, level=1, inplace=True)
assert result.equals(expected)


Expand Down Expand Up @@ -329,3 +342,19 @@ def test_set_levels_with_iterable():
[expected_sizes, colors], names=["size", "color"]
)
tm.assert_index_equal(result, expected)


@pytest.mark.parametrize("inplace", [True, False])
def test_set_codes_inplace_deprecated(idx, inplace):
new_codes = idx.codes[1][::-1]

with tm.assert_produces_warning(FutureWarning):
idx.set_codes(codes=new_codes, level=1, inplace=inplace)


@pytest.mark.parametrize("inplace", [True, False])
def test_set_levels_inplace_deprecated(idx, inplace):
new_level = idx.levels[1].copy()

with tm.assert_produces_warning(FutureWarning):
idx.set_levels(levels=new_level, level=1, inplace=inplace)
3 changes: 2 additions & 1 deletion pandas/tests/indexes/multi/test_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ def test_metadata_immutable(idx):
def test_level_setting_resets_attributes():
ind = pd.MultiIndex.from_arrays([["A", "A", "B", "B", "B"], [1, 2, 1, 2, 3]])
assert ind.is_monotonic
ind.set_levels([["A", "B"], [1, 3, 2]], inplace=True)
with tm.assert_produces_warning(FutureWarning):
ind.set_levels([["A", "B"], [1, 3, 2]], inplace=True)
# if this fails, probably didn't reset the cache correctly.
assert not ind.is_monotonic

Expand Down
8 changes: 6 additions & 2 deletions pandas/tests/indexing/multiindex/test_sorted.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ def test_frame_getitem_not_sorted2(self, key):
df2 = df.set_index(["col1", "col2"])
df2_original = df2.copy()

return_value = df2.index.set_levels(["b", "d", "a"], level="col1", inplace=True)
with tm.assert_produces_warning(FutureWarning):
return_value = df2.index.set_levels(
["b", "d", "a"], level="col1", inplace=True
)
assert return_value is None
return_value = df2.index.set_codes([0, 1, 0, 2], level="col1", inplace=True)
with tm.assert_produces_warning(FutureWarning):
return_value = df2.index.set_codes([0, 1, 0, 2], level="col1", inplace=True)
assert return_value is None
assert not df2.index.is_lexsorted()
assert not df2.index.is_monotonic
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/reshape/test_melt.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ def test_invalid_separator(self):
expected = expected.set_index(["id", "year"])[
["X", "A2010", "A2011", "B2010", "A", "B"]
]
expected.index.set_levels([0, 1], level=0, inplace=True)
expected.index = expected.index.set_levels([0, 1], level=0)
result = wide_to_long(df, ["A", "B"], i="id", j="year", sep=sep)
tm.assert_frame_equal(result.sort_index(axis=1), expected.sort_index(axis=1))

Expand Down Expand Up @@ -861,7 +861,7 @@ def test_invalid_suffixtype(self):
expected = pd.DataFrame(exp_data).astype({"year": "int"})

expected = expected.set_index(["id", "year"])
expected.index.set_levels([0, 1], level=0, inplace=True)
expected.index = expected.index.set_levels([0, 1], level=0)
result = wide_to_long(df, ["A", "B"], i="id", j="year")
tm.assert_frame_equal(result.sort_index(axis=1), expected.sort_index(axis=1))

Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/test_multilevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def setup_method(self, method):
).sum()

# use Int64Index, to make sure things work
self.ymd.index.set_levels(
[lev.astype("i8") for lev in self.ymd.index.levels], inplace=True
self.ymd.index = self.ymd.index.set_levels(
[lev.astype("i8") for lev in self.ymd.index.levels]
)
self.ymd.index.set_names(["year", "month", "day"], inplace=True)

Expand Down