Skip to content

Commit 18bb625

Browse files
committed
ENH: GH4521 A Series of dtype timedelta64[ns] can now be divided/multipled by an integer series
1 parent 4d2d571 commit 18bb625

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

doc/source/release.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pandas 0.13
5353
- Add ``rename`` and ``set_names`` methods to ``Index`` as well as
5454
``set_names``, ``set_levels``, ``set_labels`` to ``MultiIndex``.
5555
(:issue:`4039`)
56+
- A Series of dtype ``Timedelta64[ns]`` can now be divided/multiplied
57+
by an integer series (:issue`4521`)
5658

5759
**API Changes**
5860

pandas/core/series.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def wrapper(self, other, name=name):
9191

9292
is_timedelta_lhs = com.is_timedelta64_dtype(self)
9393
is_datetime_lhs = com.is_datetime64_dtype(self)
94+
is_integer_lhs = lvalues.dtype == np.int64
9495

9596
if is_datetime_lhs or is_timedelta_lhs:
9697

@@ -115,8 +116,8 @@ def convert_to_array(values):
115116
# py3 compat where dtype is 'm' but is an integer
116117
if values.dtype.kind == 'm':
117118
values = values.astype('timedelta64[ns]')
118-
else:
119-
raise ValueError("incompatible type for a datetime/timedelta operation")
119+
elif name not in ['__div__','__mul__']:
120+
raise TypeError("incompatible type for a datetime/timedelta operation")
120121
elif isinstance(values[0],DateOffset):
121122
# handle DateOffsets
122123
values = pa.array([ v.delta for v in values ])
@@ -131,9 +132,22 @@ def convert_to_array(values):
131132

132133
is_datetime_rhs = com.is_datetime64_dtype(rvalues)
133134
is_timedelta_rhs = com.is_timedelta64_dtype(rvalues) or (not is_datetime_rhs and _np_version_under1p7)
135+
is_integer_rhs = rvalues.dtype == np.int64
136+
mask = None
137+
138+
# timedelta and integer mul/div
139+
if (is_timedelta_lhs and is_integer_rhs) or (is_integer_lhs and is_timedelta_rhs):
140+
141+
if name not in ['__div__','__mul__']:
142+
raise TypeError("can only operate on a timedelta and an integer for "
143+
"division, but the operator [%s] was passed" % name)
144+
dtype = 'timedelta64[ns]'
145+
mask = isnull(lvalues) | isnull(rvalues)
146+
lvalues = lvalues.astype(np.int64)
147+
rvalues = rvalues.astype(np.int64)
134148

135149
# 2 datetimes or 2 timedeltas
136-
if (is_timedelta_lhs and is_timedelta_rhs) or (is_datetime_lhs and
150+
elif (is_timedelta_lhs and is_timedelta_rhs) or (is_datetime_lhs and
137151
is_datetime_rhs):
138152
if is_datetime_lhs and name != '__sub__':
139153
raise TypeError("can only operate on a datetimes for subtraction, "
@@ -143,13 +157,7 @@ def convert_to_array(values):
143157
"addition and subtraction, but the operator [%s] was passed" % name)
144158

145159
dtype = 'timedelta64[ns]'
146-
147160
mask = isnull(lvalues) | isnull(rvalues)
148-
if mask.any():
149-
def wrap_results(x):
150-
x = pa.array(x,dtype='timedelta64[ns]')
151-
np.putmask(x,mask,tslib.iNaT)
152-
return x
153161

154162
# datetime and timedelta
155163
elif is_timedelta_rhs and is_datetime_lhs:
@@ -167,9 +175,18 @@ def wrap_results(x):
167175
dtype = 'M8[ns]'
168176

169177
else:
170-
raise ValueError('cannot operate on a series with out a rhs '
171-
'of a series/ndarray of type datetime64[ns] '
172-
'or a timedelta')
178+
raise TypeError('cannot operate on a series with out a rhs '
179+
'of a series/ndarray of type datetime64[ns] '
180+
'or a timedelta')
181+
182+
# if we need to mask the results
183+
if mask is not None:
184+
if mask.any():
185+
def f(x):
186+
x = pa.array(x,dtype='timedelta64[ns]')
187+
np.putmask(x,mask,tslib.iNaT)
188+
return x
189+
wrap_results = f
173190

174191
lvalues = lvalues.view('i8')
175192
rvalues = rvalues.view('i8')

pandas/tests/test_series.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,51 @@ def test_operators_timedelta64(self):
19991999
[Timestamp('20130101 9:01:00.005'), Timestamp('20130101 9:02:00.005')])
20002000
assert_series_equal(result, expected)
20012001

2002+
# GH 4521
2003+
# divide/multiply by integers
2004+
startdate = Series(date_range('2013-01-01', '2013-01-03'))
2005+
enddate = Series(date_range('2013-03-01', '2013-03-03'))
2006+
2007+
s1 = enddate - startdate
2008+
s1[2] = np.nan
2009+
s2 = Series([2, 3, 4])
2010+
expected = Series(s1.values.astype(np.int64) / s2, dtype='m8[ns]')
2011+
expected[2] = np.nan
2012+
result = s1 / s2
2013+
assert_series_equal(result,expected)
2014+
2015+
s2 = Series([20, 30, 40])
2016+
expected = Series(s1.values.astype(np.int64) / s2, dtype='m8[ns]')
2017+
expected[2] = np.nan
2018+
result = s1 / s2
2019+
assert_series_equal(result,expected)
2020+
2021+
result = s1 / 2
2022+
expected = Series(s1.values.astype(np.int64) / 2, dtype='m8[ns]')
2023+
expected[2] = np.nan
2024+
assert_series_equal(result,expected)
2025+
2026+
s2 = Series([20, 30, 40])
2027+
expected = Series(s1.values.astype(np.int64) * s2, dtype='m8[ns]')
2028+
expected[2] = np.nan
2029+
result = s1 * s2
2030+
assert_series_equal(result,expected)
2031+
2032+
result = s1 * 2
2033+
expected = Series(s1.values.astype(np.int64) * 2, dtype='m8[ns]')
2034+
expected[2] = np.nan
2035+
assert_series_equal(result,expected)
2036+
2037+
self.assertRaises(TypeError, s1.__div__, s2.astype(float))
2038+
self.assertRaises(TypeError, s1.__mul__, s2.astype(float))
2039+
self.assertRaises(TypeError, s1.__div__, 2.)
2040+
self.assertRaises(TypeError, s1.__mul__, 2.)
2041+
2042+
self.assertRaises(TypeError, s1.__add__, 1)
2043+
self.assertRaises(TypeError, s1.__sub__, 1)
2044+
self.assertRaises(TypeError, s1.__add__, s2.values)
2045+
self.assertRaises(TypeError, s1.__sub__, s2.values)
2046+
20022047
def test_timedelta64_equal_timedelta_supported_ops(self):
20032048
ser = Series([Timestamp('20130301'), Timestamp('20130228 23:00:00'),
20042049
Timestamp('20130228 22:00:00'),

0 commit comments

Comments
 (0)