diff --git a/doc/api.rst b/doc/api.rst index 96b4864804f..f2ff809e45f 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -557,6 +557,7 @@ Datetimelike properties DataArray.dt.seconds DataArray.dt.microseconds DataArray.dt.nanoseconds + DataArray.dt.total_seconds **Timedelta methods**: diff --git a/doc/whats-new.rst b/doc/whats-new.rst index b6bad62dd7c..e28177814b7 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -24,6 +24,8 @@ New Features - Use `opt_einsum `_ for :py:func:`xarray.dot` by default if installed. By `Deepak Cherian `_. (:issue:`7764`, :pull:`8373`). +- Add ``DataArray.dt.total_seconds()`` method to match the Pandas API. (:pull:`8435`). + By `Ben Mares `_. Breaking changes ~~~~~~~~~~~~~~~~ diff --git a/xarray/core/accessor_dt.py b/xarray/core/accessor_dt.py index 0d4a402cd19..b57c2f3857c 100644 --- a/xarray/core/accessor_dt.py +++ b/xarray/core/accessor_dt.py @@ -74,6 +74,8 @@ def _access_through_series(values, name): if name == "season": months = values_as_series.dt.month.values field_values = _season_from_months(months) + elif name == "total_seconds": + field_values = values_as_series.dt.total_seconds().values elif name == "isocalendar": # special NaT-handling can be removed when # https://github.com/pandas-dev/pandas/issues/54657 is resolved @@ -574,6 +576,13 @@ class TimedeltaAccessor(TimeAccessor[T_DataArray]): 43200, 64800]) Coordinates: * time (time) timedelta64[ns] 1 days 00:00:00 ... 5 days 18:00:00 + >>> ts.dt.total_seconds() + + array([ 86400., 108000., 129600., 151200., 172800., 194400., 216000., + 237600., 259200., 280800., 302400., 324000., 345600., 367200., + 388800., 410400., 432000., 453600., 475200., 496800.]) + Coordinates: + * time (time) timedelta64[ns] 1 days 00:00:00 ... 5 days 18:00:00 """ @property @@ -596,6 +605,11 @@ def nanoseconds(self) -> T_DataArray: """Number of nanoseconds (>= 0 and less than 1 microsecond) for each element""" return self._date_field("nanoseconds", np.int64) + # Not defined as a property in order to match the Pandas API + def total_seconds(self) -> T_DataArray: + """Total duration of each element expressed in seconds.""" + return self._date_field("total_seconds", np.float64) + class CombinedDatetimelikeAccessor( DatetimeAccessor[T_DataArray], TimedeltaAccessor[T_DataArray] diff --git a/xarray/tests/test_accessor_dt.py b/xarray/tests/test_accessor_dt.py index 64b487628c8..a8d5e722b66 100644 --- a/xarray/tests/test_accessor_dt.py +++ b/xarray/tests/test_accessor_dt.py @@ -6,6 +6,7 @@ import xarray as xr from xarray.tests import ( + assert_allclose, assert_array_equal, assert_chunks_equal, assert_equal, @@ -100,6 +101,19 @@ def test_field_access(self, field) -> None: assert expected.dtype == actual.dtype assert_identical(expected, actual) + def test_total_seconds(self) -> None: + # Subtract a value in the middle of the range to ensure that some values + # are negative + delta = self.data.time - np.datetime64("2000-01-03") + actual = delta.dt.total_seconds() + expected = xr.DataArray( + np.arange(-48, 52, dtype=np.float64) * 3600, + name="total_seconds", + coords=[self.data.time], + ) + # This works with assert_identical when pandas is >=1.5.0. + assert_allclose(expected, actual) + @pytest.mark.parametrize( "field, pandas_field", [