Skip to content

Commit 7442028

Browse files
authored
Solve missing interpolation method (cubicspline) (#33670)
1 parent f5a4e78 commit 7442028

File tree

4 files changed

+108
-12
lines changed

4 files changed

+108
-12
lines changed

doc/source/whatsnew/v1.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Other enhancements
9999
``df.to_csv(path, compression={'method': 'gzip', 'compresslevel': 1}``
100100
(:issue:`33196`)
101101
- :meth:`~pandas.core.groupby.GroupBy.transform` has gained ``engine`` and ``engine_kwargs`` arguments that supports executing functions with ``Numba`` (:issue:`32854`)
102+
- :meth:`~pandas.core.resample.Resampler.interpolate` now supports SciPy interpolation method :class:`scipy.interpolate.CubicSpline` as method ``cubicspline`` (:issue:`33670`)
102103
-
103104

104105
.. ---------------------------------------------------------------------------

pandas/core/generic.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6671,9 +6671,9 @@ def replace(
66716671
values of the index. Both 'polynomial' and 'spline' require that
66726672
you also specify an `order` (int), e.g.
66736673
``df.interpolate(method='polynomial', order=5)``.
6674-
* 'krogh', 'piecewise_polynomial', 'spline', 'pchip', 'akima':
6675-
Wrappers around the SciPy interpolation methods of similar
6676-
names. See `Notes`.
6674+
* 'krogh', 'piecewise_polynomial', 'spline', 'pchip', 'akima',
6675+
'cubicspline': Wrappers around the SciPy interpolation methods of
6676+
similar names. See `Notes`.
66776677
* 'from_derivatives': Refers to
66786678
`scipy.interpolate.BPoly.from_derivatives` which
66796679
replaces 'piecewise_polynomial' interpolation method in

pandas/core/missing.py

+86-9
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def clean_interp_method(method, **kwargs):
112112
"akima",
113113
"spline",
114114
"from_derivatives",
115+
"cubicspline",
115116
]
116117
if method in ("spline", "polynomial") and order is None:
117118
raise ValueError("You must specify the order of the spline or polynomial.")
@@ -293,6 +294,7 @@ def interpolate_1d(
293294
"piecewise_polynomial",
294295
"pchip",
295296
"akima",
297+
"cubicspline",
296298
]
297299

298300
if method in sp_methods:
@@ -341,14 +343,11 @@ def _interpolate_scipy_wrapper(
341343
x, new_x = x._values.astype("i8"), new_x.astype("i8")
342344

343345
if method == "pchip":
344-
try:
345-
alt_methods["pchip"] = interpolate.pchip_interpolate
346-
except AttributeError as err:
347-
raise ImportError(
348-
"Your version of Scipy does not support PCHIP interpolation."
349-
) from err
346+
alt_methods["pchip"] = interpolate.pchip_interpolate
350347
elif method == "akima":
351348
alt_methods["akima"] = _akima_interpolate
349+
elif method == "cubicspline":
350+
alt_methods["cubicspline"] = _cubicspline_interpolate
352351

353352
interp1d_methods = [
354353
"nearest",
@@ -406,7 +405,7 @@ def _from_derivatives(xi, yi, x, order=None, der=0, extrapolate=False):
406405
der : int or list
407406
How many derivatives to extract; None for all potentially nonzero
408407
derivatives (that is a number equal to the number of points), or a
409-
list of derivatives to extract. This numberincludes the function
408+
list of derivatives to extract. This number includes the function
410409
value as 0th derivative.
411410
extrapolate : bool, optional
412411
Whether to extrapolate to ouf-of-bounds points based on first and last
@@ -446,8 +445,7 @@ def _akima_interpolate(xi, yi, x, der=0, axis=0):
446445
A 1-D array of real values. `yi`'s length along the interpolation
447446
axis must be equal to the length of `xi`. If N-D array, use axis
448447
parameter to select correct axis.
449-
x : scalar or array_like
450-
Of length M.
448+
x : scalar or array_like of length M.
451449
der : int or list, optional
452450
How many derivatives to extract; None for all potentially
453451
nonzero derivatives (that is a number equal to the number
@@ -478,6 +476,85 @@ def _akima_interpolate(xi, yi, x, der=0, axis=0):
478476
return [P(x, nu) for nu in der]
479477

480478

479+
def _cubicspline_interpolate(xi, yi, x, axis=0, bc_type="not-a-knot", extrapolate=None):
480+
"""
481+
Convenience function for cubic spline data interpolator.
482+
483+
See `scipy.interpolate.CubicSpline` for details.
484+
485+
Parameters
486+
----------
487+
xi : array_like, shape (n,)
488+
1-d array containing values of the independent variable.
489+
Values must be real, finite and in strictly increasing order.
490+
yi : array_like
491+
Array containing values of the dependent variable. It can have
492+
arbitrary number of dimensions, but the length along ``axis``
493+
(see below) must match the length of ``x``. Values must be finite.
494+
x : scalar or array_like, shape (m,)
495+
axis : int, optional
496+
Axis along which `y` is assumed to be varying. Meaning that for
497+
``x[i]`` the corresponding values are ``np.take(y, i, axis=axis)``.
498+
Default is 0.
499+
bc_type : string or 2-tuple, optional
500+
Boundary condition type. Two additional equations, given by the
501+
boundary conditions, are required to determine all coefficients of
502+
polynomials on each segment [2]_.
503+
If `bc_type` is a string, then the specified condition will be applied
504+
at both ends of a spline. Available conditions are:
505+
* 'not-a-knot' (default): The first and second segment at a curve end
506+
are the same polynomial. It is a good default when there is no
507+
information on boundary conditions.
508+
* 'periodic': The interpolated functions is assumed to be periodic
509+
of period ``x[-1] - x[0]``. The first and last value of `y` must be
510+
identical: ``y[0] == y[-1]``. This boundary condition will result in
511+
``y'[0] == y'[-1]`` and ``y''[0] == y''[-1]``.
512+
* 'clamped': The first derivative at curves ends are zero. Assuming
513+
a 1D `y`, ``bc_type=((1, 0.0), (1, 0.0))`` is the same condition.
514+
* 'natural': The second derivative at curve ends are zero. Assuming
515+
a 1D `y`, ``bc_type=((2, 0.0), (2, 0.0))`` is the same condition.
516+
If `bc_type` is a 2-tuple, the first and the second value will be
517+
applied at the curve start and end respectively. The tuple values can
518+
be one of the previously mentioned strings (except 'periodic') or a
519+
tuple `(order, deriv_values)` allowing to specify arbitrary
520+
derivatives at curve ends:
521+
* `order`: the derivative order, 1 or 2.
522+
* `deriv_value`: array_like containing derivative values, shape must
523+
be the same as `y`, excluding ``axis`` dimension. For example, if
524+
`y` is 1D, then `deriv_value` must be a scalar. If `y` is 3D with
525+
the shape (n0, n1, n2) and axis=2, then `deriv_value` must be 2D
526+
and have the shape (n0, n1).
527+
extrapolate : {bool, 'periodic', None}, optional
528+
If bool, determines whether to extrapolate to out-of-bounds points
529+
based on first and last intervals, or to return NaNs. If 'periodic',
530+
periodic extrapolation is used. If None (default), ``extrapolate`` is
531+
set to 'periodic' for ``bc_type='periodic'`` and to True otherwise.
532+
533+
See Also
534+
--------
535+
scipy.interpolate.CubicHermiteSpline
536+
537+
Returns
538+
-------
539+
y : scalar or array_like
540+
The result, of shape (m,)
541+
542+
References
543+
----------
544+
.. [1] `Cubic Spline Interpolation
545+
<https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation>`_
546+
on Wikiversity.
547+
.. [2] Carl de Boor, "A Practical Guide to Splines", Springer-Verlag, 1978.
548+
"""
549+
from scipy import interpolate
550+
551+
P = interpolate.CubicSpline(
552+
xi, yi, axis=axis, bc_type=bc_type, extrapolate=extrapolate
553+
)
554+
555+
return P(x)
556+
557+
481558
def interpolate_2d(
482559
values, method="pad", axis=0, limit=None, fill_value=None, dtype=None
483560
):

pandas/tests/series/methods/test_interpolate.py

+18
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"from_derivatives",
2727
"pchip",
2828
"akima",
29+
"cubicspline",
2930
]
3031
)
3132
def nontemporal_method(request):
@@ -55,6 +56,7 @@ def nontemporal_method(request):
5556
"from_derivatives",
5657
"pchip",
5758
"akima",
59+
"cubicspline",
5860
]
5961
)
6062
def interp_methods_ind(request):
@@ -97,6 +99,22 @@ def test_interpolate_time_raises_for_non_timeseries(self):
9799
with pytest.raises(ValueError, match=msg):
98100
non_ts.interpolate(method="time")
99101

102+
@td.skip_if_no_scipy
103+
def test_interpolate_cubicspline(self):
104+
105+
ser = Series([10, 11, 12, 13])
106+
107+
expected = Series(
108+
[11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00],
109+
index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]),
110+
)
111+
# interpolate at new_index
112+
new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype(
113+
float
114+
)
115+
result = ser.reindex(new_index).interpolate(method="cubicspline")[1:3]
116+
tm.assert_series_equal(result, expected)
117+
100118
@td.skip_if_no_scipy
101119
def test_interpolate_pchip(self):
102120

0 commit comments

Comments
 (0)