Skip to content

Commit 7850436

Browse files
committed
BUG: do not raise UnsortedIndexError if sorting is not required
closes pandas-dev#16734
1 parent 125c414 commit 7850436

File tree

4 files changed

+29
-13
lines changed

4 files changed

+29
-13
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Indexing
9898
^^^^^^^^
9999

100100
- When called with a null slice (e.g. ``df.iloc[:]``), the``iloc`` and ``loc`` indexers return a shallow copy of the original object. Previously they returned the original object. (:issue:`13873`).
101+
- When called on an unsorted ``MultiIndex``, the ``loc`` indexer now will raise ``UnsortedIndexError`` only if proper slicing is used on non-sorted levels (:issue:`16734`).
101102

102103

103104
I/O

pandas/core/indexes/multi.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -1035,11 +1035,12 @@ def is_lexsorted(self):
10351035
"""
10361036
return self.lexsort_depth == self.nlevels
10371037

1038-
def is_lexsorted_for_tuple(self, tup):
1038+
def _true_slices_indices(self, tup):
10391039
"""
1040-
Return True if we are correctly lexsorted given the passed tuple
1040+
Return indices of (non-trivial) slices in "tup"
10411041
"""
1042-
return len(tup) <= self.lexsort_depth
1042+
slices = lambda k: isinstance(k, slice) and not is_null_slice(k)
1043+
return [(i if slices(k) else -1) for (i, k) in enumerate(tup)]
10431044

10441045
@cache_readonly
10451046
def lexsort_depth(self):
@@ -2262,12 +2263,12 @@ def get_locs(self, tup):
22622263
"""
22632264

22642265
# must be lexsorted to at least as many levels
2265-
if not self.is_lexsorted_for_tuple(tup):
2266+
last_slice = max(self._true_slices_indices(tup))
2267+
if last_slice >= self.lexsort_depth:
22662268
raise UnsortedIndexError('MultiIndex Slicing requires the index '
2267-
'to be fully lexsorted tuple len ({0}), '
2268-
'lexsort depth ({1})'
2269-
.format(len(tup), self.lexsort_depth))
2270-
2269+
'to be lexsorted: slicing on level '
2270+
'({0}), lexsort depth ({1})'
2271+
.format(last_slice, self.lexsort_depth))
22712272
# indexer
22722273
# this is the list of all values that we want to select
22732274
n = len(self)

pandas/tests/indexes/test_multi.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -2826,8 +2826,13 @@ def test_unsortedindex(self):
28262826
df = pd.DataFrame([[i, 10 * i] for i in lrange(6)], index=mi,
28272827
columns=['one', 'two'])
28282828

2829+
# GH 16734: not sorted, but no real slicing
2830+
result = df.loc(axis=0)['z', 'a']
2831+
expected = df.iloc[0]
2832+
tm.assert_series_equal(result, expected)
2833+
28292834
with pytest.raises(UnsortedIndexError):
2830-
df.loc(axis=0)['z', :]
2835+
df.loc(axis=0)['z', slice('a')]
28312836
df.sort_index(inplace=True)
28322837
assert len(df.loc(axis=0)['z', :]) == 2
28332838

pandas/tests/indexing/test_multiindex.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -817,9 +817,13 @@ def f():
817817
assert df.index.lexsort_depth == 0
818818
with tm.assert_raises_regex(
819819
UnsortedIndexError,
820-
'MultiIndex Slicing requires the index to be fully '
821-
r'lexsorted tuple len \(2\), lexsort depth \(0\)'):
822-
df.loc[(slice(None), df.loc[:, ('a', 'bar')] > 5), :]
820+
'MultiIndex Slicing requires the index to be '
821+
r'lexsorted: slicing on level \(1\), lexsort depth \(0\)'):
822+
df.loc[(slice(None), slice('bar')), :]
823+
824+
# GH 16734: not sorted, but no real slicing
825+
result = df.loc[(slice(None), df.loc[:, ('a', 'bar')] > 5), :]
826+
tm.assert_frame_equal(result, df.iloc[[1, 3], :])
823827

824828
def test_multiindex_slicers_non_unique(self):
825829

@@ -1001,9 +1005,14 @@ def test_per_axis_per_level_doc_examples(self):
10011005

10021006
# not sorted
10031007
def f():
1004-
df.loc['A1', (slice(None), 'foo')]
1008+
df.loc['A1', ('a', slice('foo'))]
10051009

10061010
pytest.raises(UnsortedIndexError, f)
1011+
1012+
# GH 16734: not sorted, but no real slicing
1013+
tm.assert_frame_equal(df.loc['A1', (slice(None), 'foo')],
1014+
df.loc['A1'].iloc[:, [0, 2]])
1015+
10071016
df = df.sort_index(axis=1)
10081017

10091018
# slicing

0 commit comments

Comments
 (0)