From 2e8be51cd4a89ea002244262dbd9326940753f5c Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 26 Oct 2020 18:23:44 -0700 Subject: [PATCH 1/3] ENH: std for dt64 --- pandas/core/arrays/datetimes.py | 22 ++++++++++++++++ pandas/core/indexes/datetimes.py | 2 ++ pandas/core/nanops.py | 4 ++- pandas/tests/arrays/test_timedeltas.py | 26 ++++++++++++++----- .../tests/reductions/test_stat_reductions.py | 2 +- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 0780dcfef23cc..80955c028cf9c 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -1856,6 +1856,28 @@ def to_julian_date(self): / 24.0 ) + # ----------------------------------------------------------------- + # Reductions + + def std( + self, + axis=None, + dtype=None, + out=None, + ddof: int = 1, + keepdims: bool = False, + skipna: bool = True, + ): + # Because std is translation-invariant, we can get self.std + # by calculating (self - Timestamp(0)).std, and we can do it + # without creating a copy by using a view on self._ndarray + from pandas.core.arrays import TimedeltaArray + + tda = TimedeltaArray(self._ndarray.view("i8")) + return tda.std( + axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims, skipna=skipna + ) + # ------------------------------------------------------------------- # Constructor Helpers diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 11417e0b3317e..4312c32771f9a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -93,6 +93,7 @@ def _new_DatetimeIndex(cls, d): "date", "time", "timetz", + "std", ] + DatetimeArray._bool_ops, DatetimeArray, @@ -193,6 +194,7 @@ class DatetimeIndex(DatetimeTimedeltaMixin): month_name day_name mean + std See Also -------- diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index d2f596a5a6800..b3922a91b2295 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -755,7 +755,6 @@ def _get_counts_nanvar( return count, d -@disallow("M8") @bottleneck_switch(ddof=1) def nanstd(values, axis=None, skipna=True, ddof=1, mask=None): """ @@ -785,6 +784,9 @@ def nanstd(values, axis=None, skipna=True, ddof=1, mask=None): >>> nanops.nanstd(s) 1.0 """ + if values.dtype == "M8[ns]": + values = values.view("m8[ns]") + orig_dtype = values.dtype values, mask, _, _, _ = _get_values(values, skipna, mask=mask) diff --git a/pandas/tests/arrays/test_timedeltas.py b/pandas/tests/arrays/test_timedeltas.py index 95265a958c35d..47ebfe311d9ea 100644 --- a/pandas/tests/arrays/test_timedeltas.py +++ b/pandas/tests/arrays/test_timedeltas.py @@ -290,8 +290,18 @@ def test_sum_2d_skipna_false(self): )._values tm.assert_timedelta_array_equal(result, expected) - def test_std(self): - tdi = pd.TimedeltaIndex(["0H", "4H", "NaT", "4H", "0H", "2H"]) + # Adding a Timestamp makes this a test for DatetimeArray.std + @pytest.mark.parametrize( + "add", + [ + pd.Timedelta(0), + pd.Timestamp.now(), + pd.Timestamp.now("UTC"), + pd.Timestamp.now("Asia/Tokyo"), + ], + ) + def test_std(self, add): + tdi = pd.TimedeltaIndex(["0H", "4H", "NaT", "4H", "0H", "2H"]) + add arr = tdi.array result = arr.std(skipna=True) @@ -303,9 +313,10 @@ def test_std(self): assert isinstance(result, pd.Timedelta) assert result == expected - result = nanops.nanstd(np.asarray(arr), skipna=True) - assert isinstance(result, pd.Timedelta) - assert result == expected + if getattr(arr, "tz", None) is None: + result = nanops.nanstd(np.asarray(arr), skipna=True) + assert isinstance(result, pd.Timedelta) + assert result == expected result = arr.std(skipna=False) assert result is pd.NaT @@ -313,8 +324,9 @@ def test_std(self): result = tdi.std(skipna=False) assert result is pd.NaT - result = nanops.nanstd(np.asarray(arr), skipna=False) - assert result is pd.NaT + if getattr(arr, "tz", None) is None: + result = nanops.nanstd(np.asarray(arr), skipna=False) + assert result is pd.NaT def test_median(self): tdi = pd.TimedeltaIndex(["0H", "3H", "NaT", "5H06m", "0H", "2H"]) diff --git a/pandas/tests/reductions/test_stat_reductions.py b/pandas/tests/reductions/test_stat_reductions.py index fd2746672a0eb..67e871f8b67c2 100644 --- a/pandas/tests/reductions/test_stat_reductions.py +++ b/pandas/tests/reductions/test_stat_reductions.py @@ -96,7 +96,7 @@ def _check_stat_op( string_series_[5:15] = np.NaN # mean, idxmax, idxmin, min, and max are valid for dates - if name not in ["max", "min", "mean", "median"]: + if name not in ["max", "min", "mean", "median", "std"]: ds = Series(pd.date_range("1/1/2001", periods=10)) with pytest.raises(TypeError): f(ds) From 3ed75f8a485c0f8c55954f319a20c8a40b3889c2 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 26 Oct 2020 18:24:48 -0700 Subject: [PATCH 2/3] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index f1f24ab7a101b..4c5814e79266b 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -223,6 +223,7 @@ Other enhancements - :class:`DataFrame` now supports ``divmod`` operation (:issue:`37165`) - :meth:`DataFrame.to_parquet` now returns a ``bytes`` object when no ``path`` argument is passed (:issue:`37105`) - :class:`Rolling` now supports the ``closed`` argument for fixed windows (:issue:`34315`) +- :class:`DatetimeIndex` and :class:`Series` with ``datetime64`` or ``datetime64tz`` dtypes now support ``std`` (:issue:`??`) .. _whatsnew_120.api_breaking.python: From d532e7baea9ae50448fa5d50197197db6a0d75b9 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 26 Oct 2020 18:26:07 -0700 Subject: [PATCH 3/3] GH ref --- doc/source/whatsnew/v1.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 4c5814e79266b..94c4052ae2d99 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -223,7 +223,7 @@ Other enhancements - :class:`DataFrame` now supports ``divmod`` operation (:issue:`37165`) - :meth:`DataFrame.to_parquet` now returns a ``bytes`` object when no ``path`` argument is passed (:issue:`37105`) - :class:`Rolling` now supports the ``closed`` argument for fixed windows (:issue:`34315`) -- :class:`DatetimeIndex` and :class:`Series` with ``datetime64`` or ``datetime64tz`` dtypes now support ``std`` (:issue:`??`) +- :class:`DatetimeIndex` and :class:`Series` with ``datetime64`` or ``datetime64tz`` dtypes now support ``std`` (:issue:`37436`) .. _whatsnew_120.api_breaking.python: