Skip to content

Commit 180d81f

Browse files
API: Series and DataFrame constructors to return shallow copy (i.e. don't share index) from another Series/DataFrame (#50539)
1 parent fe0cc48 commit 180d81f

File tree

5 files changed

+34
-7
lines changed

5 files changed

+34
-7
lines changed

doc/source/whatsnew/v2.0.0.rst

+5
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,11 @@ Other API changes
783783
or :attr:`~DataFrame.iloc` (thus, ``df.loc[:, :]`` or ``df.iloc[:, :]``) now returns a
784784
new DataFrame (shallow copy) instead of the original DataFrame, consistent with other
785785
methods to get a full slice (for example ``df.loc[:]`` or ``df[:]``) (:issue:`49469`)
786+
- The :class:`Series` and :class:`DataFrame` constructors will now return a shallow copy
787+
(i.e. share data, but not attributes) when passed a Series and DataFrame,
788+
respectively, and with the default of ``copy=False`` (and if no other keyword triggers
789+
a copy). Previously, the new Series or DataFrame would share the index attribute (e.g.
790+
``df.index = ...`` would also update the index of the parent or child) (:issue:`49523`)
786791
- Disallow computing ``cumprod`` for :class:`Timedelta` object; previously this returned incorrect values (:issue:`50246`)
787792
- :class:`DataFrame` objects read from a :class:`HDFStore` file without an index now have a :class:`RangeIndex` instead of an ``int64`` index (:issue:`51076`)
788793
- Instantiating an :class:`Index` with an numeric numpy dtype with data containing :class:`NA` and/or :class:`NaT` now raises a ``ValueError``. Previously a ``TypeError`` was raised (:issue:`51050`)

pandas/core/frame.py

+4
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,10 @@ def __init__(
652652

653653
if isinstance(data, DataFrame):
654654
data = data._mgr
655+
if not copy:
656+
# if not copying data, ensure to still return a shallow copy
657+
# to avoid the result sharing the same Manager
658+
data = data.copy(deep=False)
655659

656660
if isinstance(data, (BlockManager, ArrayManager)):
657661
# first check if a Manager is passed without any other arguments

pandas/core/series.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,7 @@ def __init__(
446446
elif isinstance(data, Series):
447447
if index is None:
448448
index = data.index
449-
if using_copy_on_write():
450-
data = data._mgr.copy(deep=False)
451-
else:
452-
data = data._mgr
449+
data = data._mgr.copy(deep=False)
453450
else:
454451
data = data.reindex(index, copy=copy)
455452
copy = False

pandas/tests/frame/test_constructors.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,8 @@ def test_constructor_dtype_nocast_view_dataframe(self, using_copy_on_write):
285285
df = DataFrame([[1, 2]])
286286
should_be_view = DataFrame(df, dtype=df[0].dtype)
287287
if using_copy_on_write:
288-
# TODO(CoW) doesn't mutate original
289288
should_be_view.iloc[0, 0] = 99
290-
# assert df.values[0, 0] == 1
291-
assert df.values[0, 0] == 99
289+
assert df.values[0, 0] == 1
292290
else:
293291
should_be_view[0][0] = 99
294292
assert df.values[0, 0] == 99
@@ -2100,6 +2098,17 @@ def test_constructor_frame_copy(self, float_frame):
21002098
assert (cop["A"] == 5).all()
21012099
assert not (float_frame["A"] == 5).all()
21022100

2101+
def test_constructor_frame_shallow_copy(self, float_frame):
2102+
# constructing a DataFrame from DataFrame with copy=False should still
2103+
# give a "shallow" copy (share data, not attributes)
2104+
# https://github.com/pandas-dev/pandas/issues/49523
2105+
orig = float_frame.copy()
2106+
cop = DataFrame(float_frame)
2107+
assert cop._mgr is not float_frame._mgr
2108+
# Overwriting index of copy doesn't change original
2109+
cop.index = np.arange(len(cop))
2110+
tm.assert_frame_equal(float_frame, orig)
2111+
21032112
def test_constructor_ndarray_copy(self, float_frame, using_array_manager):
21042113
if not using_array_manager:
21052114
df = DataFrame(float_frame.values)

pandas/tests/series/test_constructors.py

+12
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,18 @@ def test_constructor_limit_copies(self, index):
727727
# we make 1 copy; this is just a smoke test here
728728
assert s._mgr.blocks[0].values is not index
729729

730+
def test_constructor_shallow_copy(self):
731+
# constructing a Series from Series with copy=False should still
732+
# give a "shallow" copy (share data, not attributes)
733+
# https://github.com/pandas-dev/pandas/issues/49523
734+
s = Series([1, 2, 3])
735+
s_orig = s.copy()
736+
s2 = Series(s)
737+
assert s2._mgr is not s._mgr
738+
# Overwriting index of s2 doesn't change s
739+
s2.index = ["a", "b", "c"]
740+
tm.assert_series_equal(s, s_orig)
741+
730742
def test_constructor_pass_none(self):
731743
s = Series(None, index=range(5))
732744
assert s.dtype == np.float64

0 commit comments

Comments
 (0)