diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 9d501b2601c09..b3386f6104032 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -483,25 +483,20 @@ def get_value(self, series, key): return series.iat[key] if isinstance(key, str): + try: + loc = self._get_string_slice(key) + return series[loc] + except (TypeError, ValueError): + pass + asdt, reso = parse_time_string(key, self.freq) grp = resolution.Resolution.get_freq_group(reso) freqn = resolution.get_freq_group(self.freq) - vals = self._ndarray_values - - # if our data is higher resolution than requested key, slice - if grp < freqn: - iv = Period(asdt, freq=(grp, 1)) - ord1 = iv.asfreq(self.freq, how="S").ordinal - ord2 = iv.asfreq(self.freq, how="E").ordinal + # _get_string_slice will handle cases where grp < freqn + assert grp >= freqn - if ord2 < vals[0] or ord1 > vals[-1]: - raise KeyError(key) - - pos = np.searchsorted(self._ndarray_values, [ord1, ord2]) - key = slice(pos[0], pos[1] + 1) - return series[key] - elif grp == freqn: + if grp == freqn: key = Period(asdt, freq=self.freq) loc = self.get_loc(key) return series.iloc[loc] @@ -643,61 +638,39 @@ def _maybe_cast_slice_bound(self, label, side, kind): return label - def _parsed_string_to_bounds(self, reso, parsed): - if reso == "year": - t1 = Period(year=parsed.year, freq="A") - elif reso == "month": - t1 = Period(year=parsed.year, month=parsed.month, freq="M") - elif reso == "quarter": - q = (parsed.month - 1) // 3 + 1 - t1 = Period(year=parsed.year, quarter=q, freq="Q-DEC") - elif reso == "day": - t1 = Period(year=parsed.year, month=parsed.month, day=parsed.day, freq="D") - elif reso == "hour": - t1 = Period( - year=parsed.year, - month=parsed.month, - day=parsed.day, - hour=parsed.hour, - freq="H", - ) - elif reso == "minute": - t1 = Period( - year=parsed.year, - month=parsed.month, - day=parsed.day, - hour=parsed.hour, - minute=parsed.minute, - freq="T", - ) - elif reso == "second": - t1 = Period( - year=parsed.year, - month=parsed.month, - day=parsed.day, - hour=parsed.hour, - minute=parsed.minute, - second=parsed.second, - freq="S", - ) - else: + def _parsed_string_to_bounds(self, reso: str, parsed: datetime): + if reso not in ["year", "month", "quarter", "day", "hour", "minute", "second"]: raise KeyError(reso) - return (t1.asfreq(self.freq, how="start"), t1.asfreq(self.freq, how="end")) + + grp = resolution.Resolution.get_freq_group(reso) + iv = Period(parsed, freq=(grp, 1)) + return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end")) def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True): # TODO: Check for non-True use_lhs/use_rhs + raw = key if not self.is_monotonic: raise ValueError("Partial indexing only valid for ordered time series") parsed, reso = parse_time_string(key, self.freq) grp = resolution.Resolution.get_freq_group(reso) freqn = resolution.get_freq_group(self.freq) - if reso in ["day", "hour", "minute", "second"] and not grp < freqn: - raise KeyError(key) + + if not grp < freqn: + # TODO: we used to also check for + # reso in ["day", "hour", "minute", "second"] + # why is that check not needed? + raise TypeError(key) t1, t2 = self._parsed_string_to_bounds(reso, parsed) + if len(self): + if t2 < self.min() or t1 > self.max(): + raise KeyError(raw) + + # Use asi8 searchsorted to avoid overhead of re-validating inputs return slice( - self.searchsorted(t1, side="left"), self.searchsorted(t2, side="right") + self.asi8.searchsorted(t1.ordinal, side="left"), + self.asi8.searchsorted(t2.ordinal, side="right"), ) def _convert_tolerance(self, tolerance, target):