diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 63c85c43f91e5..e93ca56836595 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -100,9 +100,12 @@ Deprecations - Deprecated :meth:`DataFrame._data` and :meth:`Series._data`, use public APIs instead (:issue:`33333`) - Deprecating pinning ``group.name`` to each group in :meth:`SeriesGroupBy.aggregate` aggregations; if your operation requires utilizing the groupby keys, iterate over the groupby object instead (:issue:`41090`) - Deprecated the default of ``observed=False`` in :meth:`DataFrame.groupby` and :meth:`Series.groupby`; this will default to ``True`` in a future version (:issue:`43999`) +- Deprecated :meth:`DataFrameGroupBy.dtypes`, check ``dtypes`` on the underlying object instead (:issue:`51045`) - Deprecated ``axis=1`` in :meth:`DataFrame.groupby` and in :class:`Grouper` constructor, do ``frame.T.groupby(...)`` instead (:issue:`51203`) - Deprecated passing a :class:`DataFrame` to :meth:`DataFrame.from_records`, use :meth:`DataFrame.set_index` or :meth:`DataFrame.drop` instead (:issue:`51353`) - Deprecated accepting slices in :meth:`DataFrame.take`, call ``obj[slicer]`` or pass a sequence of integers instead (:issue:`51539`) +- Deprecated ``axis=1`` in :meth:`DataFrame.ewm`, :meth:`DataFrame.rolling`, :meth:`DataFrame.expanding`, transpose before calling the method instead (:issue:`51778`) +- Deprecated the ``axis`` keyword in :meth:`DataFrame.ewm`, :meth:`Series.ewm`, :meth:`DataFrame.rolling`, :meth:`Series.rolling`, :meth:`DataFrame.expanding`, :meth:`Series.expanding` (:issue:`51778`) - Deprecated 'method', 'limit', and 'fill_axis' keywords in :meth:`DataFrame.align` and :meth:`Series.align`, explicitly call ``fillna`` on the alignment results instead (:issue:`51856`) - Deprecated the 'axis' keyword in :meth:`.GroupBy.idxmax`, :meth:`.GroupBy.idxmin`, :meth:`.GroupBy.fillna`, :meth:`.GroupBy.take`, :meth:`.GroupBy.skew`, :meth:`.GroupBy.rank`, :meth:`.GroupBy.cumprod`, :meth:`.GroupBy.cumsum`, :meth:`.GroupBy.cummax`, :meth:`.GroupBy.cummin`, :meth:`.GroupBy.pct_change`, :meth:`GroupBy.diff`, :meth:`.GroupBy.shift`, and :meth:`DataFrameGroupBy.corrwith`; for ``axis=1`` operate on the underlying :class:`DataFrame` instead (:issue:`50405`, :issue:`51046`) - diff --git a/pandas/core/apply.py b/pandas/core/apply.py index 282734ed6020a..08618d5a6aa16 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -656,10 +656,6 @@ def columns(self) -> Index: def values(self): return self.obj.values - @cache_readonly - def dtypes(self) -> Series: - return self.obj.dtypes - def apply(self) -> DataFrame | Series: """compute the results""" # dispatch to agg diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 5e2c2360f4b70..d621a1c68b0f8 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -11956,12 +11956,32 @@ def rolling( center: bool_t = False, win_type: str | None = None, on: str | None = None, - axis: Axis = 0, + axis: Axis | lib.NoDefault = lib.no_default, closed: str | None = None, step: int | None = None, method: str = "single", ) -> Window | Rolling: - axis = self._get_axis_number(axis) + if axis is not lib.no_default: + axis = self._get_axis_number(axis) + name = "rolling" + if axis == 1: + warnings.warn( + f"Support for axis=1 in {type(self).__name__}.{name} is " + "deprecated and will be removed in a future version. " + f"Use obj.T.{name}(...) instead", + FutureWarning, + stacklevel=find_stack_level(), + ) + else: + warnings.warn( + f"The 'axis' keyword in {type(self).__name__}.{name} is " + "deprecated and will be removed in a future version. " + "Call the method without the axis keyword instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + else: + axis = 0 if win_type is not None: return Window( @@ -11995,10 +12015,30 @@ def rolling( def expanding( self, min_periods: int = 1, - axis: Axis = 0, + axis: Axis | lib.NoDefault = lib.no_default, method: str = "single", ) -> Expanding: - axis = self._get_axis_number(axis) + if axis is not lib.no_default: + axis = self._get_axis_number(axis) + name = "expanding" + if axis == 1: + warnings.warn( + f"Support for axis=1 in {type(self).__name__}.{name} is " + "deprecated and will be removed in a future version. " + f"Use obj.T.{name}(...) instead", + FutureWarning, + stacklevel=find_stack_level(), + ) + else: + warnings.warn( + f"The 'axis' keyword in {type(self).__name__}.{name} is " + "deprecated and will be removed in a future version. " + "Call the method without the axis keyword instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + else: + axis = 0 return Expanding(self, min_periods=min_periods, axis=axis, method=method) @final @@ -12012,11 +12052,32 @@ def ewm( min_periods: int | None = 0, adjust: bool_t = True, ignore_na: bool_t = False, - axis: Axis = 0, + axis: Axis | lib.NoDefault = lib.no_default, times: np.ndarray | DataFrame | Series | None = None, method: str = "single", ) -> ExponentialMovingWindow: - axis = self._get_axis_number(axis) + if axis is not lib.no_default: + axis = self._get_axis_number(axis) + name = "ewm" + if axis == 1: + warnings.warn( + f"Support for axis=1 in {type(self).__name__}.{name} is " + "deprecated and will be removed in a future version. " + f"Use obj.T.{name}(...) instead", + FutureWarning, + stacklevel=find_stack_level(), + ) + else: + warnings.warn( + f"The 'axis' keyword in {type(self).__name__}.{name} is " + "deprecated and will be removed in a future version. " + "Call the method without the axis keyword instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + else: + axis = 0 + return ExponentialMovingWindow( self, com=com, diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index ce4cd3476ec83..a9df4237601db 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -2637,6 +2637,14 @@ def hist( @property @doc(DataFrame.dtypes.__doc__) def dtypes(self) -> Series: + # GH#51045 + warnings.warn( + f"{type(self).__name__}.dtypes is deprecated and will be removed in " + "a future version. Check the dtypes on the base object instead", + FutureWarning, + stacklevel=find_stack_level(), + ) + # error: Incompatible return value type (got "DataFrame", expected "Series") return self.apply(lambda df: df.dtypes) # type: ignore[return-value] diff --git a/pandas/tests/groupby/test_allowlist.py b/pandas/tests/groupby/test_allowlist.py index bc9388075218e..894e9afa86fa2 100644 --- a/pandas/tests/groupby/test_allowlist.py +++ b/pandas/tests/groupby/test_allowlist.py @@ -274,7 +274,9 @@ def test_groupby_selection_other_methods(df): # methods which aren't just .foo() tm.assert_frame_equal(g.fillna(0), g_exp.fillna(0)) - tm.assert_frame_equal(g.dtypes, g_exp.dtypes) + msg = "DataFrameGroupBy.dtypes is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + tm.assert_frame_equal(g.dtypes, g_exp.dtypes) tm.assert_frame_equal(g.apply(lambda x: x.sum()), g_exp.apply(lambda x: x.sum())) tm.assert_frame_equal(g.resample("D").mean(), g_exp.resample("D").mean()) diff --git a/pandas/tests/window/test_api.py b/pandas/tests/window/test_api.py index 68b3f2b9f8e77..d6cca5061671b 100644 --- a/pandas/tests/window/test_api.py +++ b/pandas/tests/window/test_api.py @@ -129,7 +129,9 @@ def test_agg(step): def test_multi_axis_1_raises(func): # GH#46904 df = DataFrame({"a": [1, 1, 2], "b": [3, 4, 5], "c": [6, 7, 8]}) - r = df.rolling(window=3, axis=1) + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + r = df.rolling(window=3, axis=1) with pytest.raises(NotImplementedError, match="axis other than 0 is not supported"): r.agg(func) @@ -344,7 +346,9 @@ def test_dont_modify_attributes_after_methods( def test_centered_axis_validation(step): # ok - Series(np.ones(10)).rolling(window=3, center=True, axis=0, step=step).mean() + msg = "The 'axis' keyword in Series.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + Series(np.ones(10)).rolling(window=3, center=True, axis=0, step=step).mean() # bad axis msg = "No axis named 1 for object type Series" @@ -352,21 +356,18 @@ def test_centered_axis_validation(step): Series(np.ones(10)).rolling(window=3, center=True, axis=1, step=step).mean() # ok ok - DataFrame(np.ones((10, 10))).rolling( - window=3, center=True, axis=0, step=step - ).mean() - DataFrame(np.ones((10, 10))).rolling( - window=3, center=True, axis=1, step=step - ).mean() + df = DataFrame(np.ones((10, 10))) + msg = "The 'axis' keyword in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + df.rolling(window=3, center=True, axis=0, step=step).mean() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + df.rolling(window=3, center=True, axis=1, step=step).mean() # bad axis msg = "No axis named 2 for object type DataFrame" with pytest.raises(ValueError, match=msg): - ( - DataFrame(np.ones((10, 10))) - .rolling(window=3, center=True, axis=2, step=step) - .mean() - ) + (df.rolling(window=3, center=True, axis=2, step=step).mean()) def test_rolling_min_min_periods(step): diff --git a/pandas/tests/window/test_apply.py b/pandas/tests/window/test_apply.py index 56c2432ab1429..ee7f57ddab0ad 100644 --- a/pandas/tests/window/test_apply.py +++ b/pandas/tests/window/test_apply.py @@ -321,6 +321,8 @@ def test_center_reindex_frame(raw, frame): def test_axis1(raw): # GH 45912 df = DataFrame([1, 2]) - result = df.rolling(window=1, axis=1).apply(np.sum, raw=raw) + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(window=1, axis=1).apply(np.sum, raw=raw) expected = DataFrame([1.0, 2.0]) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/window/test_ewm.py b/pandas/tests/window/test_ewm.py index 9870c02692a7f..f4643be53f120 100644 --- a/pandas/tests/window/test_ewm.py +++ b/pandas/tests/window/test_ewm.py @@ -198,7 +198,9 @@ def test_float_dtype_ewma(func, expected, float_numpy_dtype): df = DataFrame( {0: range(5), 1: range(6, 11), 2: range(10, 20, 2)}, dtype=float_numpy_dtype ) - e = df.ewm(alpha=0.5, axis=1) + msg = "Support for axis=1 in DataFrame.ewm is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + e = df.ewm(alpha=0.5, axis=1) result = getattr(e, func)() tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/window/test_expanding.py b/pandas/tests/window/test_expanding.py index 638df10fa6d50..b4c5edeae949b 100644 --- a/pandas/tests/window/test_expanding.py +++ b/pandas/tests/window/test_expanding.py @@ -85,14 +85,17 @@ def test_expanding_axis(axis_frame): axis = df._get_axis_number(axis_frame) if axis == 0: + msg = "The 'axis' keyword in DataFrame.expanding is deprecated" expected = DataFrame( {i: [np.nan] * 2 + [float(j) for j in range(3, 11)] for i in range(20)} ) else: # axis == 1 + msg = "Support for axis=1 in DataFrame.expanding is deprecated" expected = DataFrame([[np.nan] * 2 + [float(i) for i in range(3, 21)]] * 10) - result = df.expanding(3, axis=axis_frame).sum() + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.expanding(3, axis=axis_frame).sum() tm.assert_frame_equal(result, expected) @@ -323,7 +326,11 @@ def test_expanding_corr_pairwise(frame): ) def test_expanding_func(func, static_comp, frame_or_series): data = frame_or_series(np.array(list(range(10)) + [np.nan] * 10)) - result = getattr(data.expanding(min_periods=1, axis=0), func)() + + msg = "The 'axis' keyword in (Series|DataFrame).expanding is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + obj = data.expanding(min_periods=1, axis=0) + result = getattr(obj, func)() assert isinstance(result, frame_or_series) expected = static_comp(data[:11]) @@ -341,26 +348,33 @@ def test_expanding_func(func, static_comp, frame_or_series): def test_expanding_min_periods(func, static_comp): ser = Series(np.random.randn(50)) - result = getattr(ser.expanding(min_periods=30, axis=0), func)() + msg = "The 'axis' keyword in Series.expanding is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = getattr(ser.expanding(min_periods=30, axis=0), func)() assert result[:29].isna().all() tm.assert_almost_equal(result.iloc[-1], static_comp(ser[:50])) # min_periods is working correctly - result = getattr(ser.expanding(min_periods=15, axis=0), func)() + with tm.assert_produces_warning(FutureWarning, match=msg): + result = getattr(ser.expanding(min_periods=15, axis=0), func)() assert isna(result.iloc[13]) assert notna(result.iloc[14]) ser2 = Series(np.random.randn(20)) - result = getattr(ser2.expanding(min_periods=5, axis=0), func)() + with tm.assert_produces_warning(FutureWarning, match=msg): + result = getattr(ser2.expanding(min_periods=5, axis=0), func)() assert isna(result[3]) assert notna(result[4]) # min_periods=0 - result0 = getattr(ser.expanding(min_periods=0, axis=0), func)() - result1 = getattr(ser.expanding(min_periods=1, axis=0), func)() + with tm.assert_produces_warning(FutureWarning, match=msg): + result0 = getattr(ser.expanding(min_periods=0, axis=0), func)() + with tm.assert_produces_warning(FutureWarning, match=msg): + result1 = getattr(ser.expanding(min_periods=1, axis=0), func)() tm.assert_almost_equal(result0, result1) - result = getattr(ser.expanding(min_periods=1, axis=0), func)() + with tm.assert_produces_warning(FutureWarning, match=msg): + result = getattr(ser.expanding(min_periods=1, axis=0), func)() tm.assert_almost_equal(result.iloc[-1], static_comp(ser[:50])) diff --git a/pandas/tests/window/test_rolling.py b/pandas/tests/window/test_rolling.py index e8533b3ca2619..4a35ff0162194 100644 --- a/pandas/tests/window/test_rolling.py +++ b/pandas/tests/window/test_rolling.py @@ -530,12 +530,15 @@ def test_rolling_axis_sum(axis_frame): axis = df._get_axis_number(axis_frame) if axis == 0: + msg = "The 'axis' keyword in DataFrame.rolling" expected = DataFrame({i: [np.nan] * 2 + [3.0] * 8 for i in range(20)}) else: # axis == 1 + msg = "Support for axis=1 in DataFrame.rolling is deprecated" expected = DataFrame([[np.nan] * 2 + [3.0] * 18] * 10) - result = df.rolling(3, axis=axis_frame).sum() + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(3, axis=axis_frame).sum() tm.assert_frame_equal(result, expected) @@ -546,11 +549,14 @@ def test_rolling_axis_count(axis_frame): axis = df._get_axis_number(axis_frame) if axis in [0, "index"]: + msg = "The 'axis' keyword in DataFrame.rolling" expected = DataFrame({"x": [1.0, 2.0, 2.0], "y": [1.0, 2.0, 2.0]}) else: + msg = "Support for axis=1 in DataFrame.rolling is deprecated" expected = DataFrame({"x": [1.0, 1.0, 1.0], "y": [2.0, 2.0, 2.0]}) - result = df.rolling(2, axis=axis_frame, min_periods=0).count() + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(2, axis=axis_frame, min_periods=0).count() tm.assert_frame_equal(result, expected) @@ -569,10 +575,15 @@ def test_rolling_datetime(axis_frame, tz_naive_fixture): df = DataFrame( {i: [1] * 2 for i in date_range("2019-8-01", "2019-08-03", freq="D", tz=tz)} ) + if axis_frame in [0, "index"]: - result = df.T.rolling("2D", axis=axis_frame).sum().T + msg = "The 'axis' keyword in DataFrame.rolling" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.T.rolling("2D", axis=axis_frame).sum().T else: - result = df.rolling("2D", axis=axis_frame).sum() + msg = "Support for axis=1 in DataFrame.rolling" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling("2D", axis=axis_frame).sum() expected = DataFrame( { **{ @@ -1076,7 +1087,10 @@ def test_rolling_mixed_dtypes_axis_1(func, value): # GH: 20649 df = DataFrame(1, index=[1, 2], columns=["a", "b", "c"]) df["c"] = 1.0 - result = getattr(df.rolling(window=2, min_periods=1, axis=1), func)() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + roll = df.rolling(window=2, min_periods=1, axis=1) + result = getattr(roll, func)() expected = DataFrame( {"a": [1.0, 1.0], "b": [value, value], "c": [value, value]}, index=[1, 2], @@ -1093,7 +1107,9 @@ def test_rolling_axis_one_with_nan(): [0, 2, 2, np.nan, 2, np.nan, 1], ] ) - result = df.rolling(window=7, min_periods=1, axis="columns").sum() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(window=7, min_periods=1, axis="columns").sum() expected = DataFrame( [ [0.0, 1.0, 3.0, 7.0, 7.0, 7.0, 7.0], @@ -1112,7 +1128,9 @@ def test_rolling_axis_1_non_numeric_dtypes(value): # GH: 20649 df = DataFrame({"a": [1, 2]}) df["b"] = value - result = df.rolling(window=2, min_periods=1, axis=1).sum() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(window=2, min_periods=1, axis=1).sum() expected = DataFrame({"a": [1.0, 2.0]}) tm.assert_frame_equal(result, expected) @@ -1121,7 +1139,9 @@ def test_rolling_on_df_transposed(): # GH: 32724 df = DataFrame({"A": [1, None], "B": [4, 5], "C": [7, 8]}) expected = DataFrame({"A": [1.0, np.nan], "B": [5.0, 5.0], "C": [11.0, 13.0]}) - result = df.rolling(min_periods=1, window=2, axis=1).sum() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(min_periods=1, window=2, axis=1).sum() tm.assert_frame_equal(result, expected) result = df.T.rolling(min_periods=1, window=2).sum().T @@ -1583,7 +1603,9 @@ def test_rolling_float_dtype(float_numpy_dtype): {"A": [np.nan] * 5, "B": range(10, 20, 2)}, dtype=float_numpy_dtype, ) - result = df.rolling(2, axis=1).sum() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(2, axis=1).sum() tm.assert_frame_equal(result, expected, check_dtype=False) @@ -1603,7 +1625,9 @@ def test_rolling_numeric_dtypes(): "j": "uint64", } ) - result = df.rolling(window=2, min_periods=1, axis=1).min() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(window=2, min_periods=1, axis=1).min() expected = DataFrame( { "a": range(0, 40, 10), diff --git a/pandas/tests/window/test_timeseries_window.py b/pandas/tests/window/test_timeseries_window.py index 0de4b863183df..265ef29e42c48 100644 --- a/pandas/tests/window/test_timeseries_window.py +++ b/pandas/tests/window/test_timeseries_window.py @@ -508,11 +508,11 @@ def test_perf_min(self): ) expected = dfp.rolling(2, min_periods=1).min() result = dfp.rolling("2s").min() - assert ((result - expected) < 0.01).all().bool() + assert ((result - expected) < 0.01).all().all() expected = dfp.rolling(200, min_periods=1).min() result = dfp.rolling("200s").min() - assert ((result - expected) < 0.01).all().bool() + assert ((result - expected) < 0.01).all().all() def test_ragged_max(self, ragged): df = ragged @@ -683,5 +683,9 @@ 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) + warn_msg = "The 'axis' keyword in DataFrame.rolling is deprecated" + if axis == 1: + warn_msg = "Support for axis=1 in DataFrame.rolling is deprecated" with pytest.raises(ValueError, match=f"{msg} values must not have NaT"): - df.rolling("D", axis=axis).mean() + with tm.assert_produces_warning(FutureWarning, match=warn_msg): + df.rolling("D", axis=axis).mean() diff --git a/pandas/tests/window/test_win_type.py b/pandas/tests/window/test_win_type.py index a0d052257ec6b..9af1e8753ffc1 100644 --- a/pandas/tests/window/test_win_type.py +++ b/pandas/tests/window/test_win_type.py @@ -679,7 +679,9 @@ def test_rolling_center_axis_1(): {"a": [1, 1, 0, 0, 0, 1], "b": [1, 0, 0, 1, 0, 0], "c": [1, 0, 0, 1, 0, 1]} ) - result = df.rolling(window=3, axis=1, win_type="boxcar", center=True).sum() + msg = "Support for axis=1 in DataFrame.rolling is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + result = df.rolling(window=3, axis=1, win_type="boxcar", center=True).sum() expected = DataFrame( {"a": [np.nan] * 6, "b": [3.0, 1.0, 0.0, 2.0, 0.0, 2.0], "c": [np.nan] * 6}