Skip to content

REF: clarify missing.interpolate_2d is inplace #44210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pandas/core/arrays/sparse/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,8 +743,10 @@ def fillna(
elif method is not None:
msg = "fillna with 'method' requires high memory usage."
warnings.warn(msg, PerformanceWarning)
filled = interpolate_2d(np.asarray(self), method=method, limit=limit)
return type(self)(filled, fill_value=self.fill_value)
new_values = np.asarray(self)
# interpolate_2d modifies new_values inplace
interpolate_2d(new_values, method=method, limit=limit)
return type(self)(new_values, fill_value=self.fill_value)

else:
new_values = np.where(isna(self.sp_values), value, self.sp_values)
Expand Down
74 changes: 37 additions & 37 deletions pandas/core/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,11 @@ def interpolate_array_2d(
coerce: bool = False,
downcast: str | None = None,
**kwargs,
):
) -> np.ndarray:
"""
Wrapper to dispatch to either interpolate_2d or _interpolate_2d_with_fill.

Returned ndarray has same dtype as 'data'.
"""
try:
m = clean_fill_method(method)
Expand All @@ -228,13 +230,14 @@ def interpolate_array_2d(
# similar to validate_fillna_kwargs
raise ValueError("Cannot pass both fill_value and method")

interp_values = interpolate_2d(
interpolate_2d(
data,
method=m,
axis=axis,
limit=limit,
limit_area=limit_area,
)
interp_values = data
else:
assert index is not None # for mypy

Expand Down Expand Up @@ -687,14 +690,14 @@ def _cubicspline_interpolate(xi, yi, x, axis=0, bc_type="not-a-knot", extrapolat


def _interpolate_with_limit_area(
values: ArrayLike, method: str, limit: int | None, limit_area: str | None
) -> ArrayLike:
values: np.ndarray, method: str, limit: int | None, limit_area: str | None
) -> None:
"""
Apply interpolation and limit_area logic to values along a to-be-specified axis.

Parameters
----------
values: array-like
values: np.ndarray
Input array.
method: str
Interpolation method. Could be "bfill" or "pad"
Expand All @@ -703,10 +706,9 @@ def _interpolate_with_limit_area(
limit_area: str
Limit area for interpolation. Can be "inside" or "outside"

Returns
-------
values: array-like
Interpolated array.
Notes
-----
Modifies values in-place.
"""

invalid = isna(values)
Expand All @@ -719,7 +721,7 @@ def _interpolate_with_limit_area(
if last is None:
last = len(values)

values = interpolate_2d(
interpolate_2d(
values,
method=method,
limit=limit,
Expand All @@ -732,23 +734,23 @@ def _interpolate_with_limit_area(

values[invalid] = np.nan

return values
return


def interpolate_2d(
values,
values: np.ndarray,
method: str = "pad",
axis: Axis = 0,
limit: int | None = None,
limit_area: str | None = None,
):
) -> None:
"""
Perform an actual interpolation of values, values will be make 2-d if
needed fills inplace, returns the result.

Parameters
----------
values: array-like
values: np.ndarray
Input array.
method: str, default "pad"
Interpolation method. Could be "bfill" or "pad"
Expand All @@ -759,13 +761,12 @@ def interpolate_2d(
limit_area: str, optional
Limit area for interpolation. Can be "inside" or "outside"

Returns
-------
values: array-like
Interpolated array.
Notes
-----
Modifies values in-place.
"""
if limit_area is not None:
return np.apply_along_axis(
np.apply_along_axis(
partial(
_interpolate_with_limit_area,
method=method,
Expand All @@ -775,32 +776,31 @@ def interpolate_2d(
axis,
values,
)
return

transf = (lambda x: x) if axis == 0 else (lambda x: x.T)

# reshape a 1 dim if needed
ndim = values.ndim
if values.ndim == 1:
if axis != 0: # pragma: no cover
raise AssertionError("cannot interpolate on a ndim == 1 with axis != 0")
values = values.reshape(tuple((1,) + values.shape))

method = clean_fill_method(method)
tvalues = transf(values)

# _pad_2d and _backfill_2d both modify tvalues inplace
if method == "pad":
result, _ = _pad_2d(tvalues, limit=limit)
_pad_2d(tvalues, limit=limit)
else:
result, _ = _backfill_2d(tvalues, limit=limit)

result = transf(result)
# reshape back
if ndim == 1:
result = result[0]
_backfill_2d(tvalues, limit=limit)

return result
return


def _fillna_prep(values, mask: np.ndarray | None = None) -> np.ndarray:
def _fillna_prep(
values, mask: npt.NDArray[np.bool_] | None = None
) -> npt.NDArray[np.bool_]:
# boilerplate for _pad_1d, _backfill_1d, _pad_2d, _backfill_2d

if mask is None:
Expand Down Expand Up @@ -834,8 +834,8 @@ def new_func(values, limit=None, mask=None):
def _pad_1d(
values: np.ndarray,
limit: int | None = None,
mask: np.ndarray | None = None,
) -> tuple[np.ndarray, np.ndarray]:
mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
mask = _fillna_prep(values, mask)
algos.pad_inplace(values, mask, limit=limit)
return values, mask
Expand All @@ -845,15 +845,15 @@ def _pad_1d(
def _backfill_1d(
values: np.ndarray,
limit: int | None = None,
mask: np.ndarray | None = None,
) -> tuple[np.ndarray, np.ndarray]:
mask: npt.NDArray[np.bool_] | None = None,
) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
mask = _fillna_prep(values, mask)
algos.backfill_inplace(values, mask, limit=limit)
return values, mask


@_datetimelike_compat
def _pad_2d(values, limit=None, mask=None):
def _pad_2d(values: np.ndarray, limit=None, mask: npt.NDArray[np.bool_] | None = None):
mask = _fillna_prep(values, mask)

if np.all(values.shape):
Expand All @@ -865,7 +865,7 @@ def _pad_2d(values, limit=None, mask=None):


@_datetimelike_compat
def _backfill_2d(values, limit=None, mask=None):
def _backfill_2d(values, limit=None, mask: npt.NDArray[np.bool_] | None = None):
mask = _fillna_prep(values, mask)

if np.all(values.shape):
Expand All @@ -890,7 +890,7 @@ def clean_reindex_fill_method(method):
return clean_fill_method(method, allow_nearest=True)


def _interp_limit(invalid: np.ndarray, fw_limit, bw_limit):
def _interp_limit(invalid: npt.NDArray[np.bool_], fw_limit, bw_limit):
"""
Get indexers of values that won't be filled
because they exceed the limits.
Expand Down Expand Up @@ -955,7 +955,7 @@ def inner(invalid, limit):
return f_idx & b_idx


def _rolling_window(a: np.ndarray, window: int):
def _rolling_window(a: npt.NDArray[np.bool_], window: int) -> npt.NDArray[np.bool_]:
"""
[True, True, False, True, False], 2 ->

Expand Down