Skip to content

Commit 8e892d4

Browse files
jbrockmendeljreback
authored andcommitted
REF: share join methods for DTI/TDI (#30595)
1 parent f3642d2 commit 8e892d4

File tree

3 files changed

+98
-137
lines changed

3 files changed

+98
-137
lines changed

pandas/core/indexes/datetimelike.py

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
import numpy as np
88

9-
from pandas._libs import NaT, iNaT, lib
9+
from pandas._libs import NaT, iNaT, join as libjoin, lib
1010
from pandas._libs.algos import unique_deltas
11+
from pandas._libs.tslibs import timezones
1112
from pandas.compat.numpy import function as nv
1213
from pandas.errors import AbstractMethodError
1314
from pandas.util._decorators import Appender, cache_readonly
@@ -72,6 +73,32 @@ def method(self, other):
7273
return method
7374

7475

76+
def _join_i8_wrapper(joinf, with_indexers: bool = True):
77+
"""
78+
Create the join wrapper methods.
79+
"""
80+
81+
@staticmethod # type: ignore
82+
def wrapper(left, right):
83+
if isinstance(left, (np.ndarray, ABCIndex, ABCSeries, DatetimeLikeArrayMixin)):
84+
left = left.view("i8")
85+
if isinstance(right, (np.ndarray, ABCIndex, ABCSeries, DatetimeLikeArrayMixin)):
86+
right = right.view("i8")
87+
88+
results = joinf(left, right)
89+
if with_indexers:
90+
# dtype should be timedelta64[ns] for TimedeltaIndex
91+
# and datetime64[ns] for DatetimeIndex
92+
dtype = left.dtype.base
93+
94+
join_index, left_indexer, right_indexer = results
95+
join_index = join_index.view(dtype)
96+
return join_index, left_indexer, right_indexer
97+
return results
98+
99+
return wrapper
100+
101+
75102
class DatetimeIndexOpsMixin(ExtensionOpsMixin):
76103
"""
77104
Common ops mixin to support a unified interface datetimelike Index.
@@ -208,32 +235,6 @@ def equals(self, other):
208235

209236
return np.array_equal(self.asi8, other.asi8)
210237

211-
@staticmethod
212-
def _join_i8_wrapper(joinf, dtype, with_indexers=True):
213-
"""
214-
Create the join wrapper methods.
215-
"""
216-
from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin
217-
218-
@staticmethod
219-
def wrapper(left, right):
220-
if isinstance(
221-
left, (np.ndarray, ABCIndex, ABCSeries, DatetimeLikeArrayMixin)
222-
):
223-
left = left.view("i8")
224-
if isinstance(
225-
right, (np.ndarray, ABCIndex, ABCSeries, DatetimeLikeArrayMixin)
226-
):
227-
right = right.view("i8")
228-
results = joinf(left, right)
229-
if with_indexers:
230-
join_index, left_indexer, right_indexer = results
231-
join_index = join_index.view(dtype)
232-
return join_index, left_indexer, right_indexer
233-
return results
234-
235-
return wrapper
236-
237238
def _ensure_localized(
238239
self, arg, ambiguous="raise", nonexistent="raise", from_utc=False
239240
):
@@ -853,6 +854,75 @@ def _can_fast_union(self, other) -> bool:
853854
# this will raise
854855
return False
855856

857+
# --------------------------------------------------------------------
858+
# Join Methods
859+
_join_precedence = 10
860+
861+
_inner_indexer = _join_i8_wrapper(libjoin.inner_join_indexer)
862+
_outer_indexer = _join_i8_wrapper(libjoin.outer_join_indexer)
863+
_left_indexer = _join_i8_wrapper(libjoin.left_join_indexer)
864+
_left_indexer_unique = _join_i8_wrapper(
865+
libjoin.left_join_indexer_unique, with_indexers=False
866+
)
867+
868+
def join(
869+
self, other, how: str = "left", level=None, return_indexers=False, sort=False
870+
):
871+
"""
872+
See Index.join
873+
"""
874+
if self._is_convertible_to_index_for_join(other):
875+
try:
876+
other = type(self)(other)
877+
except (TypeError, ValueError):
878+
pass
879+
880+
this, other = self._maybe_utc_convert(other)
881+
return Index.join(
882+
this,
883+
other,
884+
how=how,
885+
level=level,
886+
return_indexers=return_indexers,
887+
sort=sort,
888+
)
889+
890+
def _maybe_utc_convert(self, other):
891+
this = self
892+
if not hasattr(self, "tz"):
893+
return this, other
894+
895+
if isinstance(other, type(self)):
896+
if self.tz is not None:
897+
if other.tz is None:
898+
raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
899+
elif other.tz is not None:
900+
raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
901+
902+
if not timezones.tz_compare(self.tz, other.tz):
903+
this = self.tz_convert("UTC")
904+
other = other.tz_convert("UTC")
905+
return this, other
906+
907+
@classmethod
908+
def _is_convertible_to_index_for_join(cls, other: Index) -> bool:
909+
"""
910+
return a boolean whether I can attempt conversion to a
911+
DatetimeIndex/TimedeltaIndex
912+
"""
913+
if isinstance(other, cls):
914+
return False
915+
elif len(other) > 0 and other.inferred_type not in (
916+
"floating",
917+
"mixed-integer",
918+
"integer",
919+
"integer-na",
920+
"mixed-integer-float",
921+
"mixed",
922+
):
923+
return True
924+
return False
925+
856926

857927
def wrap_arithmetic_op(self, other, result):
858928
if result is NotImplemented:

pandas/core/indexes/datetimes.py

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import numpy as np
66

77
from pandas._libs import NaT, Timestamp, index as libindex, lib, tslib as libts
8-
import pandas._libs.join as libjoin
98
from pandas._libs.tslibs import ccalendar, fields, parsing, timezones
109
from pandas.util._decorators import Appender, Substitution, cache_readonly
1110

@@ -32,7 +31,6 @@
3231
import pandas.core.common as com
3332
from pandas.core.indexes.base import Index, maybe_extract_name
3433
from pandas.core.indexes.datetimelike import (
35-
DatetimeIndexOpsMixin,
3634
DatetimelikeDelegateMixin,
3735
DatetimeTimedeltaMixin,
3836
)
@@ -195,17 +193,6 @@ class DatetimeIndex(DatetimeTimedeltaMixin, DatetimeDelegateMixin):
195193
"""
196194

197195
_typ = "datetimeindex"
198-
_join_precedence = 10
199-
200-
def _join_i8_wrapper(joinf, **kwargs):
201-
return DatetimeIndexOpsMixin._join_i8_wrapper(joinf, dtype="M8[ns]", **kwargs)
202-
203-
_inner_indexer = _join_i8_wrapper(libjoin.inner_join_indexer)
204-
_outer_indexer = _join_i8_wrapper(libjoin.outer_join_indexer)
205-
_left_indexer = _join_i8_wrapper(libjoin.left_join_indexer)
206-
_left_indexer_unique = _join_i8_wrapper(
207-
libjoin.left_join_indexer_unique, with_indexers=False
208-
)
209196

210197
_engine_type = libindex.DatetimeEngine
211198
_supports_partial_string_indexing = True
@@ -645,54 +632,6 @@ def snap(self, freq="S"):
645632
# we know it conforms; skip check
646633
return DatetimeIndex._simple_new(snapped, name=self.name, tz=self.tz, freq=freq)
647634

648-
def join(
649-
self, other, how: str = "left", level=None, return_indexers=False, sort=False
650-
):
651-
"""
652-
See Index.join
653-
"""
654-
if (
655-
not isinstance(other, DatetimeIndex)
656-
and len(other) > 0
657-
and other.inferred_type
658-
not in (
659-
"floating",
660-
"integer",
661-
"integer-na",
662-
"mixed-integer",
663-
"mixed-integer-float",
664-
"mixed",
665-
)
666-
):
667-
try:
668-
other = DatetimeIndex(other)
669-
except (TypeError, ValueError):
670-
pass
671-
672-
this, other = self._maybe_utc_convert(other)
673-
return Index.join(
674-
this,
675-
other,
676-
how=how,
677-
level=level,
678-
return_indexers=return_indexers,
679-
sort=sort,
680-
)
681-
682-
def _maybe_utc_convert(self, other):
683-
this = self
684-
if isinstance(other, DatetimeIndex):
685-
if self.tz is not None:
686-
if other.tz is None:
687-
raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
688-
elif other.tz is not None:
689-
raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
690-
691-
if not timezones.tz_compare(self.tz, other.tz):
692-
this = self.tz_convert("UTC")
693-
other = other.tz_convert("UTC")
694-
return this, other
695-
696635
def _wrap_joined_index(self, joined, other):
697636
name = get_op_result_name(self, other)
698637
if (

pandas/core/indexes/timedeltas.py

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import numpy as np
55

6-
from pandas._libs import NaT, Timedelta, index as libindex, join as libjoin, lib
6+
from pandas._libs import NaT, Timedelta, index as libindex, lib
77
from pandas.util._decorators import Appender, Substitution
88

99
from pandas.core.dtypes.common import (
@@ -121,17 +121,6 @@ class TimedeltaIndex(
121121
"""
122122

123123
_typ = "timedeltaindex"
124-
_join_precedence = 10
125-
126-
def _join_i8_wrapper(joinf, **kwargs):
127-
return DatetimeIndexOpsMixin._join_i8_wrapper(joinf, dtype="m8[ns]", **kwargs)
128-
129-
_inner_indexer = _join_i8_wrapper(libjoin.inner_join_indexer)
130-
_outer_indexer = _join_i8_wrapper(libjoin.outer_join_indexer)
131-
_left_indexer = _join_i8_wrapper(libjoin.left_join_indexer)
132-
_left_indexer_unique = _join_i8_wrapper(
133-
libjoin.left_join_indexer_unique, with_indexers=False
134-
)
135124

136125
_engine_type = libindex.TimedeltaEngine
137126

@@ -294,25 +283,6 @@ def _union(self, other, sort):
294283
result._set_freq("infer")
295284
return result
296285

297-
def join(self, other, how="left", level=None, return_indexers=False, sort=False):
298-
"""
299-
See Index.join
300-
"""
301-
if _is_convertible_to_index(other):
302-
try:
303-
other = TimedeltaIndex(other)
304-
except (TypeError, ValueError):
305-
pass
306-
307-
return Index.join(
308-
self,
309-
other,
310-
how=how,
311-
level=level,
312-
return_indexers=return_indexers,
313-
sort=sort,
314-
)
315-
316286
def _wrap_joined_index(self, joined, other):
317287
name = get_op_result_name(self, other)
318288
if (
@@ -569,24 +539,6 @@ def delete(self, loc):
569539
TimedeltaIndex._add_datetimelike_methods()
570540

571541

572-
def _is_convertible_to_index(other) -> bool:
573-
"""
574-
return a boolean whether I can attempt conversion to a TimedeltaIndex
575-
"""
576-
if isinstance(other, TimedeltaIndex):
577-
return True
578-
elif len(other) > 0 and other.inferred_type not in (
579-
"floating",
580-
"mixed-integer",
581-
"integer",
582-
"integer-na",
583-
"mixed-integer-float",
584-
"mixed",
585-
):
586-
return True
587-
return False
588-
589-
590542
def timedelta_range(
591543
start=None, end=None, periods=None, freq=None, name=None, closed=None
592544
) -> TimedeltaIndex:

0 commit comments

Comments
 (0)