From deb076b1dfd5df3a7f60a0d5fd585ef20ae63742 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 25 May 2021 16:22:17 -0700 Subject: [PATCH 1/2] BUG: PeriodIndex.get_loc with mismatched freq --- pandas/core/indexes/period.py | 25 ++------------ pandas/tests/indexes/period/test_indexing.py | 35 ++++++++++++++++---- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 266df655e326c..055d6c18f5462 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -4,10 +4,7 @@ datetime, timedelta, ) -from typing import ( - Any, - Hashable, -) +from typing import Hashable import warnings import numpy as np @@ -321,24 +318,6 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: return False return dtype.freq == self.freq - # ------------------------------------------------------------------------ - # Indexing - - @doc(Index.__contains__) - def __contains__(self, key: Any) -> bool: - if isinstance(key, Period): - if key.freq != self.freq: - return False - else: - return key.ordinal in self._engine - else: - hash(key) - try: - self.get_loc(key) - return True - except KeyError: - return False - # ------------------------------------------------------------------------ # Index Methods @@ -475,6 +454,8 @@ def get_loc(self, key, method=None, tolerance=None): elif is_integer(key): # Period constructor will cast to string, which we dont want raise KeyError(key) + elif isinstance(key, Period) and key.freq != self.freq: + raise KeyError(key) try: key = Period(key, freq=self.freq) diff --git a/pandas/tests/indexes/period/test_indexing.py b/pandas/tests/indexes/period/test_indexing.py index e820c2250256e..a41d02cfbd394 100644 --- a/pandas/tests/indexes/period/test_indexing.py +++ b/pandas/tests/indexes/period/test_indexing.py @@ -338,15 +338,21 @@ def test_get_loc_integer(self): pi2.get_loc(46) # TODO: This method came from test_period; de-dup with version above - def test_get_loc2(self): + @pytest.mark.parametrize("method", [None, "pad", "backfill", "nearest"]) + def test_get_loc_method(self, method): idx = period_range("2000-01-01", periods=3) - for method in [None, "pad", "backfill", "nearest"]: - assert idx.get_loc(idx[1], method) == 1 - assert idx.get_loc(idx[1].asfreq("H", how="start"), method) == 1 - assert idx.get_loc(idx[1].to_timestamp(), method) == 1 - assert idx.get_loc(idx[1].to_timestamp().to_pydatetime(), method) == 1 - assert idx.get_loc(str(idx[1]), method) == 1 + assert idx.get_loc(idx[1], method) == 1 + assert idx.get_loc(idx[1].to_timestamp(), method) == 1 + assert idx.get_loc(idx[1].to_timestamp().to_pydatetime(), method) == 1 + assert idx.get_loc(str(idx[1]), method) == 1 + + key = idx[1].asfreq("H", how="start") + with pytest.raises(KeyError, match=str(key)): + idx.get_loc(key, method=method) + + # TODO: This method came from test_period; de-dup with version above + def test_get_loc3(self): idx = period_range("2000-01-01", periods=5)[::2] assert idx.get_loc("2000-01-02T12", method="nearest", tolerance="1 day") == 1 @@ -401,6 +407,21 @@ def test_get_loc_invalid_string_raises_keyerror(self): assert "A" not in ser assert "A" not in pi + def test_get_loc_mismatched_freq(self): + # see also test_get_indexer_mismatched_dtype testing we get analogous + # behavior for get_loc + dti = date_range("2016-01-01", periods=3) + pi = dti.to_period("D") + pi2 = dti.to_period("W") + pi3 = pi.view(pi2.dtype) # i.e. matching i8 representations + + with pytest.raises(KeyError, match="W-SUN"): + pi.get_loc(pi2[0]) + + with pytest.raises(KeyError, match="W-SUN"): + # even though we have matching i8 values + pi.get_loc(pi3[0]) + class TestGetIndexer: def test_get_indexer(self): From 29219629614c5e2d7d47230502596b1e018f2eba Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 27 May 2021 14:50:49 -0700 Subject: [PATCH 2/2] whatsnew --- doc/source/whatsnew/v1.3.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 8a3d6cf63d4f1..acf5202a28409 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -937,6 +937,7 @@ Indexing - Bug in :meth:`Series.__delitem__` with ``ExtensionDtype`` incorrectly casting to ``ndarray`` (:issue:`40386`) - Bug in :meth:`DataFrame.loc` returning :class:`MultiIndex` in wrong order if indexer has duplicates (:issue:`40978`) - Bug in :meth:`DataFrame.__setitem__` raising ``TypeError`` when using a str subclass as the column name with a :class:`DatetimeIndex` (:issue:`37366`) +- Bug in :meth:`PeriodIndex.get_loc` failing to raise ``KeyError`` when given a :class:`Period` with a mismatched ``freq`` (:issue:`41670`) Missing ^^^^^^^