diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index a0d33cb513722..64b367f2ed36d 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -293,6 +293,7 @@ Other enhancements - :meth:`RangeIndex.union` now can return a :class:`RangeIndex` instead of a :class:`Int64Index` if the resulting values are equally spaced (:issue:`47557`, :issue:`43885`) - :meth:`DataFrame.compare` now accepts an argument ``result_names`` to allow the user to specify the result's names of both left and right DataFrame which are being compared. This is by default ``'self'`` and ``'other'`` (:issue:`44354`) - :meth:`Series.add_suffix`, :meth:`DataFrame.add_suffix`, :meth:`Series.add_prefix` and :meth:`DataFrame.add_prefix` support a ``copy`` argument. If ``False``, the underlying data is not copied in the returned object (:issue:`47934`) +- :meth:`DataFrame.drop` with ``axis=1`` can now accept ``copy=False`` to prevent a copy of the underlying data (:issue:`47993`) .. --------------------------------------------------------------------------- .. _whatsnew_150.notable_bug_fixes: diff --git a/pandas/core/base.py b/pandas/core/base.py index f7e6c4434da32..e7bef361252b4 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -221,9 +221,7 @@ def _obj_with_exclusions(self): if len(self.exclusions) > 0: # equivalent to `self.obj.drop(self.exclusions, axis=1) # but this avoids consolidating and making a copy - # TODO: following GH#45287 can we now use .drop directly without - # making a copy? - return self.obj._drop_axis(self.exclusions, axis=1, only_slice=True) + return self.obj.drop(self.exclusions, axis=1, copy=False) else: return self.obj diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 49e5bc24786dd..010a537b45c9b 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -5096,6 +5096,7 @@ def drop( level: Level | None = ..., inplace: Literal[True], errors: IgnoreRaise = ..., + copy: bool | lib.NoDefault = ..., ) -> None: ... @@ -5110,6 +5111,7 @@ def drop( level: Level | None = ..., inplace: Literal[False] = ..., errors: IgnoreRaise = ..., + copy: bool | lib.NoDefault = ..., ) -> DataFrame: ... @@ -5124,6 +5126,7 @@ def drop( level: Level | None = ..., inplace: bool = ..., errors: IgnoreRaise = ..., + copy: bool | lib.NoDefault = ..., ) -> DataFrame | None: ... @@ -5139,6 +5142,8 @@ def drop( # type: ignore[override] level: Level | None = None, inplace: bool = False, errors: IgnoreRaise = "raise", + *, + copy: bool | lib.NoDefault = lib.no_default, ) -> DataFrame | None: """ Drop specified labels from rows or columns. @@ -5171,6 +5176,10 @@ def drop( # type: ignore[override] errors : {'ignore', 'raise'}, default 'raise' If 'ignore', suppress error and only existing labels are dropped. + copy : bool, default True + If False and axis == 1, do not make a copy of the underlying data. + + .. versionadded:: 1.5.0 Returns ------- @@ -5285,6 +5294,7 @@ def drop( # type: ignore[override] level=level, inplace=inplace, errors=errors, + copy=copy, ) @overload diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 003fe2571401f..ba03ed8879611 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -4376,6 +4376,7 @@ def drop( level: Level | None = ..., inplace: Literal[True], errors: IgnoreRaise = ..., + copy: bool_t | lib.NoDefault = ..., ) -> None: ... @@ -4390,6 +4391,7 @@ def drop( level: Level | None = ..., inplace: Literal[False] = ..., errors: IgnoreRaise = ..., + copy: bool_t | lib.NoDefault = ..., ) -> NDFrameT: ... @@ -4404,6 +4406,7 @@ def drop( level: Level | None = ..., inplace: bool_t = ..., errors: IgnoreRaise = ..., + copy: bool_t | lib.NoDefault = ..., ) -> NDFrameT | None: ... @@ -4417,9 +4420,17 @@ def drop( level: Level | None = None, inplace: bool_t = False, errors: IgnoreRaise = "raise", + *, + copy: bool_t | lib.NoDefault = lib.no_default, ) -> NDFrameT | None: inplace = validate_bool_kwarg(inplace, "inplace") + if inplace: + if copy is not lib.no_default: + raise ValueError("Cannot pass both inplace=True and copy") + copy = True + elif copy is lib.no_default: + copy = True if labels is not None: if index is not None or columns is not None: @@ -4437,7 +4448,9 @@ def drop( for axis, labels in axes.items(): if labels is not None: - obj = obj._drop_axis(labels, axis, level=level, errors=errors) + obj = obj._drop_axis( + labels, axis, level=level, errors=errors, only_slice=not copy + ) if inplace: self._update_inplace(obj) diff --git a/pandas/core/series.py b/pandas/core/series.py index 206fcbe05d006..7fe5f9a86594c 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4992,7 +4992,8 @@ def reindex(self, *args, **kwargs) -> Series: kwargs.update({"index": index}) return super().reindex(**kwargs) - @overload + # error: Signature of "drop" incompatible with supertype "NDFrame" [override] + @overload # type: ignore[override] def drop( self, labels: IndexLabel = ..., @@ -5141,6 +5142,7 @@ def drop( # type: ignore[override] level=level, inplace=inplace, errors=errors, + copy=lib.no_default, ) @overload diff --git a/pandas/tests/frame/methods/test_drop.py b/pandas/tests/frame/methods/test_drop.py index 50b60f9e06ef1..a4aee6bda679d 100644 --- a/pandas/tests/frame/methods/test_drop.py +++ b/pandas/tests/frame/methods/test_drop.py @@ -67,6 +67,25 @@ def test_drop_with_non_unique_datetime_index_and_invalid_keys(): class TestDataFrameDrop: + def test_drop_copy(self): + df = DataFrame( + [[1, 2, 3], [3, 4, 5], [5, 6, 7]], + index=["a", "b", "c"], + columns=["d", "e", "f"], + ) + + msg = "Cannot pass both inplace=True and copy" + with pytest.raises(ValueError, match=msg): + df.drop("d", axis=1, inplace=True, copy=True) + with pytest.raises(ValueError, match=msg): + df.drop("d", axis=1, inplace=True, copy=False) + + res = df.drop("d", axis=1, copy=True) + assert not any(tm.shares_memory(res[c], df[c]) for c in res.columns) + + res = df.drop("d", axis=1, copy=False) + assert all(tm.shares_memory(res[c], df[c]) for c in res.columns) + def test_drop_names(self): df = DataFrame( [[1, 2, 3], [3, 4, 5], [5, 6, 7]],