diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 21b3a0c033702..db3f2de703c34 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4879,7 +4879,7 @@ def align( join: AlignJoin = "outer", axis: Axis | None = None, level: Level = None, - copy: bool = True, + copy: bool | None = None, fill_value=None, method: FillnaOptions | None = None, limit: int | None = None, diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 7a40e30c0ae7a..481e9f5ee847f 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -5254,7 +5254,7 @@ def _reindex_with_indexers( self: NDFrameT, reindexers, fill_value=None, - copy: bool_t = False, + copy: bool_t | None = False, allow_dups: bool_t = False, ) -> NDFrameT: """allow_dups indicates an internal call here""" @@ -5283,8 +5283,8 @@ def _reindex_with_indexers( # If we've made a copy once, no need to make another one copy = False - if copy and new_data is self._mgr: - new_data = new_data.copy() + if (copy or copy is None) and new_data is self._mgr: + new_data = new_data.copy(deep=copy) return self._constructor(new_data).__finalize__(self) @@ -9058,7 +9058,7 @@ def align( join: AlignJoin = "outer", axis: Axis | None = None, level: Level = None, - copy: bool_t = True, + copy: bool_t | None = None, fill_value: Hashable = None, method: FillnaOptions | None = None, limit: int | None = None, @@ -9251,7 +9251,7 @@ def _align_frame( join: AlignJoin = "outer", axis: Axis | None = None, level=None, - copy: bool_t = True, + copy: bool_t | None = None, fill_value=None, method=None, limit=None, @@ -9315,7 +9315,7 @@ def _align_series( join: AlignJoin = "outer", axis: Axis | None = None, level=None, - copy: bool_t = True, + copy: bool_t | None = None, fill_value=None, method=None, limit=None, @@ -9344,7 +9344,7 @@ def _align_series( if is_series: left = self._reindex_indexer(join_index, lidx, copy) elif lidx is None or join_index is None: - left = self.copy() if copy else self + left = self.copy(deep=copy) if copy or copy is None else self else: left = self._constructor( self._mgr.reindex_indexer(join_index, lidx, axis=1, copy=copy) @@ -9373,7 +9373,7 @@ def _align_series( left = self._constructor(fdata) if ridx is None: - right = other + right = other.copy(deep=copy) if copy or copy is None else other else: right = other.reindex(join_index, level=level) diff --git a/pandas/core/series.py b/pandas/core/series.py index 1bdf92e1dcf02..8400dc1f2ed91 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4591,15 +4591,18 @@ def _reduce( return op(delegate, skipna=skipna, **kwds) def _reindex_indexer( - self, new_index: Index | None, indexer: npt.NDArray[np.intp] | None, copy: bool + self, + new_index: Index | None, + indexer: npt.NDArray[np.intp] | None, + copy: bool | None, ) -> Series: # Note: new_index is None iff indexer is None # if not None, indexer is np.intp if indexer is None and ( new_index is None or new_index.names == self.index.names ): - if copy: - return self.copy() + if copy or copy is None: + return self.copy(deep=copy) return self new_values = algorithms.take_nd( @@ -4626,7 +4629,7 @@ def align( join: AlignJoin = "outer", axis: Axis | None = None, level: Level = None, - copy: bool = True, + copy: bool | None = None, fill_value: Hashable = None, method: FillnaOptions | None = None, limit: int | None = None, diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index f5c7b31e59bc5..f37fafebd0e99 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -172,6 +172,53 @@ def test_select_dtypes(using_copy_on_write): tm.assert_frame_equal(df, df_orig) +@pytest.mark.parametrize( + "func", + [ + lambda x, y: x.align(y), + lambda x, y: x.align(y.a, axis=0), + lambda x, y: x.align(y.a.iloc[slice(0, 1)], axis=1), + ], +) +def test_align_frame(using_copy_on_write, func): + df = DataFrame({"a": [1, 2, 3], "b": "a"}) + df_orig = df.copy() + df_changed = df[["b", "a"]].copy() + df2, _ = func(df, df_changed) + + if using_copy_on_write: + assert np.shares_memory(get_array(df2, "a"), get_array(df, "a")) + else: + assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a")) + + df2.iloc[0, 0] = 0 + if using_copy_on_write: + assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a")) + tm.assert_frame_equal(df, df_orig) + + +def test_align_series(using_copy_on_write): + ser = Series([1, 2]) + ser_orig = ser.copy() + ser_other = ser.copy() + ser2, ser_other_result = ser.align(ser_other) + + if using_copy_on_write: + assert np.shares_memory(ser2.values, ser.values) + assert np.shares_memory(ser_other_result.values, ser_other.values) + else: + assert not np.shares_memory(ser2.values, ser.values) + assert not np.shares_memory(ser_other_result.values, ser_other.values) + + ser2.iloc[0] = 0 + ser_other_result.iloc[0] = 0 + if using_copy_on_write: + assert not np.shares_memory(ser2.values, ser.values) + assert not np.shares_memory(ser_other_result.values, ser_other.values) + tm.assert_series_equal(ser, ser_orig) + tm.assert_series_equal(ser_other, ser_orig) + + def test_to_frame(using_copy_on_write): # Case: converting a Series to a DataFrame with to_frame ser = Series([1, 2, 3]) diff --git a/pandas/tests/frame/methods/test_align.py b/pandas/tests/frame/methods/test_align.py index 73c79996e5b81..88963dcc4b0f7 100644 --- a/pandas/tests/frame/methods/test_align.py +++ b/pandas/tests/frame/methods/test_align.py @@ -402,3 +402,12 @@ def _check_align_fill(self, frame, kind, meth, ax, fax): self._check_align( empty, empty, axis=ax, fill_axis=fax, how=kind, method=meth, limit=1 ) + + def test_align_series_check_copy(self): + # GH# + df = DataFrame({0: [1, 2]}) + ser = Series([1], name=0) + expected = ser.copy() + result, other = df.align(ser, axis=1) + ser.iloc[0] = 100 + tm.assert_series_equal(other, expected)