test_indexing.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. import re
  2. import numpy as np
  3. import pytest
  4. from pandas.errors import InvalidIndexError
  5. from pandas import (
  6. NA,
  7. CategoricalIndex,
  8. DatetimeIndex,
  9. Index,
  10. Interval,
  11. IntervalIndex,
  12. MultiIndex,
  13. NaT,
  14. Timedelta,
  15. Timestamp,
  16. array,
  17. date_range,
  18. interval_range,
  19. period_range,
  20. timedelta_range,
  21. )
  22. import pandas._testing as tm
  23. class TestGetLoc:
  24. @pytest.mark.parametrize("side", ["right", "left", "both", "neither"])
  25. def test_get_loc_interval(self, closed, side):
  26. idx = IntervalIndex.from_tuples([(0, 1), (2, 3)], closed=closed)
  27. for bound in [[0, 1], [1, 2], [2, 3], [3, 4], [0, 2], [2.5, 3], [-1, 4]]:
  28. # if get_loc is supplied an interval, it should only search
  29. # for exact matches, not overlaps or covers, else KeyError.
  30. msg = re.escape(f"Interval({bound[0]}, {bound[1]}, closed='{side}')")
  31. if closed == side:
  32. if bound == [0, 1]:
  33. assert idx.get_loc(Interval(0, 1, closed=side)) == 0
  34. elif bound == [2, 3]:
  35. assert idx.get_loc(Interval(2, 3, closed=side)) == 1
  36. else:
  37. with pytest.raises(KeyError, match=msg):
  38. idx.get_loc(Interval(*bound, closed=side))
  39. else:
  40. with pytest.raises(KeyError, match=msg):
  41. idx.get_loc(Interval(*bound, closed=side))
  42. @pytest.mark.parametrize("scalar", [-0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5])
  43. def test_get_loc_scalar(self, closed, scalar):
  44. # correct = {side: {query: answer}}.
  45. # If query is not in the dict, that query should raise a KeyError
  46. correct = {
  47. "right": {0.5: 0, 1: 0, 2.5: 1, 3: 1},
  48. "left": {0: 0, 0.5: 0, 2: 1, 2.5: 1},
  49. "both": {0: 0, 0.5: 0, 1: 0, 2: 1, 2.5: 1, 3: 1},
  50. "neither": {0.5: 0, 2.5: 1},
  51. }
  52. idx = IntervalIndex.from_tuples([(0, 1), (2, 3)], closed=closed)
  53. # if get_loc is supplied a scalar, it should return the index of
  54. # the interval which contains the scalar, or KeyError.
  55. if scalar in correct[closed].keys():
  56. assert idx.get_loc(scalar) == correct[closed][scalar]
  57. else:
  58. with pytest.raises(KeyError, match=str(scalar)):
  59. idx.get_loc(scalar)
  60. @pytest.mark.parametrize("scalar", [-1, 0, 0.5, 3, 4.5, 5, 6])
  61. def test_get_loc_length_one_scalar(self, scalar, closed):
  62. # GH 20921
  63. index = IntervalIndex.from_tuples([(0, 5)], closed=closed)
  64. if scalar in index[0]:
  65. result = index.get_loc(scalar)
  66. assert result == 0
  67. else:
  68. with pytest.raises(KeyError, match=str(scalar)):
  69. index.get_loc(scalar)
  70. @pytest.mark.parametrize("other_closed", ["left", "right", "both", "neither"])
  71. @pytest.mark.parametrize("left, right", [(0, 5), (-1, 4), (-1, 6), (6, 7)])
  72. def test_get_loc_length_one_interval(self, left, right, closed, other_closed):
  73. # GH 20921
  74. index = IntervalIndex.from_tuples([(0, 5)], closed=closed)
  75. interval = Interval(left, right, closed=other_closed)
  76. if interval == index[0]:
  77. result = index.get_loc(interval)
  78. assert result == 0
  79. else:
  80. with pytest.raises(
  81. KeyError,
  82. match=re.escape(f"Interval({left}, {right}, closed='{other_closed}')"),
  83. ):
  84. index.get_loc(interval)
  85. # Make consistent with test_interval_new.py (see #16316, #16386)
  86. @pytest.mark.parametrize(
  87. "breaks",
  88. [
  89. date_range("20180101", periods=4),
  90. date_range("20180101", periods=4, tz="US/Eastern"),
  91. timedelta_range("0 days", periods=4),
  92. ],
  93. ids=lambda x: str(x.dtype),
  94. )
  95. def test_get_loc_datetimelike_nonoverlapping(self, breaks):
  96. # GH 20636
  97. # nonoverlapping = IntervalIndex method and no i8 conversion
  98. index = IntervalIndex.from_breaks(breaks)
  99. value = index[0].mid
  100. result = index.get_loc(value)
  101. expected = 0
  102. assert result == expected
  103. interval = Interval(index[0].left, index[0].right)
  104. result = index.get_loc(interval)
  105. expected = 0
  106. assert result == expected
  107. @pytest.mark.parametrize(
  108. "arrays",
  109. [
  110. (date_range("20180101", periods=4), date_range("20180103", periods=4)),
  111. (
  112. date_range("20180101", periods=4, tz="US/Eastern"),
  113. date_range("20180103", periods=4, tz="US/Eastern"),
  114. ),
  115. (
  116. timedelta_range("0 days", periods=4),
  117. timedelta_range("2 days", periods=4),
  118. ),
  119. ],
  120. ids=lambda x: str(x[0].dtype),
  121. )
  122. def test_get_loc_datetimelike_overlapping(self, arrays):
  123. # GH 20636
  124. index = IntervalIndex.from_arrays(*arrays)
  125. value = index[0].mid + Timedelta("12 hours")
  126. result = index.get_loc(value)
  127. expected = slice(0, 2, None)
  128. assert result == expected
  129. interval = Interval(index[0].left, index[0].right)
  130. result = index.get_loc(interval)
  131. expected = 0
  132. assert result == expected
  133. @pytest.mark.parametrize(
  134. "values",
  135. [
  136. date_range("2018-01-04", periods=4, freq="-1D"),
  137. date_range("2018-01-04", periods=4, freq="-1D", tz="US/Eastern"),
  138. timedelta_range("3 days", periods=4, freq="-1D"),
  139. np.arange(3.0, -1.0, -1.0),
  140. np.arange(3, -1, -1),
  141. ],
  142. ids=lambda x: str(x.dtype),
  143. )
  144. def test_get_loc_decreasing(self, values):
  145. # GH 25860
  146. index = IntervalIndex.from_arrays(values[1:], values[:-1])
  147. result = index.get_loc(index[0])
  148. expected = 0
  149. assert result == expected
  150. @pytest.mark.parametrize("key", [[5], (2, 3)])
  151. def test_get_loc_non_scalar_errors(self, key):
  152. # GH 31117
  153. idx = IntervalIndex.from_tuples([(1, 3), (2, 4), (3, 5), (7, 10), (3, 10)])
  154. msg = str(key)
  155. with pytest.raises(InvalidIndexError, match=msg):
  156. idx.get_loc(key)
  157. def test_get_indexer_with_nans(self):
  158. # GH#41831
  159. index = IntervalIndex([np.nan, Interval(1, 2), np.nan])
  160. expected = np.array([True, False, True])
  161. for key in [None, np.nan, NA]:
  162. assert key in index
  163. result = index.get_loc(key)
  164. tm.assert_numpy_array_equal(result, expected)
  165. for key in [NaT, np.timedelta64("NaT", "ns"), np.datetime64("NaT", "ns")]:
  166. with pytest.raises(KeyError, match=str(key)):
  167. index.get_loc(key)
  168. class TestGetIndexer:
  169. @pytest.mark.parametrize(
  170. "query, expected",
  171. [
  172. ([Interval(2, 4, closed="right")], [1]),
  173. ([Interval(2, 4, closed="left")], [-1]),
  174. ([Interval(2, 4, closed="both")], [-1]),
  175. ([Interval(2, 4, closed="neither")], [-1]),
  176. ([Interval(1, 4, closed="right")], [-1]),
  177. ([Interval(0, 4, closed="right")], [-1]),
  178. ([Interval(0.5, 1.5, closed="right")], [-1]),
  179. ([Interval(2, 4, closed="right"), Interval(0, 1, closed="right")], [1, -1]),
  180. ([Interval(2, 4, closed="right"), Interval(2, 4, closed="right")], [1, 1]),
  181. ([Interval(5, 7, closed="right"), Interval(2, 4, closed="right")], [2, 1]),
  182. ([Interval(2, 4, closed="right"), Interval(2, 4, closed="left")], [1, -1]),
  183. ],
  184. )
  185. def test_get_indexer_with_interval(self, query, expected):
  186. tuples = [(0, 2), (2, 4), (5, 7)]
  187. index = IntervalIndex.from_tuples(tuples, closed="right")
  188. result = index.get_indexer(query)
  189. expected = np.array(expected, dtype="intp")
  190. tm.assert_numpy_array_equal(result, expected)
  191. @pytest.mark.parametrize(
  192. "query, expected",
  193. [
  194. ([-0.5], [-1]),
  195. ([0], [-1]),
  196. ([0.5], [0]),
  197. ([1], [0]),
  198. ([1.5], [1]),
  199. ([2], [1]),
  200. ([2.5], [-1]),
  201. ([3], [-1]),
  202. ([3.5], [2]),
  203. ([4], [2]),
  204. ([4.5], [-1]),
  205. ([1, 2], [0, 1]),
  206. ([1, 2, 3], [0, 1, -1]),
  207. ([1, 2, 3, 4], [0, 1, -1, 2]),
  208. ([1, 2, 3, 4, 2], [0, 1, -1, 2, 1]),
  209. ],
  210. )
  211. def test_get_indexer_with_int_and_float(self, query, expected):
  212. tuples = [(0, 1), (1, 2), (3, 4)]
  213. index = IntervalIndex.from_tuples(tuples, closed="right")
  214. result = index.get_indexer(query)
  215. expected = np.array(expected, dtype="intp")
  216. tm.assert_numpy_array_equal(result, expected)
  217. @pytest.mark.parametrize("item", [[3], np.arange(0.5, 5, 0.5)])
  218. def test_get_indexer_length_one(self, item, closed):
  219. # GH 17284
  220. index = IntervalIndex.from_tuples([(0, 5)], closed=closed)
  221. result = index.get_indexer(item)
  222. expected = np.array([0] * len(item), dtype="intp")
  223. tm.assert_numpy_array_equal(result, expected)
  224. @pytest.mark.parametrize("size", [1, 5])
  225. def test_get_indexer_length_one_interval(self, size, closed):
  226. # GH 17284
  227. index = IntervalIndex.from_tuples([(0, 5)], closed=closed)
  228. result = index.get_indexer([Interval(0, 5, closed)] * size)
  229. expected = np.array([0] * size, dtype="intp")
  230. tm.assert_numpy_array_equal(result, expected)
  231. @pytest.mark.parametrize(
  232. "target",
  233. [
  234. IntervalIndex.from_tuples([(7, 8), (1, 2), (3, 4), (0, 1)]),
  235. IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4), np.nan]),
  236. IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)], closed="both"),
  237. [-1, 0, 0.5, 1, 2, 2.5, np.nan],
  238. ["foo", "foo", "bar", "baz"],
  239. ],
  240. )
  241. def test_get_indexer_categorical(self, target, ordered):
  242. # GH 30063: categorical and non-categorical results should be consistent
  243. index = IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)])
  244. categorical_target = CategoricalIndex(target, ordered=ordered)
  245. result = index.get_indexer(categorical_target)
  246. expected = index.get_indexer(target)
  247. tm.assert_numpy_array_equal(result, expected)
  248. def test_get_indexer_categorical_with_nans(self):
  249. # GH#41934 nans in both index and in target
  250. ii = IntervalIndex.from_breaks(range(5))
  251. ii2 = ii.append(IntervalIndex([np.nan]))
  252. ci2 = CategoricalIndex(ii2)
  253. result = ii2.get_indexer(ci2)
  254. expected = np.arange(5, dtype=np.intp)
  255. tm.assert_numpy_array_equal(result, expected)
  256. # not-all-matches
  257. result = ii2[1:].get_indexer(ci2[::-1])
  258. expected = np.array([3, 2, 1, 0, -1], dtype=np.intp)
  259. tm.assert_numpy_array_equal(result, expected)
  260. # non-unique target, non-unique nans
  261. result = ii2.get_indexer(ci2.append(ci2))
  262. expected = np.array([0, 1, 2, 3, 4, 0, 1, 2, 3, 4], dtype=np.intp)
  263. tm.assert_numpy_array_equal(result, expected)
  264. def test_get_indexer_datetime(self):
  265. ii = IntervalIndex.from_breaks(date_range("2018-01-01", periods=4))
  266. result = ii.get_indexer(DatetimeIndex(["2018-01-02"]))
  267. expected = np.array([0], dtype=np.intp)
  268. tm.assert_numpy_array_equal(result, expected)
  269. result = ii.get_indexer(DatetimeIndex(["2018-01-02"]).astype(str))
  270. tm.assert_numpy_array_equal(result, expected)
  271. # TODO this should probably be deprecated?
  272. # https://github.com/pandas-dev/pandas/issues/47772
  273. result = ii.get_indexer(DatetimeIndex(["2018-01-02"]).asi8)
  274. tm.assert_numpy_array_equal(result, expected)
  275. @pytest.mark.parametrize(
  276. "tuples, closed",
  277. [
  278. ([(0, 2), (1, 3), (3, 4)], "neither"),
  279. ([(0, 5), (1, 4), (6, 7)], "left"),
  280. ([(0, 1), (0, 1), (1, 2)], "right"),
  281. ([(0, 1), (2, 3), (3, 4)], "both"),
  282. ],
  283. )
  284. def test_get_indexer_errors(self, tuples, closed):
  285. # IntervalIndex needs non-overlapping for uniqueness when querying
  286. index = IntervalIndex.from_tuples(tuples, closed=closed)
  287. msg = (
  288. "cannot handle overlapping indices; use "
  289. "IntervalIndex.get_indexer_non_unique"
  290. )
  291. with pytest.raises(InvalidIndexError, match=msg):
  292. index.get_indexer([0, 2])
  293. @pytest.mark.parametrize(
  294. "query, expected",
  295. [
  296. ([-0.5], ([-1], [0])),
  297. ([0], ([0], [])),
  298. ([0.5], ([0], [])),
  299. ([1], ([0, 1], [])),
  300. ([1.5], ([0, 1], [])),
  301. ([2], ([0, 1, 2], [])),
  302. ([2.5], ([1, 2], [])),
  303. ([3], ([2], [])),
  304. ([3.5], ([2], [])),
  305. ([4], ([-1], [0])),
  306. ([4.5], ([-1], [0])),
  307. ([1, 2], ([0, 1, 0, 1, 2], [])),
  308. ([1, 2, 3], ([0, 1, 0, 1, 2, 2], [])),
  309. ([1, 2, 3, 4], ([0, 1, 0, 1, 2, 2, -1], [3])),
  310. ([1, 2, 3, 4, 2], ([0, 1, 0, 1, 2, 2, -1, 0, 1, 2], [3])),
  311. ],
  312. )
  313. def test_get_indexer_non_unique_with_int_and_float(self, query, expected):
  314. tuples = [(0, 2.5), (1, 3), (2, 4)]
  315. index = IntervalIndex.from_tuples(tuples, closed="left")
  316. result_indexer, result_missing = index.get_indexer_non_unique(query)
  317. expected_indexer = np.array(expected[0], dtype="intp")
  318. expected_missing = np.array(expected[1], dtype="intp")
  319. tm.assert_numpy_array_equal(result_indexer, expected_indexer)
  320. tm.assert_numpy_array_equal(result_missing, expected_missing)
  321. # TODO we may also want to test get_indexer for the case when
  322. # the intervals are duplicated, decreasing, non-monotonic, etc..
  323. def test_get_indexer_non_monotonic(self):
  324. # GH 16410
  325. idx1 = IntervalIndex.from_tuples([(2, 3), (4, 5), (0, 1)])
  326. idx2 = IntervalIndex.from_tuples([(0, 1), (2, 3), (6, 7), (8, 9)])
  327. result = idx1.get_indexer(idx2)
  328. expected = np.array([2, 0, -1, -1], dtype=np.intp)
  329. tm.assert_numpy_array_equal(result, expected)
  330. result = idx1.get_indexer(idx1[1:])
  331. expected = np.array([1, 2], dtype=np.intp)
  332. tm.assert_numpy_array_equal(result, expected)
  333. def test_get_indexer_with_nans(self):
  334. # GH#41831
  335. index = IntervalIndex([np.nan, np.nan])
  336. other = IntervalIndex([np.nan])
  337. assert not index._index_as_unique
  338. result = index.get_indexer_for(other)
  339. expected = np.array([0, 1], dtype=np.intp)
  340. tm.assert_numpy_array_equal(result, expected)
  341. def test_get_index_non_unique_non_monotonic(self):
  342. # GH#44084 (root cause)
  343. index = IntervalIndex.from_tuples(
  344. [(0.0, 1.0), (1.0, 2.0), (0.0, 1.0), (1.0, 2.0)]
  345. )
  346. result, _ = index.get_indexer_non_unique([Interval(1.0, 2.0)])
  347. expected = np.array([1, 3], dtype=np.intp)
  348. tm.assert_numpy_array_equal(result, expected)
  349. def test_get_indexer_multiindex_with_intervals(self):
  350. # GH#44084 (MultiIndex case as reported)
  351. interval_index = IntervalIndex.from_tuples(
  352. [(2.0, 3.0), (0.0, 1.0), (1.0, 2.0)], name="interval"
  353. )
  354. foo_index = Index([1, 2, 3], name="foo")
  355. multi_index = MultiIndex.from_product([foo_index, interval_index])
  356. result = multi_index.get_level_values("interval").get_indexer_for(
  357. [Interval(0.0, 1.0)]
  358. )
  359. expected = np.array([1, 4, 7], dtype=np.intp)
  360. tm.assert_numpy_array_equal(result, expected)
  361. @pytest.mark.parametrize("box", [IntervalIndex, array, list])
  362. def test_get_indexer_interval_index(self, box):
  363. # GH#30178
  364. rng = period_range("2022-07-01", freq="D", periods=3)
  365. idx = box(interval_range(Timestamp("2022-07-01"), freq="3D", periods=3))
  366. actual = rng.get_indexer(idx)
  367. expected = np.array([-1, -1, -1], dtype=np.intp)
  368. tm.assert_numpy_array_equal(actual, expected)
  369. class TestSliceLocs:
  370. def test_slice_locs_with_interval(self):
  371. # increasing monotonically
  372. index = IntervalIndex.from_tuples([(0, 2), (1, 3), (2, 4)])
  373. assert index.slice_locs(start=Interval(0, 2), end=Interval(2, 4)) == (0, 3)
  374. assert index.slice_locs(start=Interval(0, 2)) == (0, 3)
  375. assert index.slice_locs(end=Interval(2, 4)) == (0, 3)
  376. assert index.slice_locs(end=Interval(0, 2)) == (0, 1)
  377. assert index.slice_locs(start=Interval(2, 4), end=Interval(0, 2)) == (2, 1)
  378. # decreasing monotonically
  379. index = IntervalIndex.from_tuples([(2, 4), (1, 3), (0, 2)])
  380. assert index.slice_locs(start=Interval(0, 2), end=Interval(2, 4)) == (2, 1)
  381. assert index.slice_locs(start=Interval(0, 2)) == (2, 3)
  382. assert index.slice_locs(end=Interval(2, 4)) == (0, 1)
  383. assert index.slice_locs(end=Interval(0, 2)) == (0, 3)
  384. assert index.slice_locs(start=Interval(2, 4), end=Interval(0, 2)) == (0, 3)
  385. # sorted duplicates
  386. index = IntervalIndex.from_tuples([(0, 2), (0, 2), (2, 4)])
  387. assert index.slice_locs(start=Interval(0, 2), end=Interval(2, 4)) == (0, 3)
  388. assert index.slice_locs(start=Interval(0, 2)) == (0, 3)
  389. assert index.slice_locs(end=Interval(2, 4)) == (0, 3)
  390. assert index.slice_locs(end=Interval(0, 2)) == (0, 2)
  391. assert index.slice_locs(start=Interval(2, 4), end=Interval(0, 2)) == (2, 2)
  392. # unsorted duplicates
  393. index = IntervalIndex.from_tuples([(0, 2), (2, 4), (0, 2)])
  394. with pytest.raises(
  395. KeyError,
  396. match=re.escape(
  397. '"Cannot get left slice bound for non-unique label: '
  398. "Interval(0, 2, closed='right')\""
  399. ),
  400. ):
  401. index.slice_locs(start=Interval(0, 2), end=Interval(2, 4))
  402. with pytest.raises(
  403. KeyError,
  404. match=re.escape(
  405. '"Cannot get left slice bound for non-unique label: '
  406. "Interval(0, 2, closed='right')\""
  407. ),
  408. ):
  409. index.slice_locs(start=Interval(0, 2))
  410. assert index.slice_locs(end=Interval(2, 4)) == (0, 2)
  411. with pytest.raises(
  412. KeyError,
  413. match=re.escape(
  414. '"Cannot get right slice bound for non-unique label: '
  415. "Interval(0, 2, closed='right')\""
  416. ),
  417. ):
  418. index.slice_locs(end=Interval(0, 2))
  419. with pytest.raises(
  420. KeyError,
  421. match=re.escape(
  422. '"Cannot get right slice bound for non-unique label: '
  423. "Interval(0, 2, closed='right')\""
  424. ),
  425. ):
  426. index.slice_locs(start=Interval(2, 4), end=Interval(0, 2))
  427. # another unsorted duplicates
  428. index = IntervalIndex.from_tuples([(0, 2), (0, 2), (2, 4), (1, 3)])
  429. assert index.slice_locs(start=Interval(0, 2), end=Interval(2, 4)) == (0, 3)
  430. assert index.slice_locs(start=Interval(0, 2)) == (0, 4)
  431. assert index.slice_locs(end=Interval(2, 4)) == (0, 3)
  432. assert index.slice_locs(end=Interval(0, 2)) == (0, 2)
  433. assert index.slice_locs(start=Interval(2, 4), end=Interval(0, 2)) == (2, 2)
  434. def test_slice_locs_with_ints_and_floats_succeeds(self):
  435. # increasing non-overlapping
  436. index = IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)])
  437. assert index.slice_locs(0, 1) == (0, 1)
  438. assert index.slice_locs(0, 2) == (0, 2)
  439. assert index.slice_locs(0, 3) == (0, 2)
  440. assert index.slice_locs(3, 1) == (2, 1)
  441. assert index.slice_locs(3, 4) == (2, 3)
  442. assert index.slice_locs(0, 4) == (0, 3)
  443. # decreasing non-overlapping
  444. index = IntervalIndex.from_tuples([(3, 4), (1, 2), (0, 1)])
  445. assert index.slice_locs(0, 1) == (3, 3)
  446. assert index.slice_locs(0, 2) == (3, 2)
  447. assert index.slice_locs(0, 3) == (3, 1)
  448. assert index.slice_locs(3, 1) == (1, 3)
  449. assert index.slice_locs(3, 4) == (1, 1)
  450. assert index.slice_locs(0, 4) == (3, 1)
  451. @pytest.mark.parametrize("query", [[0, 1], [0, 2], [0, 3], [0, 4]])
  452. @pytest.mark.parametrize(
  453. "tuples",
  454. [
  455. [(0, 2), (1, 3), (2, 4)],
  456. [(2, 4), (1, 3), (0, 2)],
  457. [(0, 2), (0, 2), (2, 4)],
  458. [(0, 2), (2, 4), (0, 2)],
  459. [(0, 2), (0, 2), (2, 4), (1, 3)],
  460. ],
  461. )
  462. def test_slice_locs_with_ints_and_floats_errors(self, tuples, query):
  463. start, stop = query
  464. index = IntervalIndex.from_tuples(tuples)
  465. with pytest.raises(
  466. KeyError,
  467. match=(
  468. "'can only get slices from an IntervalIndex if bounds are "
  469. "non-overlapping and all monotonic increasing or decreasing'"
  470. ),
  471. ):
  472. index.slice_locs(start, stop)
  473. class TestPutmask:
  474. @pytest.mark.parametrize("tz", ["US/Pacific", None])
  475. def test_putmask_dt64(self, tz):
  476. # GH#37968
  477. dti = date_range("2016-01-01", periods=9, tz=tz)
  478. idx = IntervalIndex.from_breaks(dti)
  479. mask = np.zeros(idx.shape, dtype=bool)
  480. mask[0:3] = True
  481. result = idx.putmask(mask, idx[-1])
  482. expected = IntervalIndex([idx[-1]] * 3 + list(idx[3:]))
  483. tm.assert_index_equal(result, expected)
  484. def test_putmask_td64(self):
  485. # GH#37968
  486. dti = date_range("2016-01-01", periods=9)
  487. tdi = dti - dti[0]
  488. idx = IntervalIndex.from_breaks(tdi)
  489. mask = np.zeros(idx.shape, dtype=bool)
  490. mask[0:3] = True
  491. result = idx.putmask(mask, idx[-1])
  492. expected = IntervalIndex([idx[-1]] * 3 + list(idx[3:]))
  493. tm.assert_index_equal(result, expected)
  494. class TestContains:
  495. # .__contains__, not .contains
  496. def test_contains_dunder(self):
  497. index = IntervalIndex.from_arrays([0, 1], [1, 2], closed="right")
  498. # __contains__ requires perfect matches to intervals.
  499. assert 0 not in index
  500. assert 1 not in index
  501. assert 2 not in index
  502. assert Interval(0, 1, closed="right") in index
  503. assert Interval(0, 2, closed="right") not in index
  504. assert Interval(0, 0.5, closed="right") not in index
  505. assert Interval(3, 5, closed="right") not in index
  506. assert Interval(-1, 0, closed="left") not in index
  507. assert Interval(0, 1, closed="left") not in index
  508. assert Interval(0, 1, closed="both") not in index