diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index a9b4ad2e5374a..f54fa9d98a592 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -402,6 +402,7 @@ Numeric - Bug in :class:`DataFrame` arithmetic ops incorrectly accepting keyword arguments (:issue:`36843`) - Bug in :class:`IntervalArray` comparisons with :class:`Series` not returning :class:`Series` (:issue:`36908`) - Bug in :class:`DataFrame` allowing arithmetic operations with list of array-likes with undefined results. Behavior changed to raising ``ValueError`` (:issue:`36702`) +- Bug in :meth:`DataFrame.std`` with ``timedelta64`` dtype and ``skipna=False`` (:issue:`37392`) Conversion ^^^^^^^^^^ diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index 83399a87e5667..b101da196fdd8 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -228,7 +228,7 @@ def _maybe_get_mask( # Boolean data cannot contain nulls, so signal via mask being None return None - if skipna: + if skipna or needs_i8_conversion(values.dtype): mask = isna(values) return mask diff --git a/pandas/tests/arrays/test_timedeltas.py b/pandas/tests/arrays/test_timedeltas.py index 976f5d0c90f19..f67a554a435ef 100644 --- a/pandas/tests/arrays/test_timedeltas.py +++ b/pandas/tests/arrays/test_timedeltas.py @@ -3,6 +3,7 @@ import pandas as pd import pandas._testing as tm +from pandas.core import nanops from pandas.core.arrays import TimedeltaArray @@ -288,12 +289,19 @@ 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 + result = arr.std(skipna=False) assert result is pd.NaT result = tdi.std(skipna=False) assert result is pd.NaT + 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"]) arr = tdi.array @@ -307,8 +315,8 @@ def test_median(self): assert isinstance(result, pd.Timedelta) assert result == expected - result = arr.std(skipna=False) + result = arr.median(skipna=False) assert result is pd.NaT - result = tdi.std(skipna=False) + result = tdi.median(skipna=False) assert result is pd.NaT diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index ddca67306d804..ee4da37ce10f3 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -753,6 +753,22 @@ def test_operators_timedelta64(self): assert df["off1"].dtype == "timedelta64[ns]" assert df["off2"].dtype == "timedelta64[ns]" + def test_std_timedelta64_skipna_false(self): + # GH#37392 + tdi = pd.timedelta_range("1 Day", periods=10) + df = DataFrame({"A": tdi, "B": tdi}) + df.iloc[-2, -1] = pd.NaT + + result = df.std(skipna=False) + expected = Series( + [df["A"].std(), pd.NaT], index=["A", "B"], dtype="timedelta64[ns]" + ) + tm.assert_series_equal(result, expected) + + result = df.std(axis=1, skipna=False) + expected = Series([pd.Timedelta(0)] * 8 + [pd.NaT, pd.Timedelta(0)]) + tm.assert_series_equal(result, expected) + def test_sum_corner(self): empty_frame = DataFrame()