Skip to content

Commit 778ffc4

Browse files
dcherianshoyer
authored andcommitted
.resample now supports loffset. (#2608)
* .resample now supports loffset. * Update whats-new.rst * Fix for pandas 0.19.2 * doc update. * Review comments.
1 parent 30288e8 commit 778ffc4

File tree

5 files changed

+55
-2
lines changed

5 files changed

+55
-2
lines changed

doc/whats-new.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ Enhancements
4545
"dayofyear" and "dayofweek" accessors (:issue:`2597`). By `Spencer Clark
4646
<https://github.com/spencerkclark>`_.
4747
- Support Dask ``HighLevelGraphs`` by `Matthew Rocklin <https://matthewrocklin.com>`_.
48+
- :py:meth:`DataArray.resample` and :py:meth:`Dataset.resample` now supports the
49+
``loffset`` kwarg just like Pandas.
50+
By `Deepak Cherian <https://github.com/dcherian>`_
4851

4952

5053
Bug fixes

xarray/core/common.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ def rolling(self, dim=None, min_periods=None, center=False, **dim_kwargs):
592592
center=center)
593593

594594
def resample(self, indexer=None, skipna=None, closed=None, label=None,
595-
base=0, keep_attrs=None, **indexer_kwargs):
595+
base=0, keep_attrs=None, loffset=None, **indexer_kwargs):
596596
"""Returns a Resample object for performing resampling operations.
597597
598598
Handles both downsampling and upsampling. If any intervals contain no
@@ -612,6 +612,9 @@ def resample(self, indexer=None, skipna=None, closed=None, label=None,
612612
For frequencies that evenly subdivide 1 day, the "origin" of the
613613
aggregated intervals. For example, for '24H' frequency, base could
614614
range from 0 through 23.
615+
loffset : timedelta or str, optional
616+
Offset used to adjust the resampled time labels. Some pandas date
617+
offset strings are supported.
615618
keep_attrs : bool, optional
616619
If True, the object's attributes (`attrs`) will be copied from
617620
the original object to the new one. If False (default), the new
@@ -700,7 +703,9 @@ def resample(self, indexer=None, skipna=None, closed=None, label=None,
700703

701704
group = DataArray(dim_coord, coords=dim_coord.coords,
702705
dims=dim_coord.dims, name=RESAMPLE_DIM)
703-
grouper = pd.Grouper(freq=freq, closed=closed, label=label, base=base)
706+
# TODO: to_offset() call required for pandas==0.19.2
707+
grouper = pd.Grouper(freq=freq, closed=closed, label=label, base=base,
708+
loffset=pd.tseries.frequencies.to_offset(loffset))
704709
resampler = self._resample_cls(self, group=group, dim=dim_name,
705710
grouper=grouper,
706711
resample_dim=RESAMPLE_DIM)

xarray/core/groupby.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import functools
44
import warnings
55

6+
import datetime
67
import numpy as np
78
import pandas as pd
89

@@ -154,6 +155,32 @@ def _unique_and_monotonic(group):
154155
return index.is_unique and index.is_monotonic
155156

156157

158+
def _apply_loffset(grouper, result):
159+
"""
160+
(copied from pandas)
161+
if loffset is set, offset the result index
162+
163+
This is NOT an idempotent routine, it will be applied
164+
exactly once to the result.
165+
166+
Parameters
167+
----------
168+
result : Series or DataFrame
169+
the result of resample
170+
"""
171+
172+
needs_offset = (
173+
isinstance(grouper.loffset, (pd.DateOffset, datetime.timedelta))
174+
and isinstance(result.index, pd.DatetimeIndex)
175+
and len(result.index) > 0
176+
)
177+
178+
if needs_offset:
179+
result.index = result.index + grouper.loffset
180+
181+
grouper.loffset = None
182+
183+
157184
class GroupBy(SupportsArithmetic):
158185
"""A object that implements the split-apply-combine pattern.
159186
@@ -235,6 +262,7 @@ def __init__(self, obj, group, squeeze=False, grouper=None, bins=None,
235262
raise ValueError('index must be monotonic for resampling')
236263
s = pd.Series(np.arange(index.size), index)
237264
first_items = s.groupby(grouper).first()
265+
_apply_loffset(grouper, first_items)
238266
full_index = first_items.index
239267
if first_items.isnull().any():
240268
first_items = first_items.dropna()

xarray/tests/test_dataarray.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,6 +2273,11 @@ def test_resample(self):
22732273
actual = array.resample(time='24H').reduce(np.mean)
22742274
assert_identical(expected, actual)
22752275

2276+
actual = array.resample(time='24H', loffset='-12H').mean()
2277+
expected = DataArray(array.to_series().resample('24H', loffset='-12H')
2278+
.mean())
2279+
assert_identical(expected, actual)
2280+
22762281
with raises_regex(ValueError, 'index must be monotonic'):
22772282
array[[2, 0, 1]].resample(time='1D')
22782283

xarray/tests/test_dataset.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,6 +2804,18 @@ def test_resample_by_mean_with_keep_attrs(self):
28042804
expected = ds.attrs
28052805
assert expected == actual
28062806

2807+
def test_resample_loffset(self):
2808+
times = pd.date_range('2000-01-01', freq='6H', periods=10)
2809+
ds = Dataset({'foo': (['time', 'x', 'y'], np.random.randn(10, 5, 3)),
2810+
'bar': ('time', np.random.randn(10), {'meta': 'data'}),
2811+
'time': times})
2812+
ds.attrs['dsmeta'] = 'dsdata'
2813+
2814+
actual = ds.resample(time='24H', loffset='-12H').mean('time').time
2815+
expected = xr.DataArray(ds.bar.to_series()
2816+
.resample('24H', loffset='-12H').mean()).time
2817+
assert_identical(expected, actual)
2818+
28072819
def test_resample_by_mean_discarding_attrs(self):
28082820
times = pd.date_range('2000-01-01', freq='6H', periods=10)
28092821
ds = Dataset({'foo': (['time', 'x', 'y'], np.random.randn(10, 5, 3)),

0 commit comments

Comments
 (0)