diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 527c4215d22ca..8e7b26c9a6c03 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -40,6 +40,7 @@ Other enhancements - :meth:`.GroupBy.min` and :meth:`.GroupBy.max` now supports `Numba `_ execution with the ``engine`` keyword (:issue:`45428`) - Implemented a ``bool``-dtype :class:`Index`, passing a bool-dtype array-like to ``pd.Index`` will now retain ``bool`` dtype instead of casting to ``object`` (:issue:`45061`) - Implemented a complex-dtype :class:`Index`, passing a complex-dtype array-like to ``pd.Index`` will now retain complex dtype instead of casting to ``object`` (:issue:`45845`) +- Improved error message in :class:`~pandas.core.window.Rolling` when ``window`` is a frequency and ``NaT`` is in the rolling axis (:issue:`46087`) - :class:`Series` and :class:`DataFrame` with ``IntegerDtype`` now supports bitwise operations (:issue:`34463`) - diff --git a/pandas/core/window/rolling.py b/pandas/core/window/rolling.py index 72ebbcbc65e5e..8001bec3b505d 100644 --- a/pandas/core/window/rolling.py +++ b/pandas/core/window/rolling.py @@ -837,12 +837,6 @@ def _gotitem(self, key, ndim, subset=None): subset = self.obj.set_index(self._on) return super()._gotitem(key, ndim, subset=subset) - def _validate_monotonic(self): - """ - Validate that "on" is monotonic; already validated at a higher level. - """ - pass - class Window(BaseWindow): """ @@ -1687,7 +1681,7 @@ def _validate(self): or isinstance(self._on, (DatetimeIndex, TimedeltaIndex, PeriodIndex)) ) and isinstance(self.window, (str, BaseOffset, timedelta)): - self._validate_monotonic() + self._validate_datetimelike_monotonic() # this will raise ValueError on non-fixed freqs try: @@ -1712,18 +1706,24 @@ def _validate(self): elif not is_integer(self.window) or self.window < 0: raise ValueError("window must be an integer 0 or greater") - def _validate_monotonic(self): + def _validate_datetimelike_monotonic(self): """ - Validate monotonic (increasing or decreasing). + Validate self._on is monotonic (increasing or decreasing) and has + no NaT values for frequency windows. """ + if self._on.hasnans: + self._raise_monotonic_error("values must not have NaT") if not (self._on.is_monotonic_increasing or self._on.is_monotonic_decreasing): - self._raise_monotonic_error() + self._raise_monotonic_error("values must be monotonic") - def _raise_monotonic_error(self): - formatted = self.on - if self.on is None: - formatted = "index" - raise ValueError(f"{formatted} must be monotonic") + def _raise_monotonic_error(self, msg: str): + on = self.on + if on is None: + if self.axis == 0: + on = "index" + else: + on = "column" + raise ValueError(f"{on} {msg}") @doc( _shared_docs["aggregate"], @@ -2630,13 +2630,3 @@ def _get_window_indexer(self) -> GroupbyIndexer: indexer_kwargs=indexer_kwargs, ) return window_indexer - - def _validate_monotonic(self): - """ - Validate that on is monotonic; - """ - if ( - not (self._on.is_monotonic_increasing or self._on.is_monotonic_decreasing) - or self._on.hasnans - ): - self._raise_monotonic_error() diff --git a/pandas/tests/window/test_groupby.py b/pandas/tests/window/test_groupby.py index a1e9ad2677777..25782d0c0617f 100644 --- a/pandas/tests/window/test_groupby.py +++ b/pandas/tests/window/test_groupby.py @@ -678,7 +678,7 @@ def test_groupby_rolling_nans_in_index(self, rollings, key): ) if key == "index": df = df.set_index("a") - with pytest.raises(ValueError, match=f"{key} must be monotonic"): + with pytest.raises(ValueError, match=f"{key} values must not have NaT"): df.groupby("c").rolling("60min", **rollings) @pytest.mark.parametrize("group_keys", [True, False]) diff --git a/pandas/tests/window/test_timeseries_window.py b/pandas/tests/window/test_timeseries_window.py index 41152c0495494..ee28b27b17365 100644 --- a/pandas/tests/window/test_timeseries_window.py +++ b/pandas/tests/window/test_timeseries_window.py @@ -5,6 +5,7 @@ DataFrame, Index, MultiIndex, + NaT, Series, Timestamp, date_range, @@ -139,7 +140,7 @@ def test_non_monotonic_on(self): assert not df.index.is_monotonic_increasing - msg = "index must be monotonic" + msg = "index values must be monotonic" with pytest.raises(ValueError, match=msg): df.rolling("2s").sum() @@ -762,3 +763,12 @@ def test_rolling_on_multi_index_level(self): {"column": [0.0, 1.0, 3.0, 6.0, 10.0, 15.0]}, index=df.index ) tm.assert_frame_equal(result, expected) + + +@pytest.mark.parametrize("msg, axis", [["column", 1], ["index", 0]]) +def test_nat_axis_error(msg, axis): + idx = [Timestamp("2020"), NaT] + kwargs = {"columns" if axis == 1 else "index": idx} + df = DataFrame(np.eye(2), **kwargs) + with pytest.raises(ValueError, match=f"{msg} values must not have NaT"): + df.rolling("D", axis=axis).mean()