一维插值#

分段线性插值#

如果只需要线性(又名折线)插值,可以使用 numpy.interp 例程。它接受两个用于插值的数据数组,xy,以及第三个数组 xnew,用于评估插值的点

>>> import numpy as np
>>> x = np.linspace(0, 10, num=11)
>>> y = np.cos(-x**2 / 9.0)

构造插值

>>> xnew = np.linspace(0, 10, num=1001)
>>> ynew = np.interp(xnew, x, y)

并绘制它

>>> import matplotlib.pyplot as plt
>>> plt.plot(xnew, ynew, '-', label='linear interp')
>>> plt.plot(x, y, 'o', label='data')
>>> plt.legend(loc='best')
>>> plt.show()
../../_images/1D-1.png

numpy.interp 的一个局限性在于它不允许控制外推。有关提供这种功能的替代例程,请参阅使用 B 样条插值部分

三次样条#

当然,分段线性插值在数据点处产生尖角,线性段在此处连接。要生成更平滑的曲线,可以使用三次样条,其中插值曲线由具有匹配的一阶和二阶导数的三次段组成。在代码中,这些对象通过 CubicSpline 类实例表示。一个实例使用数据数组 xy 构建,然后可以使用目标 xnew 值进行评估

>>> from scipy.interpolate import CubicSpline
>>> spl = CubicSpline([1, 2, 3, 4, 5, 6], [1, 4, 8, 16, 25, 36])
>>> spl(2.5)
5.57

CubicSpline 对象的 __call__ 方法接受标量值和数组。它还接受第二个参数 nu,以评估 nu 阶的导数。例如,我们绘制样条的导数

>>> from scipy.interpolate import CubicSpline
>>> x = np.linspace(0, 10, num=11)
>>> y = np.cos(-x**2 / 9.)
>>> spl = CubicSpline(x, y)
>>> import matplotlib.pyplot as plt
>>> fig, ax = plt.subplots(4, 1, figsize=(5, 7))
>>> xnew = np.linspace(0, 10, num=1001)
>>> ax[0].plot(xnew, spl(xnew))
>>> ax[0].plot(x, y, 'o', label='data')
>>> ax[1].plot(xnew, spl(xnew, nu=1), '--', label='1st derivative')
>>> ax[2].plot(xnew, spl(xnew, nu=2), '--', label='2nd derivative')
>>> ax[3].plot(xnew, spl(xnew, nu=3), '--', label='3rd derivative')
>>> for j in range(4):
...     ax[j].legend(loc='best')
>>> plt.tight_layout()
>>> plt.show()
../../_images/1D-2.png

请注意,根据构造,一阶和二阶导数是连续的,而三阶导数在数据点处跳跃。

单调插值器#

根据构造,三次样条是两次连续可微的。这可能导致样条函数在数据点之间振荡和“过冲”。在这些情况下,另一种选择是使用所谓的单调三次插值器:它们的构造使其仅一次连续可微,并试图保留数据所暗示的局部形状。scipy.interpolate 提供了两种此类对象:PchipInterpolatorAkima1DInterpolator。为了说明,让我们考虑包含异常值的数据

>>> from scipy.interpolate import CubicSpline, PchipInterpolator, Akima1DInterpolator
>>> x = np.array([1., 2., 3., 4., 4.5, 5., 6., 7., 8])
>>> y = x**2
>>> y[4] += 101
>>> import matplotlib.pyplot as plt
>>> xx = np.linspace(1, 8, 51)
>>> plt.plot(xx, CubicSpline(x, y)(xx), '--', label='spline')
>>> plt.plot(xx, Akima1DInterpolator(x, y)(xx), '-', label='Akima1D')
>>> plt.plot(xx, PchipInterpolator(x, y)(xx), '-', label='pchip')
>>> plt.plot(x, y, 'o')
>>> plt.legend()
>>> plt.show()
../../_images/1D-3.png

使用 B 样条进行插值#

B 样条构成了分段多项式的另一种(如果形式上等效)表示。该基通常比幂基在计算上更稳定,并且对于包括插值、回归和曲线表示在内的各种应用非常有用。详细信息在分段多项式部分中给出,在这里我们通过构造正弦函数的插值来演示它们的用法

>>> x = np.linspace(0, 3/2, 7)
>>> y = np.sin(np.pi*x)

为了构造给定数据数组 xy 的插值对象,我们使用 make_interp_spline 函数

>>> from scipy.interpolate import make_interp_spline
>>> bspl = make_interp_spline(x, y, k=3)

此函数返回一个对象,该对象具有与 CubicSpline 对象类似的接口。特别是,可以在数据点处对其求值和微分

>>> der = bspl.derivative()      # a BSpline representing the derivative
>>> import matplotlib.pyplot as plt
>>> xx = np.linspace(0, 3/2, 51)
>>> plt.plot(xx, bspl(xx), '--', label=r'$\sin(\pi x)$ approx')
>>> plt.plot(x, y, 'o', label='data')
>>> plt.plot(xx, der(xx)/np.pi, '--', label=r'$d \sin(\pi x)/dx / \pi$ approx')
>>> plt.legend()
>>> plt.show()
../../_images/1D-4.png

请注意,通过在 make_interp_spline 调用中指定 k=3,我们请求了一个三次样条(这是默认值,因此可以省略 k=3);三次的导数是二次的

>>> bspl.k, der.k
(3, 2)

默认情况下,make_interp_spline(x, y) 的结果等效于 CubicSpline(x, y)。不同之处在于前者允许多种可选功能:它可以构造各种度的样条(通过可选参数 k)和预定义的结点(通过可选参数 t)。

可以通过 make_interp_spline 函数和 CubicSpline 构造函数的 bc_type 参数控制样条插值的边界条件。默认情况下,两者都使用“非结点”边界条件。

非三次样条#

make_interp_spline 的一个用途是构造具有线性外推的线性插值器,因为 make_interp_spline 默认进行外推。考虑

>>> from scipy.interpolate import make_interp_spline
>>> x = np.linspace(0, 5, 11)
>>> y = 2*x
>>> spl = make_interp_spline(x, y, k=1)  # k=1: linear
>>> spl([-1, 6])
[-2., 12.]
>>> np.interp([-1, 6], x, y)
[0., 10.]

有关更多详细信息和讨论,请参阅外推部分

参数样条曲线#

到目前为止,我们考虑了样条函数,其中数据 y 预计明确依赖于自变量 x——因此插值函数满足 \(f(x_j) = y_j\)。样条曲线xy 数组视为平面上点的坐标 \(\mathbf{p}_j\),并且通过一些附加参数(通常称为 u)参数化穿过这些点的插值曲线。请注意,此构造很容易推广到更高维度,其中 \(\mathbf{p}_j\) 是 N 维空间中的点。

使用插值函数处理多维数据数组的事实可以轻松构造样条曲线。参数 u 的值对应于数据点,需要用户单独提供。

参数化的选择取决于问题,并且不同的参数化可能会产生截然不同的曲线。例如,我们考虑参考 [1] 第 6 章中列出的(有些困难)数据集的三种参数化,参考 [1] 列在BSpline 文档字符串中

>>> x = [0, 1, 2, 3, 4, 5, 6]
>>> y = [0, 0, 0, 9, 0, 0, 0]
>>> p = np.stack((x, y))
>>> p
array([[0, 1, 2, 3, 4, 5, 6],
       [0, 0, 0, 9, 0, 0, 0]])

我们将 p 数组的元素视为平面上七个点的坐标,其中 p[:, j] 给出点 \(\mathbf{p}_j\) 的坐标。

首先,考虑均匀参数化,\(u_j = j\)

>>> u_unif = x

其次,我们考虑所谓的弦长参数化,它不过是连接数据点的直线段的累积长度

\[u_j = u_{j-1} + |\mathbf{p}_j - \mathbf{p}_{j-1}|\]

对于 \(j=1, 2, \dots\)\(u_0 = 0\)。此处 \(| \cdots |\) 是平面上连续点 \(p_j\) 之间的长度。

>>> dp = p[:, 1:] - p[:, :-1]      # 2-vector distances between points
>>> l = (dp**2).sum(axis=0)        # squares of lengths of 2-vectors between points
>>> u_cord = np.sqrt(l).cumsum()   # cumulative sums of 2-norms
>>> u_cord = np.r_[0, u_cord]      # the first point is parameterized at zero

最后,我们考虑有时被称为向心参数化的方法:\(u_j = u_{j-1} + |\mathbf{p}_j - \mathbf{p}_{j-1}|^{1/2}\)。由于额外的平方根,连续值之间的差 \(u_j - u_{j-1}\) 将小于弦长参数化。

>>> u_c = np.r_[0, np.cumsum((dp**2).sum(axis=0)**0.25)]

现在绘制生成的曲线

>>> from scipy.interpolate import make_interp_spline
>>> import matplotlib.pyplot as plt
>>> fig, ax = plt.subplots(1, 3, figsize=(8, 3))
>>> parametrizations = ['uniform', 'cord length', 'centripetal']
>>>
>>> for j, u in enumerate([u_unif, u_cord, u_c]):
...    spl = make_interp_spline(u, p, axis=1)    # note p is a 2D array
...
...    uu = np.linspace(u[0], u[-1], 51)
...    xx, yy = spl(uu)
...
...    ax[j].plot(xx, yy, '--')
...    ax[j].plot(p[0, :], p[1, :], 'o')
...    ax[j].set_title(parametrizations[j])
>>> plt.show()
../../_images/1D-5.png

1维插值的传统接口 (interp1d)#

注意

interp1d 被认为是传统 API,不建议在新代码中使用。请考虑使用 更具体的插值器代替

interp1d 类在 scipy.interpolate 中是一个方便的方法,可以基于固定的数据点创建函数,该函数可以使用线性插值在给定数据定义的域内的任何位置进行评估。通过传递包含数据的一维向量来创建此类的实例。该类的实例定义了一个 __call__ 方法,因此可以像一个函数一样处理,该函数在已知数据值之间进行插值以获得未知值。可以在实例化时指定边界处的行为。以下示例演示了它在线性和三次样条插值中的用法

>>> from scipy.interpolate import interp1d
>>> x = np.linspace(0, 10, num=11, endpoint=True)
>>> y = np.cos(-x**2/9.0)
>>> f = interp1d(x, y)
>>> f2 = interp1d(x, y, kind='cubic')
>>> xnew = np.linspace(0, 10, num=41, endpoint=True)
>>> import matplotlib.pyplot as plt
>>> plt.plot(x, y, 'o', xnew, f(xnew), '-', xnew, f2(xnew), '--')
>>> plt.legend(['data', 'linear', 'cubic'], loc='best')
>>> plt.show()
"This code generates an X-Y plot of a time-series with amplitude on the Y axis and time on the X axis. The original time-series is shown as a series of blue markers roughly defining some kind of oscillation. An orange trace showing the linear interpolation is drawn atop the data forming a jagged representation of the original signal. A dotted green cubic interpolation is also drawn that appears to smoothly represent the source data."

interp1d 的 “cubic” 种类等效于 make_interp_spline,“linear” 种类等效于 numpy.interp,同时还允许 N 维 y 数组。

interp1d 中的另一组插值是 nearestpreviousnext,它们分别返回沿 x 轴的最近、前一个或下一个点。最近和下一个可以被认为是因果插值滤波器的一种特殊情况。以下示例演示了它们的用法,使用与前一个示例相同的数据

>>> from scipy.interpolate import interp1d
>>> x = np.linspace(0, 10, num=11, endpoint=True)
>>> y = np.cos(-x**2/9.0)
>>> f1 = interp1d(x, y, kind='nearest')
>>> f2 = interp1d(x, y, kind='previous')
>>> f3 = interp1d(x, y, kind='next')
>>> xnew = np.linspace(0, 10, num=1001, endpoint=True)
>>> import matplotlib.pyplot as plt
>>> plt.plot(x, y, 'o')
>>> plt.plot(xnew, f1(xnew), '-', xnew, f2(xnew), '--', xnew, f3(xnew), ':')
>>> plt.legend(['data', 'nearest', 'previous', 'next'], loc='best')
>>> plt.show()
"This code generates an X-Y plot of a time-series with amplitude on the Y axis and time on the X axis. The original time-series is shown as a series of blue markers roughly defining some kind of oscillation. An orange trace showing the nearest neighbor interpolation is drawn atop the original with a stair-like appearance where the original data is right in the middle of each stair step. A green trace showing the previous neighbor interpolation looks similar to the orange trace but the original data is at the back of each stair step. Similarly a dotted red trace showing the next neighbor interpolation goes through each of the previous points, but it is centered at the front edge of each stair."

缺失数据#

我们注意到 scipy.interpolate 支持使用缺失数据进行插值。表示缺失数据的两种常用方法是使用 numpy.ma 库的掩码数组,以及将缺失值编码为非数字 NaN

scipy.interpolate 中不直接支持这两种方法。各个例程可能会提供部分支持和/或解决方法,但总的来说,该库坚定地遵守 IEEE 754 语义,其中 NaN 表示非数字,即非法数学运算的结果(例如除以零),而不是缺失