From 2a7f8d0bc4ff7180fa7af3cbf5bdc72787475e5a Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sun, 5 Jan 2020 17:03:05 -0800 Subject: [PATCH] REF: cosmetic differences between DTA/TDA/PA comparison methods --- pandas/core/arrays/datetimes.py | 19 ++++++++++++++----- pandas/core/arrays/period.py | 23 +++++++++++++---------- pandas/core/arrays/timedeltas.py | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index cc54fb5e5af13..6aa910c0f6ab8 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -26,12 +26,14 @@ _INT64_DTYPE, _NS_DTYPE, is_categorical_dtype, + is_datetime64_any_dtype, is_datetime64_dtype, is_datetime64_ns_dtype, is_datetime64tz_dtype, is_dtype_equal, is_extension_array_dtype, is_float_dtype, + is_list_like, is_object_dtype, is_period_dtype, is_string_dtype, @@ -148,17 +150,22 @@ def wrapper(self, other): # string that cannot be parsed to Timestamp return invalid_comparison(self, other, op) - if isinstance(other, (datetime, np.datetime64)): - other = Timestamp(other) + if isinstance(other, self._recognized_scalars) or other is NaT: + other = self._scalar_type(other) self._assert_tzawareness_compat(other) - result = op(self.asi8, other.value) + other_i8 = other.value + + result = op(self.view("i8"), other_i8) if isna(other): result.fill(nat_result) - elif lib.is_scalar(other) or np.ndim(other) == 0: + + elif not is_list_like(other): return invalid_comparison(self, other, op) + elif len(other) != len(self): raise ValueError("Lengths must match") + else: if isinstance(other, list): other = np.array(other) @@ -178,7 +185,7 @@ def wrapper(self, other): ) o_mask = isna(other) - elif not (is_datetime64_dtype(other) or is_datetime64tz_dtype(other)): + elif not cls._is_recognized_dtype(other.dtype): # e.g. is_timedelta64_dtype(other) return invalid_comparison(self, other, op) @@ -239,6 +246,8 @@ class DatetimeArray(dtl.DatetimeLikeArrayMixin, dtl.TimelikeOps, dtl.DatelikeOps _typ = "datetimearray" _scalar_type = Timestamp + _recognized_scalars = (datetime, np.datetime64) + _is_recognized_dtype = is_datetime64_any_dtype # define my properties & methods for delegation _bool_ops = [ diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 6b9c7f4e1eb38..7aa6f8e8aa090 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -75,9 +75,6 @@ def _period_array_cmp(cls, op): def wrapper(self, other): ordinal_op = getattr(self.asi8, opname) - if is_list_like(other) and len(other) != len(self): - raise ValueError("Lengths must match") - if isinstance(other, str): try: other = self._scalar_from_string(other) @@ -90,18 +87,22 @@ def wrapper(self, other): other = Period(other, freq=self.freq) result = ordinal_op(other.ordinal) - if isinstance(other, Period): + if isinstance(other, self._recognized_scalars) or other is NaT: + other = self._scalar_type(other) self._check_compatible_with(other) - result = ordinal_op(other.ordinal) + other_i8 = self._unbox_scalar(other) - elif other is NaT: - result = np.empty(len(self.asi8), dtype=bool) - result.fill(nat_result) + result = op(self.view("i8"), other_i8) + if isna(other): + result.fill(nat_result) elif not is_list_like(other): return invalid_comparison(self, other, op) + elif len(other) != len(self): + raise ValueError("Lengths must match") + else: if isinstance(other, list): # TODO: could use pd.Index to do inference? @@ -117,7 +118,7 @@ def wrapper(self, other): ) o_mask = isna(other) - elif not is_period_dtype(other): + elif not cls._is_recognized_dtype(other.dtype): # e.g. is_timedelta64_dtype(other) return invalid_comparison(self, other, op) @@ -126,7 +127,7 @@ def wrapper(self, other): self._check_compatible_with(other) - result = ordinal_op(other.asi8) + result = op(self.view("i8"), other.view("i8")) o_mask = other._isnan if o_mask.any(): @@ -195,6 +196,8 @@ class PeriodArray(dtl.DatetimeLikeArrayMixin, dtl.DatelikeOps): __array_priority__ = 1000 _typ = "periodarray" # ABCPeriodArray _scalar_type = Period + _recognized_scalars = (Period,) + _is_recognized_dtype = is_period_dtype # Names others delegate to us _other_ops: List[str] = [] diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 1874517f0f2e4..953a242380311 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -89,10 +89,13 @@ def wrapper(self, other): # failed to parse as timedelta return invalid_comparison(self, other, op) - if _is_convertible_to_td(other) or other is NaT: - other = Timedelta(other) + if isinstance(other, self._recognized_scalars) or other is NaT: + other = self._scalar_type(other) + self._check_compatible_with(other) + + other_i8 = self._unbox_scalar(other) - result = op(self.view("i8"), other.value) + result = op(self.view("i8"), other_i8) if isna(other): result.fill(nat_result) @@ -116,13 +119,15 @@ def wrapper(self, other): ) o_mask = isna(other) - elif not is_timedelta64_dtype(other): + elif not cls._is_recognized_dtype(other.dtype): # e.g. other is datetimearray return invalid_comparison(self, other, op) else: other = type(self)._from_sequence(other) + self._check_compatible_with(other) + result = op(self.view("i8"), other.view("i8")) o_mask = other._isnan @@ -172,6 +177,9 @@ class TimedeltaArray(dtl.DatetimeLikeArrayMixin, dtl.TimelikeOps): _typ = "timedeltaarray" _scalar_type = Timedelta + _recognized_scalars = (timedelta, np.timedelta64, Tick) + _is_recognized_dtype = is_timedelta64_dtype + __array_priority__ = 1000 # define my properties & methods for delegation _other_ops: List[str] = []