Skip to content

Commit a0cdec1

Browse files
committed
BUG/CLN: Clear _tuples on setting MI levels/labels
1 parent 5ea3f4a commit a0cdec1

File tree

4 files changed

+44
-3
lines changed

4 files changed

+44
-3
lines changed

doc/source/release.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,9 @@ Bug Fixes
597597
- Bug in ``to_datetime`` with a format and ``coerce=True`` not raising (:issue:`5195`)
598598
- Bug in ``loc`` setting with multiple indexers and a rhs of a Series that needs
599599
broadcasting (:issue:`5206`)
600+
- Fixed bug where inplace setting of levels or labels on ``MultiIndex`` would
601+
not clear cached ``values`` property and therefore return wrong ``values``.
602+
(:issue:`5215`)
600603

601604
pandas 0.12.0
602605
-------------

pandas/core/index.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1903,8 +1903,9 @@ def _set_levels(self, levels, copy=False, validate=True):
19031903
for lev in levels)
19041904
names = self.names
19051905
self._levels = levels
1906-
if len(names):
1906+
if any(names):
19071907
self._set_names(names)
1908+
self._tuples = None
19081909

19091910
def set_levels(self, levels, inplace=False):
19101911
"""
@@ -1947,6 +1948,7 @@ def _set_labels(self, labels, copy=False, validate=True):
19471948
raise ValueError("Length of labels must match length of levels")
19481949
self._labels = FrozenList(_ensure_frozen(labs, copy=copy)._shallow_copy()
19491950
for labs in labels)
1951+
self._tuples = None
19501952

19511953
def set_labels(self, labels, inplace=False):
19521954
"""

pandas/tests/test_index.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,41 @@ def test_metadata_immutable(self):
13191319
with assertRaisesRegexp(TypeError, mutable_regex):
13201320
names[0] = names[0]
13211321

1322+
def test_inplace_mutation_resets_values(self):
1323+
levels = [['a', 'b', 'c'], [4]]
1324+
levels2 = [[1, 2, 3], ['a']]
1325+
labels = [[0, 1, 0, 2, 2, 0], [0, 0, 0, 0, 0, 0]]
1326+
mi1 = MultiIndex(levels=levels, labels=labels)
1327+
mi2 = MultiIndex(levels=levels2, labels=labels)
1328+
vals = mi1.values.copy()
1329+
vals2 = mi2.values.copy()
1330+
self.assert_(mi1._tuples is not None)
1331+
1332+
# make sure level setting works
1333+
new_vals = mi1.set_levels(levels2).values
1334+
assert_almost_equal(vals2, new_vals)
1335+
# non-inplace doesn't kill _tuples [implementation detail]
1336+
assert_almost_equal(mi1._tuples, vals)
1337+
# and values is still same too
1338+
assert_almost_equal(mi1.values, vals)
1339+
1340+
# inplace should kill _tuples
1341+
mi1.set_levels(levels2, inplace=True)
1342+
assert_almost_equal(mi1.values, vals2)
1343+
1344+
# make sure label setting works too
1345+
labels2 = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
1346+
exp_values = np.array([(1, 'a')] * 6, dtype=object)
1347+
new_values = mi2.set_labels(labels2).values
1348+
# not inplace shouldn't change
1349+
assert_almost_equal(mi2._tuples, vals2)
1350+
# should have correct values
1351+
assert_almost_equal(exp_values, new_values)
1352+
1353+
# and again setting inplace should kill _tuples, etc
1354+
mi2.set_labels(labels2, inplace=True)
1355+
assert_almost_equal(mi2.values, new_values)
1356+
13221357
def test_copy_in_constructor(self):
13231358
levels = np.array(["a", "b", "c"])
13241359
labels = np.array([1, 1, 2, 0, 0, 1, 1])

pandas/util/testing.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,14 +382,15 @@ def assert_almost_equal(a, b, check_less_precise=False):
382382
return assert_dict_equal(a, b)
383383

384384
if isinstance(a, compat.string_types):
385-
assert a == b, "%s != %s" % (a, b)
385+
assert a == b, "%r != %r" % (a, b)
386386
return True
387387

388388
if isiterable(a):
389389
np.testing.assert_(isiterable(b))
390390
na, nb = len(a), len(b)
391391
assert na == nb, "%s != %s" % (na, nb)
392-
if np.array_equal(a, b):
392+
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray) and\
393+
np.array_equal(a, b):
393394
return True
394395
else:
395396
for i in range(na):

0 commit comments

Comments
 (0)