From 9b8f2af0cc434d1e51015ffc77b1715980e12e66 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 18 Dec 2017 10:25:19 -0500 Subject: [PATCH 1/2] DEPR: Deprecate is_copy (#18801) - Renamed 'is_copy' attribute to '_is_copy' for internal use - Setup getter and setter for 'is_copy' - Added tests for deprecation warning --- doc/source/whatsnew/v0.22.0.txt | 1 + pandas/core/generic.py | 42 ++++++++++++------- pandas/core/indexes/accessors.py | 12 +++--- pandas/core/indexing.py | 2 +- pandas/tests/indexes/test_multi.py | 4 +- .../indexing/test_chaining_and_caching.py | 38 +++++++++++------ pandas/tests/test_panel.py | 2 +- pandas/tests/test_panel4d.py | 2 +- 8 files changed, 64 insertions(+), 39 deletions(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index c4a7bab0f9406..68f9dd006b2b8 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -204,6 +204,7 @@ Deprecations - ``DataFrame.as_matrix`` is deprecated. Use ``DataFrame.values`` instead (:issue:`18458`). - ``Series.asobject``, ``DatetimeIndex.asobject``, ``PeriodIndex.asobject`` and ``TimeDeltaIndex.asobject`` have been deprecated. Use ``.astype(object)`` instead (:issue:`18572`) - ``Series.valid`` is deprecated. Use :meth:`Series.dropna` instead (:issue:`18800`). +- 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..dacf98e3808f2 100644 --- a/pandas/tests/indexing/test_chaining_and_caching.py +++ b/pandas/tests/indexing/test_chaining_and_caching.py @@ -8,6 +8,7 @@ from pandas import (compat, DataFrame, option_context, Series, MultiIndex, date_range, Timestamp) from pandas.util import testing as tm +from pandas.core.common import SettingWithCopyError, SettingWithCopyWarning class TestCaching(object): @@ -136,7 +137,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 +146,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 +154,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 +167,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 +187,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 +212,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 +229,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 +237,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 +253,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 +419,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 + is_copy = 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): From a5f090e369eb9caf49bf36fa54ec5942daee5ff5 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 18 Dec 2017 10:32:29 -0500 Subject: [PATCH 2/2] Fixed flake8 issues. --- pandas/tests/indexing/test_chaining_and_caching.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pandas/tests/indexing/test_chaining_and_caching.py b/pandas/tests/indexing/test_chaining_and_caching.py index dacf98e3808f2..0e396a3248e3f 100644 --- a/pandas/tests/indexing/test_chaining_and_caching.py +++ b/pandas/tests/indexing/test_chaining_and_caching.py @@ -8,7 +8,6 @@ from pandas import (compat, DataFrame, option_context, Series, MultiIndex, date_range, Timestamp) from pandas.util import testing as tm -from pandas.core.common import SettingWithCopyError, SettingWithCopyWarning class TestCaching(object): @@ -421,11 +420,11 @@ def test_cache_updating(self): tm.assert_series_equal(df.f, expected) def test_deprecate_is_copy(self): - #GH18801 + # GH18801 df = DataFrame({"A": [1, 2, 3]}) with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # getter - is_copy = df.is_copy + df.is_copy with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # setter