Skip to content
6 changes: 5 additions & 1 deletion doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,11 @@ Conversion
- Bug in :class:`Series` floor-division where operating on a scalar ``timedelta`` raises an exception (:issue:`18846`)
- Bug in :class:`FY5253Quarter`, :class:`LastWeekOfMonth` where rollback and rollforward behavior was inconsistent with addition and subtraction behavior (:issue:`18854`)
- Bug in :class:`Index` constructor with ``dtype=CategoricalDtype(...)`` where ``categories`` and ``ordered`` are not maintained (issue:`19032`)

- Bug in :class:`Index` constructor with ``dtype=CategoricalDtype(...)`` where ``categories`` and ``ordered`` are not maintained (issue:`19032`)
- Bug in :class:`Series`` with ``dtype='timedelta64[ns]`` where addition or subtraction of ``TimedeltaIndex`` had results cast to ``dtype='int64'`` (:issue:`17250`)
- Bug in :class:`TimedeltaIndex` where division by a ``Series`` would return a ``TimedeltaIndex`` instead of a ``Series`` (issue:`19042`)
- Bug in :class:`Series` with ``dtype='timedelta64[ns]`` where addition or subtraction of ``TimedeltaIndex`` could return a ``Series`` with an incorrect name (issue:`19043`)
-

Indexing
^^^^^^^^
Expand Down
3 changes: 3 additions & 0 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ def _add_delta(self, delta):
return result

def _evaluate_with_timedelta_like(self, other, op, opstr):
if isinstance(other, ABCSeries):
# GH#19042
return NotImplemented

# allow division by a timedelta
if opstr in ['__div__', '__truediv__', '__floordiv__']:
Expand Down
25 changes: 11 additions & 14 deletions pandas/core/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,15 @@ def _align_method_SERIES(left, right, align_asobject=False):


def _construct_result(left, result, index, name, dtype):
return left._constructor(result, index=index, name=name, dtype=dtype)
"""
If the raw op result has a non-None name (e.g. it is an Index object) and
the name argument is None, then passing name to the constructor will
not be enough; we still need to override the name attribute.
"""
out = left._constructor(result, index=index, dtype=dtype)

out.name = name
return out


def _construct_divmod_result(left, result, index, name, dtype):
Expand Down Expand Up @@ -687,21 +695,10 @@ def wrapper(left, right, name=name, na_op=na_op):
not isinstance(lvalues, ABCDatetimeIndex)):
lvalues = lvalues.values

if isinstance(right, (ABCSeries, pd.Index)):
# `left` is always a Series object
res_name = _maybe_match_name(left, right)
else:
res_name = left.name

result = wrap_results(safe_na_op(lvalues, rvalues))
res_name = _get_series_op_result_name(left, right)
return construct_result(
left,
result,
index=left.index,
name=res_name,
dtype=dtype,
)
return construct_result(left, result,
index=left.index, name=res_name, dtype=dtype)

return wrapper

Expand Down
68 changes: 68 additions & 0 deletions pandas/tests/series/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,74 @@ def test_timedelta_floordiv(self, scalar_td):
expected = Series([0, 0, np.nan])
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize('names', [(None, None, None),
('Egon', 'Venkman', None),
('NCC1701D', 'NCC1701D', 'NCC1701D')])
def test_td64_series_with_tdi(self, names):
# GH#17250 make sure result dtype is correct
# GH#19043 make sure names are propogated correctly
tdi = pd.TimedeltaIndex(['0 days', '1 day'], name=names[0])
ser = Series([Timedelta(hours=3), Timedelta(hours=4)], name=names[1])
expected = Series([Timedelta(hours=3), Timedelta(days=1, hours=4)],
name=names[2])

result = tdi + ser
tm.assert_series_equal(result, expected)
assert result.dtype == 'timedelta64[ns]'

result = ser + tdi
tm.assert_series_equal(result, expected)
assert result.dtype == 'timedelta64[ns]'

expected = Series([Timedelta(hours=-3), Timedelta(days=1, hours=-4)],
name=names[2])

result = tdi - ser
tm.assert_series_equal(result, expected)
assert result.dtype == 'timedelta64[ns]'

result = ser - tdi
tm.assert_series_equal(result, -expected)
assert result.dtype == 'timedelta64[ns]'

@pytest.mark.parametrize('names', [(None, None, None),
('Egon', 'Venkman', None),
('NCC1701D', 'NCC1701D', 'NCC1701D')])
def test_tdi_mul_int_series(self, names):
# GH#19042
tdi = pd.TimedeltaIndex(['0days', '1day', '2days', '3days', '4days'],
name=names[0])
ser = Series([0, 1, 2, 3, 4], dtype=np.int64, name=names[1])

expected = Series(['0days', '1day', '4days', '9days', '16days'],
dtype='timedelta64[ns]',
name=names[2])

result = ser * tdi
tm.assert_series_equal(result, expected)

# The direct operation tdi * ser still needs to be fixed.
result = ser.__rmul__(tdi)
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize('names', [(None, None, None),
('Egon', 'Venkman', None),
('NCC1701D', 'NCC1701D', 'NCC1701D')])
def test_float_series_rdiv_tdi(self, names):
# GH#19042
# TODO: the direct operation TimedeltaIndex / Series still
# needs to be fixed.
tdi = pd.TimedeltaIndex(['0days', '1day', '2days', '3days', '4days'],
name=names[0])
ser = Series([1.5, 3, 4.5, 6, 7.5], dtype=np.float64, name=names[1])

expected = Series([tdi[n] / ser[n] for n in range(len(ser))],
dtype='timedelta64[ns]',
name=names[2])

result = ser.__rdiv__(tdi)
tm.assert_series_equal(result, expected)


class TestDatetimeSeriesArithmetic(object):
@pytest.mark.parametrize(
Expand Down