Skip to content

SciPy - 插值

插值(Interpolation)是一种基本的技术,用于估计介于已知数据点之间的未知值。它就像通过现有数据“绘制一条线”或“拟合一条曲线”,以预测中间点的值。本章将探讨 SciPy 的 scipy.interpolate 模块如何促进各种插值方法。

想象你有一组测量数据,例如,在特定时间点的温度读数。插值帮助你估计没有直接测量数据的时间点的温度,前提是这个时间点落在你的观测时间范围之内。它是统计学、科学、工程和金融领域的宝贵工具,用于数据平滑、重采样和填充缺失数据等任务。

让我们生成一些样本数据来演示插值:

import numpy as np
from scipy import interpolate
import matplotlib.pyplot as plt # 用于可视化
# 生成一些样本数据点 (x, y)
# np.linspace(起始值, 结束值, 点的数量)
x_known = np.linspace(0, 10, 11) # 从 0 到 10 的 11 个点
y_known = np.cos(-x_known**2 / 9.0) + 0.5 * np.sin(x_known / 2.0)
print("Known x values:", x_known)
print("Known y values:", y_known)
# 可视化已知数据点
# plt.plot(x_known, y_known, 'o', label='Known Data Points')
# plt.xlabel("x")
# plt.ylabel("y")
# plt.title("Original Data Points")
# plt.legend()
# plt.grid(True)
# plt.show()

这段代码创建了 x_known 和 y_known 数组。如果绘制出来,这些点将看起来散布着,遵循一个复杂的曲线。例如,输出值如下:

Known x values: [ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]
Known y values: [ 1. 1.22690959 1.12199856 0.62092957 -0.13149013 -0.77131049
-0.9996715 -0.75164093 -0.11188687 0.66798572 1.1840709 ]

如果你绘制这些 x_known 和 y_known 点,你会看到一个散点图,表示函数 $y = \cos(-x^2/9) + 0.5 \sin(x/2)$ 在离散 x 值上的取值。插值的任务就是估计不在 x_known 中的 x 对应的 y 值。

scipy.interpolate 中的 interp1d 类提供了一种从固定数据点创建插值函数的便捷方法。然后可以在原始数据域内的任何点评估此函数。

让我们使用线性(linear)和三次(cubic)方法创建插值函数:

# 创建插值函数
# 'linear' 插值:用直线连接已知点
interp_linear = interpolate.interp1d(x_known, y_known, kind='linear')
# 'cubic' 插值:通过已知点拟合三次样条曲线,获得更平滑的曲线
interp_cubic = interpolate.interp1d(x_known, y_known, kind='cubic')
# 生成我们想要插值 y 值的新 x 值
x_new = np.linspace(0, 10, 101) # 101 个点,用于生成更平滑的曲线
# 使用创建的函数对 x_new 的 y 值进行插值
y_linear_interp = interp_linear(x_new)
y_cubic_interp = interp_cubic(x_new)
# 可视化结果(需要 matplotlib)
# plt.figure(figsize=(10, 6))
# plt.plot(x_known, y_known, 'o', label='Known Data Points', markersize=8)
# plt.plot(x_new, y_linear_interp, '-', label='Linear Interpolation')
# plt.plot(x_new, y_cubic_interp, '--', label='Cubic Interpolation')
# plt.xlabel("x")
# plt.ylabel("y")
# plt.title("1-D Interpolation Examples")
# plt.legend()
# plt.grid(True)
# plt.show()

interp1d 接受已知的 x 和 y 值以及一个指定插值方法的 kind 参数。常见的 kind 包括:‘linear’, ‘nearest’, ‘zero’, ‘slinear’ (spline linear), ‘quadratic’, ‘cubic’。返回的对象(interp_linear, interp_cubic)是可调用的函数(callable functions)。

如果你绘制这个结果,你会看到原始数据点是圆圈。‘Linear Interpolation’ 将是一系列连接这些点的直线。‘Cubic Interpolation’ 将是一条穿过所有原始点的更平滑的曲线,通常能更准确地表示潜在的光滑函数。

前几个插值 y 值的文本输出如下:

# x_new[0] 到 x_new[4] 的线性插值的前 5 个 y 值
# print("Linear interp y_new[:5]:", y_linear_interp[:5])
# x_new[0] 到 x_new[4] 的三次插值的前 5 个 y 值
# print("Cubic interp y_new[:5]:", y_cubic_interp[:5])
# 输出示例片段:
# Linear interp y_new[:5]: [1. 1.02269096 1.04538192 1.06807287 1.09076383]
# Cubic interp y_new[:5]: [1. 1.03396658 1.06506318 1.09264928 1.1160844 ]

样条(Splines)是由分段多项式定义的特殊函数。它们广泛用于通过数据点拟合平滑曲线。历史上,绘图员使用灵活的木条(机械样条),弯曲使其通过指定的点(结),来绘制平滑曲线。数字样条通过数学方法实现了类似的效果。

使用 UnivariateSpline 的单变量样条

Section titled “使用 UnivariateSpline 的单变量样条”

scipy.interpolate 中的 UnivariateSpline 类为给定的一组一维数据点拟合一个平滑样条。与 interp1d (对于如 ‘cubic’ 等 kind)精确穿过所有点不同,UnivariateSpline 可以创建一个平滑的近似曲线,由平滑因子 s 控制。

UnivariateSpline(x, y, w=None, bbox=[None, None], k=3, s=None, ext=0, check_finite=False) 的关键参数:

  • x, y:用于拟合的数据点。
  • w:样条拟合的权重。如果为 None,所有点具有相同的权重。
  • k:平滑样条的次数(例如,k=1 表示线性,k=3 表示三次)。默认为 3。
  • s:平滑因子。如果 s=0,样条将(如果可能)插值通过所有数据点。如果 s 较大,样条将更平滑,但可能不会通过所有点。如果为 None,则根据数据自动选择 s。
  • ext:控制原始数据范围外点的外插行为。

让我们生成带噪声的数据并拟合样条:

import numpy as np
from scipy.interpolate import UnivariateSpline
import matplotlib.pyplot as plt # 用于可视化
# 生成带噪声的数据
np.random.seed(42)
x_noisy = np.linspace(-3, 3, 60)
y_noisy = np.exp(-x_noisy**2) + 0.15 * np.random.randn(len(x_noisy))
# 拟合 UnivariateSpline(默认为 k=3,如果数据允许,s 可能自动选择或为 0)
# 如果未给定权重且任务可能,s=0 会强制样条通过所有点进行插值
spline_interp = UnivariateSpline(x_noisy, y_noisy, s=0) # s=0 尝试进行插值
# 拟合一个具有一定平滑度的样条 (s > 0)
# 's' 值越大意味着平滑度越高。
spline_smooth = UnivariateSpline(x_noisy, y_noisy, s=0.5) # s=0.5 用于平滑
# 生成用于绘制样条曲线的点
x_plot = np.linspace(-3, 3, 200)
y_spline_interp_plot = spline_interp(x_plot)
y_spline_smooth_plot = spline_smooth(x_plot)
# 可视化(需要 matplotlib)
# plt.figure(figsize=(10, 6))
# plt.plot(x_noisy, y_noisy, 'o', label='Noisy Data Points', alpha=0.6)
# plt.plot(x_plot, y_spline_interp_plot, '-', label='Spline (s=0, interpolating)')
# plt.plot(x_plot, y_spline_smooth_plot, '--', label='Spline (s=0.5, smoothing)')
# plt.xlabel("x")
# plt.ylabel("y")
# plt.title("Univariate Spline Interpolation and Smoothing")
# plt.legend()
# plt.grid(True)
# plt.show()

如果你绘制这个结果:‘Noisy Data Points’ 将是散布的点。‘Spline (s=0, interpolating)’ 曲线将试图穿过所有这些噪声点,可能导致一条弯曲的线。‘Spline (s=0.5, smoothing)’ 曲线将更平滑,捕捉数据的总体趋势,而不是拟合每一个噪声波动。

在创建样条对象后,你可以使用 spline_object.set_smoothing_factor(new_s_value) 调整平滑因子 s,如果你想尝试不同程度的平滑。

插值在许多领域至关重要:信号处理(音频或图像重采样)、金融建模(估计交易之间的资产价格)、天气预报(估计未监测地点的状况)和计算机图形学(创建平滑的路径或表面)。

scipy.interpolate 还提供了更高级的插值技术,包括使用诸如 griddata, LinearNDInterpolator, CloughTocher2DInterpolator 和径向基函数(Radial Basis Function, Rbf)插值等函数进行多变量插值(针对 2D、3D 或更高维度的数据)。