diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 9f1c4755bc54f..0f1b71c690ce0 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -150,6 +150,7 @@ Other enhancements - Added ``validate`` argument to :meth:`DataFrame.join` (:issue:`46622`) - A :class:`errors.PerformanceWarning` is now thrown when using ``string[pyarrow]`` dtype with methods that don't dispatch to ``pyarrow.compute`` methods (:issue:`42613`) - Added ``numeric_only`` argument to :meth:`Resampler.sum`, :meth:`Resampler.prod`, :meth:`Resampler.min`, :meth:`Resampler.max`, :meth:`Resampler.first`, and :meth:`Resampler.last` (:issue:`46442`) +- ``times`` argument in :class:`.ExponentialMovingWindow` now accepts ``np.timedelta64`` (:issue:`47003`) .. --------------------------------------------------------------------------- .. _whatsnew_150.notable_bug_fixes: @@ -736,6 +737,7 @@ Groupby/resample/rolling - Bug in :meth:`.Rolling.var` would segfault calculating weighted variance when window size was larger than data size (:issue:`46760`) - Bug in :meth:`Grouper.__repr__` where ``dropna`` was not included. Now it is (:issue:`46754`) - Bug in :meth:`DataFrame.rolling` gives ValueError when center=True, axis=1 and win_type is specified (:issue:`46135`) +- Bug in :class:`.ExponentialMovingWindow` where ``alpha``, ``com``, or ``span`` were incorrectly allowed when ``times`` and ``halflife`` were passed (:issue:`47003`) Reshaping ^^^^^^^^^ diff --git a/pandas/core/window/ewm.py b/pandas/core/window/ewm.py index 32cb4938344c4..8a6f7b4b798ee 100644 --- a/pandas/core/window/ewm.py +++ b/pandas/core/window/ewm.py @@ -389,22 +389,20 @@ def __init__( raise ValueError("times must be datetime64[ns] dtype.") if len(self.times) != len(obj): raise ValueError("times must be the same length as the object.") - if not isinstance(self.halflife, (str, datetime.timedelta)): + if not isinstance(self.halflife, (str, datetime.timedelta, np.timedelta64)): raise ValueError( "halflife must be a string or datetime.timedelta object" ) if isna(self.times).any(): raise ValueError("Cannot convert NaT values to integer") self._deltas = _calculate_deltas(self.times, self.halflife) - # Halflife is no longer applicable when calculating COM - # But allow COM to still be calculated if the user passes other decay args - if common.count_not_none(self.com, self.span, self.alpha) > 0: - self._com = get_center_of_mass(self.com, self.span, None, self.alpha) - else: - self._com = 1.0 + # GH 47003 + # get_center_of_mass will validate and raise if the user has also + # passed in com, span or alpha (1.0 is a placeholder value) + self._com = get_center_of_mass(self.com, self.span, 1.0, self.alpha) else: if self.halflife is not None and isinstance( - self.halflife, (str, datetime.timedelta) + self.halflife, (str, datetime.timedelta, np.timedelta64) ): raise ValueError( "halflife can only be a timedelta convertible argument if " diff --git a/pandas/tests/window/conftest.py b/pandas/tests/window/conftest.py index f42a1a5449c5c..8977d1a0d9d1b 100644 --- a/pandas/tests/window/conftest.py +++ b/pandas/tests/window/conftest.py @@ -102,7 +102,7 @@ def engine_and_raw(request): return request.param -@pytest.fixture(params=["1 day", timedelta(days=1)]) +@pytest.fixture(params=["1 day", timedelta(days=1), np.timedelta64(1, "D")]) def halflife_with_times(request): """Halflife argument for EWM when times is specified.""" return request.param diff --git a/pandas/tests/window/test_ewm.py b/pandas/tests/window/test_ewm.py index b1e8b43258750..9acc34c67243a 100644 --- a/pandas/tests/window/test_ewm.py +++ b/pandas/tests/window/test_ewm.py @@ -666,3 +666,11 @@ def test_ewm_pairwise_cov_corr(func, frame): result.index = result.index.droplevel(1) expected = getattr(frame[1].ewm(span=10, min_periods=5), func)(frame[5]) tm.assert_series_equal(result, expected, check_names=False) + + +@pytest.mark.parametrize("decay", ["alpha", "com", "span"]) +def test_validate_times_halflife_with_other_decay(decay): + ser = Series([1, 2]) + msg = "comass, span, halflife, and alpha are mutually exclusive" + with pytest.raises(ValueError, match=msg): + ser.ewm(**{decay: 1}, halflife="1 Day", times=DatetimeIndex(["2021", "2022"]))