Skip to content

REF: share parts of DTI and PI #47038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,12 @@ def _summary(self, name=None) -> str:
# --------------------------------------------------------------------
# Indexing Methods

@final
def _can_partial_date_slice(self, reso: Resolution) -> bool:
raise NotImplementedError
# e.g. test_getitem_setitem_periodindex
# History of conversation GH#3452, GH#3931, GH#2369, GH#14826
return reso > self._resolution_obj
# NB: for DTI/PI, not TDI

def _parsed_string_to_bounds(self, reso: Resolution, parsed):
raise NotImplementedError
Expand Down
46 changes: 19 additions & 27 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,10 +593,6 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
end = self._maybe_cast_for_get_loc(end)
return start, end

def _can_partial_date_slice(self, reso: Resolution) -> bool:
# History of conversation GH#3452, GH#3931, GH#2369, GH#14826
return reso > self._resolution_obj

def _deprecate_mismatched_indexing(self, key) -> None:
# GH#36148
# we get here with isinstance(key, self._data._recognized_scalars)
Expand Down Expand Up @@ -651,12 +647,8 @@ def get_loc(self, key, method=None, tolerance=None):
except KeyError as err:
if method is None:
raise KeyError(key) from err
try:
key = self._maybe_cast_for_get_loc(key)
except ValueError as err:
# FIXME(dateutil#1180): we get here because parse_with_reso
# doesn't raise on "t2m"
raise KeyError(key) from err

key = self._maybe_cast_for_get_loc(key)

elif isinstance(key, timedelta):
# GH#20464
Expand All @@ -682,7 +674,16 @@ def get_loc(self, key, method=None, tolerance=None):

def _maybe_cast_for_get_loc(self, key) -> Timestamp:
# needed to localize naive datetimes or dates (GH 35690)
key = Timestamp(key)
try:
key = Timestamp(key)
except ValueError as err:
# FIXME(dateutil#1180): we get here because parse_with_reso
# doesn't raise on "t2m"
if not isinstance(key, str):
# Not expected to be reached, but check to be sure
raise # pragma: no cover
raise KeyError(key) from err

if key.tzinfo is None:
key = key.tz_localize(self.tz)
else:
Expand All @@ -691,6 +692,13 @@ def _maybe_cast_for_get_loc(self, key) -> Timestamp:

@doc(DatetimeTimedeltaMixin._maybe_cast_slice_bound)
def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default):

# GH#42855 handle date here instead of get_slice_bound
if isinstance(label, date) and not isinstance(label, datetime):
# Pandas supports slicing with dates, treated as datetimes at midnight.
# https://github.com/pandas-dev/pandas/issues/31501
label = Timestamp(label).to_pydatetime()

label = super()._maybe_cast_slice_bound(label, side, kind=kind)
self._deprecate_mismatched_indexing(label)
return self._maybe_cast_for_get_loc(label)
Expand Down Expand Up @@ -722,13 +730,6 @@ def slice_indexer(self, start=None, end=None, step=None, kind=lib.no_default):
if isinstance(start, time) or isinstance(end, time):
raise KeyError("Cannot mix time and non-time slice keys")

# Pandas supports slicing with dates, treated as datetimes at midnight.
# https://github.com/pandas-dev/pandas/issues/31501
if isinstance(start, date) and not isinstance(start, datetime):
start = datetime.combine(start, time(0, 0))
if isinstance(end, date) and not isinstance(end, datetime):
end = datetime.combine(end, time(0, 0))

def check_str_or_none(point):
return point is not None and not isinstance(point, str)

Expand Down Expand Up @@ -768,15 +769,6 @@ def check_str_or_none(point):
else:
return indexer

@doc(Index.get_slice_bound)
def get_slice_bound(
self, label, side: Literal["left", "right"], kind=lib.no_default
) -> int:
# GH#42855 handle date here instead of _maybe_cast_slice_bound
if isinstance(label, date) and not isinstance(label, datetime):
label = Timestamp(label).to_pydatetime()
return super().get_slice_bound(label, side=side, kind=kind)

# --------------------------------------------------------------------

@property
Expand Down
73 changes: 44 additions & 29 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
DtypeObj,
npt,
)
from pandas.util._decorators import doc
from pandas.util._decorators import (
cache_readonly,
doc,
)
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.common import (
Expand Down Expand Up @@ -159,6 +162,12 @@ class PeriodIndex(DatetimeIndexOpsMixin):
_engine_type = libindex.PeriodEngine
_supports_partial_string_indexing = True

@cache_readonly
# Signature of "_resolution_obj" incompatible with supertype "DatetimeIndexOpsMixin"
def _resolution_obj(self) -> Resolution: # type: ignore[override]
# for compat with DatetimeIndex
return self.dtype._resolution_obj

# --------------------------------------------------------------------
# methods that dispatch to array and wrap result in Index
# These are defined here instead of via inherit_names for mypy
Expand Down Expand Up @@ -446,39 +455,25 @@ def get_loc(self, key, method=None, tolerance=None):
# TODO: pass if method is not None, like DTI does?
raise KeyError(key) from err

if reso == self.dtype._resolution_obj:
# the reso < self.dtype._resolution_obj case goes
if reso == self._resolution_obj:
# the reso < self._resolution_obj case goes
# through _get_string_slice
key = Period(parsed, freq=self.freq)
key = self._cast_partial_indexing_scalar(key)
loc = self.get_loc(key, method=method, tolerance=tolerance)
# Recursing instead of falling through matters for the exception
# message in test_get_loc3 (though not clear if that really matters)
return loc
elif method is None:
raise KeyError(key)
else:
key = Period(parsed, freq=self.freq)
key = self._cast_partial_indexing_scalar(parsed)

elif isinstance(key, Period):
sfreq = self.freq
kfreq = key.freq
if not (
sfreq.n == kfreq.n
# error: "BaseOffset" has no attribute "_period_dtype_code"
and sfreq._period_dtype_code # type: ignore[attr-defined]
# error: "BaseOffset" has no attribute "_period_dtype_code"
== kfreq._period_dtype_code # type: ignore[attr-defined]
):
# GH#42247 For the subset of DateOffsets that can be Period freqs,
# checking these two attributes is sufficient to check equality,
# and much more performant than `self.freq == key.freq`
raise KeyError(key)
key = self._maybe_cast_for_get_loc(key)

elif isinstance(key, datetime):
try:
key = Period(key, freq=self.freq)
except ValueError as err:
# we cannot construct the Period
raise KeyError(orig_key) from err
key = self._cast_partial_indexing_scalar(key)

else:
# in particular integer, which Period constructor would cast to string
raise KeyError(key)
Expand All @@ -488,22 +483,42 @@ def get_loc(self, key, method=None, tolerance=None):
except KeyError as err:
raise KeyError(orig_key) from err

def _maybe_cast_for_get_loc(self, key: Period) -> Period:
# name is a misnomer, chosen for compat with DatetimeIndex
sfreq = self.freq
kfreq = key.freq
if not (
sfreq.n == kfreq.n
# error: "BaseOffset" has no attribute "_period_dtype_code"
and sfreq._period_dtype_code # type: ignore[attr-defined]
# error: "BaseOffset" has no attribute "_period_dtype_code"
== kfreq._period_dtype_code # type: ignore[attr-defined]
):
# GH#42247 For the subset of DateOffsets that can be Period freqs,
# checking these two attributes is sufficient to check equality,
# and much more performant than `self.freq == key.freq`
raise KeyError(key)
return key

def _cast_partial_indexing_scalar(self, label):
try:
key = Period(label, freq=self.freq)
except ValueError as err:
# we cannot construct the Period
raise KeyError(label) from err
return key

@doc(DatetimeIndexOpsMixin._maybe_cast_slice_bound)
def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default):
if isinstance(label, datetime):
label = Period(label, freq=self.freq)
label = self._cast_partial_indexing_scalar(label)

return super()._maybe_cast_slice_bound(label, side, kind=kind)

def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
iv = Period(parsed, freq=reso.attr_abbrev)
return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end"))

def _can_partial_date_slice(self, reso: Resolution) -> bool:
assert isinstance(reso, Resolution), (type(reso), reso)
# e.g. test_getitem_setitem_periodindex
return reso > self.dtype._resolution_obj


def period_range(
start=None, end=None, periods: int | None = None, freq=None, name=None
Expand Down