Skip to content

Commit 08bdf31

Browse files
authored
REF: share _partial_date_slice between PeriodIndex/DatetimeIndex (#31766)
1 parent 7d1f825 commit 08bdf31

File tree

3 files changed

+63
-67
lines changed

3 files changed

+63
-67
lines changed

pandas/core/indexes/datetimelike.py

+52
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Base and utility classes for tseries type pandas objects.
33
"""
4+
from datetime import datetime
45
from typing import Any, List, Optional, Union
56

67
import numpy as np
@@ -411,6 +412,57 @@ def _convert_scalar_indexer(self, key, kind: str):
411412

412413
return super()._convert_scalar_indexer(key, kind=kind)
413414

415+
def _validate_partial_date_slice(self, reso: str):
416+
raise NotImplementedError
417+
418+
def _parsed_string_to_bounds(self, reso: str, parsed: datetime):
419+
raise NotImplementedError
420+
421+
def _partial_date_slice(
422+
self, reso: str, parsed: datetime, use_lhs: bool = True, use_rhs: bool = True
423+
):
424+
"""
425+
Parameters
426+
----------
427+
reso : str
428+
parsed : datetime
429+
use_lhs : bool, default True
430+
use_rhs : bool, default True
431+
432+
Returns
433+
-------
434+
slice or ndarray[intp]
435+
"""
436+
self._validate_partial_date_slice(reso)
437+
438+
t1, t2 = self._parsed_string_to_bounds(reso, parsed)
439+
i8vals = self.asi8
440+
unbox = self._data._unbox_scalar
441+
442+
if self.is_monotonic:
443+
444+
if len(self) and (
445+
(use_lhs and t1 < self[0] and t2 < self[0])
446+
or ((use_rhs and t1 > self[-1] and t2 > self[-1]))
447+
):
448+
# we are out of range
449+
raise KeyError
450+
451+
# TODO: does this depend on being monotonic _increasing_?
452+
453+
# a monotonic (sorted) series can be sliced
454+
# Use asi8.searchsorted to avoid re-validating Periods/Timestamps
455+
left = i8vals.searchsorted(unbox(t1), side="left") if use_lhs else None
456+
right = i8vals.searchsorted(unbox(t2), side="right") if use_rhs else None
457+
return slice(left, right)
458+
459+
else:
460+
lhs_mask = (i8vals >= unbox(t1)) if use_lhs else True
461+
rhs_mask = (i8vals <= unbox(t2)) if use_rhs else True
462+
463+
# try to find the dates
464+
return (lhs_mask & rhs_mask).nonzero()[0]
465+
414466
# --------------------------------------------------------------------
415467

416468
__add__ = make_wrapped_arith_op("__add__")

pandas/core/indexes/datetimes.py

+2-37
Original file line numberDiff line numberDiff line change
@@ -503,19 +503,9 @@ def _parsed_string_to_bounds(self, reso: str, parsed: datetime):
503503
end = end.tz_localize(self.tz)
504504
return start, end
505505

506-
def _partial_date_slice(
507-
self, reso: str, parsed: datetime, use_lhs: bool = True, use_rhs: bool = True
508-
):
509-
"""
510-
Parameters
511-
----------
512-
reso : str
513-
use_lhs : bool, default True
514-
use_rhs : bool, default True
515-
"""
516-
is_monotonic = self.is_monotonic
506+
def _validate_partial_date_slice(self, reso: str):
517507
if (
518-
is_monotonic
508+
self.is_monotonic
519509
and reso in ["day", "hour", "minute", "second"]
520510
and self._resolution >= Resolution.get_reso(reso)
521511
):
@@ -530,31 +520,6 @@ def _partial_date_slice(
530520
# _parsed_string_to_bounds allows it.
531521
raise KeyError
532522

533-
t1, t2 = self._parsed_string_to_bounds(reso, parsed)
534-
stamps = self.asi8
535-
536-
if is_monotonic:
537-
538-
# we are out of range
539-
if len(stamps) and (
540-
(use_lhs and t1.value < stamps[0] and t2.value < stamps[0])
541-
or ((use_rhs and t1.value > stamps[-1] and t2.value > stamps[-1]))
542-
):
543-
raise KeyError
544-
545-
# a monotonic (sorted) series can be sliced
546-
# Use asi8.searchsorted to avoid re-validating
547-
left = stamps.searchsorted(t1.value, side="left") if use_lhs else None
548-
right = stamps.searchsorted(t2.value, side="right") if use_rhs else None
549-
550-
return slice(left, right)
551-
552-
lhs_mask = (stamps >= t1.value) if use_lhs else True
553-
rhs_mask = (stamps <= t2.value) if use_rhs else True
554-
555-
# try to find a the dates
556-
return (lhs_mask & rhs_mask).nonzero()[0]
557-
558523
def _maybe_promote(self, other):
559524
if other.inferred_type == "date":
560525
other = DatetimeIndex(other)

pandas/core/indexes/period.py

+9-30
Original file line numberDiff line numberDiff line change
@@ -606,45 +606,24 @@ def _parsed_string_to_bounds(self, reso: str, parsed: datetime):
606606
iv = Period(parsed, freq=(grp, 1))
607607
return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end"))
608608

609-
def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True):
610-
# TODO: Check for non-True use_lhs/use_rhs
611-
parsed, reso = parse_time_string(key, self.freq)
609+
def _validate_partial_date_slice(self, reso: str):
612610
grp = resolution.Resolution.get_freq_group(reso)
613611
freqn = resolution.get_freq_group(self.freq)
614612

615613
if not grp < freqn:
616614
# TODO: we used to also check for
617615
# reso in ["day", "hour", "minute", "second"]
618616
# why is that check not needed?
619-
raise ValueError(key)
620-
621-
t1, t2 = self._parsed_string_to_bounds(reso, parsed)
622-
i8vals = self.asi8
623-
624-
if self.is_monotonic:
625-
626-
# we are out of range
627-
if len(self) and (
628-
(use_lhs and t1 < self[0] and t2 < self[0])
629-
or ((use_rhs and t1 > self[-1] and t2 > self[-1]))
630-
):
631-
raise KeyError(key)
632-
633-
# TODO: does this depend on being monotonic _increasing_?
634-
# If so, DTI will also be affected.
617+
raise ValueError
635618

636-
# a monotonic (sorted) series can be sliced
637-
# Use asi8.searchsorted to avoid re-validating Periods
638-
left = i8vals.searchsorted(t1.ordinal, side="left") if use_lhs else None
639-
right = i8vals.searchsorted(t2.ordinal, side="right") if use_rhs else None
640-
return slice(left, right)
641-
642-
else:
643-
lhs_mask = (i8vals >= t1.ordinal) if use_lhs else True
644-
rhs_mask = (i8vals <= t2.ordinal) if use_rhs else True
619+
def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True):
620+
# TODO: Check for non-True use_lhs/use_rhs
621+
parsed, reso = parse_time_string(key, self.freq)
645622

646-
# try to find a the dates
647-
return (lhs_mask & rhs_mask).nonzero()[0]
623+
try:
624+
return self._partial_date_slice(reso, parsed, use_lhs, use_rhs)
625+
except KeyError:
626+
raise KeyError(key)
648627

649628
def _convert_tolerance(self, tolerance, target):
650629
tolerance = DatetimeIndexOpsMixin._convert_tolerance(self, tolerance, target)

0 commit comments

Comments
 (0)