Skip to content

Commit de9e54a

Browse files
BUG: Fix infinite recursion loop when pivot of IntervalTree is ±inf
Attempts to fix #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.
1 parent 9289c46 commit de9e54a

File tree

4 files changed

+47
-0
lines changed

4 files changed

+47
-0
lines changed

doc/source/whatsnew/v1.5.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,8 @@ Indexing
786786
- Bug in :meth:`Series.asof` and :meth:`DataFrame.asof` incorrectly casting bool-dtype results to ``float64`` dtype (:issue:`16063`)
787787
- Bug in :meth:`NDFrame.xs`, :meth:`DataFrame.iterrows`, :meth:`DataFrame.loc` and :meth:`DataFrame.iloc` not always propagating metadata (:issue:`28283`)
788788
- Bug in :meth:`DataFrame.sum` min_count changes dtype if input contains NaNs (:issue:`46947`)
789+
- Bug in :class:`IntervalTree` that lead to an infinite recursion. (:issue:`46658`)
790+
-
789791

790792
Missing
791793
^^^^^^^

pandas/_libs/intervaltree.pxi.in

+7
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@ cdef class {{dtype_title}}Closed{{closed_title}}IntervalNode(IntervalNode):
342342
# calculate a pivot so we can create child nodes
343343
self.is_leaf_node = False
344344
self.pivot = np.median(left / 2 + right / 2)
345+
if np.isinf(self.pivot):
346+
self.pivot = cython.cast({{dtype}}_t, 0)
347+
if self.pivot > np.max(right):
348+
self.pivot = np.max(left)
349+
if self.pivot < np.min(left):
350+
self.pivot = np.min(right)
351+
345352
left_set, right_set, center_set = self.classify_intervals(
346353
left, right)
347354

pandas/tests/indexes/interval/test_interval_tree.py

+17
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,20 @@ def test_interval_tree_error_and_warning(self):
207207
):
208208
left, right = np.arange(10), [np.iinfo(np.int64).max] * 10
209209
IntervalTree(left, right, closed="both")
210+
211+
@pytest.mark.parametrize(
212+
"left, right, expected",
213+
[
214+
([-np.inf, 1.0], [1.0, 2.0], 0.0),
215+
([-np.inf, -2.0], [-2.0, -1.0], -2.0),
216+
([-2.0, -1.0], [-1.0, np.inf], 0.0),
217+
([1.0, 2.0], [2.0, np.inf], 2.0),
218+
],
219+
)
220+
def test_inf_bound_infinite_recursion(self, left, right, expected):
221+
# GH 46658
222+
223+
tree = IntervalTree(left * 101, right * 101)
224+
225+
result = tree.root.pivot
226+
assert result == expected

pandas/tests/indexing/interval/test_interval_new.py

+21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from pandas import (
7+
Index,
78
Interval,
89
IntervalIndex,
910
Series,
@@ -217,3 +218,23 @@ def test_loc_getitem_missing_key_error_message(
217218
obj = frame_or_series(ser)
218219
with pytest.raises(KeyError, match=r"\[6\]"):
219220
obj.loc[[4, 5, 6]]
221+
222+
223+
@pytest.mark.parametrize(
224+
"intervals",
225+
[
226+
([Interval(-np.inf, 0.0), Interval(0.0, 1.0)]),
227+
([Interval(-np.inf, -2.0), Interval(-2.0, -1.0)]),
228+
([Interval(-1.0, 0.0), Interval(0.0, np.inf)]),
229+
([Interval(1.0, 2.0), Interval(2.0, np.inf)]),
230+
],
231+
)
232+
def test_repeating_interval_index_with_infs(intervals):
233+
# GH 46658
234+
235+
interval_index = Index(intervals * 51)
236+
237+
expected = np.arange(1, 102, 2, dtype=np.intp)
238+
result = interval_index.get_indexer_for([intervals[1]])
239+
240+
tm.assert_equal(result, expected)

0 commit comments

Comments
 (0)