Skip to content

Commit 7384108

Browse files
jbrockmendelhweecat
authored andcommitted
REF: share join methods for DTI/TDI (pandas-dev#30595)
1 parent f12dd39 commit 7384108

File tree

3 files changed

+98
-137
lines changed

3 files changed

+98
-137
lines changed

pandas/core/indexes/datetimelike.py

+97-27
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, deprecate_kwarg
@@ -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
):
@@ -854,6 +855,75 @@ def _can_fast_union(self, other) -> bool:
854855
# this will raise
855856
return False
856857

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

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

pandas/core/indexes/datetimes.py

-61
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

+1-49
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)