From f03c9fbb5e33fc758832092bb948b29103c4447d Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 18 Aug 2022 09:17:56 -0700 Subject: [PATCH] DEPR: set_axis inplace keyword (#48130) * ENH: copy keyword to set_axis * GH ref * fix test * fix test * troubleshoot mypy * mypy fixup * test inplace=True/copy=False case * DEPR: inplace keyword in set_axis * GH ref --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/core/frame.py | 11 +++--- pandas/core/generic.py | 37 +++++++++++++------ pandas/core/groupby/groupby.py | 9 +++-- pandas/core/series.py | 6 +-- pandas/tests/frame/methods/test_set_axis.py | 17 ++++++--- pandas/tests/generic/test_duplicate_labels.py | 12 +++++- 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index a9d0f75cc605de..3ac316cd099a38 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -841,6 +841,7 @@ Other Deprecations - Deprecated the ``inplace`` keyword in :meth:`Categorical.set_ordered`, :meth:`Categorical.as_ordered`, and :meth:`Categorical.as_unordered` (:issue:`37643`) - Deprecated setting a categorical's categories with ``cat.categories = ['a', 'b', 'c']``, use :meth:`Categorical.rename_categories` instead (:issue:`37643`) - Deprecated unused arguments ``encoding`` and ``verbose`` in :meth:`Series.to_excel` and :meth:`DataFrame.to_excel` (:issue:`47912`) +- Deprecated the ``inplace`` keyword in :meth:`DataFrame.set_axis` and :meth:`Series.set_axis`, use ``obj = obj.set_axis(..., copy=False)`` instead (:issue:`48130`) - Deprecated producing a single element when iterating over a :class:`DataFrameGroupBy` or a :class:`SeriesGroupBy` that has been grouped by a list of length 1; A tuple of length one will be returned instead (:issue:`42795`) - Fixed up warning message of deprecation of :meth:`MultiIndex.lesort_depth` as public method, as the message previously referred to :meth:`MultiIndex.is_lexsorted` instead (:issue:`38701`) - Deprecated the ``inplace`` keyword in :meth:`DataFrame.set_index`, use ``df = df.set_index(..., copy=False)`` instead (:issue:`48115`) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 53fddb032a487e..04e168f1ab6cad 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -5061,7 +5061,7 @@ def set_axis( labels, *, axis: Axis = ..., - inplace: Literal[False] = ..., + inplace: Literal[False] | lib.NoDefault = ..., copy: bool | lib.NoDefault = ..., ) -> DataFrame: ... @@ -5083,7 +5083,7 @@ def set_axis( labels, *, axis: Axis = ..., - inplace: bool = ..., + inplace: bool | lib.NoDefault = ..., copy: bool | lib.NoDefault = ..., ) -> DataFrame | None: ... @@ -5112,10 +5112,9 @@ def set_axis( 1 2 5 2 3 6 - Now, update the labels inplace. + Now, update the labels without copying the underlying data. - >>> df.set_axis(['i', 'ii'], axis='columns', inplace=True) - >>> df + >>> df.set_axis(['i', 'ii'], axis='columns', copy=False) i ii 0 1 4 1 2 5 @@ -5133,7 +5132,7 @@ def set_axis( self, labels, axis: Axis = 0, - inplace: bool = False, + inplace: bool | lib.NoDefault = lib.no_default, *, copy: bool | lib.NoDefault = lib.no_default, ): diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 5686ae324dd511..88184285d36837 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -715,7 +715,7 @@ def set_axis( labels, *, axis: Axis = ..., - inplace: Literal[False] = ..., + inplace: Literal[False] | lib.NoDefault = ..., copy: bool_t | lib.NoDefault = ..., ) -> NDFrameT: ... @@ -737,7 +737,7 @@ def set_axis( labels, *, axis: Axis = ..., - inplace: bool_t = ..., + inplace: bool_t | lib.NoDefault = ..., copy: bool_t | lib.NoDefault = ..., ) -> NDFrameT | None: ... @@ -747,7 +747,7 @@ def set_axis( self: NDFrameT, labels, axis: Axis = 0, - inplace: bool_t = False, + inplace: bool_t | lib.NoDefault = lib.no_default, *, copy: bool_t | lib.NoDefault = lib.no_default, ) -> NDFrameT | None: @@ -769,6 +769,8 @@ def set_axis( inplace : bool, default False Whether to return a new %(klass)s instance. + .. deprecated:: 1.5.0 + copy : bool, default True Whether to make a copy of the underlying data. @@ -783,6 +785,17 @@ def set_axis( -------- %(klass)s.rename_axis : Alter the name of the index%(see_also_sub)s. """ + if inplace is not lib.no_default: + warnings.warn( + f"{type(self).__name__}.set_axis 'inplace' keyword is deprecated " + "and will be removed in a future version. Use " + "`obj = obj.set_axis(..., copy=False)` instead", + FutureWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) + else: + inplace = False + if inplace: if copy is True: raise ValueError("Cannot specify both inplace=True and copy=True") @@ -795,14 +808,13 @@ def set_axis( @final def _set_axis_nocheck(self, labels, axis: Axis, inplace: bool_t, copy: bool_t): - # NDFrame.rename with inplace=False calls set_axis(inplace=True) on a copy. if inplace: setattr(self, self._get_axis_name(axis), labels) else: # With copy=False, we create a new object but don't copy the # underlying data. obj = self.copy(deep=copy) - obj.set_axis(labels, axis=axis, inplace=True) + setattr(obj, obj._get_axis_name(axis), labels) return obj def _set_axis(self, axis: int, labels: AnyArrayLike | list) -> None: @@ -904,7 +916,7 @@ def droplevel(self: NDFrameT, level: IndexLabel, axis: Axis = 0) -> NDFrameT: """ labels = self._get_axis(axis) new_labels = labels.droplevel(level) - return self.set_axis(new_labels, axis=axis, inplace=False) + return self.set_axis(new_labels, axis=axis) def pop(self, item: Hashable) -> Series | Any: result = self[item] @@ -1363,7 +1375,11 @@ def _set_axis_name(self, name, axis=0, inplace=False): inplace = validate_bool_kwarg(inplace, "inplace") renamed = self if inplace else self.copy() - renamed.set_axis(idx, axis=axis, inplace=True) + if axis == 0: + renamed.index = idx + else: + renamed.columns = idx + if not inplace: return renamed @@ -10205,8 +10221,7 @@ def slice_shift(self: NDFrameT, periods: int = 1, axis=0) -> NDFrameT: new_obj = self._slice(vslicer, axis=axis) shifted_axis = self._get_axis(axis)[islicer] - new_obj.set_axis(shifted_axis, axis=axis, inplace=True) - + new_obj = new_obj.set_axis(shifted_axis, axis=axis, copy=False) return new_obj.__finalize__(self, method="slice_shift") @final @@ -10465,7 +10480,7 @@ def _tz_convert(ax, tz): ax = _tz_convert(ax, tz) result = self.copy(deep=copy) - result = result.set_axis(ax, axis=axis, inplace=False) + result = result.set_axis(ax, axis=axis, copy=False) return result.__finalize__(self, method="tz_convert") @final @@ -10635,7 +10650,7 @@ def _tz_localize(ax, tz, ambiguous, nonexistent): ax = _tz_localize(ax, tz, ambiguous, nonexistent) result = self.copy(deep=copy) - result = result.set_axis(ax, axis=axis, inplace=False) + result = result.set_axis(ax, axis=axis, copy=False) return result.__finalize__(self, method="tz_localize") # ---------------------------------------------------------------------- diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py index 9523857925f8fa..43316873a2ea76 100644 --- a/pandas/core/groupby/groupby.py +++ b/pandas/core/groupby/groupby.py @@ -1200,21 +1200,22 @@ def _set_result_index_ordered( # set the result index on the passed values object and # return the new object, xref 8046 + obj_axis = self.obj._get_axis(self.axis) + if self.grouper.is_monotonic and not self.grouper.has_dropped_na: # shortcut if we have an already ordered grouper - result.set_axis(self.obj._get_axis(self.axis), axis=self.axis, inplace=True) + result = result.set_axis(obj_axis, axis=self.axis, copy=False) return result # row order is scrambled => sort the rows by position in original index original_positions = Index(self.grouper.result_ilocs()) - result.set_axis(original_positions, axis=self.axis, inplace=True) + result = result.set_axis(original_positions, axis=self.axis, copy=False) result = result.sort_index(axis=self.axis) - obj_axis = self.obj._get_axis(self.axis) if self.grouper.has_dropped_na: # Add back in any missing rows due to dropna - index here is integral # with values referring to the row of the input so can use RangeIndex result = result.reindex(RangeIndex(len(obj_axis)), axis=self.axis) - result.set_axis(obj_axis, axis=self.axis, inplace=True) + result = result.set_axis(obj_axis, axis=self.axis, copy=False) return result diff --git a/pandas/core/series.py b/pandas/core/series.py index f55d6a26255a08..13aa12287072ce 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4980,7 +4980,7 @@ def set_axis( labels, *, axis: Axis = ..., - inplace: Literal[False] = ..., + inplace: Literal[False] | lib.NoDefault = ..., copy: bool | lib.NoDefault = ..., ) -> Series: ... @@ -5002,7 +5002,7 @@ def set_axis( labels, *, axis: Axis = ..., - inplace: bool = ..., + inplace: bool | lib.NoDefault = ..., copy: bool | lib.NoDefault = ..., ) -> Series | None: ... @@ -5038,7 +5038,7 @@ def set_axis( # type: ignore[override] self, labels, axis: Axis = 0, - inplace: bool = False, + inplace: bool | lib.NoDefault = lib.no_default, copy: bool | lib.NoDefault = lib.no_default, ) -> Series | None: return super().set_axis(labels, axis=axis, inplace=inplace, copy=copy) diff --git a/pandas/tests/frame/methods/test_set_axis.py b/pandas/tests/frame/methods/test_set_axis.py index 67488dff3c3350..f105a38e6fdd08 100644 --- a/pandas/tests/frame/methods/test_set_axis.py +++ b/pandas/tests/frame/methods/test_set_axis.py @@ -21,7 +21,9 @@ def test_set_axis(self, obj): expected.index = new_index # inplace=False - result = obj.set_axis(new_index, axis=0, inplace=False) + msg = "set_axis 'inplace' keyword is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = obj.set_axis(new_index, axis=0, inplace=False) tm.assert_equal(expected, result) def test_set_axis_copy(self, obj): @@ -35,7 +37,8 @@ def test_set_axis_copy(self, obj): with pytest.raises( ValueError, match="Cannot specify both inplace=True and copy=True" ): - obj.set_axis(new_index, axis=0, inplace=True, copy=True) + with tm.assert_produces_warning(FutureWarning): + obj.set_axis(new_index, axis=0, inplace=True, copy=True) result = obj.set_axis(new_index, axis=0, copy=True) tm.assert_equal(expected, result) @@ -75,7 +78,8 @@ def test_set_axis_copy(self, obj): ) # Do this last since it alters obj inplace - res = obj.set_axis(new_index, inplace=True, copy=False) + with tm.assert_produces_warning(FutureWarning): + res = obj.set_axis(new_index, inplace=True, copy=False) assert res is None tm.assert_equal(expected, obj) # check we did NOT make a copy @@ -103,7 +107,8 @@ def test_set_axis_inplace_axis(self, axis, obj): expected.columns = new_index result = obj.copy() - result.set_axis(new_index, axis=axis, inplace=True) + with tm.assert_produces_warning(FutureWarning): + result.set_axis(new_index, axis=axis, inplace=True) tm.assert_equal(result, expected) def test_set_axis_unnamed_kwarg_warns(self, obj): @@ -113,7 +118,9 @@ def test_set_axis_unnamed_kwarg_warns(self, obj): expected = obj.copy() expected.index = new_index - with tm.assert_produces_warning(None): + with tm.assert_produces_warning( + FutureWarning, match="set_axis 'inplace' keyword" + ): result = obj.set_axis(new_index, inplace=False) tm.assert_equal(result, expected) diff --git a/pandas/tests/generic/test_duplicate_labels.py b/pandas/tests/generic/test_duplicate_labels.py index c83c8e1d568e68..0546534d913995 100644 --- a/pandas/tests/generic/test_duplicate_labels.py +++ b/pandas/tests/generic/test_duplicate_labels.py @@ -429,11 +429,19 @@ def test_inplace_raises(method, frame_only): s.flags.allows_duplicate_labels = False msg = "Cannot specify" + warn_msg = "Series.set_axis 'inplace' keyword" + if "set_axis" in str(method): + warn = FutureWarning + else: + warn = None + with pytest.raises(ValueError, match=msg): - method(df) + with tm.assert_produces_warning(warn, match=warn_msg): + method(df) if not frame_only: with pytest.raises(ValueError, match=msg): - method(s) + with tm.assert_produces_warning(warn, match=warn_msg): + method(s) def test_pickle():