From d9d518d36d0adc6720a1a0c16f8ebf11c4252e3f Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 14 Mar 2023 11:53:04 -0700 Subject: [PATCH 1/5] DEPR: method, limit, fill_axis keywords in align --- doc/source/whatsnew/v2.1.0.rst | 1 + pandas/core/frame.py | 2 +- pandas/core/generic.py | 26 +++++++++++++++--- pandas/core/series.py | 2 +- pandas/tests/frame/methods/test_align.py | 33 ++++++++++++++++++----- pandas/tests/series/methods/test_align.py | 14 ++++++++-- 6 files changed, 64 insertions(+), 14 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 410a324be829e..fa8dc94aeb45a 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -100,6 +100,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 650df0d18e54d..b5837bf604d88 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -5009,7 +5009,7 @@ def align( fill_value=None, method: FillnaOptions | None = None, limit: int | None = None, - fill_axis: Axis = 0, + fill_axis: Axis | None = None, broadcast_axis: Axis | None = None, ) -> tuple[DataFrame, NDFrameT]: return super().align( diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 17e4a4c142f66..17224dea41dfc 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9326,7 +9326,7 @@ def align( fill_value: Hashable = None, method: FillnaOptions | None = None, limit: int | None = None, - fill_axis: Axis = 0, + fill_axis: Axis | None = None, broadcast_axis: Axis | None = None, ) -> tuple[NDFrameT, NDFrameTb]: """ @@ -9355,6 +9355,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 @@ -9362,8 +9364,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. @@ -9440,6 +9448,18 @@ def align( """ method = clean_fill_method(method) + if method is not None or limit is not None or fill_axis is not None: + # 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 None: + fill_axis = 0 if broadcast_axis == 1 and self.ndim != other.ndim: if isinstance(self, ABCSeries): @@ -9567,9 +9587,7 @@ def _align_frame( ) if method is not None: - _left = left.fillna(method=method, axis=fill_axis, limit=limit) - assert _left is not None # needed for mypy - left = _left + left = left.fillna(method=method, axis=fill_axis, limit=limit) right = right.fillna(method=method, axis=fill_axis, limit=limit) # if DatetimeIndex have different tz, convert to UTC diff --git a/pandas/core/series.py b/pandas/core/series.py index e8d6491e43007..d21f2f92e37a2 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4591,7 +4591,7 @@ def align( fill_value: Hashable = None, method: FillnaOptions | None = None, limit: int | None = None, - fill_axis: Axis = 0, + fill_axis: Axis | None = None, broadcast_axis: Axis | None = None, ) -> tuple[Series, NDFrameT]: return super().align( diff --git a/pandas/tests/frame/methods/test_align.py b/pandas/tests/frame/methods/test_align.py index ec7d75ef4debb..3dbb9dee948a3 100644 --- a/pandas/tests/frame/methods/test_align.py +++ b/pandas/tests/frame/methods/test_align.py @@ -83,7 +83,12 @@ 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( @@ -132,13 +137,23 @@ 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): @@ -346,10 +361,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) From 777ce35b6fd6f592bf26e4e68fc7ee6e9da7135f Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 14 Mar 2023 12:49:00 -0700 Subject: [PATCH 2/5] fix test --- pandas/tests/series/methods/test_align.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/series/methods/test_align.py b/pandas/tests/series/methods/test_align.py index 04951e6f42e36..95aae2ba02f5c 100644 --- a/pandas/tests/series/methods/test_align.py +++ b/pandas/tests/series/methods/test_align.py @@ -182,7 +182,8 @@ def test_align_with_dataframe_method(method): "The 'method', 'limit', and 'fill_axis' keywords in Series.align " "are deprecated" ) - with tm.assert_produces_warning(FutureWarning, match=msg): + warn = None if method is None else FutureWarning + with tm.assert_produces_warning(warn, 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) From 4e4f47be424198ef77c230a418782772477de637 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 15 Mar 2023 13:09:37 -0700 Subject: [PATCH 3/5] None->lib.no_default --- pandas/core/frame.py | 6 +++--- pandas/core/generic.py | 21 ++++++++++++++------- pandas/core/series.py | 6 +++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index b5837bf604d88..68577b8772997 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -5007,9 +5007,9 @@ def align( level: Level = None, copy: bool | None = None, fill_value=None, - method: FillnaOptions | None = None, - limit: int | None = None, - fill_axis: Axis | None = None, + 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[DataFrame, NDFrameT]: return super().align( diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 17224dea41dfc..06039fea62d2b 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9324,9 +9324,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 | None = None, + 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[NDFrameT, NDFrameTb]: """ @@ -9446,9 +9446,11 @@ def align( 3 60.0 70.0 80.0 90.0 NaN 4 600.0 700.0 800.0 900.0 NaN """ - - method = clean_fill_method(method) - if method is not None or limit is not None or fill_axis is not None: + 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 " @@ -9458,8 +9460,13 @@ def align( FutureWarning, stacklevel=find_stack_level(), ) - if fill_axis is None: + 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: if isinstance(self, ABCSeries): diff --git a/pandas/core/series.py b/pandas/core/series.py index d21f2f92e37a2..70d4e8329b923 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4589,9 +4589,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 | None = None, + 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[Series, NDFrameT]: return super().align( From 84454f6989f7fea3cdf7ebaa19425d9278bcb9bd Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 15 Mar 2023 15:17:26 -0700 Subject: [PATCH 4/5] update tests --- pandas/tests/frame/methods/test_align.py | 36 ++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/pandas/tests/frame/methods/test_align.py b/pandas/tests/frame/methods/test_align.py index 6cf82ba3cdfa6..995bbec11a5c4 100644 --- a/pandas/tests/frame/methods/test_align.py +++ b/pandas/tests/frame/methods/test_align.py @@ -91,14 +91,24 @@ def test_align_float(self, float_frame, using_copy_on_write): 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 @@ -162,17 +172,27 @@ 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( From d2cf6dbb1e290038cc4eedb76854c69bf63b4221 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 15 Mar 2023 15:21:45 -0700 Subject: [PATCH 5/5] fix Series.align test --- pandas/tests/series/methods/test_align.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/series/methods/test_align.py b/pandas/tests/series/methods/test_align.py index 95aae2ba02f5c..04951e6f42e36 100644 --- a/pandas/tests/series/methods/test_align.py +++ b/pandas/tests/series/methods/test_align.py @@ -182,8 +182,7 @@ def test_align_with_dataframe_method(method): "The 'method', 'limit', and 'fill_axis' keywords in Series.align " "are deprecated" ) - warn = None if method is None else FutureWarning - with tm.assert_produces_warning(warn, match=msg): + 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)