diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 66b6923ef13e4..777cb42080f98 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -642,6 +642,7 @@ Timedelta - Bug in :class:`Series` with numeric dtype when adding or subtracting an an array or ``Series`` with ``timedelta64`` dtype (:issue:`22390`) - Bug in :class:`Index` with numeric dtype when multiplying or dividing an array with dtype ``timedelta64`` (:issue:`22390`) - Bug in :class:`TimedeltaIndex` incorrectly allowing indexing with ``Timestamp`` object (:issue:`20464`) +- Fixed bug where subtracting :class:`Timedelta` from an object-dtyped array would raise ``TypeError`` (:issue:`21980`) - - diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 9b13ef5982396..9c8be1901d1dc 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -541,10 +541,12 @@ def _binary_op_method_timedeltalike(op, name): elif hasattr(other, 'dtype'): # nd-array like - if other.dtype.kind not in ['m', 'M']: - # raise rathering than letting numpy return wrong answer + if other.dtype.kind in ['m', 'M']: + return op(self.to_timedelta64(), other) + elif other.dtype.kind == 'O': + return np.array([op(self, x) for x in other]) + else: return NotImplemented - return op(self.to_timedelta64(), other) elif not _validate_ops_compat(other): return NotImplemented diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index 9636c92ec22d5..fce1ef29235cc 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -200,6 +200,57 @@ def test_td_rsub_numeric_raises(self): with pytest.raises(TypeError): 2.0 - td + def test_td_sub_timedeltalike_object_dtype_array(self): + # GH 21980 + arr = np.array([Timestamp('20130101 9:01'), + Timestamp('20121230 9:02')]) + exp = np.array([Timestamp('20121231 9:01'), + Timestamp('20121229 9:02')]) + res = arr - pd.Timedelta('1D') + tm.assert_numpy_array_equal(res, exp) + + def test_td_sub_mixed_most_timedeltalike_object_dtype_array(self): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D'), + np.timedelta64(2, 'h')]) + exp = np.array([now - pd.Timedelta('1D'), + pd.Timedelta('0D'), + np.timedelta64(2, 'h') - pd.Timedelta('1D')]) + res = arr - pd.Timedelta('1D') + tm.assert_numpy_array_equal(res, exp) + + def test_td_rsub_mixed_most_timedeltalike_object_dtype_array(self): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D'), + np.timedelta64(2, 'h')]) + with pytest.raises(TypeError): + pd.Timedelta('1D') - arr + + @pytest.mark.parametrize('op', [operator.add, ops.radd]) + def test_td_add_timedeltalike_object_dtype_array(self, op): + # GH 21980 + arr = np.array([Timestamp('20130101 9:01'), + Timestamp('20121230 9:02')]) + exp = np.array([Timestamp('20130102 9:01'), + Timestamp('20121231 9:02')]) + res = op(arr, pd.Timedelta('1D')) + tm.assert_numpy_array_equal(res, exp) + + @pytest.mark.parametrize('op', [operator.add, ops.radd]) + def test_td_add_mixed_timedeltalike_object_dtype_array(self, op): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D')]) + exp = np.array([now + pd.Timedelta('1D'), + pd.Timedelta('2D')]) + res = op(arr, pd.Timedelta('1D')) + tm.assert_numpy_array_equal(res, exp) + class TestTimedeltaMultiplicationDivision(object): """ @@ -616,3 +667,17 @@ def test_rdivmod_invalid(self): with pytest.raises(TypeError): divmod(np.array([22, 24]), td) + + @pytest.mark.parametrize('op', [ + operator.mul, + ops.rmul, + operator.truediv, + ops.rdiv, + ops.rsub]) + @pytest.mark.parametrize('arr', [ + np.array([Timestamp('20130101 9:01'), Timestamp('20121230 9:02')]), + np.array([pd.Timestamp.now(), pd.Timedelta('1D')]) + ]) + def test_td_op_timedelta_timedeltalike_array(self, op, arr): + with pytest.raises(TypeError): + op(arr, pd.Timedelta('1D'))