diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 84ac2d0c17676..ac7a2ddd477c9 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -659,6 +659,7 @@ Indexing - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`MultiIndex` and a level named ``"0"`` (:issue:`37194`) - 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`) - Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`) +- Bug in :meth:`DataFrame.loc` returning empty result when indexer is a slice with negative step size (:issue:`38071`) - 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`) - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from a :class:`MultiIndex` (:issue:`27104`) - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using a listlike indexer containing NA values (:issue:`37722`) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index d575c67cb36aa..6af6555007c2f 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3078,8 +3078,11 @@ def convert_indexer(start, stop, step, indexer=indexer, codes=level_codes): # given the inputs and the codes/indexer, compute an indexer set # if we have a provided indexer, then this need not consider # the entire labels set - + if step is not None and step < 0: + # Switch elements for negative step size + start, stop = stop - 1, start - 1 r = np.arange(start, stop, step) + if indexer is not None and len(indexer) != len(codes): # we have an indexer which maps the locations in the labels @@ -3342,6 +3345,8 @@ def _reorder_indexer( k_codes = k_codes[k_codes >= 0] # Filter absent keys # True if the given codes are not ordered need_sort = (k_codes[:-1] > k_codes[1:]).any() + elif isinstance(k, slice) and k.step is not None and k.step < 0: + need_sort = True # Bail out if both index and seq are sorted if not need_sort: return indexer @@ -3368,6 +3373,8 @@ def _reorder_indexer( key_order_map[level_indexer] = np.arange(len(level_indexer)) new_order = key_order_map[self.codes[i][indexer]] + elif isinstance(k, slice) and k.step is not None and k.step < 0: + new_order = np.arange(n)[k][indexer] elif isinstance(k, slice) and k.start is None and k.stop is None: # slice(None) should not determine order GH#31330 new_order = np.ones((n,))[indexer] diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py index d58bc4713f99f..51684f092aefd 100644 --- a/pandas/tests/indexing/multiindex/test_slice.py +++ b/pandas/tests/indexing/multiindex/test_slice.py @@ -779,3 +779,13 @@ def test_non_reducing_slice_on_multiindex(self): result = df.loc[tslice_] expected = DataFrame({("b", "d"): [4, 1]}) tm.assert_frame_equal(result, expected) + + def test_loc_slice_negative_stepsize(self): + # GH#38071 + mi = MultiIndex.from_product([["a", "b"], [0, 1]]) + df = DataFrame([[1, 2], [3, 4], [5, 6], [7, 8]], index=mi) + result = df.loc[("a", slice(None, None, -1)), :] + expected = DataFrame( + [[3, 4], [1, 2]], index=MultiIndex.from_tuples([("a", 1), ("a", 0)]) + ) + tm.assert_frame_equal(result, expected)