From f24444593034c15b039348a8212ad9d063341400 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 13 Nov 2017 11:17:57 +0100 Subject: [PATCH 1/2] CLN/DEPR: remove setter for MultiIndex levels/labels properties --- doc/source/whatsnew/v0.22.0.txt | 2 +- pandas/core/indexes/multi.py | 23 +++++------------------ pandas/tests/indexes/test_multi.py | 28 ++++++++++++++++++---------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index dd5b849b42a08..4e156ec61f992 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -61,7 +61,7 @@ Removal of prior version deprecations/changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Warnings against the obsolete usage ``Categorical(codes, categories)``, which were emitted for instance when the first two arguments to ``Categorical()`` had different dtypes, and recommended the use of ``Categorical.from_codes``, have now been removed (:issue:`8074`) -- +- The ``levels`` and ``labels`` attributes of a ``MultiIndex`` can no longer be set directly (:issue:`4039`). - .. _whatsnew_0220.performance: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index f603a0eef36a5..0abfba3a45b15 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -28,8 +28,7 @@ is_true_slices) import pandas.core.base as base -from pandas.util._decorators import (Appender, cache_readonly, - deprecate, deprecate_kwarg) +from pandas.util._decorators import Appender, cache_readonly, deprecate_kwarg import pandas.core.common as com import pandas.core.missing as missing import pandas.core.algorithms as algos @@ -177,7 +176,8 @@ def _verify_integrity(self, labels=None, levels=None): " inconsistent state" % (i, label.max(), len(level))) - def _get_levels(self): + @property + def levels(self): return self._levels def _set_levels(self, levels, level=None, copy=False, validate=True, @@ -279,14 +279,8 @@ def set_levels(self, levels, level=None, inplace=False, if not inplace: return idx - # remove me in 0.14 and change to read only property - __set_levels = deprecate("setting `levels` directly", - partial(set_levels, inplace=True, - verify_integrity=True), - alt_name="set_levels") - levels = property(fget=_get_levels, fset=__set_levels) - - def _get_labels(self): + @property + def labels(self): return self._labels def _set_labels(self, labels, level=None, copy=False, validate=True, @@ -379,13 +373,6 @@ def set_labels(self, labels, level=None, inplace=False, if not inplace: return idx - # remove me in 0.14 and change to readonly property - __set_labels = deprecate("setting labels directly", - partial(set_labels, inplace=True, - verify_integrity=True), - alt_name="set_labels") - labels = property(fget=_get_labels, fset=__set_labels) - def copy(self, names=None, dtype=None, levels=None, labels=None, deep=False, _set_identity=False, **kwargs): """ diff --git a/pandas/tests/indexes/test_multi.py b/pandas/tests/indexes/test_multi.py index b69b958d4e4ba..dbd18de16cebd 100644 --- a/pandas/tests/indexes/test_multi.py +++ b/pandas/tests/indexes/test_multi.py @@ -158,6 +158,24 @@ def test_set_name_methods(self): assert res is None assert ind.names == new_names2 + def test_set_levels_labels_directly(self): + # setting levels/labels directly raises AttributeError + + levels = self.index.levels + new_levels = [[lev + 'a' for lev in level] for level in levels] + + labels = self.index.labels + major_labels, minor_labels = labels + major_labels = [(x + 1) % 3 for x in major_labels] + minor_labels = [(x + 1) % 1 for x in minor_labels] + new_labels = [major_labels, minor_labels] + + with pytest.raises(AttributeError): + self.index.levels = new_levels + + with pytest.raises(AttributeError): + self.index.labels = new_labels + def test_set_levels(self): # side note - you probably wouldn't want to use levels and labels # directly like this - but it is possible. @@ -578,16 +596,6 @@ def test_constructor_mismatched_label_levels(self): with tm.assert_raises_regex(ValueError, label_error): self.index.copy().set_labels([[0, 0, 0, 0], [0, 0]]) - # deprecated properties - with warnings.catch_warnings(): - warnings.simplefilter('ignore') - - with tm.assert_raises_regex(ValueError, length_error): - self.index.copy().levels = [['a'], ['b']] - - with tm.assert_raises_regex(ValueError, label_error): - self.index.copy().labels = [[0, 0, 0, 0], [0, 0]] - def assert_multiindex_copied(self, copy, original): # Levels should be (at least, shallow copied) tm.assert_copy(copy.levels, original.levels) From a393159d2fb282b009f8a37fd1109dee6bc5aa9c Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 13 Nov 2017 11:53:26 +0100 Subject: [PATCH 2/2] remove unused import --- pandas/core/indexes/multi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 0abfba3a45b15..ada6a39e7bbe4 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2,7 +2,6 @@ # pylint: disable=E1101,E1103,W0232 import datetime import warnings -from functools import partial from sys import getsizeof import numpy as np