diff --git a/doc/whats-new.rst b/doc/whats-new.rst index d1505bfa081..469b678953b 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -104,7 +104,14 @@ Bug Fixes entries; ``to_dataframe`` indexes by the union of stored entries across all sparse variables sharing the same dims (:issue:`4007`). By `patnr `_. +- Following `pandas-dev/pandas#64793`_, ensure that resampling an array to a + ``Day`` frequency along a :py:class:`xarray.CFTimeIndex` produces the same + results as resampling to an equivalent ``Hour`` frequency, including with the + use of ``origin`` and ``offset`` options (:pull:`11546`). This effectively + rolls back the resample-related changes introduced in :pull:`10650`. By + `Spencer Clark `_. +.. _`pandas-dev/pandas#64793`: https://github.com/pandas-dev/pandas/pull/64793 Documentation ~~~~~~~~~~~~~ diff --git a/xarray/core/resample_cftime.py b/xarray/core/resample_cftime.py index 82e99fc9247..67022635bb0 100644 --- a/xarray/core/resample_cftime.py +++ b/xarray/core/resample_cftime.py @@ -45,8 +45,9 @@ import pandas as pd from xarray.coding.cftime_offsets import ( - CFTIME_TICKS, BaseCFTimeOffset, + Day, + Hour, MonthEnd, QuarterEnd, Tick, @@ -86,19 +87,19 @@ def __init__( self.freq = to_offset(freq) self.origin = origin - if not isinstance(self.freq, CFTIME_TICKS): + if not isinstance(self.freq, (Tick, Day)): if offset is not None: message = ( "The 'offset' keyword does not take effect when " - "resampling with a 'freq' that is not Tick-like (h, m, s, " - "ms, us)" + "resampling with a 'freq' that is not Tick-like (D, h, m, " + "s, ms, us)" ) emit_user_level_warning(message, category=RuntimeWarning) if origin != "start_day": message = ( "The 'origin' keyword does not take effect when " - "resampling with a 'freq' that is not Tick-like (h, m, s, " - "ms, us)" + "resampling with a 'freq' that is not Tick-like (D, h, m, " + "s, ms, us)" ) emit_user_level_warning(message, category=RuntimeWarning) @@ -338,7 +339,9 @@ def _get_range_edges( last : cftime.datetime Corrected ending datetime object for resampled CFTimeIndex range. """ - if isinstance(freq, Tick): + if isinstance(freq, (Tick, Day)): + if isinstance(freq, Day): + freq = Hour(24 * freq.n) first, last = _adjust_dates_anchored( first, last, freq, closed=closed, origin=origin, offset=offset ) diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py index 5dd8793a977..15326b1d7e7 100644 --- a/xarray/tests/__init__.py +++ b/xarray/tests/__init__.py @@ -198,7 +198,7 @@ def get_dask_chunkmanager(): has_flox, requires_flox = _importorskip("flox") has_netcdf, requires_netcdf = _importorskip("netcdf") has_pandas_3, requires_pandas_3 = _importorskip("pandas", "3.0.0") - +has_pandas_3_1, requires_pandas_3_1 = _importorskip("pandas", "3.1.0.dev0") # some special cases has_scipy_or_netCDF4 = has_scipy or has_netCDF4 diff --git a/xarray/tests/test_cftimeindex_resample.py b/xarray/tests/test_cftimeindex_resample.py index 1f31c1373a4..d40b4a95f2a 100644 --- a/xarray/tests/test_cftimeindex_resample.py +++ b/xarray/tests/test_cftimeindex_resample.py @@ -9,14 +9,14 @@ import xarray as xr from xarray.coding.cftime_offsets import ( - CFTIME_TICKS, Day, + Tick, to_offset, ) from xarray.coding.cftimeindex import CFTimeIndex from xarray.core.resample_cftime import CFTimeGrouper from xarray.core.types import PDDatetimeUnitOptions -from xarray.tests import has_pandas_3 +from xarray.tests import has_pandas_3_1 cftime = pytest.importorskip("cftime") @@ -60,9 +60,11 @@ def has_tick_resample_freq(freqs): - resample_freq, _ = freqs + _, resample_freq = freqs resample_freq_as_offset = to_offset(resample_freq) - return isinstance(resample_freq_as_offset, CFTIME_TICKS) + # As of pandas 3.1, in the context of resampling a timezone-naive index, + # Day is treated like a Tick frequency (pandas-dev/pandas#64793). + return isinstance(resample_freq_as_offset, (Tick, Day)) def has_non_tick_resample_freq(freqs): @@ -137,6 +139,9 @@ def da(index) -> xr.DataArray: @pytest.mark.parametrize("offset", [None, "5s"], ids=lambda x: f"{x}") def test_resample_with_tick_resample_freq(freqs, closed, label, offset) -> None: initial_freq, resample_freq = freqs + resample_freq_as_offset = to_offset(resample_freq) + if isinstance(resample_freq_as_offset, Day) and not has_pandas_3_1: + pytest.skip("Only valid for pandas >= 3.1") start = "2000-01-01T12:07:01" origin = "start" @@ -165,9 +170,6 @@ def test_resample_with_tick_resample_freq(freqs, closed, label, offset) -> None: @pytest.mark.parametrize("label", [None, "left", "right"]) def test_resample_with_non_tick_resample_freq(freqs, closed, label) -> None: initial_freq, resample_freq = freqs - resample_freq_as_offset = to_offset(resample_freq) - if isinstance(resample_freq_as_offset, Day) and not has_pandas_3: - pytest.skip("Only valid for pandas >= 3.0") start = "2000-01-01T12:07:01" # Set offset and origin to their default values since they have no effect