diff --git a/doc/source/release.rst b/doc/source/release.rst index 429e4ea8169d1..190cf64884b44 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -295,7 +295,7 @@ Improvements to existing features the func (:issue:`6289`) - ``plot(legend='reverse')`` will now reverse the order of legend labels for most plot kinds. (:issue:`6014`) -- Allow multi-index slicers (:issue:`6134`, :issue:`4036`, :issue:`3057`, :issue:`2598`, :issue:`5641`) +- Allow multi-index slicers (:issue:`6134`, :issue:`4036`, :issue:`3057`, :issue:`2598`, :issue:`5641`, :issue:`7106`) - improve performance of slice indexing on Series with string keys (:issue:`6341`, :issue:`6372`) - implement joining a single-level indexed DataFrame on a matching column of a multi-indexed DataFrame (:issue:`3662`) - Performance improvement in indexing into a multi-indexed Series (:issue:`5567`) diff --git a/doc/source/v0.14.0.txt b/doc/source/v0.14.0.txt index 4f3f7976e8007..49551c5bd3550 100644 --- a/doc/source/v0.14.0.txt +++ b/doc/source/v0.14.0.txt @@ -290,7 +290,7 @@ You can use ``slice(None)`` to select all the contents of *that* level. You do n As usual, **both sides** of the slicers are included as this is label indexing. See :ref:`the docs` -See also issues (:issue:`6134`, :issue:`4036`, :issue:`3057`, :issue:`2598`, :issue:`5641`) +See also issues (:issue:`6134`, :issue:`4036`, :issue:`3057`, :issue:`2598`, :issue:`5641`, :issue:`7106`) .. warning:: diff --git a/pandas/core/index.py b/pandas/core/index.py index 7a2a160e0dc33..3cbefbf141491 100644 --- a/pandas/core/index.py +++ b/pandas/core/index.py @@ -2595,7 +2595,7 @@ def get_level_values(self, level): unique = self.levels[num] # .values labels = self.labels[num] filled = com.take_1d(unique.values, labels, fill_value=unique._na_value) - values = unique._simple_new(filled, self.names[num], + values = unique._simple_new(filled, self.names[num], freq=getattr(unique, 'freq', None), tz=getattr(unique, 'tz', None)) return values @@ -3556,8 +3556,6 @@ def get_locs(self, tup): if not self.is_lexsorted_for_tuple(tup): raise KeyError('MultiIndex Slicing requires the index to be fully lexsorted' ' tuple len ({0}), lexsort depth ({1})'.format(len(tup), self.lexsort_depth)) - if not self.is_unique: - raise ValueError('MultiIndex Slicing requires a unique index') def _convert_indexer(r): if isinstance(r, slice): diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index 27fc8cee738c9..d7ab08d38eac7 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -1356,6 +1356,47 @@ def f(): df.loc[(slice(None),[1])] self.assertRaises(KeyError, f) + # not lexsorted + self.assertEquals(df.index.lexsort_depth,2) + df = df.sortlevel(level=1,axis=0) + self.assertEquals(df.index.lexsort_depth,0) + with tm.assertRaisesRegexp(KeyError, 'MultiIndex Slicing requires the index to be fully lexsorted tuple len \(2\), lexsort depth \(0\)'): + df.loc[(slice(None),df.loc[:,('a','bar')]>5),:] + + def test_multiindex_slicers_non_unique(self): + + # GH 7106 + # non-unique mi index support + df = DataFrame(dict(A = ['foo','foo','foo','foo'], + B = ['a','a','a','a'], + C = [1,2,1,3], + D = [1,2,3,4])).set_index(['A','B','C']).sortlevel() + self.assertFalse(df.index.is_unique) + expected = DataFrame(dict(A = ['foo','foo'], + B = ['a','a'], + C = [1,1], + D = [1,3])).set_index(['A','B','C']).sortlevel() + result = df.loc[(slice(None),slice(None),1),:] + assert_frame_equal(result, expected) + + # this is equivalent of an xs expression + result = df.xs(1,level=2,drop_level=False) + assert_frame_equal(result, expected) + + df = DataFrame(dict(A = ['foo','foo','foo','foo'], + B = ['a','a','a','a'], + C = [1,2,1,2], + D = [1,2,3,4])).set_index(['A','B','C']).sortlevel() + self.assertFalse(df.index.is_unique) + expected = DataFrame(dict(A = ['foo','foo'], + B = ['a','a'], + C = [1,1], + D = [1,3])).set_index(['A','B','C']).sortlevel() + result = df.loc[(slice(None),slice(None),1),:] + self.assertFalse(result.index.is_unique) + assert_frame_equal(result, expected) + + def test_per_axis_per_level_doc_examples(self): # test index maker