diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 7cf88a642f511..24a5c27b734c1 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -780,6 +780,7 @@ Deprecations - :meth:`Index.is_object` has been deprecated. Use :func:`pandas.api.types.is_object_dtype` instead (:issue:`50042`) - :meth:`Index.is_interval` has been deprecated. Use :func:`pandas.api.types.is_intterval_dtype` instead (:issue:`50042`) - Deprecated ``all`` and ``any`` reductions with ``datetime64`` and :class:`DatetimeTZDtype` dtypes, use e.g. ``(obj != pd.Timestamp(0), tz=obj.tz).all()`` instead (:issue:`34479`) +- Deprecated unused arguments ``*args`` and ``**kwargs`` in :class:`Resampler` (:issue:`50977`) - Deprecated calling ``float`` or ``int`` on a single element :class:`Series` to return a ``float`` or ``int`` respectively. Extract the element before calling ``float`` or ``int`` instead (:issue:`51101`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 255b926ba226a..8f6f780beb432 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -10,6 +10,7 @@ final, no_type_check, ) +import warnings import numpy as np @@ -47,6 +48,7 @@ Substitution, doc, ) +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.generic import ( ABCDataFrame, @@ -892,6 +894,7 @@ def sum( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "sum", args, kwargs) nv.validate_resampler_func("sum", args, kwargs) return self._downsample("sum", numeric_only=numeric_only, min_count=min_count) @@ -903,6 +906,7 @@ def prod( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "prod", args, kwargs) nv.validate_resampler_func("prod", args, kwargs) return self._downsample("prod", numeric_only=numeric_only, min_count=min_count) @@ -913,6 +917,7 @@ def min( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "min", args, kwargs) nv.validate_resampler_func("min", args, kwargs) return self._downsample("min", numeric_only=numeric_only, min_count=min_count) @@ -923,6 +928,7 @@ def max( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "max", args, kwargs) nv.validate_resampler_func("max", args, kwargs) return self._downsample("max", numeric_only=numeric_only, min_count=min_count) @@ -934,6 +940,7 @@ def first( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "first", args, kwargs) nv.validate_resampler_func("first", args, kwargs) return self._downsample("first", numeric_only=numeric_only, min_count=min_count) @@ -945,11 +952,13 @@ def last( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "last", args, kwargs) nv.validate_resampler_func("last", args, kwargs) return self._downsample("last", numeric_only=numeric_only, min_count=min_count) @doc(GroupBy.median) def median(self, numeric_only: bool = False, *args, **kwargs): + maybe_warn_args_and_kwargs(type(self), "median", args, kwargs) nv.validate_resampler_func("median", args, kwargs) return self._downsample("median", numeric_only=numeric_only) @@ -976,6 +985,7 @@ def mean( DataFrame or Series Mean of values within each group. """ + maybe_warn_args_and_kwargs(type(self), "mean", args, kwargs) nv.validate_resampler_func("mean", args, kwargs) return self._downsample("mean", numeric_only=numeric_only) @@ -1007,6 +1017,7 @@ def std( DataFrame or Series Standard deviation of values within each group. """ + maybe_warn_args_and_kwargs(type(self), "std", args, kwargs) nv.validate_resampler_func("std", args, kwargs) return self._downsample("std", ddof=ddof, numeric_only=numeric_only) @@ -1039,6 +1050,7 @@ def var( DataFrame or Series Variance of values within each group. """ + maybe_warn_args_and_kwargs(type(self), "var", args, kwargs) nv.validate_resampler_func("var", args, kwargs) return self._downsample("var", ddof=ddof, numeric_only=numeric_only) @@ -1050,6 +1062,7 @@ def sem( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "sem", args, kwargs) nv.validate_resampler_func("sem", args, kwargs) return self._downsample("sem", ddof=ddof, numeric_only=numeric_only) @@ -1059,6 +1072,7 @@ def ohlc( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "ohlc", args, kwargs) nv.validate_resampler_func("ohlc", args, kwargs) return self._downsample("ohlc") @@ -1068,6 +1082,7 @@ def nunique( *args, **kwargs, ): + maybe_warn_args_and_kwargs(type(self), "nunique", args, kwargs) nv.validate_resampler_func("nunique", args, kwargs) return self._downsample("nunique") @@ -2219,3 +2234,37 @@ def _asfreq_compat(index: DatetimeIndex | PeriodIndex | TimedeltaIndex, freq): else: # pragma: no cover raise TypeError(type(index)) return new_index + + +def maybe_warn_args_and_kwargs(cls, kernel: str, args, kwargs) -> None: + """ + Warn for deprecation of args and kwargs in resample functions. + + Parameters + ---------- + cls : type + Class to warn about. + kernel : str + Operation name. + args : tuple or None + args passed by user. Will be None if and only if kernel does not have args. + kwargs : dict or None + kwargs passed by user. Will be None if and only if kernel does not have kwargs. + """ + warn_args = args is not None and len(args) > 0 + warn_kwargs = kwargs is not None and len(kwargs) > 0 + if warn_args and warn_kwargs: + msg = "args and kwargs" + elif warn_args: + msg = "args" + elif warn_kwargs: + msg = "kwargs" + else: + return + warnings.warn( + f"Passing additional {msg} to {cls.__name__}.{kernel} has " + "no impact on the result and is deprecated. This will " + "raise a TypeError in a future version of pandas.", + category=FutureWarning, + stacklevel=find_stack_level(), + ) diff --git a/pandas/tests/resample/test_datetime_index.py b/pandas/tests/resample/test_datetime_index.py index d5720f9628073..d18db6ab5f643 100644 --- a/pandas/tests/resample/test_datetime_index.py +++ b/pandas/tests/resample/test_datetime_index.py @@ -9,7 +9,6 @@ from pandas._libs import lib from pandas._typing import DatetimeNaTType -from pandas.errors import UnsupportedFunctionCall import pandas as pd from pandas import ( @@ -243,22 +242,6 @@ def _ohlc(group): tm.assert_frame_equal(result, expected) -@pytest.mark.parametrize("func", ["min", "max", "sum", "prod", "mean", "var", "std"]) -def test_numpy_compat(func, unit): - # see gh-12811 - s = Series( - [1, 2, 3, 4, 5], index=date_range("20130101", periods=5, freq="s").as_unit(unit) - ) - r = s.resample("2s") - - msg = "numpy operations are not valid with resample" - - with pytest.raises(UnsupportedFunctionCall, match=msg): - getattr(r, func)(func, 1, 2, 3) - with pytest.raises(UnsupportedFunctionCall, match=msg): - getattr(r, func)(axis=1) - - def test_resample_how_callables(unit): # GH#7929 data = np.arange(5, dtype=np.int64) diff --git a/pandas/tests/resample/test_resample_api.py b/pandas/tests/resample/test_resample_api.py index 77b068aba765c..0f18c5c5774b7 100644 --- a/pandas/tests/resample/test_resample_api.py +++ b/pandas/tests/resample/test_resample_api.py @@ -4,6 +4,7 @@ import pytest from pandas._libs import lib +from pandas.errors import UnsupportedFunctionCall import pandas as pd from pandas import ( @@ -916,3 +917,43 @@ def test_series_downsample_method(method, numeric_only, expected_data): result = func(**kwargs) expected = Series(expected_data, index=expected_index) tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "method, raises", + [ + ("sum", True), + ("prod", True), + ("min", True), + ("max", True), + ("first", False), + ("last", False), + ("median", False), + ("mean", True), + ("std", True), + ("var", True), + ("sem", False), + ("ohlc", False), + ("nunique", False), + ], +) +def test_args_kwargs_depr(method, raises): + index = date_range("20180101", periods=3, freq="h") + df = Series([2, 4, 6], index=index) + resampled = df.resample("30min") + args = () + + func = getattr(resampled, method) + + error_msg = "numpy operations are not valid with resample." + error_msg_type = "too many arguments passed in" + warn_msg = f"Passing additional args to DatetimeIndexResampler.{method}" + + if raises: + with tm.assert_produces_warning(FutureWarning, match=warn_msg): + with pytest.raises(UnsupportedFunctionCall, match=error_msg): + func(*args, 1, 2, 3) + else: + with tm.assert_produces_warning(FutureWarning, match=warn_msg): + with pytest.raises(TypeError, match=error_msg_type): + func(*args, 1, 2, 3)