diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index ea085b3d1f6ab..48c9305cf2ccc 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -2235,7 +2235,7 @@ def interpolate( *, method, axis: int, - index: Index | None, + index: Index, limit, limit_direction, limit_area, @@ -2255,7 +2255,7 @@ def interpolate( else: out_data = self._ndarray.copy() - missing.interpolate_array_2d( + missing.interpolate_2d_inplace( out_data, method=method, axis=axis, diff --git a/pandas/core/arrays/numpy_.py b/pandas/core/arrays/numpy_.py index 113f22ad968bc..6d60657c3100b 100644 --- a/pandas/core/arrays/numpy_.py +++ b/pandas/core/arrays/numpy_.py @@ -1,6 +1,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import ( + TYPE_CHECKING, + Literal, +) import numpy as np @@ -32,6 +35,7 @@ from pandas._typing import ( AxisInt, Dtype, + FillnaOptions, NpDtype, Scalar, Self, @@ -224,12 +228,42 @@ def _values_for_factorize(self) -> tuple[np.ndarray, float | None]: fv = np.nan return self._ndarray, fv + def pad_or_backfill( + self, + *, + method: FillnaOptions, + axis: int, + limit: int | None, + limit_area: Literal["inside", "outside"] | None = None, + copy: bool = True, + ) -> Self: + """ + ffill or bfill + """ + if copy: + out_data = self._ndarray.copy() + else: + out_data = self._ndarray + + meth = missing.clean_fill_method(method) + missing.pad_or_backfill_inplace( + out_data, + method=meth, + axis=axis, + limit=limit, + limit_area=limit_area, + ) + + if not copy: + return self + return type(self)._simple_new(out_data, dtype=self.dtype) + def interpolate( self, *, method, axis: int, - index: Index | None, + index: Index, limit, limit_direction, limit_area, @@ -246,7 +280,8 @@ def interpolate( else: out_data = self._ndarray.copy() - missing.interpolate_array_2d( + # TODO: assert we have floating dtype? + missing.interpolate_2d_inplace( out_data, method=method, axis=axis, diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py index 1872176394d02..aba6811c5eeb7 100644 --- a/pandas/core/arrays/sparse/array.py +++ b/pandas/core/arrays/sparse/array.py @@ -79,7 +79,7 @@ check_array_indexer, unpack_tuple_and_ellipses, ) -from pandas.core.missing import interpolate_2d +from pandas.core.missing import pad_or_backfill_inplace from pandas.core.nanops import check_below_min_count from pandas.io.formats import printing @@ -764,11 +764,11 @@ def fillna( stacklevel=find_stack_level(), ) new_values = np.asarray(self) - # interpolate_2d modifies new_values inplace - # error: Argument "method" to "interpolate_2d" has incompatible type - # "Literal['backfill', 'bfill', 'ffill', 'pad']"; expected + # pad_or_backfill_inplace modifies new_values inplace + # error: Argument "method" to "pad_or_backfill_inplace" has incompatible + # type "Literal['backfill', 'bfill', 'ffill', 'pad']"; expected # "Literal['pad', 'backfill']" - interpolate_2d( + pad_or_backfill_inplace( new_values, method=method, limit=limit # type: ignore[arg-type] ) return type(self)(new_values, fill_value=self.fill_value) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index e61d233a0ae84..ab5ad8148b063 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -1352,7 +1352,7 @@ def interpolate( inplace: bool = False, limit: int | None = None, limit_direction: Literal["forward", "backward", "both"] = "forward", - limit_area: str | None = None, + limit_area: Literal["inside", "outside"] | None = None, fill_value: Any | None = None, downcast: Literal["infer"] | None = None, using_cow: bool = False, @@ -1410,17 +1410,32 @@ def interpolate( # Dispatch to the PandasArray method. # We know self.array_values is a PandasArray bc EABlock overrides - new_values = cast(PandasArray, self.array_values).interpolate( - method=method, - axis=axis, - index=index, - limit=limit, - limit_direction=limit_direction, - limit_area=limit_area, - fill_value=fill_value, - inplace=arr_inplace, - **kwargs, - ) + if m is not None: + if fill_value is not None: + # similar to validate_fillna_kwargs + raise ValueError("Cannot pass both fill_value and method") + + # TODO: warn about ignored kwargs, limit_direction, index...? + new_values = cast(PandasArray, self.array_values).pad_or_backfill( + method=method, + axis=axis, + limit=limit, + limit_area=limit_area, + copy=not arr_inplace, + ) + else: + assert index is not None # for mypy + new_values = cast(PandasArray, self.array_values).interpolate( + method=method, + axis=axis, + index=index, + limit=limit, + limit_direction=limit_direction, + limit_area=limit_area, + fill_value=fill_value, + inplace=arr_inplace, + **kwargs, + ) data = new_values._ndarray nb = self.make_block_same_class(data, refs=refs) diff --git a/pandas/core/missing.py b/pandas/core/missing.py index 2e05375ca85e7..58b0e2907b8ce 100644 --- a/pandas/core/missing.py +++ b/pandas/core/missing.py @@ -302,60 +302,7 @@ def get_interp_index(method, index: Index) -> Index: return index -def interpolate_array_2d( - data: np.ndarray, - method: str = "pad", - axis: AxisInt = 0, - index: Index | None = None, - limit: int | None = None, - limit_direction: str = "forward", - limit_area: str | None = None, - fill_value: Any | None = None, - **kwargs, -) -> None: - """ - Wrapper to dispatch to either interpolate_2d or _interpolate_2d_with_fill. - - Notes - ----- - Alters 'data' in-place. - """ - try: - m = clean_fill_method(method) - except ValueError: - m = None - - if m is not None: - if fill_value is not None: - # similar to validate_fillna_kwargs - raise ValueError("Cannot pass both fill_value and method") - - interpolate_2d( - data, - method=m, - axis=axis, - limit=limit, - # error: Argument "limit_area" to "interpolate_2d" has incompatible - # type "Optional[str]"; expected "Optional[Literal['inside', 'outside']]" - limit_area=limit_area, # type: ignore[arg-type] - ) - else: - assert index is not None # for mypy - - _interpolate_2d_with_fill( - data=data, - index=index, - axis=axis, - method=method, - limit=limit, - limit_direction=limit_direction, - limit_area=limit_area, - fill_value=fill_value, - **kwargs, - ) - - -def _interpolate_2d_with_fill( +def interpolate_2d_inplace( data: np.ndarray, # floating dtype index: Index, axis: AxisInt, @@ -845,7 +792,7 @@ def _interpolate_with_limit_area( if last is None: last = len(values) - interpolate_2d( + pad_or_backfill_inplace( values, method=method, limit=limit, @@ -861,7 +808,7 @@ def _interpolate_with_limit_area( values[invalid] = np.nan -def interpolate_2d( +def pad_or_backfill_inplace( values: np.ndarray, method: Literal["pad", "backfill"] = "pad", axis: AxisInt = 0,