diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 5ee61d3df1062..e63d9c7b85a55 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -102,6 +102,7 @@ Deprecations - 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 'method', 'limit', and 'fill_axis' keywords in :meth:`DataFrame.align` and :meth:`Series.align`, explicitly call ``fillna`` on the alignment results instead (:issue:`51856`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 393d0fb9b4b8c..8523089fcfdc9 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -5014,9 +5014,9 @@ def align( level: Level = None, copy: bool | None = None, fill_value=None, - method: FillnaOptions | None = None, - limit: int | None = None, - fill_axis: Axis = 0, + method: FillnaOptions | None | lib.NoDefault = lib.no_default, + limit: int | None | lib.NoDefault = lib.no_default, + fill_axis: Axis | lib.NoDefault = lib.no_default, broadcast_axis: Axis | None = None, ) -> tuple[Self, NDFrameT]: return super().align( diff --git a/pandas/core/generic.py b/pandas/core/generic.py index c3818a0a425b9..a3174fb70e712 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9318,9 +9318,9 @@ def align( level: Level = None, copy: bool_t | None = None, fill_value: Hashable = None, - method: FillnaOptions | None = None, - limit: int | None = None, - fill_axis: Axis = 0, + method: FillnaOptions | None | lib.NoDefault = lib.no_default, + limit: int | None | lib.NoDefault = lib.no_default, + fill_axis: Axis | lib.NoDefault = lib.no_default, broadcast_axis: Axis | None = None, ) -> tuple[Self, NDFrameT]: """ @@ -9349,6 +9349,8 @@ def align( - pad / ffill: propagate last valid observation forward to next valid. - backfill / bfill: use NEXT valid observation to fill gap. + .. deprecated:: 2.1 + limit : int, default None If method is specified, this is the maximum number of consecutive NaN values to forward/backward fill. In other words, if there is @@ -9356,8 +9358,14 @@ def align( be partially filled. If method is not specified, this is the maximum number of entries along the entire axis where NaNs will be filled. Must be greater than 0 if not None. + + .. deprecated:: 2.1 + fill_axis : {axes_single_arg}, default 0 Filling axis, method and limit. + + .. deprecated:: 2.1 + broadcast_axis : {axes_single_arg}, default None Broadcast values along this axis, if aligning two objects of different dimensions. @@ -9432,7 +9440,26 @@ def align( 3 60.0 70.0 80.0 90.0 NaN 4 600.0 700.0 800.0 900.0 NaN """ - + if ( + method is not lib.no_default + or limit is not lib.no_default + or fill_axis is not lib.no_default + ): + # GH#51856 + warnings.warn( + "The 'method', 'limit', and 'fill_axis' keywords in " + f"{type(self).__name__}.align are deprecated and will be removed " + "in a future version. Call fillna directly on the returned objects " + "instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + if fill_axis is lib.no_default: + fill_axis = 0 + if method is lib.no_default: + method = None + if limit is lib.no_default: + limit = None method = clean_fill_method(method) if broadcast_axis == 1 and self.ndim != other.ndim: diff --git a/pandas/core/series.py b/pandas/core/series.py index 7050dd0ffb7df..16423059fe32e 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4596,9 +4596,9 @@ def align( level: Level = None, copy: bool | None = None, fill_value: Hashable = None, - method: FillnaOptions | None = None, - limit: int | None = None, - fill_axis: Axis = 0, + method: FillnaOptions | None | lib.NoDefault = lib.no_default, + limit: int | None | lib.NoDefault = lib.no_default, + fill_axis: Axis | lib.NoDefault = lib.no_default, broadcast_axis: Axis | None = None, ) -> tuple[Self, NDFrameT]: return super().align( diff --git a/pandas/tests/frame/methods/test_align.py b/pandas/tests/frame/methods/test_align.py index a50aed401e82c..995bbec11a5c4 100644 --- a/pandas/tests/frame/methods/test_align.py +++ b/pandas/tests/frame/methods/test_align.py @@ -83,17 +83,32 @@ def test_align_float(self, float_frame, using_copy_on_write): af, bf = float_frame.align(other, join="inner", axis=1) tm.assert_index_equal(bf.columns, other.columns) - af, bf = float_frame.align(other, join="inner", axis=1, method="pad") + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" + ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = float_frame.align(other, join="inner", axis=1, method="pad") tm.assert_index_equal(bf.columns, other.columns) - af, bf = float_frame.align( - other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=None + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = float_frame.align( + other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=None + ) tm.assert_index_equal(bf.index, Index([])) - af, bf = float_frame.align( - other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=0 + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = float_frame.align( + other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=0 + ) tm.assert_index_equal(bf.index, Index([])) # Try to align DataFrame to Series along bad axis @@ -134,30 +149,50 @@ def test_align_int(self, int_frame): # test other non-float types other = DataFrame(index=range(5), columns=["A", "B", "C"]) - af, bf = int_frame.align(other, join="inner", axis=1, method="pad") + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" + ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = int_frame.align(other, join="inner", axis=1, method="pad") tm.assert_index_equal(bf.columns, other.columns) def test_align_mixed_type(self, float_string_frame): - af, bf = float_string_frame.align( - float_string_frame, join="inner", axis=1, method="pad" + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = float_string_frame.align( + float_string_frame, join="inner", axis=1, method="pad" + ) tm.assert_index_equal(bf.columns, float_string_frame.columns) def test_align_mixed_float(self, mixed_float_frame): # mixed floats/ints other = DataFrame(index=range(5), columns=["A", "B", "C"]) - af, bf = mixed_float_frame.align( - other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=0 + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = mixed_float_frame.align( + other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=0 + ) tm.assert_index_equal(bf.index, Index([])) def test_align_mixed_int(self, mixed_int_frame): other = DataFrame(index=range(5), columns=["A", "B", "C"]) - af, bf = mixed_int_frame.align( - other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=0 + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" ) + with tm.assert_produces_warning(FutureWarning, match=msg): + af, bf = mixed_int_frame.align( + other.iloc[:, 0], join="inner", axis=1, method=None, fill_value=0 + ) tm.assert_index_equal(bf.index, Index([])) @pytest.mark.parametrize( @@ -348,10 +383,16 @@ def test_missing_axis_specification_exception(self): df.align(series) def _check_align(self, a, b, axis, fill_axis, how, method, limit=None): - aa, ab = a.align( - b, axis=axis, join=how, method=method, limit=limit, fill_axis=fill_axis + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " + "are deprecated" ) + with tm.assert_produces_warning(FutureWarning, match=msg): + aa, ab = a.align( + b, axis=axis, join=how, method=method, limit=limit, fill_axis=fill_axis + ) + join_index, join_columns = None, None ea, eb = a, b diff --git a/pandas/tests/series/methods/test_align.py b/pandas/tests/series/methods/test_align.py index 7f34f4046d33c..04951e6f42e36 100644 --- a/pandas/tests/series/methods/test_align.py +++ b/pandas/tests/series/methods/test_align.py @@ -69,7 +69,12 @@ def test_align_fill_method( a = datetime_series[slice(*first_slice)] b = datetime_series[slice(*second_slice)] - aa, ab = a.align(b, join=join_type, method=method, limit=limit) + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in Series.align " + "are deprecated" + ) + with tm.assert_produces_warning(FutureWarning, match=msg): + aa, ab = a.align(b, join=join_type, method=method, limit=limit) join_index = a.index.join(b.index, how=join_type) ea = a.reindex(join_index) @@ -173,7 +178,12 @@ def test_align_with_dataframe_method(method): ser = Series(range(3), index=range(3)) df = pd.DataFrame(0.0, index=range(3), columns=range(3)) - result_ser, result_df = ser.align(df, method=method) + msg = ( + "The 'method', 'limit', and 'fill_axis' keywords in Series.align " + "are deprecated" + ) + with tm.assert_produces_warning(FutureWarning, match=msg): + result_ser, result_df = ser.align(df, method=method) tm.assert_series_equal(result_ser, ser) tm.assert_frame_equal(result_df, df)