Skip to content

Commit 4c8d66e

Browse files
authored
BUG: DataFrame.loc returning empty result with negative stepsize for MultiIndex (#38071)
1 parent 5fdf642 commit 4c8d66e

File tree

3 files changed

+19
-1
lines changed

3 files changed

+19
-1
lines changed

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ Indexing
659659
- Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`MultiIndex` and a level named ``"0"`` (:issue:`37194`)
660660
- Bug in :meth:`Series.__getitem__` when using an unsigned integer array as an indexer giving incorrect results or segfaulting instead of raising ``KeyError`` (:issue:`37218`)
661661
- Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`)
662+
- Bug in :meth:`DataFrame.loc` returning empty result when indexer is a slice with negative step size (:issue:`38071`)
662663
- Bug in :meth:`Series.loc` and :meth:`DataFrame.loc` raises when the index was of ``object`` dtype and the given numeric label was in the index (:issue:`26491`)
663664
- Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from a :class:`MultiIndex` (:issue:`27104`)
664665
- Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using a listlike indexer containing NA values (:issue:`37722`)

pandas/core/indexes/multi.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -3078,8 +3078,11 @@ def convert_indexer(start, stop, step, indexer=indexer, codes=level_codes):
30783078
# given the inputs and the codes/indexer, compute an indexer set
30793079
# if we have a provided indexer, then this need not consider
30803080
# the entire labels set
3081-
3081+
if step is not None and step < 0:
3082+
# Switch elements for negative step size
3083+
start, stop = stop - 1, start - 1
30823084
r = np.arange(start, stop, step)
3085+
30833086
if indexer is not None and len(indexer) != len(codes):
30843087

30853088
# we have an indexer which maps the locations in the labels
@@ -3342,6 +3345,8 @@ def _reorder_indexer(
33423345
k_codes = k_codes[k_codes >= 0] # Filter absent keys
33433346
# True if the given codes are not ordered
33443347
need_sort = (k_codes[:-1] > k_codes[1:]).any()
3348+
elif isinstance(k, slice) and k.step is not None and k.step < 0:
3349+
need_sort = True
33453350
# Bail out if both index and seq are sorted
33463351
if not need_sort:
33473352
return indexer
@@ -3368,6 +3373,8 @@ def _reorder_indexer(
33683373
key_order_map[level_indexer] = np.arange(len(level_indexer))
33693374

33703375
new_order = key_order_map[self.codes[i][indexer]]
3376+
elif isinstance(k, slice) and k.step is not None and k.step < 0:
3377+
new_order = np.arange(n)[k][indexer]
33713378
elif isinstance(k, slice) and k.start is None and k.stop is None:
33723379
# slice(None) should not determine order GH#31330
33733380
new_order = np.ones((n,))[indexer]

pandas/tests/indexing/multiindex/test_slice.py

+10
Original file line numberDiff line numberDiff line change
@@ -779,3 +779,13 @@ def test_non_reducing_slice_on_multiindex(self):
779779
result = df.loc[tslice_]
780780
expected = DataFrame({("b", "d"): [4, 1]})
781781
tm.assert_frame_equal(result, expected)
782+
783+
def test_loc_slice_negative_stepsize(self):
784+
# GH#38071
785+
mi = MultiIndex.from_product([["a", "b"], [0, 1]])
786+
df = DataFrame([[1, 2], [3, 4], [5, 6], [7, 8]], index=mi)
787+
result = df.loc[("a", slice(None, None, -1)), :]
788+
expected = DataFrame(
789+
[[3, 4], [1, 2]], index=MultiIndex.from_tuples([("a", 1), ("a", 0)])
790+
)
791+
tm.assert_frame_equal(result, expected)

0 commit comments

Comments
 (0)