diff --git a/pandas/conftest.py b/pandas/conftest.py index bc455092ebe86..79204c8896854 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -1565,6 +1565,14 @@ def indexer_si(request): return request.param +@pytest.fixture(params=[tm.setitem, tm.loc]) +def indexer_sl(request): + """ + Parametrize over __setitem__, loc.__setitem__ + """ + return request.param + + @pytest.fixture def using_array_manager(request): """ diff --git a/pandas/tests/indexing/interval/test_interval.py b/pandas/tests/indexing/interval/test_interval.py index f4e7296598d54..95f5115a8c28b 100644 --- a/pandas/tests/indexing/interval/test_interval.py +++ b/pandas/tests/indexing/interval/test_interval.py @@ -7,87 +7,83 @@ class TestIntervalIndex: - def setup_method(self, method): - self.s = Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) + @pytest.fixture + def series_with_interval_index(self): + return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) - def test_getitem_with_scalar(self): + def test_getitem_with_scalar(self, series_with_interval_index, indexer_sl): - s = self.s + ser = series_with_interval_index.copy() - expected = s.iloc[:3] - tm.assert_series_equal(expected, s[:3]) - tm.assert_series_equal(expected, s[:2.5]) - tm.assert_series_equal(expected, s[0.1:2.5]) + expected = ser.iloc[:3] + tm.assert_series_equal(expected, indexer_sl(ser)[:3]) + tm.assert_series_equal(expected, indexer_sl(ser)[:2.5]) + tm.assert_series_equal(expected, indexer_sl(ser)[0.1:2.5]) + if indexer_sl is tm.loc: + tm.assert_series_equal(expected, ser.loc[-1:3]) - expected = s.iloc[1:4] - tm.assert_series_equal(expected, s[[1.5, 2.5, 3.5]]) - tm.assert_series_equal(expected, s[[2, 3, 4]]) - tm.assert_series_equal(expected, s[[1.5, 3, 4]]) + expected = ser.iloc[1:4] + tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 2.5, 3.5]]) + tm.assert_series_equal(expected, indexer_sl(ser)[[2, 3, 4]]) + tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 3, 4]]) - expected = s.iloc[2:5] - tm.assert_series_equal(expected, s[s >= 2]) + expected = ser.iloc[2:5] + tm.assert_series_equal(expected, indexer_sl(ser)[ser >= 2]) @pytest.mark.parametrize("direction", ["increasing", "decreasing"]) - def test_nonoverlapping_monotonic(self, direction, closed): + def test_nonoverlapping_monotonic(self, direction, closed, indexer_sl): tpls = [(0, 1), (2, 3), (4, 5)] if direction == "decreasing": tpls = tpls[::-1] idx = IntervalIndex.from_tuples(tpls, closed=closed) - s = Series(list("abc"), idx) + ser = Series(list("abc"), idx) - for key, expected in zip(idx.left, s): + for key, expected in zip(idx.left, ser): if idx.closed_left: - assert s[key] == expected - assert s.loc[key] == expected + assert indexer_sl(ser)[key] == expected else: with pytest.raises(KeyError, match=str(key)): - s[key] - with pytest.raises(KeyError, match=str(key)): - s.loc[key] + indexer_sl(ser)[key] - for key, expected in zip(idx.right, s): + for key, expected in zip(idx.right, ser): if idx.closed_right: - assert s[key] == expected - assert s.loc[key] == expected + assert indexer_sl(ser)[key] == expected else: with pytest.raises(KeyError, match=str(key)): - s[key] - with pytest.raises(KeyError, match=str(key)): - s.loc[key] + indexer_sl(ser)[key] - for key, expected in zip(idx.mid, s): - assert s[key] == expected - assert s.loc[key] == expected + for key, expected in zip(idx.mid, ser): + assert indexer_sl(ser)[key] == expected - def test_non_matching(self): - s = self.s + def test_non_matching(self, series_with_interval_index, indexer_sl): + ser = series_with_interval_index.copy() # this is a departure from our current # indexing scheme, but simpler with pytest.raises(KeyError, match=r"^\[-1\]$"): - s.loc[[-1, 3, 4, 5]] + indexer_sl(ser)[[-1, 3, 4, 5]] with pytest.raises(KeyError, match=r"^\[-1\]$"): - s.loc[[-1, 3]] + indexer_sl(ser)[[-1, 3]] @pytest.mark.arm_slow def test_large_series(self): - s = Series( + ser = Series( np.arange(1000000), index=IntervalIndex.from_breaks(np.arange(1000001)) ) - result1 = s.loc[:80000] - result2 = s.loc[0:80000] - result3 = s.loc[0:80000:1] + result1 = ser.loc[:80000] + result2 = ser.loc[0:80000] + result3 = ser.loc[0:80000:1] tm.assert_series_equal(result1, result2) tm.assert_series_equal(result1, result3) def test_loc_getitem_frame(self): # CategoricalIndex with IntervalIndex categories df = DataFrame({"A": range(10)}) - s = pd.cut(df.A, 5) - df["B"] = s + ser = pd.cut(df.A, 5) + df["B"] = ser df = df.set_index("B") result = df.loc[4] diff --git a/pandas/tests/indexing/interval/test_interval_new.py b/pandas/tests/indexing/interval/test_interval_new.py index a9512bc97d9de..8935eb94c1c49 100644 --- a/pandas/tests/indexing/interval/test_interval_new.py +++ b/pandas/tests/indexing/interval/test_interval_new.py @@ -8,89 +8,65 @@ class TestIntervalIndex: - def setup_method(self, method): - self.s = Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) + @pytest.fixture + def series_with_interval_index(self): + return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) - def test_loc_with_interval(self): + def test_loc_with_interval(self, series_with_interval_index, indexer_sl): # loc with single label / list of labels: # - Intervals: only exact matches # - scalars: those that contain it - s = self.s + ser = series_with_interval_index.copy() expected = 0 - result = s.loc[Interval(0, 1)] - assert result == expected - result = s[Interval(0, 1)] + result = indexer_sl(ser)[Interval(0, 1)] assert result == expected - expected = s.iloc[3:5] - result = s.loc[[Interval(3, 4), Interval(4, 5)]] - tm.assert_series_equal(expected, result) - result = s[[Interval(3, 4), Interval(4, 5)]] + expected = ser.iloc[3:5] + result = indexer_sl(ser)[[Interval(3, 4), Interval(4, 5)]] tm.assert_series_equal(expected, result) # missing or not exact with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='left')")): - s.loc[Interval(3, 5, closed="left")] - - with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='left')")): - s[Interval(3, 5, closed="left")] - - with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): - s[Interval(3, 5)] + indexer_sl(ser)[Interval(3, 5, closed="left")] with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): - s.loc[Interval(3, 5)] - - with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): - s[Interval(3, 5)] - - with pytest.raises( - KeyError, match=re.escape("Interval(-2, 0, closed='right')") - ): - s.loc[Interval(-2, 0)] + indexer_sl(ser)[Interval(3, 5)] with pytest.raises( KeyError, match=re.escape("Interval(-2, 0, closed='right')") ): - s[Interval(-2, 0)] - - with pytest.raises(KeyError, match=re.escape("Interval(5, 6, closed='right')")): - s.loc[Interval(5, 6)] + indexer_sl(ser)[Interval(-2, 0)] with pytest.raises(KeyError, match=re.escape("Interval(5, 6, closed='right')")): - s[Interval(5, 6)] + indexer_sl(ser)[Interval(5, 6)] - def test_loc_with_scalar(self): + def test_loc_with_scalar(self, series_with_interval_index, indexer_sl): # loc with single label / list of labels: # - Intervals: only exact matches # - scalars: those that contain it - s = self.s + ser = series_with_interval_index.copy() - assert s.loc[1] == 0 - assert s.loc[1.5] == 1 - assert s.loc[2] == 1 + assert indexer_sl(ser)[1] == 0 + assert indexer_sl(ser)[1.5] == 1 + assert indexer_sl(ser)[2] == 1 - assert s[1] == 0 - assert s[1.5] == 1 - assert s[2] == 1 + expected = ser.iloc[1:4] + tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 2.5, 3.5]]) + tm.assert_series_equal(expected, indexer_sl(ser)[[2, 3, 4]]) + tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 3, 4]]) - expected = s.iloc[1:4] - tm.assert_series_equal(expected, s.loc[[1.5, 2.5, 3.5]]) - tm.assert_series_equal(expected, s.loc[[2, 3, 4]]) - tm.assert_series_equal(expected, s.loc[[1.5, 3, 4]]) + expected = ser.iloc[[1, 1, 2, 1]] + tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 2, 2.5, 1.5]]) - expected = s.iloc[[1, 1, 2, 1]] - tm.assert_series_equal(expected, s.loc[[1.5, 2, 2.5, 1.5]]) + expected = ser.iloc[2:5] + tm.assert_series_equal(expected, indexer_sl(ser)[ser >= 2]) - expected = s.iloc[2:5] - tm.assert_series_equal(expected, s.loc[s >= 2]) - - def test_loc_with_slices(self): + def test_loc_with_slices(self, series_with_interval_index, indexer_sl): # loc with slices: # - Interval objects: only works with exact matches @@ -99,178 +75,130 @@ def test_loc_with_slices(self): # contains them: # (slice_loc(start, stop) == (idx.get_loc(start), idx.get_loc(stop)) - s = self.s + ser = series_with_interval_index.copy() # slice of interval - expected = s.iloc[:3] - result = s.loc[Interval(0, 1) : Interval(2, 3)] - tm.assert_series_equal(expected, result) - result = s[Interval(0, 1) : Interval(2, 3)] + expected = ser.iloc[:3] + result = indexer_sl(ser)[Interval(0, 1) : Interval(2, 3)] tm.assert_series_equal(expected, result) - expected = s.iloc[3:] - result = s.loc[Interval(3, 4) :] - tm.assert_series_equal(expected, result) - result = s[Interval(3, 4) :] + expected = ser.iloc[3:] + result = indexer_sl(ser)[Interval(3, 4) :] tm.assert_series_equal(expected, result) msg = "Interval objects are not currently supported" with pytest.raises(NotImplementedError, match=msg): - s.loc[Interval(3, 6) :] + indexer_sl(ser)[Interval(3, 6) :] with pytest.raises(NotImplementedError, match=msg): - s[Interval(3, 6) :] - - with pytest.raises(NotImplementedError, match=msg): - s.loc[Interval(3, 4, closed="left") :] - - with pytest.raises(NotImplementedError, match=msg): - s[Interval(3, 4, closed="left") :] - - # slice of scalar + indexer_sl(ser)[Interval(3, 4, closed="left") :] - expected = s.iloc[:3] - tm.assert_series_equal(expected, s.loc[:3]) - tm.assert_series_equal(expected, s.loc[:2.5]) - tm.assert_series_equal(expected, s.loc[0.1:2.5]) - tm.assert_series_equal(expected, s.loc[-1:3]) - - tm.assert_series_equal(expected, s[:3]) - tm.assert_series_equal(expected, s[:2.5]) - tm.assert_series_equal(expected, s[0.1:2.5]) - - def test_slice_step_ne1(self): + def test_slice_step_ne1(self, series_with_interval_index): # GH#31658 slice of scalar with step != 1 - s = self.s - expected = s.iloc[0:4:2] + ser = series_with_interval_index.copy() + expected = ser.iloc[0:4:2] - result = s[0:4:2] + result = ser[0:4:2] tm.assert_series_equal(result, expected) - result2 = s[0:4][::2] + result2 = ser[0:4][::2] tm.assert_series_equal(result2, expected) - def test_slice_float_start_stop(self): + def test_slice_float_start_stop(self, series_with_interval_index): # GH#31658 slicing with integers is positional, with floats is not # supported - ser = Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) + ser = series_with_interval_index.copy() msg = "label-based slicing with step!=1 is not supported for IntervalIndex" with pytest.raises(ValueError, match=msg): ser[1.5:9.5:2] - def test_slice_interval_step(self): + def test_slice_interval_step(self, series_with_interval_index): # GH#31658 allows for integer step!=1, not Interval step - s = self.s + ser = series_with_interval_index.copy() msg = "label-based slicing with step!=1 is not supported for IntervalIndex" with pytest.raises(ValueError, match=msg): - s[0 : 4 : Interval(0, 1)] + ser[0 : 4 : Interval(0, 1)] - def test_loc_with_overlap(self): + def test_loc_with_overlap(self, indexer_sl): idx = IntervalIndex.from_tuples([(1, 5), (3, 7)]) - s = Series(range(len(idx)), index=idx) + ser = Series(range(len(idx)), index=idx) # scalar - expected = s - result = s.loc[4] - tm.assert_series_equal(expected, result) - - result = s[4] - tm.assert_series_equal(expected, result) - - result = s.loc[[4]] + expected = ser + result = indexer_sl(ser)[4] tm.assert_series_equal(expected, result) - result = s[[4]] + result = indexer_sl(ser)[[4]] tm.assert_series_equal(expected, result) # interval expected = 0 - result = s.loc[Interval(1, 5)] + result = indexer_sl(ser)[Interval(1, 5)] result == expected - result = s[Interval(1, 5)] - result == expected - - expected = s - result = s.loc[[Interval(1, 5), Interval(3, 7)]] - tm.assert_series_equal(expected, result) - - result = s[[Interval(1, 5), Interval(3, 7)]] + expected = ser + result = indexer_sl(ser)[[Interval(1, 5), Interval(3, 7)]] tm.assert_series_equal(expected, result) with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): - s.loc[Interval(3, 5)] + indexer_sl(ser)[Interval(3, 5)] with pytest.raises(KeyError, match=r"^\[Interval\(3, 5, closed='right'\)\]$"): - s.loc[[Interval(3, 5)]] - - with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): - s[Interval(3, 5)] - - with pytest.raises(KeyError, match=r"^\[Interval\(3, 5, closed='right'\)\]$"): - s[[Interval(3, 5)]] + indexer_sl(ser)[[Interval(3, 5)]] # slices with interval (only exact matches) - expected = s - result = s.loc[Interval(1, 5) : Interval(3, 7)] - tm.assert_series_equal(expected, result) - - result = s[Interval(1, 5) : Interval(3, 7)] + expected = ser + result = indexer_sl(ser)[Interval(1, 5) : Interval(3, 7)] tm.assert_series_equal(expected, result) msg = "'can only get slices from an IntervalIndex if bounds are" " non-overlapping and all monotonic increasing or decreasing'" with pytest.raises(KeyError, match=msg): - s.loc[Interval(1, 6) : Interval(3, 8)] + indexer_sl(ser)[Interval(1, 6) : Interval(3, 8)] - with pytest.raises(KeyError, match=msg): - s[Interval(1, 6) : Interval(3, 8)] - - # slices with scalar raise for overlapping intervals - # TODO KeyError is the appropriate error? - with pytest.raises(KeyError, match=msg): - s.loc[1:4] + if indexer_sl is tm.loc: + # slices with scalar raise for overlapping intervals + # TODO KeyError is the appropriate error? + with pytest.raises(KeyError, match=msg): + ser.loc[1:4] - def test_non_unique(self): + def test_non_unique(self, indexer_sl): idx = IntervalIndex.from_tuples([(1, 3), (3, 7)]) - s = Series(range(len(idx)), index=idx) + ser = Series(range(len(idx)), index=idx) - result = s.loc[Interval(1, 3)] + result = indexer_sl(ser)[Interval(1, 3)] assert result == 0 - result = s.loc[[Interval(1, 3)]] - expected = s.iloc[0:1] + result = indexer_sl(ser)[[Interval(1, 3)]] + expected = ser.iloc[0:1] tm.assert_series_equal(expected, result) - def test_non_unique_moar(self): + def test_non_unique_moar(self, indexer_sl): idx = IntervalIndex.from_tuples([(1, 3), (1, 3), (3, 7)]) - s = Series(range(len(idx)), index=idx) - - expected = s.iloc[[0, 1]] - result = s.loc[Interval(1, 3)] - tm.assert_series_equal(expected, result) + ser = Series(range(len(idx)), index=idx) - expected = s - result = s.loc[Interval(1, 3) :] + expected = ser.iloc[[0, 1]] + result = indexer_sl(ser)[Interval(1, 3)] tm.assert_series_equal(expected, result) - expected = s - result = s[Interval(1, 3) :] + expected = ser + result = indexer_sl(ser)[Interval(1, 3) :] tm.assert_series_equal(expected, result) - expected = s.iloc[[0, 1]] - result = s[[Interval(1, 3)]] + expected = ser.iloc[[0, 1]] + result = indexer_sl(ser)[[Interval(1, 3)]] tm.assert_series_equal(expected, result) - def test_missing_key_error_message(self, frame_or_series): + def test_missing_key_error_message( + self, frame_or_series, series_with_interval_index + ): # GH#27365 - obj = frame_or_series( - np.arange(5), index=IntervalIndex.from_breaks(np.arange(6)) - ) + ser = series_with_interval_index.copy() + obj = frame_or_series(ser) with pytest.raises(KeyError, match=r"\[6\]"): obj.loc[[4, 5, 6]]