Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: fix raise of TypeError when subtracting timedelta array #22054

Merged
merged 1 commit into from
Sep 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.24.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
-
-

Expand Down
8 changes: 5 additions & 3 deletions pandas/_libs/tslibs/timedeltas.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbrockmendel I am a bit confused here why simply returning NotImplemented is not sufficient ?
(I tested it, and that doesn't seem to work. Although with a datetime.timedelta it does, and that one does return NotImplemented ..)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pd.Timedelta version fails because both arr.__add__(td) and td.__radd__(arr) return NotImplemented. arr.__add__(td.to_pytimedelta()) returns OK, so presumably it is something on the numpy implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was playing a bit with it, and I think Timedelta behaves differently as datetime.timedelta because of the __array_priority__ we add to the _Timedelta class (by commenting it out, the simple example works), which is needed to get other behaviors working I suppose

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorisvandenbossche ok otherwise this PR looks ok, are you suggesting that we remove that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also confused.

else:
return NotImplemented
return op(self.to_timedelta64(), other)

elif not _validate_ops_compat(other):
return NotImplemented
Expand Down
65 changes: 65 additions & 0 deletions pandas/tests/scalar/timedelta/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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'))