Skip to content

Commit b72147e

Browse files
committed
FIX: Index._searchsorted_monotonic for decreasing indexes
Fixed an issue where Index._searchsorted_monotonic(..., side='right') returns the left side position for monotonic decreasing indexes. Issue had a downstream effect on scalar lookups in IntervalIndex as well.
1 parent 20fee85 commit b72147e

File tree

4 files changed

+32
-1
lines changed

4 files changed

+32
-1
lines changed

doc/source/whatsnew/v0.21.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ Indexing
418418
- Bug in ``.isin()`` in which checking membership in empty ``Series`` objects raised an error (:issue:`16991`)
419419
- Bug in ``CategoricalIndex`` reindexing in which specified indices containing duplicates were not being respected (:issue:`17323`)
420420
- Bug in intersection of ``RangeIndex`` with negative step (:issue:`17296`)
421+
- Bug in ``IntervalIndex`` where performing a scalar lookup fails for included right endpoints of non-overlapping monotonic decreasing indexes (:issue:`16417`, :issue:`17271`)
421422

422423
I/O
423424
^^^

pandas/core/indexes/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3465,7 +3465,7 @@ def _searchsorted_monotonic(self, label, side='left'):
34653465
# everything for it to work (element ordering, search side and
34663466
# resulting value).
34673467
pos = self[::-1].searchsorted(label, side='right' if side == 'left'
3468-
else 'right')
3468+
else 'left')
34693469
return len(self) - pos
34703470

34713471
raise ValueError('index must be monotonic increasing or decreasing')

pandas/tests/indexes/test_base.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,6 +2119,27 @@ def test_intersect_str_dates(self):
21192119

21202120
assert len(res) == 0
21212121

2122+
def test_searchsorted_monotonic(self):
2123+
# GH17271
2124+
idx_inc = Index([0, 2, 4])
2125+
idx_dec = Index([4, 2, 0])
2126+
2127+
# test included value.
2128+
assert idx_inc._searchsorted_monotonic(0, side='left') == 0
2129+
assert idx_inc._searchsorted_monotonic(0, side='right') == 1
2130+
assert idx_dec._searchsorted_monotonic(0, side='left') == 2
2131+
assert idx_dec._searchsorted_monotonic(0, side='right') == 3
2132+
2133+
# test non-included value.
2134+
for side in ('left', 'right'):
2135+
assert idx_inc._searchsorted_monotonic(1, side=side) == 1
2136+
assert idx_dec._searchsorted_monotonic(1, side=side) == 2
2137+
2138+
# non-monotonic should raise.
2139+
idx_bad = Index([0, 4, 2])
2140+
with pytest.raises(ValueError):
2141+
idx_bad._searchsorted_monotonic(3)
2142+
21222143

21232144
class TestIndexUtils(object):
21242145

pandas/tests/indexing/test_interval.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class TestIntervalIndex(object):
1111
def setup_method(self, method):
1212
self.s = Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6)))
1313

14+
idx_dec = IntervalIndex.from_tuples([(2, 3), (1, 2), (0, 1)])
15+
self.s_dec = Series(list('abc'), idx_dec)
16+
1417
def test_loc_with_scalar(self):
1518

1619
s = self.s
@@ -39,6 +42,9 @@ def test_loc_with_scalar(self):
3942
expected = s.iloc[2:5]
4043
tm.assert_series_equal(expected, s.loc[s >= 2])
4144

45+
# test endpoint of non-overlapping monotonic decreasing (GH16417)
46+
assert self.s_dec.loc[3] == 'a'
47+
4248
def test_getitem_with_scalar(self):
4349

4450
s = self.s
@@ -67,6 +73,9 @@ def test_getitem_with_scalar(self):
6773
expected = s.iloc[2:5]
6874
tm.assert_series_equal(expected, s[s >= 2])
6975

76+
# test endpoint of non-overlapping monotonic decreasing (GH16417)
77+
assert self.s_dec[3] == 'a'
78+
7079
def test_with_interval(self):
7180

7281
s = self.s

0 commit comments

Comments
 (0)