一维插值#

分段线性插值#

如果您只需要线性(又名折线)插值,您可以使用 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

三次样条#

当然,分段线性插值在数据点处产生角点,线性片段在该处连接。为了产生更平滑的曲线,您可以使用三次样条,其中插值曲线由具有匹配的一阶和二阶导数的三次片段组成。在代码中,这些对象通过 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)。

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

参数样条曲线#

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

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

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

>>> 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

一维插值的旧接口 (interp1d)#

注意

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

scipy.interpolate 中,interp1d 类提供了一种便捷的方法,可以根据固定数据点创建函数。该函数可以使用线性插值在给定数据定义的域内的任何位置进行评估。此类的一个实例是通过传递包含数据的 1 维向量来创建的。此类实例定义了一个 __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 轴上的最近点、前一点或后一点。Nearest 和 next 可以被认为是因果插值滤波器的特例。以下示例演示了它们的使用方法,使用与上一个示例中相同的数据。

>>> 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 表示非数字,即非法数学运算的结果(例如,除以零),而不是缺失