Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions _stubtest/allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ xarray\.core\.dataset\.DatasetResample$

xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.date_type$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.day$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.day_of_week$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.dayofweek$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.day_of_year$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.dayofyear$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.days_in_month$
xarray\.core\.(dataarray|dataset)\.CFTimeIndex\.hour$
Expand Down
4 changes: 4 additions & 0 deletions doc/api-hidden.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@
core.accessor_dt.DatetimeAccessor.date
core.accessor_dt.DatetimeAccessor.day
core.accessor_dt.DatetimeAccessor.dayofweek
core.accessor_dt.DatetimeAccessor.day_of_week
core.accessor_dt.DatetimeAccessor.dayofyear
core.accessor_dt.DatetimeAccessor.day_of_year
core.accessor_dt.DatetimeAccessor.days_in_month
core.accessor_dt.DatetimeAccessor.daysinmonth
core.accessor_dt.DatetimeAccessor.hour
Expand Down Expand Up @@ -409,6 +411,8 @@
CFTimeIndex.ceil
CFTimeIndex.contains
CFTimeIndex.copy
CFTimeIndex.day_of_week
CFTimeIndex.day_of_year
CFTimeIndex.days_in_month
CFTimeIndex.delete
CFTimeIndex.difference
Expand Down
2 changes: 2 additions & 0 deletions doc/api/dataarray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,10 @@ Datetimelike properties
DataArray.dt.second
DataArray.dt.microsecond
DataArray.dt.nanosecond
DataArray.dt.day_of_week
DataArray.dt.dayofweek
DataArray.dt.weekday
DataArray.dt.day_of_year
DataArray.dt.dayofyear
DataArray.dt.quarter
DataArray.dt.days_in_month
Expand Down
2 changes: 1 addition & 1 deletion doc/getting-started-guide/why-xarray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ powerful and concise interface. For example:
dimensions (array broadcasting) based on dimension names, not shape.
- Easily use the `split-apply-combine <https://vita.had.co.nz/papers/plyr.pdf>`_
paradigm with ``groupby``:
``x.groupby('time.dayofyear').mean()``.
``x.groupby('time.day_of_year').mean()``.
- Database-like alignment based on coordinate labels that smoothly
handles missing values: ``x, y = xr.align(x, y, join='outer')``.
- Keep track of arbitrary metadata in the form of a Python dictionary:
Expand Down
8 changes: 4 additions & 4 deletions doc/user-guide/time-series.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ given ``DataArray`` can be quickly computed using a special ``.dt`` accessor.

.. jupyter-execute::

ds.time.dt.dayofweek
ds.time.dt.day_of_week

The ``.dt`` accessor works on both coordinate dimensions as well as
multi-dimensional data.

Xarray also supports a notion of "virtual" or "derived" coordinates for
`datetime components`__ implemented by pandas, including "year", "month",
"day", "hour", "minute", "second", "dayofyear", "week", "dayofweek", "weekday"
and "quarter":
"day", "hour", "minute", "second", "day_of_year", "week", "day_of_week", and
"quarter":

__ https://pandas.pydata.org/pandas-docs/stable/api.html#time-date-components

Expand All @@ -168,7 +168,7 @@ __ https://pandas.pydata.org/pandas-docs/stable/api.html#time-date-components

.. jupyter-execute::

ds["time.dayofyear"]
ds["time.day_of_year"]

For use as a derived coordinate, xarray adds ``'season'`` to the list of
datetime components supported by pandas:
Expand Down
8 changes: 4 additions & 4 deletions doc/user-guide/weather-climate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ For data indexed by a :py:class:`~xarray.CFTimeIndex` xarray currently supports:

- Access of basic datetime components via the ``dt`` accessor (in this case
just "year", "month", "day", "hour", "minute", "second", "microsecond",
"season", "dayofyear", "dayofweek", and "days_in_month") with the addition
of "calendar", absent from pandas:
"season", "day_of_year", "day_of_week", and "days_in_month") with the
addition of "calendar", absent from pandas:

.. jupyter-execute::

Expand All @@ -201,11 +201,11 @@ For data indexed by a :py:class:`~xarray.CFTimeIndex` xarray currently supports:

.. jupyter-execute::

da.time.dt.dayofyear
da.time.dt.day_of_year

.. jupyter-execute::

da.time.dt.dayofweek
da.time.dt.day_of_week

.. jupyter-execute::

Expand Down
20 changes: 19 additions & 1 deletion doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,25 @@ Breaking Changes

Deprecations
~~~~~~~~~~~~

- Following pandas, on xarray's
:py:class:`~xarray.core.accessor_dt.DatetimeAccessor`,
:py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.daysinmonth`
is deprecated in favor of
:py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.days_in_month`;
:py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.dayofweek` and
:py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.weekday` are deprecated
in favor of :py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.day_of_week`;
and :py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.dayofyear` is
deprecated in favor of
:py:attr:`~xarray.core.accessor_dt.DatetimeAccessor.day_of_year`
(:issue:`11268`, :pull:`11270`). By `Spencer Clark
<https://github.com/spencerkclark>`_.
- Following pandas, on xarray's :py:class:`~xarray.CFTimeIndex`,
:py:attr:`~xarray.CFTimeIndex.dayofweek` and
:py:attr:`~xarray.CFTimeIndex.dayofyear` are deprecated in favor of
:py:attr:`~xarray.CFTimeIndex.day_of_week` and
:py:attr:`~xarray.CFTimeIndex.day_of_year`, respectively (:pull:`11270`).
By `Spencer Clark <https://github.com/spencerkclark>`_.

Bug Fixes
~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions xarray/coding/calendar_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def _interpolate_day_of_year(times, target_calendar):
source_calendar = times.dt.calendar
return np.round(
_days_in_year(times.dt.year, target_calendar)
* times.dt.dayofyear
* times.dt.day_of_year
/ _days_in_year(times.dt.year, source_calendar)
).astype(int)

Expand All @@ -272,7 +272,7 @@ def _random_day_of_year(time, target_calendar, use_cftime):
new_doy = np.insert(new_doy, rm_idx - np.arange(5), -1)
if _days_in_year(year, source_calendar) == 366:
new_doy = np.insert(new_doy, 60, -1)
return new_doy[time.dt.dayofyear - 1]
return new_doy[time.dt.day_of_year - 1]


def _convert_to_new_calendar_with_new_day_of_year(
Expand Down
12 changes: 10 additions & 2 deletions xarray/coding/cftime_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1623,10 +1623,18 @@ def date_range_like(source, calendar, use_cftime=None):
end = convert_time_or_go_back(source_end, date_type)

# For the cases where the source ends on the end of the month, we expect the same in the new calendar.
if source_end.day == source_end.daysinmonth and isinstance(
if isinstance(source_end, pd.Timestamp):
source_end_days_in_month = source_end.days_in_month
else:
source_end_days_in_month = source_end.daysinmonth
if isinstance(end, pd.Timestamp):
end_days_in_month = end.days_in_month
else:
end_days_in_month = end.daysinmonth
if source_end.day == source_end_days_in_month and isinstance(
freq_as_offset, YearEnd | QuarterEnd | MonthEnd | Day
):
end = end.replace(day=end.daysinmonth)
end = end.replace(day=end_days_in_month)

return date_range(
start=start.isoformat(),
Expand Down
31 changes: 29 additions & 2 deletions xarray/coding/cftimeindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,12 @@ def get_date_field(datetimes, field):
return np.array([getattr(date, field) for date in datetimes], dtype=np.int64)


def _field_accessor(name, docstring=None, min_cftime_version="0.0"):
def _field_accessor(
name: str,
docstring: str | None = None,
min_cftime_version: str = "0.0",
deprecation_pair: tuple[str, str] | None = None,
):
"""Adapted from pandas.tseries.index._field_accessor"""

def f(self, min_cftime_version=min_cftime_version):
Expand All @@ -136,6 +141,14 @@ def f(self, min_cftime_version=min_cftime_version):
else:
cftime = attempt_import("cftime")

if deprecation_pair is not None:
original, replacement = deprecation_pair
emit_user_level_warning(
f"CFTimeIndex.{original} is deprecated and will be removed in "
f"a future version. Use CFTimeIndex.{replacement} instead",
FutureWarning,
)

if Version(cftime.__version__) >= Version(min_cftime_version):
return get_date_field(self._data, name)
else:
Expand Down Expand Up @@ -249,9 +262,23 @@ class CFTimeIndex(pd.Index):
second = _field_accessor("second", "The seconds of the datetime")
microsecond = _field_accessor("microsecond", "The microseconds of the datetime")
dayofyear = _field_accessor(
"dayofyr",
"The ordinal day of year of the datetime",
"1.0.2.1",
("dayofyear", "day_of_year"),
)
dayofweek = _field_accessor(
"dayofwk",
"The day of week of the datetime",
"1.0.2.1",
("dayofweek", "day_of_week"),
)
day_of_year = _field_accessor(
"dayofyr", "The ordinal day of year of the datetime", "1.0.2.1"
)
dayofweek = _field_accessor("dayofwk", "The day of week of the datetime", "1.0.2.1")
day_of_week = _field_accessor(
"dayofwk", "The day of week of the datetime", "1.0.2.1"
)
days_in_month = _field_accessor(
"daysinmonth", "The number of days in the month of the datetime", "1.1.0.0"
)
Expand Down
49 changes: 43 additions & 6 deletions xarray/core/accessor_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
is_np_timedelta_like,
)
from xarray.core.types import T_DataArray
from xarray.core.utils import emit_user_level_warning
from xarray.core.variable import IndexVariable, Variable
from xarray.namedarray.utils import is_duck_dask_array

Expand Down Expand Up @@ -338,8 +339,8 @@ class DatetimeAccessor(TimeAccessor[T_DataArray]):
* time (time) datetime64[us] 80B 2000-01-01 2000-01-02 ... 2000-01-10
>>> ts.dt # doctest: +ELLIPSIS
<xarray.core.accessor_dt.DatetimeAccessor object at 0x...>
>>> ts.dt.dayofyear
<xarray.DataArray 'dayofyear' (time: 10)> Size: 80B
>>> ts.dt.day_of_year
<xarray.DataArray 'day_of_year' (time: 10)> Size: 80B
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
Coordinates:
* time (time) datetime64[us] 80B 2000-01-01 2000-01-02 ... 2000-01-10
Expand Down Expand Up @@ -465,17 +466,45 @@ def weekofyear(self) -> DataArray:

week = weekofyear

@property
def day_of_week(self) -> T_DataArray:
"""The day of the week with Monday=0, Sunday=6"""
return self._date_field("day_of_week", np.int64)

@property
def dayofweek(self) -> T_DataArray:
"""The day of the week with Monday=0, Sunday=6"""
return self._date_field("dayofweek", np.int64)
emit_user_level_warning(
"dt.dayofweek is deprecated and will be removed in a future "
"version. Use dt.day_of_week instead.",
FutureWarning,
)
return self._date_field("day_of_week", np.int64)

weekday = dayofweek
@property
def weekday(self) -> T_DataArray:
"""The day of the week with Monday=0, Sunday=6"""
emit_user_level_warning(
"dt.weekday is deprecated and will be removed in a "
"future version. Use dt.day_of_week instead.",
FutureWarning,
)
return self._date_field("day_of_week", np.int64)

@property
def day_of_year(self) -> T_DataArray:
"""The ordinal day of the year"""
return self._date_field("day_of_year", np.int64)

@property
def dayofyear(self) -> T_DataArray:
"""The ordinal day of the year"""
return self._date_field("dayofyear", np.int64)
emit_user_level_warning(
"dt.dayofyear is deprecated and will be removed in a future "
"version. Use dt.day_of_year instead.",
FutureWarning,
)
return self._date_field("day_of_year", np.int64)

@property
def quarter(self) -> T_DataArray:
Expand All @@ -487,7 +516,15 @@ def days_in_month(self) -> T_DataArray:
"""The number of days in the month"""
return self._date_field("days_in_month", np.int64)

daysinmonth = days_in_month
@property
def daysinmonth(self) -> T_DataArray:
"""The number of days in the month"""
emit_user_level_warning(
"dt.daysinmonth is deprecated and will be removed in a future "
"version. Use dt.days_in_month instead.",
FutureWarning,
)
return self._date_field("days_in_month", np.int64)

@property
def season(self) -> T_DataArray:
Expand Down
14 changes: 7 additions & 7 deletions xarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -6966,21 +6966,21 @@ def groupby(
1.826e+03], shape=(1827,))
Coordinates:
* time (time) datetime64[us] 15kB 2000-01-01 2000-01-02 ... 2004-12-31
>>> da.groupby("time.dayofyear") - da.groupby("time.dayofyear").mean("time")
>>> da.groupby("time.day_of_year") - da.groupby("time.day_of_year").mean("time")
<xarray.DataArray (time: 1827)> Size: 15kB
array([-730.8, -730.8, -730.8, ..., 730.2, 730.2, 730.5], shape=(1827,))
Coordinates:
* time (time) datetime64[us] 15kB 2000-01-01 2000-01-02 ... 2004-12-31
dayofyear (time) int64 15kB 1 2 3 4 5 6 7 8 ... 360 361 362 363 364 365 366
* time (time) datetime64[us] 15kB 2000-01-01 2000-01-02 ... 2004-12-31
day_of_year (time) int64 15kB 1 2 3 4 5 6 7 ... 360 361 362 363 364 365 366

Use a ``Grouper`` object to be more explicit

>>> da.coords["dayofyear"] = da.time.dt.dayofyear
>>> da.groupby(dayofyear=xr.groupers.UniqueGrouper()).mean()
<xarray.DataArray (dayofyear: 366)> Size: 3kB
>>> da.coords["day_of_year"] = da.time.dt.day_of_year
>>> da.groupby(day_of_year=xr.groupers.UniqueGrouper()).mean()
<xarray.DataArray (day_of_year: 366)> Size: 3kB
array([ 730.8, 731.8, 732.8, ..., 1093.8, 1094.8, 1095.5])
Coordinates:
* dayofyear (dayofyear) int64 3kB 1 2 3 4 5 6 7 ... 361 362 363 364 365 366
* day_of_year (day_of_year) int64 3kB 1 2 3 4 5 6 ... 361 362 363 364 365 366

>>> da = xr.DataArray(
... data=np.arange(12).reshape((4, 3)),
Expand Down
Loading
Loading