diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 0f669beaa036f..ad84058d2b75a 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -446,6 +446,7 @@ Indexing Missing ^^^^^^^ +- Bug in :meth:`DataFrame.interpolate` failing to fill across multiblock data when ``method`` is "pad", "ffill", "bfill", or "backfill" (:issue:`53898`) - Bug in :meth:`DataFrame.interpolate` ignoring ``inplace`` when :class:`DataFrame` is empty (:issue:`53199`) - Bug in :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` failing to raise on invalid ``downcast`` keyword, which can be only ``None`` or "infer" (:issue:`53103`) - Bug in :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` with complex dtype incorrectly failing to fill ``NaN`` entries (:issue:`53635`) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8f213a1b7a1e2..59d22126ef0f0 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -7980,22 +7980,18 @@ def interpolate( raise ValueError("downcast must be either None or 'infer'") inplace = validate_bool_kwarg(inplace, "inplace") - axis = self._get_axis_number(axis) - fillna_methods = ["ffill", "bfill", "pad", "backfill"] - should_transpose = axis == 1 and method not in fillna_methods - - obj = self.T if should_transpose else self - - if obj.empty: + if self.empty: if inplace: return None return self.copy() if not isinstance(method, str): raise ValueError("'method' should be a string, not None.") - elif method.lower() in fillna_methods: + + fillna_methods = ["ffill", "bfill", "pad", "backfill"] + if method.lower() in fillna_methods: # GH#53581 warnings.warn( f"{type(self).__name__}.interpolate with method={method} is " @@ -8004,17 +8000,26 @@ def interpolate( FutureWarning, stacklevel=find_stack_level(), ) - elif np.any(obj.dtypes == object): - # GH#53631 - if not (obj.ndim == 2 and np.all(obj.dtypes == object)): - # don't warn in cases that already raise - warnings.warn( - f"{type(self).__name__}.interpolate with object dtype is " - "deprecated and will raise in a future version. Call " - "obj.infer_objects(copy=False) before interpolating instead.", - FutureWarning, - stacklevel=find_stack_level(), - ) + obj, should_transpose = self, False + else: + obj, should_transpose = (self.T, True) if axis == 1 else (self, False) + if np.any(obj.dtypes == object): + # GH#53631 + if not (obj.ndim == 2 and np.all(obj.dtypes == object)): + # don't warn in cases that already raise + warnings.warn( + f"{type(self).__name__}.interpolate with object dtype is " + "deprecated and will raise in a future version. Call " + "obj.infer_objects(copy=False) before interpolating instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + + if "fill_value" in kwargs: + raise ValueError( + "'fill_value' is not a valid keyword for " + f"{type(self).__name__}.interpolate" + ) if isinstance(obj.index, MultiIndex) and method != "linear": raise ValueError( @@ -8023,23 +8028,22 @@ def interpolate( limit_direction = missing.infer_limit_direction(limit_direction, method) - if obj.ndim == 2 and np.all(obj.dtypes == np.dtype("object")): + if obj.ndim == 2 and np.all(obj.dtypes == object): raise TypeError( "Cannot interpolate with all object-dtype columns " "in the DataFrame. Try setting at least one " "column to a numeric dtype." ) - if "fill_value" in kwargs: - raise ValueError( - "'fill_value' is not a valid keyword for " - f"{type(self).__name__}.interpolate" - ) - if method.lower() in fillna_methods: # TODO(3.0): remove this case # TODO: warn/raise on limit_direction or kwargs which are ignored? # as of 2023-06-26 no tests get here with either + if not self._mgr.is_single_block and axis == 1: + # GH#53898 + if inplace: + raise NotImplementedError() + obj, axis, should_transpose = self.T, 1 - axis, True new_data = obj._mgr.pad_or_backfill( method=method, diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index ac862e5673411..fcdf7db35446e 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -451,8 +451,11 @@ def test_interp_string_axis(self, axis_name, axis_number): expected = df.interpolate(method="linear", axis=axis_number) tm.assert_frame_equal(result, expected) + @pytest.mark.parametrize("multiblock", [True, False]) @pytest.mark.parametrize("method", ["ffill", "bfill", "pad"]) - def test_interp_fillna_methods(self, request, axis, method, using_array_manager): + def test_interp_fillna_methods( + self, request, axis, multiblock, method, using_array_manager + ): # GH 12918 if using_array_manager and axis in (1, "columns"): # TODO(ArrayManager) support axis=1 @@ -465,6 +468,10 @@ def test_interp_fillna_methods(self, request, axis, method, using_array_manager) "C": [3.0, 6.0, 9.0, np.nan, np.nan, 30.0], } ) + if multiblock: + df["D"] = np.nan + df["E"] = 1.0 + method2 = method if method != "pad" else "ffill" expected = getattr(df, method2)(axis=axis) msg = f"DataFrame.interpolate with method={method} is deprecated"