diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 434f8d62fcdd5..74d4397847f6c 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -1346,32 +1346,44 @@ def fillna( def pad_or_backfill( self, *, - method: FillnaOptions = "pad", + method: FillnaOptions, axis: AxisInt = 0, inplace: bool = False, limit: int | None = None, limit_area: Literal["inside", "outside"] | None = None, downcast: Literal["infer"] | None = None, using_cow: bool = False, - **kwargs, ) -> list[Block]: - return self.interpolate( + if not self._can_hold_na: + # If there are no NAs, then interpolate is a no-op + if using_cow: + return [self.copy(deep=False)] + return [self] if inplace else [self.copy()] + + copy, refs = self._get_refs_and_copy(using_cow, inplace) + + # Dispatch to the PandasArray method. + # We know self.array_values is a PandasArray bc EABlock overrides + new_values = cast(PandasArray, self.array_values).pad_or_backfill( method=method, axis=axis, - inplace=inplace, limit=limit, limit_area=limit_area, - downcast=downcast, - using_cow=using_cow, - **kwargs, + copy=copy, ) + data = extract_array(new_values, extract_numpy=True) + + nb = self.make_block_same_class(data, refs=refs) + return nb._maybe_downcast([nb], downcast, using_cow) + + @final def interpolate( self, *, - method: FillnaOptions | InterpolateOptions = "pad", - axis: AxisInt = 0, - index: Index | None = None, + method: InterpolateOptions, + axis: AxisInt, + index: Index, inplace: bool = False, limit: int | None = None, limit_direction: Literal["forward", "backward", "both"] = "forward", @@ -1381,6 +1393,10 @@ def interpolate( **kwargs, ) -> list[Block]: inplace = validate_bool_kwarg(inplace, "inplace") + # error: Non-overlapping equality check [...] + if method == "asfreq": # type: ignore[comparison-overlap] + # clean_fill_method used to allow this + missing.clean_fill_method(method) if not self._can_hold_na: # If there are no NAs, then interpolate is a no-op @@ -1389,7 +1405,7 @@ def interpolate( return [self] if inplace else [self.copy()] # TODO(3.0): this case will not be reachable once GH#53638 is enforced - if not _interp_method_is_pad_or_backfill(method) and self.dtype == _dtype_obj: + if self.dtype == _dtype_obj: # only deal with floats # bc we already checked that can_hold_na, we don't have int dtype here # test_interp_basic checks that we make a copy here @@ -1414,29 +1430,17 @@ def interpolate( copy, refs = self._get_refs_and_copy(using_cow, inplace) - # Dispatch to the PandasArray method. - # We know self.array_values is a PandasArray bc EABlock overrides - if _interp_method_is_pad_or_backfill(method): - # TODO: warn about ignored kwargs, limit_direction, index...? - new_values = cast(PandasArray, self.array_values).pad_or_backfill( - method=cast(FillnaOptions, method), - axis=axis, - limit=limit, - limit_area=limit_area, - copy=copy, - ) - else: - assert index is not None # for mypy - new_values = cast(PandasArray, self.array_values).interpolate( - method=cast(InterpolateOptions, method), - axis=axis, - index=index, - limit=limit, - limit_direction=limit_direction, - limit_area=limit_area, - copy=copy, - **kwargs, - ) + # Dispatch to the EA method. + new_values = self.array_values.interpolate( + method=method, + axis=axis, + index=index, + limit=limit, + limit_direction=limit_direction, + limit_area=limit_area, + copy=copy, + **kwargs, + ) data = extract_array(new_values, extract_numpy=True) nb = self.make_block_same_class(data, refs=refs) @@ -1863,42 +1867,25 @@ def get_values(self, dtype: DtypeObj | None = None) -> np.ndarray: def values_for_json(self) -> np.ndarray: return np.asarray(self.values) - def interpolate( + @final + def pad_or_backfill( self, *, - method: FillnaOptions | InterpolateOptions = "pad", - index: Index | None = None, - axis: int = 0, + method: FillnaOptions, + axis: AxisInt = 0, inplace: bool = False, limit: int | None = None, - fill_value=None, + limit_area: Literal["inside", "outside"] | None = None, + downcast: Literal["infer"] | None = None, using_cow: bool = False, - **kwargs, - ): + ) -> list[Block]: values = self.values - - if not _interp_method_is_pad_or_backfill(method): - imeth = cast(InterpolateOptions, method) - return super().interpolate( - method=imeth, - index=index, - axis=axis, - inplace=inplace, - limit=limit, - fill_value=fill_value, - using_cow=using_cow, - **kwargs, - ) + if values.ndim == 2 and axis == 0: + # NDArrayBackedExtensionArray.fillna assumes axis=1 + new_values = values.T.fillna(method=method, limit=limit).T else: - meth = cast(FillnaOptions, method) - if values.ndim == 2 and axis == 0: - # NDArrayBackedExtensionArray.fillna assumes axis=1 - new_values = values.T.fillna( - value=fill_value, method=meth, limit=limit - ).T - else: - new_values = values.fillna(value=fill_value, method=meth, limit=limit) - return self.make_block_same_class(new_values) + new_values = values.fillna(method=method, limit=limit) + return [self.make_block_same_class(new_values)] class ExtensionBlock(libinternals.Block, EABackedBlock): @@ -2553,14 +2540,3 @@ def external_values(values: ArrayLike) -> ArrayLike: # TODO(CoW) we should also mark our ExtensionArrays as read-only return values - - -def _interp_method_is_pad_or_backfill(method: str) -> bool: - try: - m = missing.clean_fill_method(method) - except ValueError: - m = None - if method == "asfreq": - # clean_fill_method used to allow this - raise - return m is not None