Skip to content

Commit d2f95f2

Browse files
committed
Merge pull request #4985 from jreback/timedelta1
API: properly box numeric timedelta ops on Series (GH4984)
2 parents edbd512 + 31a7742 commit d2f95f2

File tree

7 files changed

+82
-12
lines changed

7 files changed

+82
-12
lines changed

doc/source/release.rst

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Improvements to existing features
9393
is frequency conversion.
9494
- Timedelta64 support ``fillna/ffill/bfill`` with an integer interpreted as seconds,
9595
or a ``timedelta`` (:issue:`3371`)
96+
- Box numeric ops on ``timedelta`` Series (:issue:`4984`)
9697
- Datetime64 support ``ffill/bfill``
9798
- Performance improvements with ``__getitem__`` on ``DataFrames`` with
9899
when the key is a column

doc/source/timeseries.rst

+19
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,25 @@ pass a timedelta to get a particular value.
12041204
y.fillna(10)
12051205
y.fillna(timedelta(days=-1,seconds=5))
12061206
1207+
.. _timeseries.timedeltas_reductions:
1208+
1209+
Time Deltas & Reductions
1210+
------------------------
1211+
1212+
.. warning::
1213+
1214+
A numeric reduction operation for ``timedelta64[ns]`` will return a single-element ``Series`` of
1215+
dtype ``timedelta64[ns]``.
1216+
1217+
You can do numeric reduction operations on timedeltas.
1218+
1219+
.. ipython:: python
1220+
1221+
y2 = y.fillna(timedelta(days=-1,seconds=5))
1222+
y2
1223+
y2.mean()
1224+
y2.quantile(.1)
1225+
12071226
.. _timeseries.timedeltas_convert:
12081227

12091228
Time Deltas & Conversions

doc/source/v0.13.0.txt

+8
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,14 @@ Enhancements
292292
td.fillna(0)
293293
td.fillna(timedelta(days=1,seconds=5))
294294

295+
- You can do numeric reduction operations on timedeltas. Note that these will return
296+
a single-element Series.
297+
298+
.. ipython:: python
299+
300+
td.mean()
301+
td.quantile(.1)
302+
295303
- ``plot(kind='kde')`` now accepts the optional parameters ``bw_method`` and
296304
``ind``, passed to scipy.stats.gaussian_kde() (for scipy >= 0.11.0) to set
297305
the bandwidth, and to gkde.evaluate() to specify the indicies at which it

pandas/core/nanops.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import numpy as np
77

8-
from pandas.core.common import isnull, notnull, _values_from_object
8+
from pandas.core.common import isnull, notnull, _values_from_object, is_float
99
import pandas.core.common as com
1010
import pandas.lib as lib
1111
import pandas.algos as algos
@@ -188,6 +188,10 @@ def _wrap_results(result,dtype):
188188
# as series will do the right thing in py3 (and deal with numpy 1.6.2
189189
# bug in that it results dtype of timedelta64[us]
190190
from pandas import Series
191+
192+
# coerce float to results
193+
if is_float(result):
194+
result = int(result)
191195
result = Series([result],dtype='timedelta64[ns]')
192196
else:
193197
result = result.view(dtype)
@@ -224,11 +228,15 @@ def nanmean(values, axis=None, skipna=True):
224228
the_mean[ct_mask] = np.nan
225229
else:
226230
the_mean = the_sum / count if count > 0 else np.nan
227-
return the_mean
231+
232+
return _wrap_results(the_mean,dtype)
228233

229234
@disallow('M8')
230235
@bottleneck_switch()
231236
def nanmedian(values, axis=None, skipna=True):
237+
238+
values, mask, dtype = _get_values(values, skipna)
239+
232240
def get_median(x):
233241
mask = notnull(x)
234242
if not skipna and not mask.all():
@@ -257,7 +265,7 @@ def get_median(x):
257265
return ret
258266

259267
# otherwise return a scalar value
260-
return get_median(values) if notempty else np.nan
268+
return _wrap_results(get_median(values),dtype) if notempty else np.nan
261269

262270

263271
@disallow('M8')

pandas/core/series.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1981,7 +1981,12 @@ def quantile(self, q=0.5):
19811981
valid_values = self.dropna().values
19821982
if len(valid_values) == 0:
19831983
return pa.NA
1984-
return _quantile(valid_values, q * 100)
1984+
result = _quantile(valid_values, q * 100)
1985+
if result.dtype == _TD_DTYPE:
1986+
from pandas.tseries.timedeltas import to_timedelta
1987+
return to_timedelta(result)
1988+
1989+
return result
19851990

19861991
def ptp(self, axis=None, out=None):
19871992
return _values_from_object(self).ptp(axis, out)

pandas/tseries/tests/test_timedeltas.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import numpy as np
88
import pandas as pd
99

10-
from pandas import (Index, Series, DataFrame, isnull, notnull,
10+
from pandas import (Index, Series, DataFrame, Timestamp, isnull, notnull,
1111
bdate_range, date_range, _np_version_under1p7)
1212
import pandas.core.common as com
1313
from pandas.compat import StringIO, lrange, range, zip, u, OrderedDict, long
@@ -123,8 +123,8 @@ def conv(v):
123123
def test_nat_converters(self):
124124
_skip_if_numpy_not_friendly()
125125

126-
self.assert_(to_timedelta('nat') == tslib.iNaT)
127-
self.assert_(to_timedelta('nan') == tslib.iNaT)
126+
self.assert_(to_timedelta('nat',box=False) == tslib.iNaT)
127+
self.assert_(to_timedelta('nan',box=False) == tslib.iNaT)
128128

129129
def test_to_timedelta(self):
130130
_skip_if_numpy_not_friendly()
@@ -133,11 +133,11 @@ def conv(v):
133133
return v.astype('m8[ns]')
134134
d1 = np.timedelta64(1,'D')
135135

136-
self.assert_(to_timedelta('1 days 06:05:01.00003') == conv(d1+np.timedelta64(6*3600+5*60+1,'s')+np.timedelta64(30,'us')))
137-
self.assert_(to_timedelta('15.5us') == conv(np.timedelta64(15500,'ns')))
136+
self.assert_(to_timedelta('1 days 06:05:01.00003',box=False) == conv(d1+np.timedelta64(6*3600+5*60+1,'s')+np.timedelta64(30,'us')))
137+
self.assert_(to_timedelta('15.5us',box=False) == conv(np.timedelta64(15500,'ns')))
138138

139139
# empty string
140-
result = to_timedelta('')
140+
result = to_timedelta('',box=False)
141141
self.assert_(result == tslib.iNaT)
142142

143143
result = to_timedelta(['', ''])
@@ -150,7 +150,7 @@ def conv(v):
150150

151151
# ints
152152
result = np.timedelta64(0,'ns')
153-
expected = to_timedelta(0)
153+
expected = to_timedelta(0,box=False)
154154
self.assert_(result == expected)
155155

156156
# Series
@@ -163,6 +163,35 @@ def conv(v):
163163
expected = to_timedelta([0,10],unit='s')
164164
tm.assert_series_equal(result, expected)
165165

166+
# single element conversion
167+
v = timedelta(seconds=1)
168+
result = to_timedelta(v,box=False)
169+
expected = to_timedelta([v])
170+
171+
v = np.timedelta64(timedelta(seconds=1))
172+
result = to_timedelta(v,box=False)
173+
expected = to_timedelta([v])
174+
175+
def test_timedelta_ops(self):
176+
_skip_if_numpy_not_friendly()
177+
178+
# GH4984
179+
# make sure ops return timedeltas
180+
s = Series([Timestamp('20130101') + timedelta(seconds=i*i) for i in range(10) ])
181+
td = s.diff()
182+
183+
result = td.mean()
184+
expected = to_timedelta(timedelta(seconds=9))
185+
tm.assert_series_equal(result, expected)
186+
187+
result = td.quantile(.1)
188+
expected = to_timedelta('00:00:02.6')
189+
tm.assert_series_equal(result, expected)
190+
191+
result = td.median()
192+
expected = to_timedelta('00:00:08')
193+
tm.assert_series_equal(result, expected)
194+
166195
if __name__ == '__main__':
167196
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
168197
exit=False)

pandas/tseries/timedeltas.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _convert_listlike(arg, box):
5858
elif is_list_like(arg):
5959
return _convert_listlike(arg, box=box)
6060

61-
return _convert_listlike([ arg ], box=False)[0]
61+
return _convert_listlike([ arg ], box=box)
6262

6363
_short_search = re.compile(
6464
"^\s*(?P<neg>-?)\s*(?P<value>\d*\.?\d*)\s*(?P<unit>d|s|ms|us|ns)?\s*$",re.IGNORECASE)

0 commit comments

Comments
 (0)