Skip to content

Commit c73736c

Browse files
authored
API: Avoid returning same object for various methods (#51032)
1 parent cc6c957 commit c73736c

File tree

8 files changed

+57
-17
lines changed

8 files changed

+57
-17
lines changed

doc/source/whatsnew/v2.0.0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ Other API changes
624624
- Loading a JSON file with duplicate columns using ``read_json(orient='split')`` renames columns to avoid duplicates, as :func:`read_csv` and the other readers do (:issue:`50370`)
625625
- The levels of the index of the :class:`Series` returned from ``Series.sparse.from_coo`` now always have dtype ``int32``. Previously they had dtype ``int64`` (:issue:`50926`)
626626
- :func:`to_datetime` with ``unit`` of either "Y" or "M" will now raise if a sequence contains a non-round ``float`` value, matching the ``Timestamp`` behavior (:issue:`50301`)
627-
-
627+
- The methods :meth:`Series.round`, :meth:`DataFrame.__invert__`, :meth:`Series.__invert__`, :meth:`DataFrame.swapaxes`, :meth:`DataFrame.first`, :meth:`DataFrame.last`, :meth:`Series.first`, :meth:`Series.last` and :meth:`DataFrame.align` will now always return new objects (:issue:`51032`)
628628

629629
.. ---------------------------------------------------------------------------
630630
.. _whatsnew_200.deprecations:

pandas/core/frame.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9945,7 +9945,7 @@ def _series_round(ser: Series, decimals: int):
99459945
concat(new_cols, axis=1), index=self.index, columns=self.columns
99469946
).__finalize__(self, method="round")
99479947
else:
9948-
return self
9948+
return self.copy(deep=False)
99499949

99509950
# ----------------------------------------------------------------------
99519951
# Statistical methods, etc.

pandas/core/generic.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -780,8 +780,6 @@ def swapaxes(
780780
j = self._get_axis_number(axis2)
781781

782782
if i == j:
783-
if copy is False and not using_copy_on_write():
784-
return self
785783
return self.copy(deep=copy)
786784

787785
mapping = {i: j, j: i}
@@ -1487,7 +1485,7 @@ def blk_func(values: ArrayLike):
14871485
def __invert__(self: NDFrameT) -> NDFrameT:
14881486
if not self.size:
14891487
# inv fails with 0 len
1490-
return self
1488+
return self.copy(deep=False)
14911489

14921490
new_data = self._mgr.apply(operator.invert)
14931491
return self._constructor(new_data).__finalize__(self, method="__invert__")
@@ -8900,7 +8898,7 @@ def first(self: NDFrameT, offset) -> NDFrameT:
89008898
raise TypeError("'first' only supports a DatetimeIndex index")
89018899

89028900
if len(self.index) == 0:
8903-
return self
8901+
return self.copy(deep=False)
89048902

89058903
offset = to_offset(offset)
89068904
if not isinstance(offset, Tick) and offset.is_on_offset(self.index[0]):
@@ -8973,7 +8971,7 @@ def last(self: NDFrameT, offset) -> NDFrameT:
89738971
raise TypeError("'last' only supports a DatetimeIndex index")
89748972

89758973
if len(self.index) == 0:
8976-
return self
8974+
return self.copy(deep=False)
89778975

89788976
offset = to_offset(offset)
89798977

@@ -9481,8 +9479,6 @@ def _align_series(
94819479
limit=None,
94829480
fill_axis: Axis = 0,
94839481
):
9484-
uses_cow = using_copy_on_write()
9485-
94869482
is_series = isinstance(self, ABCSeries)
94879483

94889484
if (not is_series and axis is None) or axis not in [None, 0, 1]:
@@ -9505,10 +9501,7 @@ def _align_series(
95059501
if is_series:
95069502
left = self._reindex_indexer(join_index, lidx, copy)
95079503
elif lidx is None or join_index is None:
9508-
if uses_cow:
9509-
left = self.copy(deep=copy)
9510-
else:
9511-
left = self.copy(deep=copy) if copy or copy is None else self
9504+
left = self.copy(deep=copy)
95129505
else:
95139506
left = self._constructor(
95149507
self._mgr.reindex_indexer(join_index, lidx, axis=1, copy=copy)
@@ -9537,10 +9530,7 @@ def _align_series(
95379530
left = self._constructor(fdata)
95389531

95399532
if ridx is None:
9540-
if uses_cow:
9541-
right = other.copy(deep=copy)
9542-
else:
9543-
right = other.copy(deep=copy) if copy or copy is None else other
9533+
right = other.copy(deep=copy)
95449534
else:
95459535
right = other.reindex(join_index, level=level)
95469536

pandas/tests/frame/methods/test_align.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,23 @@ def test_align_series_check_copy(self):
414414
result, other = df.align(ser, axis=1)
415415
ser.iloc[0] = 100
416416
tm.assert_series_equal(other, expected)
417+
418+
def test_align_identical_different_object(self):
419+
# GH#51032
420+
df = DataFrame({"a": [1, 2]})
421+
ser = Series([3, 4])
422+
result, result2 = df.align(ser, axis=0)
423+
tm.assert_frame_equal(result, df)
424+
tm.assert_series_equal(result2, ser)
425+
assert df is not result
426+
assert ser is not result2
427+
428+
def test_align_identical_different_object_columns(self):
429+
# GH#51032
430+
df = DataFrame({"a": [1, 2]})
431+
ser = Series([1], index=["a"])
432+
result, result2 = df.align(ser, axis=1)
433+
tm.assert_frame_equal(result, df)
434+
tm.assert_series_equal(result2, ser)
435+
assert df is not result
436+
assert ser is not result2

pandas/tests/frame/methods/test_first_and_last.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
import pytest
55

6+
import pandas as pd
67
from pandas import (
78
DataFrame,
89
bdate_range,
@@ -86,3 +87,11 @@ def test_first_with_first_day_end_of_frq_n_greater_one(self, frame_or_series):
8687
[1] * 23, index=bdate_range("2010-03-31", "2010-04-30")
8788
)
8889
tm.assert_equal(result, expected)
90+
91+
@pytest.mark.parametrize("func", ["first", "last"])
92+
def test_empty_not_input(self, func):
93+
# GH#51032
94+
df = DataFrame(index=pd.DatetimeIndex([]))
95+
result = getattr(df, func)(offset=1)
96+
tm.assert_frame_equal(df, result)
97+
assert df is not result

pandas/tests/frame/methods/test_round.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,10 @@ def test_round_interval_category_columns(self):
216216
result = df.round()
217217
expected = DataFrame([[1.0, 1.0], [0.0, 0.0]], columns=columns)
218218
tm.assert_frame_equal(result, expected)
219+
220+
def test_round_empty_not_input(self):
221+
# GH#51032
222+
df = DataFrame()
223+
result = df.round()
224+
tm.assert_frame_equal(df, result)
225+
assert df is not result

pandas/tests/frame/methods/test_swapaxes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,10 @@ def test_swapaxes_invalid_axis(self):
2020
msg = "No axis named 2 for object type DataFrame"
2121
with pytest.raises(ValueError, match=msg):
2222
df.swapaxes(2, 5)
23+
24+
def test_round_empty_not_input(self):
25+
# GH#51032
26+
df = DataFrame({"a": [1, 2]})
27+
result = df.swapaxes("index", "index")
28+
tm.assert_frame_equal(df, result)
29+
assert df is not result

pandas/tests/frame/test_unary.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ def test_invert_mixed(self):
8484
)
8585
tm.assert_frame_equal(result, expected)
8686

87+
def test_invert_empy_not_input(self):
88+
# GH#51032
89+
df = pd.DataFrame()
90+
result = ~df
91+
tm.assert_frame_equal(df, result)
92+
assert df is not result
93+
8794
@pytest.mark.parametrize(
8895
"df",
8996
[

0 commit comments

Comments
 (0)