From 24f47d6bcca034b1c20f62e4326b2dc39b74decb Mon Sep 17 00:00:00 2001 From: phofl Date: Tue, 24 Nov 2020 22:24:49 +0100 Subject: [PATCH 1/4] Fix issues with negative stepsize --- pandas/core/indexes/multi.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index be0b0c5208b1c..1143d3ee4ceda 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3075,8 +3075,10 @@ 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 - - r = np.arange(start, stop, step) + if step is not None and step < 0: + r = np.arange(stop - 1, start - 1, step) + else: + 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 @@ -3339,6 +3341,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 @@ -3362,6 +3366,11 @@ 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] else: # For all other case, use the same order as the level new_order = np.arange(n)[indexer] From c01569c84a17e0100c7eb01b921e8538a7746495 Mon Sep 17 00:00:00 2001 From: phofl Date: Wed, 25 Nov 2020 22:15:14 +0100 Subject: [PATCH 2/4] Add test --- pandas/core/indexes/multi.py | 7 ++++--- pandas/tests/indexing/multiindex/test_slice.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index bcc1be85a3269..cf98c2261078d 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3076,9 +3076,10 @@ def convert_indexer(start, stop, step, indexer=indexer, codes=level_codes): # if we have a provided indexer, then this need not consider # the entire labels set if step is not None and step < 0: - r = np.arange(stop - 1, start - 1, step) - else: - r = np.arange(start, stop, step) + # 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 diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py index 024cc3ad72688..5a0937f91be65 100644 --- a/pandas/tests/indexing/multiindex/test_slice.py +++ b/pandas/tests/indexing/multiindex/test_slice.py @@ -744,3 +744,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# + 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) From 95be3a468c07d387954e4993a5bd62c732564d1c Mon Sep 17 00:00:00 2001 From: phofl Date: Wed, 25 Nov 2020 22:19:19 +0100 Subject: [PATCH 3/4] Add gh reference --- pandas/tests/indexing/multiindex/test_slice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py index 5a0937f91be65..e8eee2dae6a53 100644 --- a/pandas/tests/indexing/multiindex/test_slice.py +++ b/pandas/tests/indexing/multiindex/test_slice.py @@ -746,7 +746,7 @@ def test_non_reducing_slice_on_multiindex(self): tm.assert_frame_equal(result, expected) def test_loc_slice_negative_stepsize(self): - # GH# + # 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)), :] From 30b551d05fd77dd44c1b5910ea3ef232bebfd0fe Mon Sep 17 00:00:00 2001 From: phofl Date: Wed, 2 Dec 2020 13:18:12 +0100 Subject: [PATCH 4/4] Add whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 27511c96faa5a..ddf7168cfce94 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -616,6 +616,7 @@ Indexing - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`MultiIndex` with 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 numeric label was given for object :class:`Index` although label was in :class:`Index` (:issue:`26491`) - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from :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`)