Skip to content

Commit 91eecef

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 58d8729 commit 91eecef

File tree

4 files changed

+32
-1
lines changed

4 files changed

+32
-1
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ Indexing
334334
- Fixes ``DataFrame.loc`` for setting with alignment and tz-aware ``DatetimeIndex`` (:issue:`16889`)
335335
- Avoids ``IndexError`` when passing an Index or Series to ``.iloc`` with older numpy (:issue:`17193`)
336336
- Allow unicode empty strings as placeholders in multilevel columns in Python 2 (:issue:`17099`)
337+
- Bug in ``IntervalIndex`` where performing a scalar lookup fails for included right endpoints of non-overlapping monotonic decreasing indexes (:issue:`16417`)
337338

338339
I/O
339340
^^^

pandas/core/indexes/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3453,7 +3453,7 @@ def _searchsorted_monotonic(self, label, side='left'):
34533453
# everything for it to work (element ordering, search side and
34543454
# resulting value).
34553455
pos = self[::-1].searchsorted(label, side='right' if side == 'left'
3456-
else 'right')
3456+
else 'left')
34573457
return len(self) - pos
34583458

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

pandas/tests/indexes/test_base.py

+21
Original file line numberDiff line numberDiff line change
@@ -2103,3 +2103,24 @@ def test_intersect_str_dates(self):
21032103
res = i2.intersection(i1)
21042104

21052105
assert len(res) == 0
2106+
2107+
def test_searchsorted_monotonic(self):
2108+
# GH17271
2109+
idx_inc = Index([0, 2, 4])
2110+
idx_dec = Index([4, 2, 0])
2111+
2112+
# test included value.
2113+
assert idx_inc._searchsorted_monotonic(0, side='left') == 0
2114+
assert idx_inc._searchsorted_monotonic(0, side='right') == 1
2115+
assert idx_dec._searchsorted_monotonic(0, side='left') == 2
2116+
assert idx_dec._searchsorted_monotonic(0, side='right') == 3
2117+
2118+
# test non-included value.
2119+
for side in ('left', 'right'):
2120+
assert idx_inc._searchsorted_monotonic(1, side=side) == 1
2121+
assert idx_dec._searchsorted_monotonic(1, side=side) == 2
2122+
2123+
# non-monotonic should raise.
2124+
idx_bad = Index([0, 4, 2])
2125+
with pytest.raises(ValueError):
2126+
idx_bad._searchsorted_monotonic(3)

pandas/tests/indexing/test_interval.py

+9
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)