diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 6e38024e02f36..cd93329a75c8a 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -279,6 +279,7 @@ Other enhancements - :class:`Series` reducers (e.g. ``min``, ``max``, ``sum``, ``mean``) will now successfully operate when the dtype is numeric and ``numeric_only=True`` is provided; previously this would raise a ``NotImplementedError`` (:issue:`47500`) - :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`) .. --------------------------------------------------------------------------- .. _whatsnew_150.notable_bug_fixes: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 7352ad2a4985d..499912596bdd9 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -4549,7 +4549,7 @@ def _update_inplace(self, result, verify_is_copy: bool_t = True) -> None: self._maybe_update_cacher(verify_is_copy=verify_is_copy, inplace=True) @final - def add_prefix(self: NDFrameT, prefix: str) -> NDFrameT: + def add_prefix(self: NDFrameT, prefix: str, copy: bool_t = True) -> NDFrameT: """ Prefix labels with string `prefix`. @@ -4560,6 +4560,10 @@ def add_prefix(self: NDFrameT, prefix: str) -> NDFrameT: ---------- prefix : str The string to add before each label. + copy : bool, default True + Whether to copy the underlying data. + + .. versionadded:: 1.5.0 Returns ------- @@ -4610,10 +4614,10 @@ def add_prefix(self: NDFrameT, prefix: str) -> NDFrameT: # expected "NDFrameT") # error: Argument 1 to "rename" of "NDFrame" has incompatible type # "**Dict[str, partial[str]]"; expected "Union[str, int, None]" - return self._rename(**mapper) # type: ignore[return-value, arg-type] + return self._rename(**mapper, copy=copy) # type: ignore[return-value, arg-type] @final - def add_suffix(self: NDFrameT, suffix: str) -> NDFrameT: + def add_suffix(self: NDFrameT, suffix: str, copy: bool_t = True) -> NDFrameT: """ Suffix labels with string `suffix`. @@ -4624,6 +4628,10 @@ def add_suffix(self: NDFrameT, suffix: str) -> NDFrameT: ---------- suffix : str The string to add after each label. + copy : bool, default True + Whether to copy the underlying data. + + .. versionadded:: 1.5.0 Returns ------- @@ -4674,7 +4682,7 @@ def add_suffix(self: NDFrameT, suffix: str) -> NDFrameT: # expected "NDFrameT") # error: Argument 1 to "rename" of "NDFrame" has incompatible type # "**Dict[str, partial[str]]"; expected "Union[str, int, None]" - return self._rename(**mapper) # type: ignore[return-value, arg-type] + return self._rename(**mapper, copy=copy) # type: ignore[return-value, arg-type] @overload def sort_values( diff --git a/pandas/tests/frame/methods/test_add_prefix_suffix.py b/pandas/tests/frame/methods/test_add_prefix_suffix.py index ea75e9ff51552..cc36a3caf6ec7 100644 --- a/pandas/tests/frame/methods/test_add_prefix_suffix.py +++ b/pandas/tests/frame/methods/test_add_prefix_suffix.py @@ -18,3 +18,56 @@ def test_add_prefix_suffix(float_frame): with_pct_suffix = float_frame.add_suffix("%") expected = Index([f"{c}%" for c in float_frame.columns]) tm.assert_index_equal(with_pct_suffix.columns, expected) + + +def test_add_prefix_suffix_copy(float_frame): + # GH#47934 + ser = float_frame.iloc[0] + + with_prefix = float_frame.add_prefix("foo#", copy=True) + expected = Index([f"foo#{c}" for c in float_frame.columns]) + tm.assert_index_equal(with_prefix.columns, expected) + assert not any( + tm.shares_memory(float_frame.iloc[:, i], with_prefix.iloc[:, i]) + for i in range(float_frame.shape[1]) + ) + + ser_with_prefix = ser.add_prefix("foo#", copy=True) + tm.assert_index_equal(ser_with_prefix.index, expected) + assert not tm.shares_memory(ser_with_prefix, ser) + + with_prefix = float_frame.add_prefix("foo#", copy=False) + expected = Index([f"foo#{c}" for c in float_frame.columns]) + tm.assert_index_equal(with_prefix.columns, expected) + assert all( + tm.shares_memory(float_frame.iloc[:, i], with_prefix.iloc[:, i]) + for i in range(float_frame.shape[1]) + ) + + ser_with_prefix = ser.add_prefix("foo#", copy=False) + tm.assert_index_equal(ser_with_prefix.index, expected) + assert tm.shares_memory(ser_with_prefix, ser) + + with_suffix = float_frame.add_suffix("#foo", copy=True) + expected = Index([f"{c}#foo" for c in float_frame.columns]) + tm.assert_index_equal(with_suffix.columns, expected) + assert not any( + tm.shares_memory(float_frame.iloc[:, i], with_suffix.iloc[:, i]) + for i in range(float_frame.shape[1]) + ) + + ser_with_suffix = ser.add_suffix("#foo", copy=True) + tm.assert_index_equal(ser_with_suffix.index, expected) + assert not tm.shares_memory(ser_with_suffix, ser) + + with_suffix = float_frame.add_suffix("#foo", copy=False) + expected = Index([f"{c}#foo" for c in float_frame.columns]) + tm.assert_index_equal(with_suffix.columns, expected) + assert all( + tm.shares_memory(float_frame.iloc[:, i], with_suffix.iloc[:, i]) + for i in range(float_frame.shape[1]) + ) + + ser_with_suffix = ser.add_suffix("#foo", copy=False) + tm.assert_index_equal(ser_with_suffix.index, expected) + assert tm.shares_memory(ser_with_suffix, ser)