diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 0579a80aad28e..3188b66c619ae 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -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: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 4eb7865523cc3..98d2c3b34459c 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -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__'] @@ -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): @@ -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 @@ -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): @@ -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): """ @@ -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 @@ -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: @@ -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" diff --git a/pandas/core/indexes/accessors.py b/pandas/core/indexes/accessors.py index 27e1006c23174..116c7eb8c7958 100644 --- a/pandas/core/indexes/accessors.py +++ b/pandas/core/indexes/accessors.py @@ -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 @@ -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 diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index c6642657e386e..de6713249a7c7 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -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)) diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index 510ca6ac83ec0..9e30ed80278e0 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -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): diff --git a/pandas/tests/indexing/test_chaining_and_caching.py b/pandas/tests/indexing/test_chaining_and_caching.py index d76c53e7f36db..0e396a3248e3f 100644 --- a/pandas/tests/indexing/test_chaining_and_caching.py +++ b/pandas/tests/indexing/test_chaining_and_caching.py @@ -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 @@ -145,7 +145,7 @@ 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 @@ -153,7 +153,7 @@ def test_detect_chained_assignment(self): 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'), @@ -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') @@ -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) @@ -211,16 +211,16 @@ 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 @@ -228,7 +228,7 @@ def random_text(nobs=100): 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 @@ -236,14 +236,14 @@ def random_text(nobs=100): 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) @@ -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: @@ -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" diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py index 040c3adbcaf93..34c1ee5683183 100644 --- a/pandas/tests/test_panel.py +++ b/pandas/tests/test_panel.py @@ -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): diff --git a/pandas/tests/test_panel4d.py b/pandas/tests/test_panel4d.py index b064e3c7012bc..e194136ec716d 100644 --- a/pandas/tests/test_panel4d.py +++ b/pandas/tests/test_panel4d.py @@ -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):