From cacf82fa34cb2f9ef7f4f080eba96db3aaa769f4 Mon Sep 17 00:00:00 2001 From: Ben Nelson Date: Wed, 25 Jul 2018 13:41:55 -0600 Subject: [PATCH] fix raise of TypeError when subtracting timedelta array closes #21980 --- doc/source/whatsnew/v0.24.0.txt | 2 +- pandas/_libs/tslibs/timedeltas.pyx | 10 +- .../tests/scalar/timedelta/test_arithmetic.py | 118 ++++++++++++++++++ 3 files changed, 125 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 43e380abd8bb53..8f76a6559f028f 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -441,7 +441,7 @@ Datetimelike Timedelta ^^^^^^^^^ -- +- 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 f7a6cf0c6dafc2..6601194b55df3c 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -542,10 +542,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 @@ -929,7 +931,7 @@ cdef class _Timedelta(timedelta): def nanoseconds(self): """ Return the number of nanoseconds (n), where 0 <= n < 1 microsecond. - + Returns ------- int diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index 9636c92ec22d52..db001f6c0c1735 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -200,6 +200,92 @@ 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_timedeltalike_object_dtype_array(self): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D')]) + exp = np.array([now - pd.Timedelta('1D'), + pd.Timedelta('0D')]) + 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_timedeltalike_object_dtype_array(self): + # GH 21980 + arr = np.array([Timestamp('20130101 9:01'), + Timestamp('20121230 9:02')]) + with pytest.raises(TypeError): + # an timedelta - timestamp doesnt make sense + pd.Timedelta('1D') - arr + + def test_td_rsub_mixed_timedeltalike_object_dtype_array(self): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D')]) + with pytest.raises(TypeError): + # an timedelta - timestamp doesnt make sense + pd.Timedelta('1D') - arr + + def test_td_add_timedeltalike_object_dtype_array(self): + # 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 = arr + pd.Timedelta('1D') + tm.assert_numpy_array_equal(res, exp) + + def test_td_add_mixed_timedeltalike_object_dtype_array(self): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D')]) + exp = np.array([now + pd.Timedelta('1D'), + pd.Timedelta('2D')]) + res = arr + pd.Timedelta('1D') + tm.assert_numpy_array_equal(res, exp) + + def test_td_radd_timedeltalike_object_dtype_array(self): + # 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 = pd.Timedelta('1D') + arr + tm.assert_numpy_array_equal(res, exp) + + def test_td_radd_mixed_timedeltalike_object_dtype_array(self): + # GH 21980 + now = pd.Timestamp.now() + arr = np.array([now, + pd.Timedelta('1D')]) + exp = np.array([now + pd.Timedelta('1D'), + pd.Timedelta('2D')]) + res = pd.Timedelta('1D') + arr + tm.assert_numpy_array_equal(res, exp) + class TestTimedeltaMultiplicationDivision(object): """ @@ -616,3 +702,35 @@ def test_rdivmod_invalid(self): with pytest.raises(TypeError): divmod(np.array([22, 24]), td) + + def test_td_div_timedelta_timedeltalike_array(self): + # GH 21980 + arr = np.array([Timestamp('20130101 9:01'), + Timestamp('20121230 9:02')]) + + with pytest.raises(TypeError): + arr / pd.Timedelta('1D') + + def test_td_rdiv_timedelta_mixed_timedeltalike_array(self): + # GH 21980 + arr = np.array([pd.Timestamp.now(), + pd.Timedelta('1D')]) + + with pytest.raises(TypeError): + pd.Timedelta('1D') / arr + + def test_td_mult_timedelta_mixed_timedeltalike_array(self): + # GH 21980 + arr = np.array([pd.Timestamp.now(), + pd.Timedelta('1D')]) + + with pytest.raises(TypeError): + pd.Timedelta('1D') * arr + + def test_td_rmult_timedelta_mixed_timedeltalike_array(self): + # GH 21980 + arr = np.array([pd.Timestamp.now(), + pd.Timedelta('1D')]) + + with pytest.raises(TypeError): + arr * pd.Timedelta('1D')