From 65dd8b3cfe412b4d11c738084145f4ed416ad14f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 27 Dec 2017 21:54:00 -0800 Subject: [PATCH 1/8] remove unused code, impossible cases. add todo comments --- pandas/core/ops.py | 97 ++++++++++++---------------------------------- 1 file changed, 24 insertions(+), 73 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index ac9ca03c13973..e7fb8d9a82180 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -156,6 +156,7 @@ def add_methods(cls, new_methods, force, select, exclude): raise TypeError("May only pass either select or exclude") if select: + # TODO: This is not hit in coverage report. Is it still needed? select = set(select) methods = {} for key, method in new_methods.items(): @@ -164,6 +165,7 @@ def add_methods(cls, new_methods, force, select, exclude): new_methods = methods if exclude: + # TODO: This is not hit in coverage report. Is it still needed? for k in exclude: new_methods.pop(k, None) @@ -360,8 +362,8 @@ class _TimeOp(_Op): def __init__(self, left, right, name, na_op): super(_TimeOp, self).__init__(left, right, name, na_op) - lvalues = self._convert_to_array(left, name=name) - rvalues = self._convert_to_array(right, name=name, other=lvalues) + lvalues = self._convert_to_array(left) + rvalues = self._convert_to_array(right, other=lvalues) # left self.is_offset_lhs = is_offsetlike(left) @@ -440,44 +442,11 @@ def _validate_timedelta(self, name): 'of a series/ndarray of type datetime64[ns] ' 'or a timedelta') - def _validate_offset(self, name): - # assumes self.is_offset_lhs - - if self.is_timedelta_rhs: - # 2 timedeltas - if name not in ('__div__', '__rdiv__', '__truediv__', - '__rtruediv__', '__add__', '__radd__', '__sub__', - '__rsub__'): - raise TypeError("can only operate on a timedeltas for addition" - ", subtraction, and division, but the operator" - " [{name}] was passed".format(name=name)) - - elif self.is_datetime_rhs: - if name not in ('__add__', '__radd__'): - raise TypeError("can only operate on a timedelta/DateOffset " - "and a datetime for addition, but the operator" - " [{name}] was passed".format(name=name)) - - else: - raise TypeError('cannot operate on a series without a rhs ' - 'of a series/ndarray of type datetime64[ns] ' - 'or a timedelta') - def _validate(self, lvalues, rvalues, name): if self.is_datetime_lhs: return self._validate_datetime(lvalues, rvalues, name) elif self.is_timedelta_lhs: return self._validate_timedelta(name) - elif self.is_offset_lhs: - return self._validate_offset(name) - - if ((self.is_integer_lhs or self.is_floating_lhs) and - self.is_timedelta_rhs): - self._check_timedelta_with_numeric(name) - else: - raise TypeError('cannot operate on a series without a rhs ' - 'of a series/ndarray of type datetime64[ns] ' - 'or a timedelta') def _check_timedelta_with_numeric(self, name): if name not in ('__div__', '__truediv__', '__mul__', '__rmul__'): @@ -486,9 +455,10 @@ def _check_timedelta_with_numeric(self, name): "multiplication, but the operator [{name}] " "was passed".format(name=name)) - def _convert_to_array(self, values, name=None, other=None): + def _convert_to_array(self, values, other=None): """converts values to ndarray""" from pandas.core.tools.timedeltas import to_timedelta + name = self.name ovalues = values supplied_dtype = None @@ -517,8 +487,7 @@ def _convert_to_array(self, values, name=None, other=None): elif isinstance(values, pd.DatetimeIndex): values = values.to_series() # datetime with tz - elif (isinstance(ovalues, datetime.datetime) and - hasattr(ovalues, 'tzinfo')): + elif isinstance(ovalues, datetime.datetime): values = pd.DatetimeIndex(values) # datetime array with tz elif is_datetimetz(values): @@ -526,6 +495,8 @@ def _convert_to_array(self, values, name=None, other=None): values = values._values elif not (isinstance(values, (np.ndarray, ABCSeries)) and is_datetime64_dtype(values)): + # TODO: This is not hit in tests. What case is it intended + # to catch? values = libts.array_to_datetime(values) elif inferred_type in ('timedelta', 'timedelta64'): # have a timedelta, convert to to ns here @@ -566,8 +537,6 @@ def _convert_for_datetime(self, lvalues, rvalues): if self.is_datetime_lhs and self.is_datetime_rhs: if self.name in ('__sub__', '__rsub__'): self.dtype = 'timedelta64[ns]' - else: - self.dtype = 'datetime64[ns]' elif self.is_datetime64tz_lhs: self.dtype = lvalues.dtype elif self.is_datetime64tz_rhs: @@ -590,9 +559,7 @@ def _offset(lvalues, rvalues): self.na_op = lambda x, y: getattr(x, self.name)(y) return lvalues, rvalues - if self.is_offset_lhs: - lvalues, rvalues = _offset(lvalues, rvalues) - elif self.is_offset_rhs: + if self.is_offset_rhs: rvalues, lvalues = _offset(rvalues, lvalues) else: @@ -611,8 +578,6 @@ def _offset(lvalues, rvalues): self.dtype = 'timedelta64[ns]' # convert Tick DateOffset to underlying delta - if self.is_offset_lhs: - lvalues = to_timedelta(lvalues, box=False) if self.is_offset_rhs: rvalues = to_timedelta(rvalues, box=False) @@ -721,6 +686,10 @@ def safe_na_op(lvalues, rvalues): return na_op(lvalues, rvalues) except Exception: if isinstance(rvalues, ABCSeries): + # TODO: This case is not hit in tests. For it to be hit would + # require `right` to be an object such that right.values + # is a Series, which is likely an indication that a screwup + # has occurred somewhere. if is_object_dtype(rvalues): # if dtype is object, try elementwise op return libalgos.arrmap_object(rvalues, @@ -731,7 +700,9 @@ def safe_na_op(lvalues, rvalues): lambda x: op(x, rvalues)) raise - def wrapper(left, right, name=name, na_op=na_op): + def wrapper(left, right, na_op=na_op): + # TODO: `na_op` does not belong in Series.__{op}__ signature. But this + # is needed here for closure/namespace purposes ATM. if isinstance(right, ABCDataFrame): return NotImplemented @@ -740,32 +711,27 @@ def wrapper(left, right, name=name, na_op=na_op): converted = _Op.get_op(left, right, name, na_op) - left, right = converted.left, converted.right lvalues, rvalues = converted.lvalues, converted.rvalues dtype = converted.dtype wrap_results = converted.wrap_results na_op = converted.na_op if isinstance(rvalues, ABCSeries): - name = _maybe_match_name(left, rvalues) + res_name = _maybe_match_name(left, rvalues) lvalues = getattr(lvalues, 'values', lvalues) rvalues = getattr(rvalues, 'values', rvalues) # _Op aligns left and right else: - name = left.name + res_name = left.name if (hasattr(lvalues, 'values') and not isinstance(lvalues, pd.DatetimeIndex)): lvalues = lvalues.values result = wrap_results(safe_na_op(lvalues, rvalues)) - return construct_result( - left, - result, - index=left.index, - name=name, - dtype=dtype, - ) + return construct_result(left, result, + index=left.index, name=res_name, dtype=dtype) + wrapper.__name__ = name return wrapper @@ -849,6 +815,8 @@ def wrapper(self, other, axis=None): # Validate the axis parameter if axis is not None: self._get_axis_number(axis) + # TODO: should this be `axis = self._get_axis_number(axis)`? + # This is not hit in tests. if isinstance(other, ABCSeries): name = _maybe_match_name(self, other) @@ -1371,23 +1339,6 @@ def f(self, other): def _arith_method_PANEL(op, name, str_rep=None, fill_zeros=None, default_axis=None, **eval_kwargs): - # copied from Series na_op above, but without unnecessary branch for - # non-scalar - def na_op(x, y): - import pandas.core.computation.expressions as expressions - - try: - result = expressions.evaluate(op, str_rep, x, y, **eval_kwargs) - except TypeError: - - # TODO: might need to find_common_type here? - result = np.empty(len(x), dtype=x.dtype) - mask = notna(x) - result[mask] = op(x[mask], y) - result, changed = maybe_upcast_putmask(result, ~mask, np.nan) - - result = missing.fill_zeros(result, x, y, name, fill_zeros) - return result # work only for scalars def f(self, other): From f65eab466eae0068d37ff31d8bbb7fcaa62edc24 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 28 Dec 2017 20:35:34 -0800 Subject: [PATCH 2/8] add tests for op names --- pandas/tests/series/test_api.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index a2838f803421c..13bf4bdc5752b 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -752,3 +752,21 @@ def test_dt_accessor_api_for_categorical(self): AttributeError, "Can only use .dt accessor with datetimelike"): invalid.dt assert not hasattr(invalid, 'str') + + +@pytest.mark.parametrize('opname', [ + '__add__', '__radd__', + '__sub__', '__rsub__', + '__mul__', '__rmul__', + '__div__', '__rdiv__', + '__truediv__', '__rtruediv__', + '__floordiv__', '__rfloordiv__', + '__mod__', '__rmod__', '__divmod__', + '__pow__', '__rpow__' + '__eq__', '__ne__', '__ge__', '__gt__', '__le__', '__lt__', + '__float__', '__int__', '__long__', + '__and__', '__rand__','__or__', '__ror__', '__xor__', '__rxor__', + 'argmax', 'argmin']) +def test_generated_op_names(opname): + method = getattr(pd.Series, opname) + assert method.__name__ == opname From 5a5d34a90447bea399b641fcecf3229dfd54856c Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 28 Dec 2017 22:09:01 -0800 Subject: [PATCH 3/8] trim the set of tested funcnames --- pandas/tests/series/test_api.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index 13bf4bdc5752b..11c30b37cf87e 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -758,15 +758,11 @@ def test_dt_accessor_api_for_categorical(self): '__add__', '__radd__', '__sub__', '__rsub__', '__mul__', '__rmul__', - '__div__', '__rdiv__', + #'__div__', '__rdiv__', '__truediv__', '__rtruediv__', '__floordiv__', '__rfloordiv__', '__mod__', '__rmod__', '__divmod__', - '__pow__', '__rpow__' - '__eq__', '__ne__', '__ge__', '__gt__', '__le__', '__lt__', - '__float__', '__int__', '__long__', - '__and__', '__rand__','__or__', '__ror__', '__xor__', '__rxor__', - 'argmax', 'argmin']) + '__pow__', '__rpow__']) def test_generated_op_names(opname): method = getattr(pd.Series, opname) assert method.__name__ == opname From 38d6c08d2e7f54a8767084d62b1c91f66f981195 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 28 Dec 2017 22:09:13 -0800 Subject: [PATCH 4/8] troubleshooting, add tests that belong elsewhere --- pandas/core/ops.py | 26 ++++++++---- pandas/tests/series/test_operators.py | 57 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index bea9c4c94f1e3..a4bf79929a4da 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -356,6 +356,9 @@ class _TimeOp(_Op): """ Wrapper around Series datetime/time/timedelta arithmetic operations. Generally, you should use classmethod ``_Op.get_op`` as an entry point. + + This is only reached in cases where either `self.is_datetime_lhs` + or `self.is_timedelta_lhs`. """ fill_value = iNaT @@ -374,6 +377,7 @@ def __init__(self, left, right, name, na_op): self.is_datetime64tz_lhs) self.is_integer_lhs = left.dtype.kind in ['i', 'u'] self.is_floating_lhs = left.dtype.kind == 'f' + #assert left.dtype.kind not in ['i', 'u', 'f'], left # right self.is_offset_rhs = is_offsetlike(right) @@ -446,7 +450,10 @@ def _validate(self, lvalues, rvalues, name): if self.is_datetime_lhs: return self._validate_datetime(lvalues, rvalues, name) elif self.is_timedelta_lhs: + # The only other option is self.is_timedelta_lhs return self._validate_timedelta(name) + else: + assert False def _check_timedelta_with_numeric(self, name): if name not in ('__div__', '__truediv__', '__mul__', '__rmul__'): @@ -540,6 +547,7 @@ def _convert_for_datetime(self, lvalues, rvalues): # datetime subtraction means timedelta if self.is_datetime_lhs and self.is_datetime_rhs: + # assert self.name in ('__sub__', '__rsub__') if self.name in ('__sub__', '__rsub__'): self.dtype = 'timedelta64[ns]' elif self.is_datetime64tz_lhs: @@ -685,7 +693,10 @@ def na_op(x, y): result = missing.fill_zeros(result, x, y, name, fill_zeros) return result - def safe_na_op(lvalues, rvalues): + def safe_na_op(lvalues, rvalues, na_op): + # We pass na_op explicitly here for namespace/closure reasons; + # the alternative is to make it an argument to `wrapper`, which + # would mess with the signatures of Series methods. try: with np.errstate(all='ignore'): return na_op(lvalues, rvalues) @@ -693,8 +704,7 @@ def safe_na_op(lvalues, rvalues): if isinstance(rvalues, ABCSeries): # TODO: This case is not hit in tests. For it to be hit would # require `right` to be an object such that right.values - # is a Series, which is likely an indication that a screwup - # has occurred somewhere. + # is a Series. This should probably be removed. if is_object_dtype(rvalues): # if dtype is object, try elementwise op return libalgos.arrmap_object(rvalues, @@ -705,9 +715,7 @@ def safe_na_op(lvalues, rvalues): lambda x: op(x, rvalues)) raise - def wrapper(left, right, na_op=na_op): - # TODO: `na_op` does not belong in Series.__{op}__ signature. But this - # is needed here for closure/namespace purposes ATM. + def wrapper(left, right):#, na_op=na_op): if isinstance(right, ABCDataFrame): return NotImplemented @@ -719,7 +727,7 @@ def wrapper(left, right, na_op=na_op): lvalues, rvalues = converted.lvalues, converted.rvalues dtype = converted.dtype wrap_results = converted.wrap_results - na_op = converted.na_op + #na_op = converted.na_op if isinstance(rvalues, ABCSeries): res_name = _maybe_match_name(left, rvalues) @@ -735,11 +743,13 @@ def wrapper(left, right, na_op=na_op): not isinstance(lvalues, pd.DatetimeIndex)): lvalues = lvalues.values - result = wrap_results(safe_na_op(lvalues, rvalues)) + result = wrap_results(safe_na_op(lvalues, rvalues, converted.na_op)) return construct_result(left, result, index=left.index, name=res_name, dtype=dtype) wrapper.__name__ = name + if hasattr(operator, name) and callable(getattr(operator, name)): + wrapper.__doc__ = getattr(operator, name).__doc__ return wrapper diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 433e3cf440cbd..139a2710afffe 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -1032,6 +1032,63 @@ def test_frame_sub_datetime64_not_ns(self): Timedelta(days=2)]) tm.assert_frame_equal(res, expected) + def test_scalar_op_retains_name(self): + # GH#18964 + ser = pd.Series([pd.Timedelta(days=1, hours=2)], name='NCC-1701D') + + dt = pd.Timestamp(1968, 7, 8) + res = ser + dt + assert res.name == ser.name + res = dt - ser + assert res.name == ser.name + + @pytest.mark.xfail(reason='GH#18963 DatetimeIndex+Series[datetime64] ' + 'returns DatetimeIndex with wrong name.') + def test_dti_op_retains_name(self): + # GH#18964 + ser = pd.Series([pd.Timedelta(days=1, hours=2)], name='NCC-1701D') + dti = pd.DatetimeIndex([pd.Timestamp(1968, 7, 8)], name='Serenity') + + expected = pd.Series([ser[0] + dti[0]], name=None) + res = ser + dti + tm.assert_series_equal(res, expected) + res = dti + ser # GH#18963 wrong type and name + tm.assert_series_equal(res, expected) + + expected = pd.Series([dti[0] - ser[0]], name=None) + res = dti - ser # GH#18963 wrong type and name + tm.assert_series_equal(res, expected) + res = -ser + dti + tm.assert_series_equal(res, expected) + + res = ser + dti.rename(name=ser.name) + tm.assert_series_equal(res, expected, check_names=False) + assert res.name == ser.name # TODO: Series.rename(name=...) fails + + @pytest.mark.xfail(reason='GH#18824 Series[timedelta64]+TimedeltaIndex' + 'gets name from TimedeltaIndex;' + 'reverse op raises ValueError') + def test_tdi_op_retains_name(self): + # GH#18964 + ser = pd.Series([pd.Timedelta(days=1, hours=2)], name='NCC-1701D') + tdi = pd.TimedeltaIndex(ser, name='Heart Of Gold') + + expected = pd.Series([ser[0] + tdi[0]], name=None) + res = ser + tdi # right type, wrong name + tm.assert_series_equal(res, expected) + res = tdi + ser + tm.assert_series_equal(res, expected) + + expected = pd.Series([ser[0] - tdi[0]], name=None) + res = ser - tdi # right type, wrong name + tm.assert_series_equal(res, expected) + res = -tdi + ser + tm.assert_series_equal(res, expected) + + res = ser + tdi.rename(name=ser.name) + tm.assert_series_equal(res, expected, check_names=False) + assert res.name == ser.name # TODO: Series.rename(name=...) fails + def test_operators_datetimelike(self): def run_ops(ops, get_ser, test_ser): From c712bfc1413dd2269ffaff2befef13c376226b85 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 28 Dec 2017 22:12:48 -0800 Subject: [PATCH 5/8] fix Series op signatures --- pandas/core/ops.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index a4bf79929a4da..6e0c2bde5042f 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -715,7 +715,7 @@ def safe_na_op(lvalues, rvalues, na_op): lambda x: op(x, rvalues)) raise - def wrapper(left, right):#, na_op=na_op): + def wrapper(left, right): if isinstance(right, ABCDataFrame): return NotImplemented @@ -727,7 +727,6 @@ def wrapper(left, right):#, na_op=na_op): lvalues, rvalues = converted.lvalues, converted.rvalues dtype = converted.dtype wrap_results = converted.wrap_results - #na_op = converted.na_op if isinstance(rvalues, ABCSeries): res_name = _maybe_match_name(left, rvalues) From 4a2a021f5fed0af6625dd6ce1e70d4609843cc38 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 28 Dec 2017 22:22:30 -0800 Subject: [PATCH 6/8] cleanup commented-out bits from troubleshooting --- pandas/core/ops.py | 7 ++----- pandas/tests/series/test_api.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 6e0c2bde5042f..1b596c9b7659b 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -369,15 +369,12 @@ def __init__(self, left, right, name, na_op): rvalues = self._convert_to_array(right, other=lvalues) # left - self.is_offset_lhs = is_offsetlike(left) self.is_timedelta_lhs = is_timedelta64_dtype(lvalues) self.is_datetime64_lhs = is_datetime64_dtype(lvalues) self.is_datetime64tz_lhs = is_datetime64tz_dtype(lvalues) self.is_datetime_lhs = (self.is_datetime64_lhs or self.is_datetime64tz_lhs) - self.is_integer_lhs = left.dtype.kind in ['i', 'u'] - self.is_floating_lhs = left.dtype.kind == 'f' - #assert left.dtype.kind not in ['i', 'u', 'f'], left + assert left.dtype.kind not in ['i', 'u', 'f'], left # right self.is_offset_rhs = is_offsetlike(right) @@ -601,7 +598,7 @@ def _offset(lvalues, rvalues): # time delta division -> unit less # integer gets converted to timedelta in np < 1.6 if ((self.is_timedelta_lhs and self.is_timedelta_rhs) and - not self.is_integer_rhs and not self.is_integer_lhs and + not self.is_integer_rhs and self.name in ('__div__', '__truediv__')): self.dtype = 'float64' self.fill_value = np.nan diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index 11c30b37cf87e..67bd3913df3e3 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -758,7 +758,7 @@ def test_dt_accessor_api_for_categorical(self): '__add__', '__radd__', '__sub__', '__rsub__', '__mul__', '__rmul__', - #'__div__', '__rdiv__', + # '__div__', '__rdiv__', # TODO: Is this different in py2 vs py3? '__truediv__', '__rtruediv__', '__floordiv__', '__rfloordiv__', '__mod__', '__rmod__', '__divmod__', From e53b7fa9c2ee4443389e09e9f40e05e7022a1135 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 29 Dec 2017 10:36:23 -0800 Subject: [PATCH 7/8] cleanup leftover troubleshooting relics --- pandas/core/ops.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 1b596c9b7659b..3cee089a4f7a6 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -446,11 +446,9 @@ def _validate_timedelta(self, name): def _validate(self, lvalues, rvalues, name): if self.is_datetime_lhs: return self._validate_datetime(lvalues, rvalues, name) - elif self.is_timedelta_lhs: + else: # The only other option is self.is_timedelta_lhs return self._validate_timedelta(name) - else: - assert False def _check_timedelta_with_numeric(self, name): if name not in ('__div__', '__truediv__', '__mul__', '__rmul__'): @@ -544,9 +542,8 @@ def _convert_for_datetime(self, lvalues, rvalues): # datetime subtraction means timedelta if self.is_datetime_lhs and self.is_datetime_rhs: - # assert self.name in ('__sub__', '__rsub__') - if self.name in ('__sub__', '__rsub__'): - self.dtype = 'timedelta64[ns]' + assert self.name in ('__sub__', '__rsub__') + self.dtype = 'timedelta64[ns]' elif self.is_datetime64tz_lhs: self.dtype = lvalues.dtype elif self.is_datetime64tz_rhs: From fdf461edb6a62f74dd20e499e545afbccd5cdd4a Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 31 Dec 2017 09:54:09 -0800 Subject: [PATCH 8/8] requested edits --- pandas/core/ops.py | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 8daf05d0d538d..70c1b93122609 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -357,8 +357,8 @@ class _TimeOp(_Op): Wrapper around Series datetime/time/timedelta arithmetic operations. Generally, you should use classmethod ``_Op.get_op`` as an entry point. - This is only reached in cases where either `self.is_datetime_lhs` - or `self.is_timedelta_lhs`. + This is only reached in cases where either self.is_datetime_lhs + or self.is_timedelta_lhs. """ fill_value = iNaT @@ -375,6 +375,7 @@ def __init__(self, left, right, name, na_op): self.is_datetime_lhs = (self.is_datetime64_lhs or self.is_datetime64tz_lhs) assert left.dtype.kind not in ['i', 'u', 'f'], left + assert self.is_timedelta_lhs or self.is_datetime_lhs # right self.is_offset_rhs = is_offsetlike(right) @@ -488,18 +489,13 @@ def _convert_to_array(self, values, other=None): # a datelike elif isinstance(values, pd.DatetimeIndex): values = values.to_series() - # datetime with tz - elif isinstance(ovalues, datetime.datetime): + elif isinstance(ovalues, (datetime.datetime, np.datetime64)): + # original input was scalar datetimelike values = pd.DatetimeIndex(values) # datetime array with tz elif is_datetimetz(values): if isinstance(values, ABCSeries): values = values._values - elif not (isinstance(values, (np.ndarray, ABCSeries)) and - is_datetime64_dtype(values)): - # TODO: This is not hit in tests. What case is it intended - # to catch? - values = libts.array_to_datetime(values) elif (is_datetime64_dtype(values) and not is_datetime64_ns_dtype(values)): # GH#7996 e.g. np.datetime64('2013-01-01') is datetime64[D] @@ -697,18 +693,9 @@ def safe_na_op(lvalues, rvalues, na_op): with np.errstate(all='ignore'): return na_op(lvalues, rvalues) except Exception: - if isinstance(rvalues, ABCSeries): - # TODO: This case is not hit in tests. For it to be hit would - # require `right` to be an object such that right.values - # is a Series. This should probably be removed. - if is_object_dtype(rvalues): - # if dtype is object, try elementwise op - return libalgos.arrmap_object(rvalues, - lambda x: op(lvalues, x)) - else: - if is_object_dtype(lvalues): - return libalgos.arrmap_object(lvalues, - lambda x: op(x, rvalues)) + if is_object_dtype(lvalues): + return libalgos.arrmap_object(lvalues, + lambda x: op(x, rvalues)) raise def wrapper(left, right): @@ -743,8 +730,6 @@ def wrapper(left, right): index=left.index, name=res_name, dtype=dtype) wrapper.__name__ = name - if hasattr(operator, name) and callable(getattr(operator, name)): - wrapper.__doc__ = getattr(operator, name).__doc__ return wrapper @@ -827,9 +812,7 @@ def na_op(x, y): def wrapper(self, other, axis=None): # Validate the axis parameter if axis is not None: - self._get_axis_number(axis) - # TODO: should this be `axis = self._get_axis_number(axis)`? - # This is not hit in tests. + axis = self._get_axis_number(axis) if isinstance(other, ABCSeries): name = _maybe_match_name(self, other)