Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEPR: Deprecate is_copy (#18801) #18812

Merged
merged 3 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.22.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ Deprecations
retain the previous behavior, use a list instead of a tuple (:issue:`18314`)
- ``Series.valid`` is deprecated. Use :meth:`Series.dropna` instead (:issue:`18800`).
- :func:`read_excel` has deprecated the ``skip_footer`` parameter. Use ``skipfooter`` instead (:issue:`18836`)
- The ``is_copy`` attribute is deprecated and will be removed in a future version (:issue:`18801`).

.. _whatsnew_0220.prior_deprecations:

Expand Down
42 changes: 27 additions & 15 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class NDFrame(PandasObject, SelectionMixin):
axes : list
copy : boolean, default False
"""
_internal_names = ['_data', '_cacher', '_item_cache', '_cache', 'is_copy',
_internal_names = ['_data', '_cacher', '_item_cache', '_cache', '_is_copy',
'_subtyp', '_name', '_index', '_default_kind',
'_default_fill_value', '_metadata', '__array_struct__',
'__array_interface__']
Expand All @@ -117,7 +117,7 @@ class NDFrame(PandasObject, SelectionMixin):
_deprecations = frozenset(['as_blocks', 'blocks',
'consolidate', 'convert_objects'])
_metadata = []
is_copy = None
_is_copy = None

def __init__(self, data, axes=None, copy=False, dtype=None,
fastpath=False):
Expand All @@ -132,10 +132,22 @@ def __init__(self, data, axes=None, copy=False, dtype=None,
for i, ax in enumerate(axes):
data = data.reindex_axis(ax, axis=i)

object.__setattr__(self, 'is_copy', None)
object.__setattr__(self, '_is_copy', None)
object.__setattr__(self, '_data', data)
object.__setattr__(self, '_item_cache', {})

@property
def is_copy(self):
warnings.warn("Attribute 'is_copy' is deprecated and will be removed "
"in a future version.", FutureWarning, stacklevel=2)
return self._is_copy

@is_copy.setter
def is_copy(self, msg):
warnings.warn("Attribute 'is_copy' is deprecated and will be removed "
"in a future version.", FutureWarning, stacklevel=2)
self._is_copy = msg

def _repr_data_resource_(self):
"""
Not a real Jupyter special repr method, but we use the same
Expand Down Expand Up @@ -2153,7 +2165,7 @@ def _get_item_cache(self, item):
res._set_as_cached(item, self)

# for a chain
res.is_copy = self.is_copy
res._is_copy = self._is_copy
return res

def _set_as_cached(self, item, cacher):
Expand Down Expand Up @@ -2264,12 +2276,12 @@ def _set_item(self, key, value):

def _set_is_copy(self, ref=None, copy=True):
if not copy:
self.is_copy = None
self._is_copy = None
else:
if ref is not None:
self.is_copy = weakref.ref(ref)
self._is_copy = weakref.ref(ref)
else:
self.is_copy = None
self._is_copy = None

def _check_is_chained_assignment_possible(self):
"""
Expand All @@ -2288,7 +2300,7 @@ def _check_is_chained_assignment_possible(self):
self._check_setitem_copy(stacklevel=4, t='referant',
force=True)
return True
elif self.is_copy:
elif self._is_copy:
self._check_setitem_copy(stacklevel=4, t='referant')
return False

Expand Down Expand Up @@ -2323,7 +2335,7 @@ def _check_setitem_copy(self, stacklevel=4, t='setting', force=False):

"""

if force or self.is_copy:
if force or self._is_copy:

value = config.get_option('mode.chained_assignment')
if value is None:
Expand All @@ -2333,23 +2345,23 @@ def _check_setitem_copy(self, stacklevel=4, t='setting', force=False):
# the copy weakref
try:
gc.collect(2)
if not gc.get_referents(self.is_copy()):
self.is_copy = None
if not gc.get_referents(self._is_copy()):
self._is_copy = None
return
except Exception:
pass

# we might be a false positive
try:
if self.is_copy().shape == self.shape:
self.is_copy = None
if self._is_copy().shape == self.shape:
self._is_copy = None
return
except Exception:
pass

# a custom message
if isinstance(self.is_copy, string_types):
t = self.is_copy
if isinstance(self._is_copy, string_types):
t = self._is_copy

elif t == 'referant':
t = ("\n"
Expand Down
12 changes: 6 additions & 6 deletions pandas/core/indexes/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ def _delegate_property_get(self, name):
result = Series(result, index=self.index, name=self.name)

# setting this object will show a SettingWithCopyWarning/Error
result.is_copy = ("modifications to a property of a datetimelike "
"object are not supported and are discarded. "
"Change values on the original.")
result._is_copy = ("modifications to a property of a datetimelike "
"object are not supported and are discarded. "
"Change values on the original.")

return result

Expand All @@ -136,9 +136,9 @@ def _delegate_method(self, name, *args, **kwargs):
result = Series(result, index=self.index, name=self.name)

# setting this object will show a SettingWithCopyWarning/Error
result.is_copy = ("modifications to a method of a datetimelike object "
"are not supported and are discarded. Change "
"values on the original.")
result._is_copy = ("modifications to a method of a datetimelike "
"object are not supported and are discarded. "
"Change values on the original.")

return result

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def _setitem_with_indexer(self, indexer, value):
labels = index.insert(len(index), key)
self.obj._data = self.obj.reindex(labels, axis=i)._data
self.obj._maybe_update_cacher(clear=True)
self.obj.is_copy = None
self.obj._is_copy = None

nindexer.append(labels.get_loc(key))

Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexes/test_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,10 @@ def test_set_value_keeps_names(self):
columns=['one', 'two', 'three', 'four'],
index=idx)
df = df.sort_index()
assert df.is_copy is None
assert df._is_copy is None
assert df.index.names == ('Name', 'Number')
df.at[('grethe', '4'), 'one'] = 99.34
assert df.is_copy is None
assert df._is_copy is None
assert df.index.names == ('Name', 'Number')

def test_copy_names(self):
Expand Down
37 changes: 24 additions & 13 deletions pandas/tests/indexing/test_chaining_and_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def test_detect_chained_assignment(self):
expected = DataFrame([[-5, 1], [-6, 3]], columns=list('AB'))
df = DataFrame(np.arange(4).reshape(2, 2),
columns=list('AB'), dtype='int64')
assert df.is_copy is None
assert df._is_copy is None

df['A'][0] = -5
df['A'][1] = -6
Expand All @@ -145,15 +145,15 @@ def test_detect_chained_assignment(self):
# test with the chaining
df = DataFrame({'A': Series(range(2), dtype='int64'),
'B': np.array(np.arange(2, 4), dtype=np.float64)})
assert df.is_copy is None
assert df._is_copy is None

with pytest.raises(com.SettingWithCopyError):
df['A'][0] = -5

with pytest.raises(com.SettingWithCopyError):
df['A'][1] = np.nan

assert df['A'].is_copy is None
assert df['A']._is_copy is None

# Using a copy (the chain), fails
df = DataFrame({'A': Series(range(2), dtype='int64'),
Expand All @@ -166,7 +166,7 @@ def test_detect_chained_assignment(self):
df = DataFrame({'a': ['one', 'one', 'two', 'three',
'two', 'one', 'six'],
'c': Series(range(7), dtype='int64')})
assert df.is_copy is None
assert df._is_copy is None

with pytest.raises(com.SettingWithCopyError):
indexer = df.a.str.startswith('o')
Expand All @@ -186,7 +186,7 @@ def test_detect_chained_assignment(self):

# gh-5475: Make sure that is_copy is picked up reconstruction
df = DataFrame({"A": [1, 2]})
assert df.is_copy is None
assert df._is_copy is None

with tm.ensure_clean('__tmp__pickle') as path:
df.to_pickle(path)
Expand All @@ -211,39 +211,39 @@ def random_text(nobs=100):

# Always a copy
x = df.iloc[[0, 1, 2]]
assert x.is_copy is not None
assert x._is_copy is not None

x = df.iloc[[0, 1, 2, 4]]
assert x.is_copy is not None
assert x._is_copy is not None

# Explicitly copy
indexer = df.letters.apply(lambda x: len(x) > 10)
df = df.loc[indexer].copy()

assert df.is_copy is None
assert df._is_copy is None
df['letters'] = df['letters'].apply(str.lower)

# Implicitly take
df = random_text(100000)
indexer = df.letters.apply(lambda x: len(x) > 10)
df = df.loc[indexer]

assert df.is_copy is not None
assert df._is_copy is not None
df['letters'] = df['letters'].apply(str.lower)

# Implicitly take 2
df = random_text(100000)
indexer = df.letters.apply(lambda x: len(x) > 10)

df = df.loc[indexer]
assert df.is_copy is not None
assert df._is_copy is not None
df.loc[:, 'letters'] = df['letters'].apply(str.lower)

# Should be ok even though it's a copy!
assert df.is_copy is None
assert df._is_copy is None

df['letters'] = df['letters'].apply(str.lower)
assert df.is_copy is None
assert df._is_copy is None

df = random_text(100000)
indexer = df.letters.apply(lambda x: len(x) > 10)
Expand All @@ -252,7 +252,7 @@ def random_text(nobs=100):

# an identical take, so no copy
df = DataFrame({'a': [1]}).dropna()
assert df.is_copy is None
assert df._is_copy is None
df['a'] += 1

# Inplace ops, originally from:
Expand Down Expand Up @@ -418,3 +418,14 @@ def test_cache_updating(self):
tm.assert_frame_equal(df, expected)
expected = Series([0, 0, 0, 2, 0], name='f')
tm.assert_series_equal(df.f, expected)

def test_deprecate_is_copy(self):
# GH18801
df = DataFrame({"A": [1, 2, 3]})
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# getter
df.is_copy

with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# setter
df.is_copy = "test deprecated is_copy"
2 changes: 1 addition & 1 deletion pandas/tests/test_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ def test_xs(self):
# Mixed-type yields a copy.
self.panel['strings'] = 'foo'
result = self.panel.xs('D', axis=2)
assert result.is_copy is not None
assert result._is_copy is not None

def test_getitem_fancy_labels(self):
with catch_warnings(record=True):
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/test_panel4d.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ def test_xs(self):
with catch_warnings(record=True):
result = self.panel4d.xs('D', axis=3)

assert result.is_copy is not None
assert result._is_copy is not None

def test_getitem_fancy_labels(self):
with catch_warnings(record=True):
Expand Down