|
6 | 6 |
|
7 | 7 | import numpy as np
|
8 | 8 |
|
9 |
| -from pandas._libs import NaT, iNaT, lib |
| 9 | +from pandas._libs import NaT, iNaT, join as libjoin, lib |
10 | 10 | from pandas._libs.algos import unique_deltas
|
| 11 | +from pandas._libs.tslibs import timezones |
11 | 12 | from pandas.compat.numpy import function as nv
|
12 | 13 | from pandas.errors import AbstractMethodError
|
13 | 14 | from pandas.util._decorators import Appender, cache_readonly, deprecate_kwarg
|
@@ -72,6 +73,32 @@ def method(self, other):
|
72 | 73 | return method
|
73 | 74 |
|
74 | 75 |
|
| 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 | + |
75 | 102 | class DatetimeIndexOpsMixin(ExtensionOpsMixin):
|
76 | 103 | """
|
77 | 104 | Common ops mixin to support a unified interface datetimelike Index.
|
@@ -208,32 +235,6 @@ def equals(self, other):
|
208 | 235 |
|
209 | 236 | return np.array_equal(self.asi8, other.asi8)
|
210 | 237 |
|
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 |
| - |
237 | 238 | def _ensure_localized(
|
238 | 239 | self, arg, ambiguous="raise", nonexistent="raise", from_utc=False
|
239 | 240 | ):
|
@@ -854,6 +855,75 @@ def _can_fast_union(self, other) -> bool:
|
854 | 855 | # this will raise
|
855 | 856 | return False
|
856 | 857 |
|
| 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 | + |
857 | 927 |
|
858 | 928 | def wrap_arithmetic_op(self, other, result):
|
859 | 929 | if result is NotImplemented:
|
|
0 commit comments