Skip to content

Commit 6458c1c

Browse files
jbrockmendelmroeschke
authored andcommitted
TYP: core.missing (pandas-dev#53625)
1 parent 00aa700 commit 6458c1c

File tree

2 files changed

+98
-69
lines changed

2 files changed

+98
-69
lines changed

pandas/core/arrays/sparse/array.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,12 @@ def fillna(
769769
)
770770
new_values = np.asarray(self)
771771
# interpolate_2d modifies new_values inplace
772-
interpolate_2d(new_values, method=method, limit=limit)
772+
# error: Argument "method" to "interpolate_2d" has incompatible type
773+
# "Literal['backfill', 'bfill', 'ffill', 'pad']"; expected
774+
# "Literal['pad', 'backfill']"
775+
interpolate_2d(
776+
new_values, method=method, limit=limit # type: ignore[arg-type]
777+
)
773778
return type(self)(new_values, fill_value=self.fill_value)
774779

775780
else:

pandas/core/missing.py

+92-68
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import (
1111
TYPE_CHECKING,
1212
Any,
13+
Literal,
1314
cast,
1415
)
1516

@@ -22,7 +23,6 @@
2223
)
2324
from pandas._typing import (
2425
ArrayLike,
25-
Axis,
2626
AxisInt,
2727
F,
2828
ReindexMethod,
@@ -223,6 +223,35 @@ def find_valid_index(how: str, is_valid: npt.NDArray[np.bool_]) -> int | None:
223223
return idxpos # type: ignore[return-value]
224224

225225

226+
def validate_limit_direction(
227+
limit_direction: str,
228+
) -> Literal["forward", "backward", "both"]:
229+
valid_limit_directions = ["forward", "backward", "both"]
230+
limit_direction = limit_direction.lower()
231+
if limit_direction not in valid_limit_directions:
232+
raise ValueError(
233+
"Invalid limit_direction: expecting one of "
234+
f"{valid_limit_directions}, got '{limit_direction}'."
235+
)
236+
# error: Incompatible return value type (got "str", expected
237+
# "Literal['forward', 'backward', 'both']")
238+
return limit_direction # type: ignore[return-value]
239+
240+
241+
def validate_limit_area(limit_area: str | None) -> Literal["inside", "outside"] | None:
242+
if limit_area is not None:
243+
valid_limit_areas = ["inside", "outside"]
244+
limit_area = limit_area.lower()
245+
if limit_area not in valid_limit_areas:
246+
raise ValueError(
247+
f"Invalid limit_area: expecting one of {valid_limit_areas}, got "
248+
f"{limit_area}."
249+
)
250+
# error: Incompatible return value type (got "Optional[str]", expected
251+
# "Optional[Literal['inside', 'outside']]")
252+
return limit_area # type: ignore[return-value]
253+
254+
226255
def infer_limit_direction(limit_direction, method):
227256
# Set `limit_direction` depending on `method`
228257
if limit_direction is None:
@@ -308,7 +337,9 @@ def interpolate_array_2d(
308337
method=m,
309338
axis=axis,
310339
limit=limit,
311-
limit_area=limit_area,
340+
# error: Argument "limit_area" to "interpolate_2d" has incompatible
341+
# type "Optional[str]"; expected "Optional[Literal['inside', 'outside']]"
342+
limit_area=limit_area, # type: ignore[arg-type]
312343
)
313344
else:
314345
assert index is not None # for mypy
@@ -362,22 +393,8 @@ def _interpolate_2d_with_fill(
362393
)
363394
method = "values"
364395

365-
valid_limit_directions = ["forward", "backward", "both"]
366-
limit_direction = limit_direction.lower()
367-
if limit_direction not in valid_limit_directions:
368-
raise ValueError(
369-
"Invalid limit_direction: expecting one of "
370-
f"{valid_limit_directions}, got '{limit_direction}'."
371-
)
372-
373-
if limit_area is not None:
374-
valid_limit_areas = ["inside", "outside"]
375-
limit_area = limit_area.lower()
376-
if limit_area not in valid_limit_areas:
377-
raise ValueError(
378-
f"Invalid limit_area: expecting one of {valid_limit_areas}, got "
379-
f"{limit_area}."
380-
)
396+
limit_direction = validate_limit_direction(limit_direction)
397+
limit_area_validated = validate_limit_area(limit_area)
381398

382399
# default limit is unlimited GH #16282
383400
limit = algos.validate_limit(nobs=None, limit=limit)
@@ -393,7 +410,7 @@ def func(yvalues: np.ndarray) -> None:
393410
method=method,
394411
limit=limit,
395412
limit_direction=limit_direction,
396-
limit_area=limit_area,
413+
limit_area=limit_area_validated,
397414
fill_value=fill_value,
398415
bounds_error=False,
399416
**kwargs,
@@ -433,10 +450,10 @@ def _index_to_interp_indices(index: Index, method: str) -> np.ndarray:
433450
def _interpolate_1d(
434451
indices: np.ndarray,
435452
yvalues: np.ndarray,
436-
method: str | None = "linear",
453+
method: str = "linear",
437454
limit: int | None = None,
438455
limit_direction: str = "forward",
439-
limit_area: str | None = None,
456+
limit_area: Literal["inside", "outside"] | None = None,
440457
fill_value: Any | None = None,
441458
bounds_error: bool = False,
442459
order: int | None = None,
@@ -539,10 +556,10 @@ def _interpolate_1d(
539556

540557

541558
def _interpolate_scipy_wrapper(
542-
x,
543-
y,
544-
new_x,
545-
method,
559+
x: np.ndarray,
560+
y: np.ndarray,
561+
new_x: np.ndarray,
562+
method: str,
546563
fill_value=None,
547564
bounds_error: bool = False,
548565
order=None,
@@ -565,19 +582,11 @@ def _interpolate_scipy_wrapper(
565582
"krogh": interpolate.krogh_interpolate,
566583
"from_derivatives": _from_derivatives,
567584
"piecewise_polynomial": _from_derivatives,
585+
"cubicspline": _cubicspline_interpolate,
586+
"akima": _akima_interpolate,
587+
"pchip": interpolate.pchip_interpolate,
568588
}
569589

570-
if getattr(x, "_is_all_dates", False):
571-
# GH 5975, scipy.interp1d can't handle datetime64s
572-
x, new_x = x._values.astype("i8"), new_x.astype("i8")
573-
574-
if method == "pchip":
575-
alt_methods["pchip"] = interpolate.pchip_interpolate
576-
elif method == "akima":
577-
alt_methods["akima"] = _akima_interpolate
578-
elif method == "cubicspline":
579-
alt_methods["cubicspline"] = _cubicspline_interpolate
580-
581590
interp1d_methods = [
582591
"nearest",
583592
"zero",
@@ -588,9 +597,11 @@ def _interpolate_scipy_wrapper(
588597
]
589598
if method in interp1d_methods:
590599
if method == "polynomial":
591-
method = order
600+
kind = order
601+
else:
602+
kind = method
592603
terp = interpolate.interp1d(
593-
x, y, kind=method, fill_value=fill_value, bounds_error=bounds_error
604+
x, y, kind=kind, fill_value=fill_value, bounds_error=bounds_error
594605
)
595606
new_y = terp(new_x)
596607
elif method == "spline":
@@ -610,13 +621,18 @@ def _interpolate_scipy_wrapper(
610621
y = y.copy()
611622
if not new_x.flags.writeable:
612623
new_x = new_x.copy()
613-
method = alt_methods[method]
614-
new_y = method(x, y, new_x, **kwargs)
624+
terp = alt_methods[method]
625+
new_y = terp(x, y, new_x, **kwargs)
615626
return new_y
616627

617628

618629
def _from_derivatives(
619-
xi, yi, x, order=None, der: int | list[int] | None = 0, extrapolate: bool = False
630+
xi: np.ndarray,
631+
yi: np.ndarray,
632+
x: np.ndarray,
633+
order=None,
634+
der: int | list[int] | None = 0,
635+
extrapolate: bool = False,
620636
):
621637
"""
622638
Convenience function for interpolate.BPoly.from_derivatives.
@@ -660,7 +676,13 @@ def _from_derivatives(
660676
return m(x)
661677

662678

663-
def _akima_interpolate(xi, yi, x, der: int | list[int] | None = 0, axis: AxisInt = 0):
679+
def _akima_interpolate(
680+
xi: np.ndarray,
681+
yi: np.ndarray,
682+
x: np.ndarray,
683+
der: int | list[int] | None = 0,
684+
axis: AxisInt = 0,
685+
):
664686
"""
665687
Convenience function for akima interpolation.
666688
xi and yi are arrays of values used to approximate some function f,
@@ -670,13 +692,13 @@ def _akima_interpolate(xi, yi, x, der: int | list[int] | None = 0, axis: AxisInt
670692
671693
Parameters
672694
----------
673-
xi : array-like
695+
xi : np.ndarray
674696
A sorted list of x-coordinates, of length N.
675-
yi : array-like
697+
yi : np.ndarray
676698
A 1-D array of real values. `yi`'s length along the interpolation
677699
axis must be equal to the length of `xi`. If N-D array, use axis
678700
parameter to select correct axis.
679-
x : scalar or array-like
701+
x : np.ndarray
680702
Of length M.
681703
der : int, optional
682704
How many derivatives to extract; None for all potentially
@@ -704,9 +726,9 @@ def _akima_interpolate(xi, yi, x, der: int | list[int] | None = 0, axis: AxisInt
704726

705727

706728
def _cubicspline_interpolate(
707-
xi,
708-
yi,
709-
x,
729+
xi: np.ndarray,
730+
yi: np.ndarray,
731+
x: np.ndarray,
710732
axis: AxisInt = 0,
711733
bc_type: str | tuple[Any, Any] = "not-a-knot",
712734
extrapolate=None,
@@ -718,14 +740,14 @@ def _cubicspline_interpolate(
718740
719741
Parameters
720742
----------
721-
xi : array-like, shape (n,)
743+
xi : np.ndarray, shape (n,)
722744
1-d array containing values of the independent variable.
723745
Values must be real, finite and in strictly increasing order.
724-
yi : array-like
746+
yi : np.ndarray
725747
Array containing values of the dependent variable. It can have
726748
arbitrary number of dimensions, but the length along ``axis``
727749
(see below) must match the length of ``x``. Values must be finite.
728-
x : scalar or array-like, shape (m,)
750+
x : np.ndarray, shape (m,)
729751
axis : int, optional
730752
Axis along which `y` is assumed to be varying. Meaning that for
731753
``x[i]`` the corresponding values are ``np.take(y, i, axis=axis)``.
@@ -790,7 +812,10 @@ def _cubicspline_interpolate(
790812

791813

792814
def _interpolate_with_limit_area(
793-
values: np.ndarray, method: str, limit: int | None, limit_area: str | None
815+
values: np.ndarray,
816+
method: Literal["pad", "backfill"],
817+
limit: int | None,
818+
limit_area: Literal["inside", "outside"],
794819
) -> None:
795820
"""
796821
Apply interpolation and limit_area logic to values along a to-be-specified axis.
@@ -803,8 +828,8 @@ def _interpolate_with_limit_area(
803828
Interpolation method. Could be "bfill" or "pad"
804829
limit: int, optional
805830
Index limit on interpolation.
806-
limit_area: str
807-
Limit area for interpolation. Can be "inside" or "outside"
831+
limit_area: {'inside', 'outside'}
832+
Limit area for interpolation.
808833
809834
Notes
810835
-----
@@ -832,16 +857,18 @@ def _interpolate_with_limit_area(
832857
invalid[first : last + 1] = False
833858
elif limit_area == "outside":
834859
invalid[:first] = invalid[last + 1 :] = False
860+
else:
861+
raise ValueError("limit_area should be 'inside' or 'outside'")
835862

836863
values[invalid] = np.nan
837864

838865

839866
def interpolate_2d(
840867
values: np.ndarray,
841-
method: str = "pad",
842-
axis: Axis = 0,
868+
method: Literal["pad", "backfill"] = "pad",
869+
axis: AxisInt = 0,
843870
limit: int | None = None,
844-
limit_area: str | None = None,
871+
limit_area: Literal["inside", "outside"] | None = None,
845872
) -> None:
846873
"""
847874
Perform an actual interpolation of values, values will be make 2-d if
@@ -880,9 +907,7 @@ def interpolate_2d(
880907
limit=limit,
881908
limit_area=limit_area,
882909
),
883-
# error: Argument 2 to "apply_along_axis" has incompatible type
884-
# "Union[str, int]"; expected "SupportsIndex"
885-
axis, # type: ignore[arg-type]
910+
axis,
886911
values,
887912
)
888913
return
@@ -898,12 +923,9 @@ def interpolate_2d(
898923
method = clean_fill_method(method)
899924
tvalues = transf(values)
900925

926+
func = get_fill_func(method, ndim=2)
901927
# _pad_2d and _backfill_2d both modify tvalues inplace
902-
if method == "pad":
903-
_pad_2d(tvalues, limit=limit)
904-
else:
905-
_backfill_2d(tvalues, limit=limit)
906-
928+
func(tvalues, limit=limit)
907929
return
908930

909931

@@ -969,7 +991,7 @@ def _pad_2d(
969991
):
970992
mask = _fillna_prep(values, mask)
971993

972-
if np.all(values.shape):
994+
if values.size:
973995
algos.pad_2d_inplace(values, mask, limit=limit)
974996
else:
975997
# for test coverage
@@ -983,7 +1005,7 @@ def _backfill_2d(
9831005
):
9841006
mask = _fillna_prep(values, mask)
9851007

986-
if np.all(values.shape):
1008+
if values.size:
9871009
algos.backfill_2d_inplace(values, mask, limit=limit)
9881010
else:
9891011
# for test coverage
@@ -1007,7 +1029,9 @@ def clean_reindex_fill_method(method) -> ReindexMethod | None:
10071029
return clean_fill_method(method, allow_nearest=True)
10081030

10091031

1010-
def _interp_limit(invalid: npt.NDArray[np.bool_], fw_limit, bw_limit):
1032+
def _interp_limit(
1033+
invalid: npt.NDArray[np.bool_], fw_limit: int | None, bw_limit: int | None
1034+
):
10111035
"""
10121036
Get indexers of values that won't be filled
10131037
because they exceed the limits.

0 commit comments

Comments
 (0)