From 87bb83a1c155a0b8f499f4aa45a51461e82e96f1 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Sat, 10 Feb 2024 23:59:39 +0100 Subject: [PATCH 1/2] Remove limit, fill_axis, broadcast_axis and method parameters from align --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/generic.py | 148 +------------------- pandas/tests/frame/methods/test_align.py | 159 +--------------------- pandas/tests/series/methods/test_align.py | 53 -------- 4 files changed, 9 insertions(+), 352 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 8b8f5bf3d028c..cae11fae528eb 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -116,6 +116,7 @@ Removal of prior version deprecations/changes - Removed ``Series.view`` (:issue:`56054`) - Removed ``axis`` argument from :meth:`DataFrame.groupby`, :meth:`Series.groupby`, :meth:`DataFrame.rolling`, :meth:`Series.rolling`, :meth:`DataFrame.resample`, and :meth:`Series.resample` (:issue:`51203`) - Removed ``axis`` argument from all groupby operations (:issue:`50405`) +- Removed ``method``, ``limit`` ``fill_axis`` and ``broadcast_axis`` keywords from :meth:`DataFrame.align` (:issue:`51968`) - Removed ``pandas.api.types.is_interval`` and ``pandas.api.types.is_period``, use ``isinstance(obj, pd.Interval)`` and ``isinstance(obj, pd.Period)`` instead (:issue:`55264`) - Removed ``pandas.io.sql.execute`` (:issue:`50185`) - Removed ``pandas.value_counts``, use :meth:`Series.value_counts` instead (:issue:`53493`) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8e0767d9b3699..caa5e7f9c2083 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9601,10 +9601,6 @@ def align( level: Level | None = None, copy: bool | None = None, fill_value: Hashable | 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 | lib.NoDefault = lib.no_default, ) -> tuple[Self, NDFrameT]: """ Align two objects on their axes with the specified join method. @@ -9646,35 +9642,6 @@ def align( fill_value : scalar, default np.nan Value to use for missing values. Defaults to NaN, but can be any "compatible" value. - method : {{'backfill', 'bfill', 'pad', 'ffill', None}}, default None - Method to use for filling holes in reindexed Series: - - - 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 - a gap with more than this number of consecutive NaNs, it will only - 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. - - .. deprecated:: 2.1 - Returns ------- tuple of ({klass}, type of other) @@ -9745,92 +9712,6 @@ 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 - - if method is not None: - method = clean_fill_method(method) - - if broadcast_axis is not lib.no_default: - # GH#51856 - # TODO(3.0): enforcing this deprecation will close GH#13194 - msg = ( - f"The 'broadcast_axis' keyword in {type(self).__name__}.align is " - "deprecated and will be removed in a future version." - ) - if broadcast_axis is not None: - if self.ndim == 1 and other.ndim == 2: - msg += ( - " Use left = DataFrame({col: left for col in right.columns}, " - "index=right.index) before calling `left.align(right)` instead." - ) - elif self.ndim == 2 and other.ndim == 1: - msg += ( - " Use right = DataFrame({col: right for col in left.columns}, " - "index=left.index) before calling `left.align(right)` instead" - ) - warnings.warn(msg, FutureWarning, stacklevel=find_stack_level()) - else: - broadcast_axis = None - - if broadcast_axis == 1 and self.ndim != other.ndim: - if isinstance(self, ABCSeries): - # this means other is a DataFrame, and we need to broadcast - # self - cons = self._constructor_expanddim - df = cons( - {c: self for c in other.columns}, **other._construct_axes_dict() - ) - # error: Incompatible return value type (got "Tuple[DataFrame, - # DataFrame]", expected "Tuple[Self, NDFrameT]") - return df._align_frame( # type: ignore[return-value] - other, # type: ignore[arg-type] - join=join, - axis=axis, - level=level, - fill_value=fill_value, - method=method, - limit=limit, - fill_axis=fill_axis, - )[:2] - elif isinstance(other, ABCSeries): - # this means self is a DataFrame, and we need to broadcast - # other - cons = other._constructor_expanddim - df = cons( - {c: other for c in self.columns}, **self._construct_axes_dict() - ) - # error: Incompatible return value type (got "Tuple[NDFrameT, - # DataFrame]", expected "Tuple[Self, NDFrameT]") - return self._align_frame( # type: ignore[return-value] - df, - join=join, - axis=axis, - level=level, - fill_value=fill_value, - method=method, - limit=limit, - fill_axis=fill_axis, - )[:2] - _right: DataFrame | Series if axis is not None: axis = self._get_axis_number(axis) @@ -9841,9 +9722,6 @@ def align( axis=axis, level=level, fill_value=fill_value, - method=method, - limit=limit, - fill_axis=fill_axis, ) elif isinstance(other, ABCSeries): @@ -9853,9 +9731,6 @@ def align( axis=axis, level=level, fill_value=fill_value, - method=method, - limit=limit, - fill_axis=fill_axis, ) else: # pragma: no cover raise TypeError(f"unsupported type: {type(other)}") @@ -9886,9 +9761,6 @@ def _align_frame( axis: Axis | None = None, level=None, fill_value=None, - method=None, - limit: int | None = None, - fill_axis: Axis = 0, ) -> tuple[Self, DataFrame, Index | None]: # defaults join_index, join_columns = None, None @@ -9925,11 +9797,6 @@ def _align_frame( fill_value=fill_value, allow_dups=True, ) - - if method is not None: - left = left._pad_or_backfill(method, axis=fill_axis, limit=limit) - right = right._pad_or_backfill(method, axis=fill_axis, limit=limit) - return left, right, join_index @final @@ -9940,9 +9807,6 @@ def _align_series( axis: Axis | None = None, level=None, fill_value=None, - method=None, - limit: int | None = None, - fill_axis: Axis = 0, ) -> tuple[Self, Series, Index | None]: is_series = isinstance(self, ABCSeries) @@ -9994,15 +9858,11 @@ def _align_series( right = other.reindex(join_index, level=level) # fill - fill_na = notna(fill_value) or (method is not None) + fill_na = notna(fill_value) if fill_na: - fill_value, method = validate_fillna_kwargs(fill_value, method) - if method is not None: - left = left._pad_or_backfill(method, limit=limit, axis=fill_axis) - right = right._pad_or_backfill(method, limit=limit) - else: - left = left.fillna(fill_value, limit=limit, axis=fill_axis) - right = right.fillna(fill_value, limit=limit) + fill_value, _ = validate_fillna_kwargs(fill_value, None) + left = left.fillna(fill_value) + right = right.fillna(fill_value) return left, right, join_index diff --git a/pandas/tests/frame/methods/test_align.py b/pandas/tests/frame/methods/test_align.py index aa539dd0b2dbe..d580be3550c03 100644 --- a/pandas/tests/frame/methods/test_align.py +++ b/pandas/tests/frame/methods/test_align.py @@ -14,14 +14,6 @@ class TestDataFrameAlign: - def test_align_asfreq_method_raises(self): - df = DataFrame({"A": [1, np.nan, 2]}) - msg = "Invalid fill method" - msg2 = "The 'method', 'limit', and 'fill_axis' keywords" - with pytest.raises(ValueError, match=msg): - with tm.assert_produces_warning(FutureWarning, match=msg2): - df.align(df.iloc[::-1], method="asfreq") - def test_frame_align_aware(self): idx1 = date_range("2001", periods=5, freq="h", tz="US/Eastern") idx2 = date_range("2001", periods=5, freq="2h", tz="US/Eastern") @@ -88,34 +80,6 @@ def test_align_float(self, float_frame): af, bf = float_frame.align(other, join="inner", axis=1) tm.assert_index_equal(bf.columns, other.columns) - 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) - - 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([]).astype(bf.index.dtype)) - - 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([]).astype(bf.index.dtype)) - # Try to align DataFrame to Series along bad axis msg = "No axis named 2 for object type DataFrame" with pytest.raises(ValueError, match=msg): @@ -131,16 +95,6 @@ def test_align_frame_with_series(self, float_frame): tm.assert_index_equal(right.index, float_frame.index) assert isinstance(right, Series) - msg = "The 'broadcast_axis' keyword in DataFrame.align is deprecated" - with tm.assert_produces_warning(FutureWarning, match=msg): - left, right = float_frame.align(s, broadcast_axis=1) - tm.assert_index_equal(left.index, float_frame.index) - expected = {c: s for c in float_frame.columns} - expected = DataFrame( - expected, index=float_frame.index, columns=float_frame.columns - ) - tm.assert_frame_equal(right, expected) - def test_align_series_condition(self): # see gh-9558 df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) @@ -152,54 +106,19 @@ def test_align_series_condition(self): expected = DataFrame({"a": [0, 2, 0], "b": [0, 5, 0]}) tm.assert_frame_equal(result, expected) - def test_align_int(self, int_frame): - # test other non-float types - other = DataFrame(index=range(5), columns=["A", "B", "C"]) - - 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): - 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"]) - - msg = ( - "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " - "are deprecated" + af, bf = mixed_float_frame.align( + other.iloc[:, 0], join="inner", axis=1, fill_value=0 ) - 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"]) - - msg = ( - "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " - "are deprecated" + af, bf = mixed_int_frame.align( + other.iloc[:, 0], join="inner", axis=1, fill_value=0 ) - 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( @@ -389,76 +308,6 @@ def test_missing_axis_specification_exception(self): with pytest.raises(ValueError, match=r"axis=0 or 1"): df.align(series) - @pytest.mark.parametrize("method", ["pad", "bfill"]) - @pytest.mark.parametrize("axis", [0, 1, None]) - @pytest.mark.parametrize("fill_axis", [0, 1]) - @pytest.mark.parametrize( - "left_slice", - [ - [slice(4), slice(10)], - [slice(0), slice(0)], - ], - ) - @pytest.mark.parametrize( - "right_slice", - [ - [slice(2, None), slice(6, None)], - [slice(0), slice(0)], - ], - ) - @pytest.mark.parametrize("limit", [1, None]) - def test_align_fill_method( - self, - join_type, - method, - axis, - fill_axis, - float_frame, - left_slice, - right_slice, - limit, - ): - how = join_type - frame = float_frame - left = frame.iloc[left_slice[0], left_slice[1]] - right = frame.iloc[right_slice[0], right_slice[1]] - - msg = ( - "The 'method', 'limit', and 'fill_axis' keywords in DataFrame.align " - "are deprecated" - ) - - with tm.assert_produces_warning(FutureWarning, match=msg): - aa, ab = left.align( - right, - axis=axis, - join=how, - method=method, - limit=limit, - fill_axis=fill_axis, - ) - - join_index, join_columns = None, None - - ea, eb = left, right - if axis is None or axis == 0: - join_index = left.index.join(right.index, how=how) - ea = ea.reindex(index=join_index) - eb = eb.reindex(index=join_index) - - if axis is None or axis == 1: - join_columns = left.columns.join(right.columns, how=how) - ea = ea.reindex(columns=join_columns) - eb = eb.reindex(columns=join_columns) - - msg = "DataFrame.fillna with 'method' is deprecated" - with tm.assert_produces_warning(FutureWarning, match=msg): - ea = ea.fillna(axis=fill_axis, method=method, limit=limit) - eb = eb.fillna(axis=fill_axis, method=method, limit=limit) - - tm.assert_frame_equal(aa, ea) - tm.assert_frame_equal(ab, eb) - def test_align_series_check_copy(self): # GH# df = DataFrame({0: [1, 2]}) diff --git a/pandas/tests/series/methods/test_align.py b/pandas/tests/series/methods/test_align.py index df3dd6f4d8ab0..3f5f8988a0ab4 100644 --- a/pandas/tests/series/methods/test_align.py +++ b/pandas/tests/series/methods/test_align.py @@ -52,43 +52,6 @@ def test_align(datetime_series, first_slice, second_slice, join_type, fill): assert eb.name == "ts" -@pytest.mark.parametrize( - "first_slice,second_slice", - [ - [[2, None], [None, -5]], - [[None, 0], [None, -5]], - [[None, -5], [None, 0]], - [[None, 0], [None, 0]], - ], -) -@pytest.mark.parametrize("method", ["pad", "bfill"]) -@pytest.mark.parametrize("limit", [None, 1]) -def test_align_fill_method( - datetime_series, first_slice, second_slice, join_type, method, limit -): - a = datetime_series[slice(*first_slice)] - b = datetime_series[slice(*second_slice)] - - 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) - eb = b.reindex(join_index) - - msg2 = "Series.fillna with 'method' is deprecated" - with tm.assert_produces_warning(FutureWarning, match=msg2): - ea = ea.fillna(method=method, limit=limit) - eb = eb.fillna(method=method, limit=limit) - - tm.assert_series_equal(aa, ea) - tm.assert_series_equal(ab, eb) - - def test_align_nocopy(datetime_series): b = datetime_series[:5].copy() @@ -166,22 +129,6 @@ def test_align_multiindex(): tm.assert_series_equal(expr, res2l) -@pytest.mark.parametrize("method", ["backfill", "bfill", "pad", "ffill", None]) -def test_align_with_dataframe_method(method): - # GH31788 - ser = Series(range(3), index=range(3)) - df = pd.DataFrame(0.0, index=range(3), columns=range(3)) - - 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) - - def test_align_dt64tzindex_mismatched_tzs(): idx1 = date_range("2001", periods=5, freq="h", tz="US/Eastern") ser = Series(np.random.default_rng(2).standard_normal(len(idx1)), index=idx1) From bed0923f218c46108ca1b8a8e466857d7ead1e1b Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Sun, 11 Feb 2024 00:47:34 +0100 Subject: [PATCH 2/2] Fixup --- pandas/core/generic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index caa5e7f9c2083..1f222beae3755 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9642,6 +9642,7 @@ def align( fill_value : scalar, default np.nan Value to use for missing values. Defaults to NaN, but can be any "compatible" value. + Returns ------- tuple of ({klass}, type of other)