Skip to content

Commit d3f607d

Browse files
committed
ENH/BUG: allow timedelta resamples
1 parent 8069f47 commit d3f607d

File tree

4 files changed

+49
-8
lines changed

4 files changed

+49
-8
lines changed

doc/source/v0.15.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ TimedeltaIndex/Scalar
563563
We introduce a new scalar type ``Timedelta``, which is a subclass of ``datetime.timedelta``, and behaves in a similar manner,
564564
but allows compatibility with ``np.timedelta64`` types as well as a host of custom representation, parsing, and attributes.
565565
This type is very similar to how ``Timestamp`` works for ``datetimes``. It is a nice-API box for the type. See the :ref:`docs <timedeltas.timedeltas>`.
566-
(:issue:`3009`, :issue:`4533`, :issue:`8209`, :issue:`8187`, :issue:`8190`, :issue:`7869`, :issue:`7661`)
566+
(:issue:`3009`, :issue:`4533`, :issue:`8209`, :issue:`8187`, :issue:`8190`, :issue:`7869`, :issue:`7661`, :issue:`8345`)
567567

568568
.. warning::
569569

pandas/tseries/resample.py

+31-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pandas.core.groupby import BinGrouper, Grouper
66
from pandas.tseries.frequencies import to_offset, is_subperiod, is_superperiod
77
from pandas.tseries.index import DatetimeIndex, date_range
8+
from pandas.tseries.tdi import TimedeltaIndex
89
from pandas.tseries.offsets import DateOffset, Tick, _delta_to_nanoseconds
910
from pandas.tseries.period import PeriodIndex, period_range
1011
import pandas.tseries.tools as tools
@@ -96,10 +97,12 @@ def resample(self, obj):
9697
obj = self.obj.to_timestamp(how=self.convention)
9798
self._set_grouper(obj)
9899
rs = self._resample_timestamps()
100+
elif isinstance(ax, TimedeltaIndex):
101+
rs = self._resample_timestamps(kind='timedelta')
99102
elif len(ax) == 0:
100103
return self.obj
101104
else: # pragma: no cover
102-
raise TypeError('Only valid with DatetimeIndex or PeriodIndex')
105+
raise TypeError('Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex')
103106

104107
rs_axis = rs._get_axis(self.axis)
105108
rs_axis.name = ax.name
@@ -109,13 +112,17 @@ def _get_grouper(self, obj):
109112
self._set_grouper(obj)
110113
return self._get_binner_for_resample()
111114

112-
def _get_binner_for_resample(self):
115+
def _get_binner_for_resample(self, kind=None):
113116
# create the BinGrouper
114117
# assume that self.set_grouper(obj) has already been called
115118

116119
ax = self.ax
117-
if self.kind is None or self.kind == 'timestamp':
120+
if kind is None:
121+
kind = self.kind
122+
if kind is None or kind == 'timestamp':
118123
self.binner, bins, binlabels = self._get_time_bins(ax)
124+
elif kind == 'timedelta':
125+
self.binner, bins, binlabels = self._get_time_delta_bins(ax)
119126
else:
120127
self.binner, bins, binlabels = self._get_time_period_bins(ax)
121128

@@ -217,6 +224,25 @@ def _adjust_bin_edges(self, binner, ax_values):
217224

218225
return binner, bin_edges
219226

227+
def _get_time_delta_bins(self, ax):
228+
if not isinstance(ax, TimedeltaIndex):
229+
raise TypeError('axis must be a TimedeltaIndex, but got '
230+
'an instance of %r' % type(ax).__name__)
231+
232+
if not len(ax):
233+
binner = labels = TimedeltaIndex(data=[], freq=self.freq, name=ax.name)
234+
return binner, [], labels
235+
236+
labels = binner = TimedeltaIndex(start=ax[0],
237+
end=ax[-1],
238+
freq=self.freq,
239+
name=ax.name)
240+
241+
end_stamps = labels + 1
242+
bins = ax.searchsorted(end_stamps, side='left')
243+
244+
return binner, bins, labels
245+
220246
def _get_time_period_bins(self, ax):
221247
if not isinstance(ax, DatetimeIndex):
222248
raise TypeError('axis must be a DatetimeIndex, but got '
@@ -242,11 +268,11 @@ def _get_time_period_bins(self, ax):
242268
def _agg_method(self):
243269
return self.how if self.how else _DEFAULT_METHOD
244270

245-
def _resample_timestamps(self):
271+
def _resample_timestamps(self, kind=None):
246272
# assumes set_grouper(obj) already called
247273
axlabels = self.ax
248274

249-
self._get_binner_for_resample()
275+
self._get_binner_for_resample(kind=kind)
250276
grouper = self.grouper
251277
binner = self.binner
252278
obj = self.obj

pandas/tseries/tests/test_resample.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def test_resample_how_callables(self):
146146
data = np.arange(5, dtype=np.int64)
147147
ind = pd.DatetimeIndex(start='2014-01-01', periods=len(data), freq='d')
148148
df = pd.DataFrame({"A": data, "B": data}, index=ind)
149-
149+
150150
def fn(x, a=1):
151151
return str(type(x))
152152

@@ -164,7 +164,18 @@ def __call__(self, x):
164164
assert_frame_equal(df_standard, df_partial)
165165
assert_frame_equal(df_standard, df_partial2)
166166
assert_frame_equal(df_standard, df_class)
167-
167+
168+
def test_resample_with_timedeltas(self):
169+
170+
expected = DataFrame({'A' : np.arange(1480)})
171+
expected = expected.groupby(expected.index // 30).sum()
172+
expected.index = pd.timedelta_range('0 days',freq='30T',periods=50)
173+
174+
df = DataFrame({'A' : np.arange(1480)},index=pd.to_timedelta(np.arange(1480),unit='T'))
175+
result = df.resample('30T',how='sum')
176+
177+
assert_frame_equal(result, expected)
178+
168179
def test_resample_basic_from_daily(self):
169180
# from daily
170181
dti = DatetimeIndex(

pandas/tseries/tests/test_timedeltas.py

+4
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ def test_timedelta_range(self):
265265
result = timedelta_range('1 days, 00:00:02',periods=5,freq='2D')
266266
tm.assert_index_equal(result, expected)
267267

268+
expected = to_timedelta(np.arange(50),unit='T')*30
269+
result = timedelta_range('0 days',freq='30T',periods=50)
270+
tm.assert_index_equal(result, expected)
271+
268272
def test_numeric_conversions(self):
269273
self.assertEqual(ct(0), np.timedelta64(0,'ns'))
270274
self.assertEqual(ct(10), np.timedelta64(10,'ns'))

0 commit comments

Comments
 (0)