Skip to content

Commit

Permalink
Fix bugs in IntervalIndex.is_non_overlapping_monotonic (pandas-dev#17238
Browse files Browse the repository at this point in the history
)
  • Loading branch information
jschendel authored and jowens committed Sep 20, 2017
1 parent a1ff671 commit df1b0dc
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 4 deletions.
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ Conversion
- Bug in assignment against datetime-like data with ``int`` may incorrectly convert to datetime-like (:issue:`14145`)
- Bug in assignment against ``int64`` data with ``np.ndarray`` with ``float64`` dtype may keep ``int64`` dtype (:issue:`14001`)
- 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`)
- 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`)
- Bug in ``IntervalIndex.is_non_overlapping_monotonic`` when intervals are closed on both sides and overlap at a point (:issue:`16560`)


Indexing
Expand Down
13 changes: 11 additions & 2 deletions pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,17 @@ def is_non_overlapping_monotonic(self):
# must be increasing (e.g., [0, 1), [1, 2), [2, 3), ... )
# or decreasing (e.g., [-1, 0), [-2, -1), [-3, -2), ...)
# we already require left <= right
return ((self.right[:-1] <= self.left[1:]).all() or
(self.left[:-1] >= self.right[1:]).all())

# strict inequality for closed == 'both'; equality implies overlapping
# at a point when both sides of intervals are included
if self.closed == 'both':
return bool((self.right[:-1] < self.left[1:]).all() or
(self.left[:-1] > self.right[1:]).all())

# non-strict inequality when closed != 'both'; at least one side is
# not included in the intervals, so equality does not imply overlapping
return bool((self.right[:-1] <= self.left[1:]).all() or
(self.left[:-1] >= self.right[1:]).all())

@Appender(_index_shared_docs['_convert_scalar_indexer'])
def _convert_scalar_indexer(self, key, kind=None):
Expand Down
41 changes: 39 additions & 2 deletions pandas/tests/indexes/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,9 @@ def slice_locs_cases(self, breaks):
assert index.slice_locs(1, 1) == (1, 1)
assert index.slice_locs(1, 2) == (1, 2)

index = IntervalIndex.from_breaks([0, 1, 2], closed='both')
assert index.slice_locs(1, 1) == (0, 2)
index = IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)],
closed='both')
assert index.slice_locs(1, 1) == (0, 1)
assert index.slice_locs(1, 2) == (0, 2)

def test_slice_locs_int64(self):
Expand Down Expand Up @@ -681,6 +682,42 @@ def f():

pytest.raises(ValueError, f)

def test_is_non_overlapping_monotonic(self):
# Should be True in all cases
tpls = [(0, 1), (2, 3), (4, 5), (6, 7)]
for closed in ('left', 'right', 'neither', 'both'):
idx = IntervalIndex.from_tuples(tpls, closed=closed)
assert idx.is_non_overlapping_monotonic is True

idx = IntervalIndex.from_tuples(reversed(tpls), closed=closed)
assert idx.is_non_overlapping_monotonic is True

# Should be False in all cases (overlapping)
tpls = [(0, 2), (1, 3), (4, 5), (6, 7)]
for closed in ('left', 'right', 'neither', 'both'):
idx = IntervalIndex.from_tuples(tpls, closed=closed)
assert idx.is_non_overlapping_monotonic is False

idx = IntervalIndex.from_tuples(reversed(tpls), closed=closed)
assert idx.is_non_overlapping_monotonic is False

# Should be False in all cases (non-monotonic)
tpls = [(0, 1), (2, 3), (6, 7), (4, 5)]
for closed in ('left', 'right', 'neither', 'both'):
idx = IntervalIndex.from_tuples(tpls, closed=closed)
assert idx.is_non_overlapping_monotonic is False

idx = IntervalIndex.from_tuples(reversed(tpls), closed=closed)
assert idx.is_non_overlapping_monotonic is False

# Should be False for closed='both', overwise True (GH16560)
idx = IntervalIndex.from_breaks(range(4), closed='both')
assert idx.is_non_overlapping_monotonic is False

for closed in ('left', 'right', 'neither'):
idx = IntervalIndex.from_breaks(range(4), closed=closed)
assert idx.is_non_overlapping_monotonic is True


class TestIntervalRange(object):

Expand Down

0 comments on commit df1b0dc

Please sign in to comment.