10
10
from typing import (
11
11
TYPE_CHECKING ,
12
12
Any ,
13
+ Literal ,
13
14
cast ,
14
15
)
15
16
22
23
)
23
24
from pandas ._typing import (
24
25
ArrayLike ,
25
- Axis ,
26
26
AxisInt ,
27
27
F ,
28
28
ReindexMethod ,
@@ -223,6 +223,35 @@ def find_valid_index(how: str, is_valid: npt.NDArray[np.bool_]) -> int | None:
223
223
return idxpos # type: ignore[return-value]
224
224
225
225
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
+
226
255
def infer_limit_direction (limit_direction , method ):
227
256
# Set `limit_direction` depending on `method`
228
257
if limit_direction is None :
@@ -308,7 +337,9 @@ def interpolate_array_2d(
308
337
method = m ,
309
338
axis = axis ,
310
339
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]
312
343
)
313
344
else :
314
345
assert index is not None # for mypy
@@ -362,22 +393,8 @@ def _interpolate_2d_with_fill(
362
393
)
363
394
method = "values"
364
395
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 )
381
398
382
399
# default limit is unlimited GH #16282
383
400
limit = algos .validate_limit (nobs = None , limit = limit )
@@ -393,7 +410,7 @@ def func(yvalues: np.ndarray) -> None:
393
410
method = method ,
394
411
limit = limit ,
395
412
limit_direction = limit_direction ,
396
- limit_area = limit_area ,
413
+ limit_area = limit_area_validated ,
397
414
fill_value = fill_value ,
398
415
bounds_error = False ,
399
416
** kwargs ,
@@ -433,10 +450,10 @@ def _index_to_interp_indices(index: Index, method: str) -> np.ndarray:
433
450
def _interpolate_1d (
434
451
indices : np .ndarray ,
435
452
yvalues : np .ndarray ,
436
- method : str | None = "linear" ,
453
+ method : str = "linear" ,
437
454
limit : int | None = None ,
438
455
limit_direction : str = "forward" ,
439
- limit_area : str | None = None ,
456
+ limit_area : Literal [ "inside" , "outside" ] | None = None ,
440
457
fill_value : Any | None = None ,
441
458
bounds_error : bool = False ,
442
459
order : int | None = None ,
@@ -539,10 +556,10 @@ def _interpolate_1d(
539
556
540
557
541
558
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 ,
546
563
fill_value = None ,
547
564
bounds_error : bool = False ,
548
565
order = None ,
@@ -565,19 +582,11 @@ def _interpolate_scipy_wrapper(
565
582
"krogh" : interpolate .krogh_interpolate ,
566
583
"from_derivatives" : _from_derivatives ,
567
584
"piecewise_polynomial" : _from_derivatives ,
585
+ "cubicspline" : _cubicspline_interpolate ,
586
+ "akima" : _akima_interpolate ,
587
+ "pchip" : interpolate .pchip_interpolate ,
568
588
}
569
589
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
-
581
590
interp1d_methods = [
582
591
"nearest" ,
583
592
"zero" ,
@@ -588,9 +597,11 @@ def _interpolate_scipy_wrapper(
588
597
]
589
598
if method in interp1d_methods :
590
599
if method == "polynomial" :
591
- method = order
600
+ kind = order
601
+ else :
602
+ kind = method
592
603
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
594
605
)
595
606
new_y = terp (new_x )
596
607
elif method == "spline" :
@@ -610,13 +621,18 @@ def _interpolate_scipy_wrapper(
610
621
y = y .copy ()
611
622
if not new_x .flags .writeable :
612
623
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 )
615
626
return new_y
616
627
617
628
618
629
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 ,
620
636
):
621
637
"""
622
638
Convenience function for interpolate.BPoly.from_derivatives.
@@ -660,7 +676,13 @@ def _from_derivatives(
660
676
return m (x )
661
677
662
678
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
+ ):
664
686
"""
665
687
Convenience function for akima interpolation.
666
688
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
670
692
671
693
Parameters
672
694
----------
673
- xi : array-like
695
+ xi : np.ndarray
674
696
A sorted list of x-coordinates, of length N.
675
- yi : array-like
697
+ yi : np.ndarray
676
698
A 1-D array of real values. `yi`'s length along the interpolation
677
699
axis must be equal to the length of `xi`. If N-D array, use axis
678
700
parameter to select correct axis.
679
- x : scalar or array-like
701
+ x : np.ndarray
680
702
Of length M.
681
703
der : int, optional
682
704
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
704
726
705
727
706
728
def _cubicspline_interpolate (
707
- xi ,
708
- yi ,
709
- x ,
729
+ xi : np . ndarray ,
730
+ yi : np . ndarray ,
731
+ x : np . ndarray ,
710
732
axis : AxisInt = 0 ,
711
733
bc_type : str | tuple [Any , Any ] = "not-a-knot" ,
712
734
extrapolate = None ,
@@ -718,14 +740,14 @@ def _cubicspline_interpolate(
718
740
719
741
Parameters
720
742
----------
721
- xi : array-like , shape (n,)
743
+ xi : np.ndarray , shape (n,)
722
744
1-d array containing values of the independent variable.
723
745
Values must be real, finite and in strictly increasing order.
724
- yi : array-like
746
+ yi : np.ndarray
725
747
Array containing values of the dependent variable. It can have
726
748
arbitrary number of dimensions, but the length along ``axis``
727
749
(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,)
729
751
axis : int, optional
730
752
Axis along which `y` is assumed to be varying. Meaning that for
731
753
``x[i]`` the corresponding values are ``np.take(y, i, axis=axis)``.
@@ -790,7 +812,10 @@ def _cubicspline_interpolate(
790
812
791
813
792
814
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" ],
794
819
) -> None :
795
820
"""
796
821
Apply interpolation and limit_area logic to values along a to-be-specified axis.
@@ -803,8 +828,8 @@ def _interpolate_with_limit_area(
803
828
Interpolation method. Could be "bfill" or "pad"
804
829
limit: int, optional
805
830
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.
808
833
809
834
Notes
810
835
-----
@@ -832,16 +857,18 @@ def _interpolate_with_limit_area(
832
857
invalid [first : last + 1 ] = False
833
858
elif limit_area == "outside" :
834
859
invalid [:first ] = invalid [last + 1 :] = False
860
+ else :
861
+ raise ValueError ("limit_area should be 'inside' or 'outside'" )
835
862
836
863
values [invalid ] = np .nan
837
864
838
865
839
866
def interpolate_2d (
840
867
values : np .ndarray ,
841
- method : str = "pad" ,
842
- axis : Axis = 0 ,
868
+ method : Literal [ "pad" , "backfill" ] = "pad" ,
869
+ axis : AxisInt = 0 ,
843
870
limit : int | None = None ,
844
- limit_area : str | None = None ,
871
+ limit_area : Literal [ "inside" , "outside" ] | None = None ,
845
872
) -> None :
846
873
"""
847
874
Perform an actual interpolation of values, values will be make 2-d if
@@ -880,9 +907,7 @@ def interpolate_2d(
880
907
limit = limit ,
881
908
limit_area = limit_area ,
882
909
),
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 ,
886
911
values ,
887
912
)
888
913
return
@@ -898,12 +923,9 @@ def interpolate_2d(
898
923
method = clean_fill_method (method )
899
924
tvalues = transf (values )
900
925
926
+ func = get_fill_func (method , ndim = 2 )
901
927
# _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 )
907
929
return
908
930
909
931
@@ -969,7 +991,7 @@ def _pad_2d(
969
991
):
970
992
mask = _fillna_prep (values , mask )
971
993
972
- if np . all ( values .shape ) :
994
+ if values .size :
973
995
algos .pad_2d_inplace (values , mask , limit = limit )
974
996
else :
975
997
# for test coverage
@@ -983,7 +1005,7 @@ def _backfill_2d(
983
1005
):
984
1006
mask = _fillna_prep (values , mask )
985
1007
986
- if np . all ( values .shape ) :
1008
+ if values .size :
987
1009
algos .backfill_2d_inplace (values , mask , limit = limit )
988
1010
else :
989
1011
# for test coverage
@@ -1007,7 +1029,9 @@ def clean_reindex_fill_method(method) -> ReindexMethod | None:
1007
1029
return clean_fill_method (method , allow_nearest = True )
1008
1030
1009
1031
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
+ ):
1011
1035
"""
1012
1036
Get indexers of values that won't be filled
1013
1037
because they exceed the limits.
0 commit comments