Skip to content

Commit df1b0dc

Browse files
jschendeljowens
authored andcommitted
Fix bugs in IntervalIndex.is_non_overlapping_monotonic (pandas-dev#17238)
1 parent a1ff671 commit df1b0dc

File tree

3 files changed

+52
-4
lines changed

3 files changed

+52
-4
lines changed

doc/source/whatsnew/v0.21.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ Conversion
313313
- Bug in assignment against datetime-like data with ``int`` may incorrectly convert to datetime-like (:issue:`14145`)
314314
- Bug in assignment against ``int64`` data with ``np.ndarray`` with ``float64`` dtype may keep ``int64`` dtype (:issue:`14001`)
315315
- Fix :func:`DataFrame.memory_usage` to support PyPy. Objects on PyPy do not have a fixed size, so an approximation is used instead (:issue:`17228`)
316+
- Fixed the return type of ``IntervalIndex.is_non_overlapping_monotonic`` to be a Python ``bool`` for consistency with similar attributes/methods. Previously returned a ``numpy.bool_``. (:issue:`17237`)
317+
- Bug in ``IntervalIndex.is_non_overlapping_monotonic`` when intervals are closed on both sides and overlap at a point (:issue:`16560`)
316318

317319

318320
Indexing

pandas/core/indexes/interval.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,17 @@ def is_non_overlapping_monotonic(self):
556556
# must be increasing (e.g., [0, 1), [1, 2), [2, 3), ... )
557557
# or decreasing (e.g., [-1, 0), [-2, -1), [-3, -2), ...)
558558
# we already require left <= right
559-
return ((self.right[:-1] <= self.left[1:]).all() or
560-
(self.left[:-1] >= self.right[1:]).all())
559+
560+
# strict inequality for closed == 'both'; equality implies overlapping
561+
# at a point when both sides of intervals are included
562+
if self.closed == 'both':
563+
return bool((self.right[:-1] < self.left[1:]).all() or
564+
(self.left[:-1] > self.right[1:]).all())
565+
566+
# non-strict inequality when closed != 'both'; at least one side is
567+
# not included in the intervals, so equality does not imply overlapping
568+
return bool((self.right[:-1] <= self.left[1:]).all() or
569+
(self.left[:-1] >= self.right[1:]).all())
561570

562571
@Appender(_index_shared_docs['_convert_scalar_indexer'])
563572
def _convert_scalar_indexer(self, key, kind=None):

pandas/tests/indexes/test_interval.py

+39-2
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,9 @@ def slice_locs_cases(self, breaks):
371371
assert index.slice_locs(1, 1) == (1, 1)
372372
assert index.slice_locs(1, 2) == (1, 2)
373373

374-
index = IntervalIndex.from_breaks([0, 1, 2], closed='both')
375-
assert index.slice_locs(1, 1) == (0, 2)
374+
index = IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)],
375+
closed='both')
376+
assert index.slice_locs(1, 1) == (0, 1)
376377
assert index.slice_locs(1, 2) == (0, 2)
377378

378379
def test_slice_locs_int64(self):
@@ -681,6 +682,42 @@ def f():
681682

682683
pytest.raises(ValueError, f)
683684

685+
def test_is_non_overlapping_monotonic(self):
686+
# Should be True in all cases
687+
tpls = [(0, 1), (2, 3), (4, 5), (6, 7)]
688+
for closed in ('left', 'right', 'neither', 'both'):
689+
idx = IntervalIndex.from_tuples(tpls, closed=closed)
690+
assert idx.is_non_overlapping_monotonic is True
691+
692+
idx = IntervalIndex.from_tuples(reversed(tpls), closed=closed)
693+
assert idx.is_non_overlapping_monotonic is True
694+
695+
# Should be False in all cases (overlapping)
696+
tpls = [(0, 2), (1, 3), (4, 5), (6, 7)]
697+
for closed in ('left', 'right', 'neither', 'both'):
698+
idx = IntervalIndex.from_tuples(tpls, closed=closed)
699+
assert idx.is_non_overlapping_monotonic is False
700+
701+
idx = IntervalIndex.from_tuples(reversed(tpls), closed=closed)
702+
assert idx.is_non_overlapping_monotonic is False
703+
704+
# Should be False in all cases (non-monotonic)
705+
tpls = [(0, 1), (2, 3), (6, 7), (4, 5)]
706+
for closed in ('left', 'right', 'neither', 'both'):
707+
idx = IntervalIndex.from_tuples(tpls, closed=closed)
708+
assert idx.is_non_overlapping_monotonic is False
709+
710+
idx = IntervalIndex.from_tuples(reversed(tpls), closed=closed)
711+
assert idx.is_non_overlapping_monotonic is False
712+
713+
# Should be False for closed='both', overwise True (GH16560)
714+
idx = IntervalIndex.from_breaks(range(4), closed='both')
715+
assert idx.is_non_overlapping_monotonic is False
716+
717+
for closed in ('left', 'right', 'neither'):
718+
idx = IntervalIndex.from_breaks(range(4), closed=closed)
719+
assert idx.is_non_overlapping_monotonic is True
720+
684721

685722
class TestIntervalRange(object):
686723

0 commit comments

Comments
 (0)