diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 89a364f7c37..01993377218 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -45,6 +45,11 @@ New Features Breaking changes ~~~~~~~~~~~~~~~~ +- The ``base`` and ``loffset`` parameters to :py:meth:`Dataset.resample` and :py:meth:`DataArray.resample` + is now removed. These parameters has been deprecated since v2023.03.0. Using the + ``origin`` or ``offset`` parameters is recommended as a replacement for using + the ``base`` parameter and using time offset arithmetic is recommended as a + replacement for using the ``loffset`` parameter. Deprecations diff --git a/xarray/core/common.py b/xarray/core/common.py index 9aa7654c1c8..52a00911d19 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -881,10 +881,8 @@ def _resample( skipna: bool | None, closed: SideOptions | None, label: SideOptions | None, - base: int | None, offset: pd.Timedelta | datetime.timedelta | str | None, origin: str | DatetimeLike, - loffset: datetime.timedelta | str | None, restore_coord_dims: bool | None, **indexer_kwargs: str | Resampler, ) -> T_Resample: @@ -906,16 +904,6 @@ def _resample( Side of each interval to treat as closed. label : {"left", "right"}, optional Side of each interval to use for labeling. - base : int, optional - For frequencies that evenly subdivide 1 day, the "origin" of the - aggregated intervals. For example, for "24H" frequency, base could - range from 0 through 23. - - .. deprecated:: 2023.03.0 - Following pandas, the ``base`` parameter is deprecated in favor - of the ``origin`` and ``offset`` parameters, and will be removed - in a future version of xarray. - origin : {'epoch', 'start', 'start_day', 'end', 'end_day'}, pd.Timestamp, datetime.datetime, np.datetime64, or cftime.datetime, default 'start_day' The datetime on which to adjust the grouping. The timezone of origin must match the timezone of the index. @@ -928,15 +916,6 @@ def _resample( - 'end_day': `origin` is the ceiling midnight of the last day offset : pd.Timedelta, datetime.timedelta, or str, default is None An offset timedelta added to the origin. - loffset : timedelta or str, optional - Offset used to adjust the resampled time labels. Some pandas date - offset strings are supported. - - .. deprecated:: 2023.03.0 - Following pandas, the ``loffset`` parameter is deprecated in favor - of using time offset arithmetic, and will be removed in a future - version of xarray. - restore_coord_dims : bool, optional If True, also restore the dimension order of multi-dimensional coordinates. @@ -1072,18 +1051,6 @@ def _resample( from xarray.core.groupers import Resampler, TimeResampler from xarray.core.resample import RESAMPLE_DIM - # note: the second argument (now 'skipna') use to be 'dim' - if ( - (skipna is not None and not isinstance(skipna, bool)) - or ("how" in indexer_kwargs and "how" not in self.dims) - or ("dim" in indexer_kwargs and "dim" not in self.dims) - ): - raise TypeError( - "resample() no longer supports the `how` or " - "`dim` arguments. Instead call methods on resample " - "objects, e.g., data.resample(time='1D').mean()" - ) - indexer = either_dict_or_kwargs(indexer, indexer_kwargs, "resample") if len(indexer) != 1: raise ValueError("Resampling only supported along single dimensions.") @@ -1093,22 +1060,13 @@ def _resample( dim_coord = self[dim] group = DataArray( - dim_coord, - coords=dim_coord.coords, - dims=dim_coord.dims, - name=RESAMPLE_DIM, + dim_coord, coords=dim_coord.coords, dims=dim_coord.dims, name=RESAMPLE_DIM ) grouper: Resampler if isinstance(freq, str): grouper = TimeResampler( - freq=freq, - closed=closed, - label=label, - origin=origin, - offset=offset, - loffset=loffset, - base=base, + freq=freq, closed=closed, label=label, origin=origin, offset=offset ) elif isinstance(freq, Resampler): grouper = freq diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 52af186cb05..67d6ab62511 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -7249,10 +7249,8 @@ def resample( skipna: bool | None = None, closed: SideOptions | None = None, label: SideOptions | None = None, - base: int | None = None, offset: pd.Timedelta | datetime.timedelta | str | None = None, origin: str | DatetimeLike = "start_day", - loffset: datetime.timedelta | str | None = None, restore_coord_dims: bool | None = None, **indexer_kwargs: str | Resampler, ) -> DataArrayResample: @@ -7274,10 +7272,6 @@ def resample( Side of each interval to treat as closed. label : {"left", "right"}, optional Side of each interval to use for labeling. - base : int, optional - For frequencies that evenly subdivide 1 day, the "origin" of the - aggregated intervals. For example, for "24H" frequency, base could - range from 0 through 23. origin : {'epoch', 'start', 'start_day', 'end', 'end_day'}, pd.Timestamp, datetime.datetime, np.datetime64, or cftime.datetime, default 'start_day' The datetime on which to adjust the grouping. The timezone of origin must match the timezone of the index. @@ -7290,15 +7284,6 @@ def resample( - 'end_day': `origin` is the ceiling midnight of the last day offset : pd.Timedelta, datetime.timedelta, or str, default is None An offset timedelta added to the origin. - loffset : timedelta or str, optional - Offset used to adjust the resampled time labels. Some pandas date - offset strings are supported. - - .. deprecated:: 2023.03.0 - Following pandas, the ``loffset`` parameter is deprecated in favor - of using time offset arithmetic, and will be removed in a future - version of xarray. - restore_coord_dims : bool, optional If True, also restore the dimension order of multi-dimensional coordinates. @@ -7403,10 +7388,8 @@ def resample( skipna=skipna, closed=closed, label=label, - base=base, offset=offset, origin=origin, - loffset=loffset, restore_coord_dims=restore_coord_dims, **indexer_kwargs, ) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 8815b42ff8d..3379e405396 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -6412,7 +6412,7 @@ def dropna( Data variables: temperature (time, location) float64 64B 23.4 24.1 nan ... 24.2 20.5 25.3 - # Drop NaN values from the dataset + Drop NaN values from the dataset >>> dataset.dropna(dim="time") Size: 80B @@ -6423,7 +6423,7 @@ def dropna( Data variables: temperature (time, location) float64 48B 23.4 24.1 21.8 24.2 20.5 25.3 - # Drop labels with any NAN values + Drop labels with any NaN values >>> dataset.dropna(dim="time", how="any") Size: 80B @@ -6434,7 +6434,7 @@ def dropna( Data variables: temperature (time, location) float64 48B 23.4 24.1 21.8 24.2 20.5 25.3 - # Drop labels with all NAN values + Drop labels with all NAN values >>> dataset.dropna(dim="time", how="all") Size: 104B @@ -6445,7 +6445,7 @@ def dropna( Data variables: temperature (time, location) float64 64B 23.4 24.1 nan ... 24.2 20.5 25.3 - # Drop labels with less than 2 non-NA values + Drop labels with less than 2 non-NA values >>> dataset.dropna(dim="time", thresh=2) Size: 80B @@ -6459,6 +6459,11 @@ def dropna( Returns ------- Dataset + + See Also + -------- + DataArray.dropna + pandas.DataFrame.dropna """ # TODO: consider supporting multiple dimensions? Or not, given that # there are some ugly edge cases, e.g., pandas's dropna differs @@ -10667,10 +10672,8 @@ def resample( skipna: bool | None = None, closed: SideOptions | None = None, label: SideOptions | None = None, - base: int | None = None, offset: pd.Timedelta | datetime.timedelta | str | None = None, origin: str | DatetimeLike = "start_day", - loffset: datetime.timedelta | str | None = None, restore_coord_dims: bool | None = None, **indexer_kwargs: str | Resampler, ) -> DatasetResample: @@ -10692,10 +10695,6 @@ def resample( Side of each interval to treat as closed. label : {"left", "right"}, optional Side of each interval to use for labeling. - base : int, optional - For frequencies that evenly subdivide 1 day, the "origin" of the - aggregated intervals. For example, for "24H" frequency, base could - range from 0 through 23. origin : {'epoch', 'start', 'start_day', 'end', 'end_day'}, pd.Timestamp, datetime.datetime, np.datetime64, or cftime.datetime, default 'start_day' The datetime on which to adjust the grouping. The timezone of origin must match the timezone of the index. @@ -10708,15 +10707,6 @@ def resample( - 'end_day': `origin` is the ceiling midnight of the last day offset : pd.Timedelta, datetime.timedelta, or str, default is None An offset timedelta added to the origin. - loffset : timedelta or str, optional - Offset used to adjust the resampled time labels. Some pandas date - offset strings are supported. - - .. deprecated:: 2023.03.0 - Following pandas, the ``loffset`` parameter is deprecated in favor - of using time offset arithmetic, and will be removed in a future - version of xarray. - restore_coord_dims : bool, optional If True, also restore the dimension order of multi-dimensional coordinates. @@ -10749,10 +10739,8 @@ def resample( skipna=skipna, closed=closed, label=label, - base=base, offset=offset, origin=origin, - loffset=loffset, restore_coord_dims=restore_coord_dims, **indexer_kwargs, ) diff --git a/xarray/core/groupers.py b/xarray/core/groupers.py index db4bd0848b5..3503d04271a 100644 --- a/xarray/core/groupers.py +++ b/xarray/core/groupers.py @@ -9,7 +9,7 @@ import datetime from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Literal, cast +from typing import Any, Literal, cast import numpy as np import pandas as pd @@ -21,12 +21,8 @@ from xarray.core.indexes import safe_cast_to_index from xarray.core.resample_cftime import CFTimeGrouper from xarray.core.types import Bins, DatetimeLike, GroupIndices, SideOptions -from xarray.core.utils import emit_user_level_warning from xarray.core.variable import Variable -if TYPE_CHECKING: - pass - __all__ = [ "EncodedGroups", "Grouper", @@ -299,17 +295,7 @@ class TimeResampler(Resampler): Side of each interval to treat as closed. label : {"left", "right"}, optional Side of each interval to use for labeling. - base : int, optional - For frequencies that evenly subdivide 1 day, the "origin" of the - aggregated intervals. For example, for "24H" frequency, base could - range from 0 through 23. - - .. deprecated:: 2023.03.0 - Following pandas, the ``base`` parameter is deprecated in favor - of the ``origin`` and ``offset`` parameters, and will be removed - in a future version of xarray. - - origin : {"epoch", "start", "start_day", "end", "end_day"}, pandas.Timestamp, datetime.datetime, numpy.datetime64, or cftime.datetime, default: "start_day" + origin : {'epoch', 'start', 'start_day', 'end', 'end_day'}, pandas.Timestamp, datetime.datetime, numpy.datetime64, or cftime.datetime, default 'start_day' The datetime on which to adjust the grouping. The timezone of origin must match the timezone of the index. @@ -321,15 +307,6 @@ class TimeResampler(Resampler): - 'end_day': `origin` is the ceiling midnight of the last day offset : pd.Timedelta, datetime.timedelta, or str, default is None An offset timedelta added to the origin. - loffset : timedelta or str, optional - Offset used to adjust the resampled time labels. Some pandas date - offset strings are supported. - - .. deprecated:: 2023.03.0 - Following pandas, the ``loffset`` parameter is deprecated in favor - of using time offset arithmetic, and will be removed in a future - version of xarray. - """ freq: str @@ -337,51 +314,15 @@ class TimeResampler(Resampler): label: SideOptions | None = field(default=None) origin: str | DatetimeLike = field(default="start_day") offset: pd.Timedelta | datetime.timedelta | str | None = field(default=None) - loffset: datetime.timedelta | str | None = field(default=None) - base: int | None = field(default=None) index_grouper: CFTimeGrouper | pd.Grouper = field(init=False, repr=False) group_as_index: pd.Index = field(init=False, repr=False) - def __repr__(self): - return ( - f"" - ) - - def __post_init__(self): - if self.loffset is not None: - emit_user_level_warning( - "Following pandas, the `loffset` parameter to resample is deprecated. " - "Switch to updating the resampled dataset time coordinate using " - "time offset arithmetic. For example:\n" - " >>> offset = pd.tseries.frequencies.to_offset(freq) / 2\n" - ' >>> resampled_ds["time"] = resampled_ds.get_index("time") + offset', - FutureWarning, - ) - - if self.base is not None: - emit_user_level_warning( - "Following pandas, the `base` parameter to resample will be deprecated in " - "a future version of xarray. Switch to using `origin` or `offset` instead.", - FutureWarning, - ) - - if self.base is not None and self.offset is not None: - raise ValueError("base and offset cannot be present at the same time") - def _init_properties(self, group: T_Group) -> None: from xarray import CFTimeIndex - from xarray.core.pdcompat import _convert_base_to_offset group_as_index = safe_cast_to_index(group) - - if self.base is not None: - # grouper constructor verifies that grouper.offset is None at this point - offset = _convert_base_to_offset(self.base, self.freq, group_as_index) - else: - offset = self.offset + offset = self.offset if not group_as_index.is_monotonic_increasing: # TODO: sort instead of raising an error @@ -396,7 +337,6 @@ def _init_properties(self, group: T_Group) -> None: label=self.label, origin=self.origin, offset=offset, - loffset=self.loffset, ) else: self.index_grouper = pd.Grouper( @@ -426,18 +366,16 @@ def first_items(self) -> tuple[pd.Series, np.ndarray]: return self.index_grouper.first_items( cast(CFTimeIndex, self.group_as_index) ) - - s = pd.Series(np.arange(self.group_as_index.size), self.group_as_index) - grouped = s.groupby(self.index_grouper) - first_items = grouped.first() - counts = grouped.count() - # This way we generate codes for the final output index: full_index. - # So for _flox_reduce we avoid one reindex and copy by avoiding - # _maybe_restore_empty_groups - codes = np.repeat(np.arange(len(first_items)), counts) - if self.loffset is not None: - _apply_loffset(self.loffset, first_items) - return first_items, codes + else: + s = pd.Series(np.arange(self.group_as_index.size), self.group_as_index) + grouped = s.groupby(self.index_grouper) + first_items = grouped.first() + counts = grouped.count() + # This way we generate codes for the final output index: full_index. + # So for _flox_reduce we avoid one reindex and copy by avoiding + # _maybe_restore_empty_groups + codes = np.repeat(np.arange(len(first_items)), counts) + return first_items, codes def factorize(self, group: T_Group) -> EncodedGroups: self._init_properties(group) @@ -461,43 +399,6 @@ def factorize(self, group: T_Group) -> EncodedGroups: ) -def _apply_loffset( - loffset: str | pd.DateOffset | datetime.timedelta | pd.Timedelta, - result: pd.Series | pd.DataFrame, -): - """ - (copied from pandas) - if loffset is set, offset the result index - - This is NOT an idempotent routine, it will be applied - exactly once to the result. - - Parameters - ---------- - result : Series or DataFrame - the result of resample - """ - # pd.Timedelta is a subclass of datetime.timedelta so we do not need to - # include it in instance checks. - if not isinstance(loffset, (str, pd.DateOffset, datetime.timedelta)): - raise ValueError( - f"`loffset` must be a str, pd.DateOffset, datetime.timedelta, or pandas.Timedelta object. " - f"Got {loffset}." - ) - - if isinstance(loffset, str): - loffset = pd.tseries.frequencies.to_offset(loffset) # type: ignore[assignment] - - needs_offset = ( - isinstance(loffset, (pd.DateOffset, datetime.timedelta)) - and isinstance(result.index, pd.DatetimeIndex) - and len(result.index) > 0 - ) - - if needs_offset: - result.index = result.index + loffset - - def unique_value_groups( ar, sort: bool = True ) -> tuple[np.ndarray | pd.Index, np.ndarray]: diff --git a/xarray/core/pdcompat.py b/xarray/core/pdcompat.py index c09dd82b074..ae4febd6beb 100644 --- a/xarray/core/pdcompat.py +++ b/xarray/core/pdcompat.py @@ -41,8 +41,6 @@ import pandas as pd from packaging.version import Version -from xarray.coding import cftime_offsets - def count_not_none(*args) -> int: """Compute the number of non-None arguments. @@ -75,26 +73,6 @@ def __repr__(self) -> str: NoDefault = Literal[_NoDefault.no_default] # For typing following pandas -def _convert_base_to_offset(base, freq, index): - """Required until we officially deprecate the base argument to resample. This - translates a provided `base` argument to an `offset` argument, following logic - from pandas. - """ - from xarray.coding.cftimeindex import CFTimeIndex - - if isinstance(index, pd.DatetimeIndex): - freq = cftime_offsets._new_to_legacy_freq(freq) - freq = pd.tseries.frequencies.to_offset(freq) - if isinstance(freq, pd.offsets.Tick): - return pd.Timedelta(base * freq.nanos // freq.n) - elif isinstance(index, CFTimeIndex): - freq = cftime_offsets.to_offset(freq) - if isinstance(freq, cftime_offsets.Tick): - return base * freq.as_timedelta() // freq.n - else: - raise ValueError("Can only resample using a DatetimeIndex or CFTimeIndex.") - - def nanosecond_precision_timestamp(*args, **kwargs) -> pd.Timestamp: """Return a nanosecond-precision Timestamp object. diff --git a/xarray/core/resample_cftime.py b/xarray/core/resample_cftime.py index a048e85b4d4..caa2d166d24 100644 --- a/xarray/core/resample_cftime.py +++ b/xarray/core/resample_cftime.py @@ -78,12 +78,10 @@ def __init__( freq: str | BaseCFTimeOffset, closed: SideOptions | None = None, label: SideOptions | None = None, - loffset: str | datetime.timedelta | BaseCFTimeOffset | None = None, origin: str | CFTimeDatetime = "start_day", offset: str | datetime.timedelta | BaseCFTimeOffset | None = None, ): self.freq = to_offset(freq) - self.loffset = loffset self.origin = origin if isinstance(self.freq, (MonthEnd, QuarterEnd, YearEnd)): @@ -145,22 +143,6 @@ def first_items(self, index: CFTimeIndex): datetime_bins, labels = _get_time_bins( index, self.freq, self.closed, self.label, self.origin, self.offset ) - if self.loffset is not None: - if not isinstance( - self.loffset, (str, datetime.timedelta, BaseCFTimeOffset) - ): - # BaseCFTimeOffset is not public API so we do not include it in - # the error message for now. - raise ValueError( - f"`loffset` must be a str or datetime.timedelta object. " - f"Got {self.loffset}." - ) - - if isinstance(self.loffset, datetime.timedelta): - labels = labels + self.loffset - else: - labels = labels + to_offset(self.loffset) - # check binner fits data if index[0] < datetime_bins[0]: raise ValueError("Value falls before first bin") diff --git a/xarray/tests/test_cftimeindex_resample.py b/xarray/tests/test_cftimeindex_resample.py index 3dda7a5f1eb..081970108a0 100644 --- a/xarray/tests/test_cftimeindex_resample.py +++ b/xarray/tests/test_cftimeindex_resample.py @@ -11,7 +11,6 @@ import xarray as xr from xarray.coding.cftime_offsets import _new_to_legacy_freq from xarray.coding.cftimeindex import CFTimeIndex -from xarray.core.pdcompat import _convert_base_to_offset from xarray.core.resample_cftime import CFTimeGrouper cftime = pytest.importorskip("cftime") @@ -61,10 +60,8 @@ def compare_against_pandas( freq, closed=None, label=None, - base=None, offset=None, origin=None, - loffset=None, ) -> None: if isinstance(origin, tuple): origin_pandas = pd.Timestamp(datetime.datetime(*origin)) @@ -78,8 +75,6 @@ def compare_against_pandas( time=freq, closed=closed, label=label, - base=base, - loffset=loffset, offset=offset, origin=origin_pandas, ).mean() @@ -89,8 +84,6 @@ def compare_against_pandas( time=freq, closed=closed, label=label, - base=base, - loffset=loffset, origin=origin_cftime, offset=offset, ).mean() @@ -99,8 +92,6 @@ def compare_against_pandas( time=freq, closed=closed, label=label, - base=base, - loffset=loffset, origin=origin_cftime, offset=offset, ).mean() @@ -117,14 +108,11 @@ def da(index) -> xr.DataArray: ) -@pytest.mark.filterwarnings("ignore:.*the `(base|loffset)` parameter to resample") @pytest.mark.parametrize("freqs", FREQS, ids=lambda x: "{}->{}".format(*x)) @pytest.mark.parametrize("closed", [None, "left", "right"]) @pytest.mark.parametrize("label", [None, "left", "right"]) -@pytest.mark.parametrize( - ("base", "offset"), [(24, None), (31, None), (None, "5s")], ids=lambda x: f"{x}" -) -def test_resample(freqs, closed, label, base, offset) -> None: +@pytest.mark.parametrize("offset", [None, "5s"], ids=lambda x: f"{x}") +def test_resample(freqs, closed, label, offset) -> None: initial_freq, resample_freq = freqs if ( resample_freq == "4001D" @@ -137,7 +125,6 @@ def test_resample(freqs, closed, label, base, offset) -> None: "result as pandas for earlier pandas versions." ) start = "2000-01-01T12:07:01" - loffset = "12h" origin = "start" datetime_index = pd.date_range( @@ -147,18 +134,15 @@ def test_resample(freqs, closed, label, base, offset) -> None: da_datetimeindex = da(datetime_index) da_cftimeindex = da(cftime_index) - with pytest.warns(FutureWarning, match="`loffset` parameter"): - compare_against_pandas( - da_datetimeindex, - da_cftimeindex, - resample_freq, - closed=closed, - label=label, - base=base, - offset=offset, - origin=origin, - loffset=loffset, - ) + compare_against_pandas( + da_datetimeindex, + da_cftimeindex, + resample_freq, + closed=closed, + label=label, + offset=offset, + origin=origin, + ) @pytest.mark.parametrize( @@ -182,28 +166,18 @@ def test_closed_label_defaults(freq, expected) -> None: @pytest.mark.filterwarnings("ignore:Converting a CFTimeIndex") -@pytest.mark.filterwarnings("ignore:.*the `(base|loffset)` parameter to resample") @pytest.mark.parametrize( "calendar", ["gregorian", "noleap", "all_leap", "360_day", "julian"] ) def test_calendars(calendar: str) -> None: # Limited testing for non-standard calendars - freq, closed, label, base = "8001min", None, None, 17 - loffset = datetime.timedelta(hours=12) + freq, closed, label = "8001min", None, None xr_index = xr.cftime_range( start="2004-01-01T12:07:01", periods=7, freq="3D", calendar=calendar ) pd_index = pd.date_range(start="2004-01-01T12:07:01", periods=7, freq="3D") - da_cftime = ( - da(xr_index) - .resample(time=freq, closed=closed, label=label, base=base, loffset=loffset) - .mean() - ) - da_datetime = ( - da(pd_index) - .resample(time=freq, closed=closed, label=label, base=base, loffset=loffset) - .mean() - ) + da_cftime = da(xr_index).resample(time=freq, closed=closed, label=label).mean() + da_datetime = da(pd_index).resample(time=freq, closed=closed, label=label).mean() # TODO (benbovy - flexible indexes): update when CFTimeIndex is a xarray Index subclass new_pd_index = da_cftime.xindexes["time"].to_pandas_index() assert isinstance(new_pd_index, CFTimeIndex) # shouldn't that be a pd.Index? @@ -217,7 +191,6 @@ class DateRangeKwargs(TypedDict): freq: str -@pytest.mark.filterwarnings("ignore:.*the `(base|loffset)` parameter to resample") @pytest.mark.parametrize("closed", ["left", "right"]) @pytest.mark.parametrize( "origin", @@ -242,14 +215,6 @@ def test_origin(closed, origin) -> None: ) -@pytest.mark.filterwarnings("ignore:.*the `(base|loffset)` parameter to resample") -def test_base_and_offset_error(): - cftime_index = xr.cftime_range("2000", periods=5) - da_cftime = da(cftime_index) - with pytest.raises(ValueError, match="base and offset cannot"): - da_cftime.resample(time="2D", base=3, offset="5s") - - @pytest.mark.parametrize("offset", ["foo", "5MS", 10]) def test_invalid_offset_error(offset: str | int) -> None: cftime_index = xr.cftime_range("2000", periods=5) @@ -268,46 +233,3 @@ def test_timedelta_offset() -> None: timedelta_result = da_cftime.resample(time="2D", offset=timedelta).mean() string_result = da_cftime.resample(time="2D", offset=string).mean() xr.testing.assert_identical(timedelta_result, string_result) - - -@pytest.mark.parametrize("loffset", ["MS", "12h", datetime.timedelta(hours=-12)]) -def test_resample_loffset_cftimeindex(loffset) -> None: - datetimeindex = pd.date_range("2000-01-01", freq="6h", periods=10) - da_datetimeindex = xr.DataArray(np.arange(10), [("time", datetimeindex)]) - - cftimeindex = xr.cftime_range("2000-01-01", freq="6h", periods=10) - da_cftimeindex = xr.DataArray(np.arange(10), [("time", cftimeindex)]) - - with pytest.warns(FutureWarning, match="`loffset` parameter"): - result = da_cftimeindex.resample(time="24h", loffset=loffset).mean() - expected = da_datetimeindex.resample(time="24h", loffset=loffset).mean() - - index = result.xindexes["time"].to_pandas_index() - assert isinstance(index, CFTimeIndex) - result["time"] = index.to_datetimeindex() - xr.testing.assert_identical(result, expected) - - -@pytest.mark.filterwarnings("ignore:.*the `(base|loffset)` parameter to resample") -def test_resample_invalid_loffset_cftimeindex() -> None: - times = xr.cftime_range("2000-01-01", freq="6h", periods=10) - da = xr.DataArray(np.arange(10), [("time", times)]) - - with pytest.raises(ValueError): - da.resample(time="24h", loffset=1) # type: ignore - - -@pytest.mark.parametrize(("base", "freq"), [(1, "10s"), (17, "3h"), (15, "5us")]) -def test__convert_base_to_offset(base, freq): - # Verify that the cftime_offset adapted version of _convert_base_to_offset - # produces the same result as the pandas version. - datetimeindex = pd.date_range("2000", periods=2) - cftimeindex = xr.cftime_range("2000", periods=2) - pandas_result = _convert_base_to_offset(base, freq, datetimeindex) - cftime_result = _convert_base_to_offset(base, freq, cftimeindex) - assert pandas_result.to_pytimedelta() == cftime_result - - -def test__convert_base_to_offset_invalid_index(): - with pytest.raises(ValueError, match="Can only resample"): - _convert_base_to_offset(1, "12h", pd.Index([0])) diff --git a/xarray/tests/test_groupby.py b/xarray/tests/test_groupby.py index bd612decc52..c44848e24d6 100644 --- a/xarray/tests/test_groupby.py +++ b/xarray/tests/test_groupby.py @@ -1,6 +1,5 @@ from __future__ import annotations -import datetime import operator import warnings from unittest import mock @@ -2205,19 +2204,6 @@ def test_upsample_interpolate_dask(self, chunked_time: bool) -> None: # done here due to floating point arithmetic assert_allclose(expected, actual, rtol=1e-16) - def test_resample_base(self) -> None: - times = pd.date_range("2000-01-01T02:03:01", freq="6h", periods=10) - array = DataArray(np.arange(10), [("time", times)]) - - base = 11 - - with pytest.warns(FutureWarning, match="the `base` parameter to resample"): - actual = array.resample(time="24h", base=base).mean() - expected = DataArray( - array.to_series().resample("24h", offset=f"{base}h").mean() - ) - assert_identical(expected, actual) - def test_resample_offset(self) -> None: times = pd.date_range("2000-01-01T02:03:01", freq="6h", periods=10) array = DataArray(np.arange(10), [("time", times)]) @@ -2236,38 +2222,6 @@ def test_resample_origin(self) -> None: expected = DataArray(array.to_series().resample("24h", origin=origin).mean()) assert_identical(expected, actual) - @pytest.mark.parametrize( - "loffset", - [ - "-12h", - datetime.timedelta(hours=-12), - pd.Timedelta(hours=-12), - pd.DateOffset(hours=-12), - ], - ) - def test_resample_loffset(self, loffset) -> None: - times = pd.date_range("2000-01-01", freq="6h", periods=10) - array = DataArray(np.arange(10), [("time", times)]) - - with pytest.warns(FutureWarning, match="`loffset` parameter"): - actual = array.resample(time="24h", loffset=loffset).mean() - series = array.to_series().resample("24h").mean() - if not isinstance(loffset, pd.DateOffset): - loffset = pd.Timedelta(loffset) - series.index = series.index + loffset - expected = DataArray(series) - assert_identical(actual, expected) - - def test_resample_invalid_loffset(self) -> None: - times = pd.date_range("2000-01-01", freq="6h", periods=10) - array = DataArray(np.arange(10), [("time", times)]) - - with pytest.warns( - FutureWarning, match="Following pandas, the `loffset` parameter" - ): - with pytest.raises(ValueError, match="`loffset` must be"): - array.resample(time="24h", loffset=1).mean() # type: ignore - class TestDatasetResample: def test_resample_and_first(self) -> None: @@ -2338,17 +2292,6 @@ def test_resample_by_mean_with_keep_attrs(self) -> None: expected = ds.attrs assert expected == actual - def test_resample_loffset(self) -> None: - times = pd.date_range("2000-01-01", freq="6h", periods=10) - ds = Dataset( - { - "foo": (["time", "x", "y"], np.random.randn(10, 5, 3)), - "bar": ("time", np.random.randn(10), {"meta": "data"}), - "time": times, - } - ) - ds.attrs["dsmeta"] = "dsdata" - def test_resample_by_mean_discarding_attrs(self) -> None: times = pd.date_range("2000-01-01", freq="6h", periods=10) ds = Dataset( @@ -2408,25 +2351,6 @@ def test_resample_drop_nondim_coords(self) -> None: actual = ds.resample(time="1h").interpolate("linear") assert "tc" not in actual.coords - def test_resample_old_api(self) -> None: - times = pd.date_range("2000-01-01", freq="6h", periods=10) - ds = Dataset( - { - "foo": (["time", "x", "y"], np.random.randn(10, 5, 3)), - "bar": ("time", np.random.randn(10), {"meta": "data"}), - "time": times, - } - ) - - with pytest.raises(TypeError, match=r"resample\(\) no longer supports"): - ds.resample("1D", "time") # type: ignore[arg-type] - - with pytest.raises(TypeError, match=r"resample\(\) no longer supports"): - ds.resample("1D", dim="time", how="mean") # type: ignore[arg-type] - - with pytest.raises(TypeError, match=r"resample\(\) no longer supports"): - ds.resample("1D", dim="time") # type: ignore[arg-type] - def test_resample_ds_da_are_the_same(self) -> None: time = pd.date_range("2000-01-01", freq="6h", periods=365 * 4) ds = xr.Dataset(