Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ Bug fixes
- Fix deprecation warning that was raised when calling ``np.array`` on an ``xr.DataArray``
in NumPy 2.0 (:issue:`9312`, :pull:`9393`)
By `Andrew Scherer <https://github.com/andrew-s28>`_.
- Fix support for using ``pandas.BaseOffset``, ``pandas.Timedelta``, and
``datetime.timedelta`` objects as ``resample`` frequencies
(:issue:`9408`, :pull:`9413`).
By `Oliver Higgs <https://github.com/oliverhiggs>`_.

Documentation
~~~~~~~~~~~~~
Expand Down
11 changes: 7 additions & 4 deletions xarray/core/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import datetime
import warnings
from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
from contextlib import suppress
Expand Down Expand Up @@ -32,8 +33,6 @@


if TYPE_CHECKING:
import datetime

from numpy.typing import DTypeLike

from xarray.core.dataarray import DataArray
Expand Down Expand Up @@ -1078,14 +1077,18 @@ def _resample(
)

grouper: Resampler
if isinstance(freq, str):
if isinstance(freq, str | datetime.timedelta | pd.Timedelta | pd.DateOffset):
grouper = TimeResampler(
freq=freq, closed=closed, label=label, origin=origin, offset=offset
)
elif isinstance(freq, Resampler):
grouper = freq
else:
raise ValueError("freq must be a str or a Resampler object")
raise ValueError(
"freq must be an object of type 'str', 'datetime.timedelta', "
"'pandas.Timedelta', 'pandas.DateOffset', or 'TimeResampler'. "
f"Received {type(freq)} instead."
)

rgrouper = ResolvedGrouper(grouper, group, self)

Expand Down
19 changes: 16 additions & 3 deletions xarray/groupers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import numpy as np
import pandas as pd

from xarray.coding.cftime_offsets import _new_to_legacy_freq
from xarray.coding.cftime_offsets import BaseCFTimeOffset, _new_to_legacy_freq
from xarray.core import duck_array_ops
from xarray.core.coordinates import Coordinates
from xarray.core.dataarray import DataArray
Expand Down Expand Up @@ -336,7 +336,7 @@ class TimeResampler(Resampler):

Attributes
----------
freq : str
freq : str, datetime.timedelta, pandas.Timestamp, pandas.DateOffset
Frequency to resample to. See `Pandas frequency
aliases <https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases>`_
for a list of possible values.
Expand All @@ -358,7 +358,7 @@ class TimeResampler(Resampler):
An offset timedelta added to the origin.
"""

freq: str
freq: str | datetime.timedelta | pd.Timedelta | pd.DateOffset
closed: SideOptions | None = field(default=None)
label: SideOptions | None = field(default=None)
origin: str | DatetimeLike = field(default="start_day")
Expand All @@ -380,6 +380,13 @@ def _init_properties(self, group: T_Group) -> None:
if isinstance(group_as_index, CFTimeIndex):
from xarray.core.resample_cftime import CFTimeGrouper

if not isinstance(self.freq, str | BaseCFTimeOffset):
raise ValueError(
"Resample frequency must be a string or 'BaseCFTimeOffset' "
"object when resampling a 'CFTimeIndex'. Received "
f"{type(self.freq)} instead."
)

self.index_grouper = CFTimeGrouper(
freq=self.freq,
closed=self.closed,
Expand All @@ -388,6 +395,12 @@ def _init_properties(self, group: T_Group) -> None:
offset=offset,
)
else:
if isinstance(self.freq, BaseCFTimeOffset):
raise ValueError(
"'BaseCFTimeOffset' resample frequencies are only supported "
"when resampling a 'CFTimeIndex'"
)

self.index_grouper = pd.Grouper(
# TODO remove once requiring pandas >= 2.2
freq=_new_to_legacy_freq(self.freq),
Expand Down
26 changes: 26 additions & 0 deletions xarray/tests/test_groupby.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import datetime
import operator
import warnings
from unittest import mock
Expand Down Expand Up @@ -1813,6 +1814,31 @@ def resample_as_pandas(array, *args, **kwargs):
with pytest.raises(ValueError):
reverse.resample(time="1D").mean()

@pytest.mark.parametrize("use_cftime", [True, False])
def test_resample_dtype(self, use_cftime: bool) -> None:
if use_cftime and not has_cftime:
pytest.skip()
array = DataArray(
np.arange(10),
[
(
"time",
xr.date_range(
"2000-01-01", freq="6h", periods=10, use_cftime=use_cftime
),
)
],
)
test_resample_freqs = ["10min"]
if not use_cftime:
test_resample_freqs += [
pd.Timedelta(hours=2),
pd.offsets.MonthBegin(),
datetime.timedelta(days=1, hours=6),
]
for freq in test_resample_freqs:
array.resample(time=freq)

@pytest.mark.parametrize("use_cftime", [True, False])
def test_resample_doctest(self, use_cftime: bool) -> None:
# run the doctest example here so we are not surprised
Expand Down