Skip to content
This repository has been archived by the owner on Jul 10, 2024. It is now read-only.

Commit

Permalink
BUG: Fix infinite recursion loop when pivot of IntervalTree is ±inf
Browse files Browse the repository at this point in the history
Attempts to fix pandas-dev#46658.

When the pivot of an IntervalTree becomes ±inf the construction of the
IntervalTree comes to an infinite loop recursion.  This patch tries to fix that
by catching those cases and set the pivot to a reasonable value.

Note that the tests are skipped on 32-bit systems (see pandas-dev#23440)
  • Loading branch information
johannes-mueller committed Jun 2, 2022
1 parent 259a15c commit f2c75c3
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 0 deletions.
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,8 @@ Indexing
- Bug in :meth:`Series.asof` and :meth:`DataFrame.asof` incorrectly casting bool-dtype results to ``float64`` dtype (:issue:`16063`)
- Bug in :meth:`NDFrame.xs`, :meth:`DataFrame.iterrows`, :meth:`DataFrame.loc` and :meth:`DataFrame.iloc` not always propagating metadata (:issue:`28283`)
- Bug in :meth:`DataFrame.sum` min_count changes dtype if input contains NaNs (:issue:`46947`)
- Bug in :class:`IntervalTree` that lead to an infinite recursion. (:issue:`46658`)
-

Missing
^^^^^^^
Expand Down
7 changes: 7 additions & 0 deletions pandas/_libs/intervaltree.pxi.in
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ cdef class {{dtype_title}}Closed{{closed_title}}IntervalNode(IntervalNode):
# calculate a pivot so we can create child nodes
self.is_leaf_node = False
self.pivot = np.median(left / 2 + right / 2)
if np.isinf(self.pivot):
self.pivot = cython.cast({{dtype}}_t, 0)
if self.pivot > np.max(right):
self.pivot = np.max(left)
if self.pivot < np.min(left):
self.pivot = np.min(right)

left_set, right_set, center_set = self.classify_intervals(
left, right)

Expand Down
18 changes: 18 additions & 0 deletions pandas/tests/indexes/interval/test_interval_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,21 @@ def test_interval_tree_error_and_warning(self):
):
left, right = np.arange(10), [np.iinfo(np.int64).max] * 10
IntervalTree(left, right, closed="both")

@pytest.mark.xfail(not IS64, reason="GH 23440")
@pytest.mark.parametrize(
"left, right, expected",
[
([-np.inf, 1.0], [1.0, 2.0], 0.0),
([-np.inf, -2.0], [-2.0, -1.0], -2.0),
([-2.0, -1.0], [-1.0, np.inf], 0.0),
([1.0, 2.0], [2.0, np.inf], 2.0),
],
)
def test_inf_bound_infinite_recursion(self, left, right, expected):
# GH 46658

tree = IntervalTree(left * 101, right * 101)

result = tree.root.pivot
assert result == expected
24 changes: 24 additions & 0 deletions pandas/tests/indexing/interval/test_interval_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import numpy as np
import pytest

from pandas.compat import IS64

from pandas import (
Index,
Interval,
IntervalIndex,
Series,
Expand Down Expand Up @@ -217,3 +220,24 @@ def test_loc_getitem_missing_key_error_message(
obj = frame_or_series(ser)
with pytest.raises(KeyError, match=r"\[6\]"):
obj.loc[[4, 5, 6]]


@pytest.mark.xfail(not IS64, reason="GH 23440")
@pytest.mark.parametrize(
"intervals",
[
([Interval(-np.inf, 0.0), Interval(0.0, 1.0)]),
([Interval(-np.inf, -2.0), Interval(-2.0, -1.0)]),
([Interval(-1.0, 0.0), Interval(0.0, np.inf)]),
([Interval(1.0, 2.0), Interval(2.0, np.inf)]),
],
)
def test_repeating_interval_index_with_infs(intervals):
# GH 46658

interval_index = Index(intervals * 51)

expected = np.arange(1, 102, 2, dtype=np.intp)
result = interval_index.get_indexer_for([intervals[1]])

tm.assert_equal(result, expected)

0 comments on commit f2c75c3

Please sign in to comment.