1010from typing import (
1111 TYPE_CHECKING ,
1212 Any ,
13+ Literal ,
1314 cast ,
1415)
1516
2223)
2324from 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+
226255def 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:
433450def _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
541558def _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
618629def _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
706728def _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
792814def _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
839866def 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