Skip to content

Commit f7c73a5

Browse files
authored
BUG: support non-nano times in ewm (#56262)
* BUG: support non-nano times in ewm * GH ref * update exception message * update test
1 parent 4f080b8 commit f7c73a5

File tree

3 files changed

+16
-31
lines changed

3 files changed

+16
-31
lines changed

doc/source/whatsnew/v2.2.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -561,12 +561,14 @@ Groupby/resample/rolling
561561
- Bug in :meth:`.DataFrameGroupBy.idxmin`, :meth:`.DataFrameGroupBy.idxmax`, :meth:`.SeriesGroupBy.idxmin`, and :meth:`.SeriesGroupBy.idxmax` would not retain :class:`.Categorical` dtype when the index was a :class:`.CategoricalIndex` that contained NA values (:issue:`54234`)
562562
- Bug in :meth:`.DataFrameGroupBy.transform` and :meth:`.SeriesGroupBy.transform` when ``observed=False`` and ``f="idxmin"`` or ``f="idxmax"`` would incorrectly raise on unobserved categories (:issue:`54234`)
563563
- Bug in :meth:`DataFrame.asfreq` and :meth:`Series.asfreq` with a :class:`DatetimeIndex` with non-nanosecond resolution incorrectly converting to nanosecond resolution (:issue:`55958`)
564+
- Bug in :meth:`DataFrame.ewm` when passed ``times`` with non-nanosecond ``datetime64`` or :class:`DatetimeTZDtype` dtype (:issue:`56262`)
564565
- Bug in :meth:`DataFrame.resample` not respecting ``closed`` and ``label`` arguments for :class:`~pandas.tseries.offsets.BusinessDay` (:issue:`55282`)
565566
- Bug in :meth:`DataFrame.resample` where bin edges were not correct for :class:`~pandas.tseries.offsets.BusinessDay` (:issue:`55281`)
566567
- Bug in :meth:`DataFrame.resample` where bin edges were not correct for :class:`~pandas.tseries.offsets.MonthBegin` (:issue:`55271`)
567568
- Bug in :meth:`DataFrameGroupBy.value_counts` and :meth:`SeriesGroupBy.value_count` could result in incorrect sorting if the columns of the DataFrame or name of the Series are integers (:issue:`55951`)
568569
- Bug in :meth:`DataFrameGroupBy.value_counts` and :meth:`SeriesGroupBy.value_count` would not respect ``sort=False`` in :meth:`DataFrame.groupby` and :meth:`Series.groupby` (:issue:`55951`)
569570
- Bug in :meth:`DataFrameGroupBy.value_counts` and :meth:`SeriesGroupBy.value_count` would sort by proportions rather than frequencies when ``sort=True`` and ``normalize=True`` (:issue:`55951`)
571+
-
570572

571573
Reshaping
572574
^^^^^^^^^

pandas/core/window/ewm.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
from pandas.util._decorators import doc
1313

1414
from pandas.core.dtypes.common import (
15-
is_datetime64_ns_dtype,
15+
is_datetime64_dtype,
1616
is_numeric_dtype,
1717
)
18+
from pandas.core.dtypes.dtypes import DatetimeTZDtype
1819
from pandas.core.dtypes.generic import ABCSeries
1920
from pandas.core.dtypes.missing import isna
2021

2122
from pandas.core import common
23+
from pandas.core.arrays.datetimelike import dtype_to_unit
2224
from pandas.core.indexers.objects import (
2325
BaseIndexer,
2426
ExponentialMovingWindowIndexer,
@@ -56,6 +58,7 @@
5658
from pandas._typing import (
5759
Axis,
5860
TimedeltaConvertibleTypes,
61+
npt,
5962
)
6063

6164
from pandas import (
@@ -101,7 +104,7 @@ def get_center_of_mass(
101104
def _calculate_deltas(
102105
times: np.ndarray | NDFrame,
103106
halflife: float | TimedeltaConvertibleTypes | None,
104-
) -> np.ndarray:
107+
) -> npt.NDArray[np.float64]:
105108
"""
106109
Return the diff of the times divided by the half-life. These values are used in
107110
the calculation of the ewm mean.
@@ -119,11 +122,11 @@ def _calculate_deltas(
119122
np.ndarray
120123
Diff of the times divided by the half-life
121124
"""
125+
unit = dtype_to_unit(times.dtype)
122126
if isinstance(times, ABCSeries):
123127
times = times._values
124128
_times = np.asarray(times.view(np.int64), dtype=np.float64)
125-
# TODO: generalize to non-nano?
126-
_halflife = float(Timedelta(halflife).as_unit("ns")._value)
129+
_halflife = float(Timedelta(halflife).as_unit(unit)._value)
127130
return np.diff(_times) / _halflife
128131

129132

@@ -366,8 +369,12 @@ def __init__(
366369
if self.times is not None:
367370
if not self.adjust:
368371
raise NotImplementedError("times is not supported with adjust=False.")
369-
if not is_datetime64_ns_dtype(self.times):
370-
raise ValueError("times must be datetime64[ns] dtype.")
372+
times_dtype = getattr(self.times, "dtype", None)
373+
if not (
374+
is_datetime64_dtype(times_dtype)
375+
or isinstance(times_dtype, DatetimeTZDtype)
376+
):
377+
raise ValueError("times must be datetime64 dtype.")
371378
if len(self.times) != len(obj):
372379
raise ValueError("times must be the same length as the object.")
373380
if not isinstance(self.halflife, (str, datetime.timedelta, np.timedelta64)):

pandas/tests/window/test_ewm.py

+1-25
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_constructor(frame_or_series):
6060

6161

6262
def test_ewma_times_not_datetime_type():
63-
msg = r"times must be datetime64\[ns\] dtype."
63+
msg = r"times must be datetime64 dtype."
6464
with pytest.raises(ValueError, match=msg):
6565
Series(range(5)).ewm(times=np.arange(5))
6666

@@ -102,30 +102,6 @@ def test_ewma_with_times_equal_spacing(halflife_with_times, times, min_periods):
102102
tm.assert_frame_equal(result, expected)
103103

104104

105-
@pytest.mark.parametrize(
106-
"unit",
107-
[
108-
pytest.param(
109-
"s",
110-
marks=pytest.mark.xfail(
111-
reason="ExponentialMovingWindow constructor raises on non-nano"
112-
),
113-
),
114-
pytest.param(
115-
"ms",
116-
marks=pytest.mark.xfail(
117-
reason="ExponentialMovingWindow constructor raises on non-nano"
118-
),
119-
),
120-
pytest.param(
121-
"us",
122-
marks=pytest.mark.xfail(
123-
reason="ExponentialMovingWindow constructor raises on non-nano"
124-
),
125-
),
126-
"ns",
127-
],
128-
)
129105
def test_ewma_with_times_variable_spacing(tz_aware_fixture, unit):
130106
tz = tz_aware_fixture
131107
halflife = "23 days"

0 commit comments

Comments
 (0)