diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 09cb024cbd95c..fcc89715a832c 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -477,6 +477,7 @@ Indexing - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from :class:`MultiIndex` (:issue:`27104`) - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`CategoricalIndex` using a listlike indexer containing NA values (:issue:`37722`) - Bug in :meth:`DataFrame.xs` ignored ``droplevel=False`` for columns (:issue:`19056`) +- Bug in :meth:`DataFrame.loc` and :meth:`Series.loc` did not raise ``KeyError`` when non-existing label was sliced in unordered :class:`DatetimeIndex` (:issue:`18531`) Missing ^^^^^^^ diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 9744eb0ecbb88..288d49128b87a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -810,7 +810,8 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): if end is not None: end_casted = self._maybe_cast_slice_bound(end, "right", kind) mask = (self <= end_casted) & mask - + if not any(mask): + raise indexer = mask.nonzero()[0][::step] if len(indexer) == len(self): return slice(None) diff --git a/pandas/tests/indexes/datetimes/test_partial_slicing.py b/pandas/tests/indexes/datetimes/test_partial_slicing.py index 57dc46e1fb415..65fb8e84a67a3 100644 --- a/pandas/tests/indexes/datetimes/test_partial_slicing.py +++ b/pandas/tests/indexes/datetimes/test_partial_slicing.py @@ -311,19 +311,22 @@ def test_partial_slicing_with_multiindex(self): tm.assert_frame_equal(result, expected) def test_partial_slice_doesnt_require_monotonicity(self): - # For historical reasons. - s = Series(np.arange(10), date_range("2014-01-01", periods=10)) + ser = Series(np.arange(10), date_range("2014-01-01", periods=10)) - nonmonotonic = s[[3, 5, 4]] - expected = nonmonotonic.iloc[:0] + nonmonotonic = ser[[3, 5, 4]] timestamp = Timestamp("2014-01-10") + msg = r"Timestamp\('2014-01-10 00:00:00'\)" - tm.assert_series_equal(nonmonotonic["2014-01-10":], expected) - with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"): + with pytest.raises(KeyError, match=msg): + nonmonotonic["2014-01-10":] + + with pytest.raises(KeyError, match=msg): nonmonotonic[timestamp:] - tm.assert_series_equal(nonmonotonic.loc["2014-01-10":], expected) - with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"): + with pytest.raises(KeyError, match=msg): + nonmonotonic.loc["2014-01-10":] + + with pytest.raises(KeyError, match=msg): nonmonotonic.loc[timestamp:] def test_loc_datetime_length_one(self): diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index 9aab867df4b17..060e13d0ce8ea 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -1564,6 +1564,23 @@ def test_loc_getitem_slice_label_td64obj(self, start, stop, expected_slice): expected = ser.iloc[expected_slice] tm.assert_series_equal(result, expected) + def test_loc_getitem_slice_unordered_dt_index(self, frame_or_series): + # GH#18531 + obj = frame_or_series( + [1, 2, 3], + index=[Timestamp("2016"), Timestamp("2019"), Timestamp("2017")], + ) + with pytest.raises(KeyError, match=r"Timestamp\('2020-01-01 00:00:00'\)"): + obj.loc["2020":"2022"] + + result = obj.loc["2018":"2022"] + expected = frame_or_series([2], index=[Timestamp("2019")]) + tm.assert_equal(result, expected) + + obj.index = ["a", "c", "b"] + with pytest.raises(KeyError, match=r"d"): + obj.loc["d":"e"] + class TestLocBooleanMask: def test_loc_setitem_bool_mask_timedeltaindex(self):