问题:如何使用scipy执行二维插值?
此Q&A旨在作为有关使用scipy进行二维(和多维)插值的规范(-ish)。经常有关于各种多维插值方法的基本语法的问题,我希望也将这些问题弄清楚。
我有一组散二维数据点,我想他们绘制作为一个很好的表面,最好使用类似contourf
或plot_surface
在matplotlib.pyplot
。如何使用scipy将二维或多维数据插值到网格?
我已经找到了该scipy.interpolate
子软件包,但是在使用interp2d
or bisplrep
或griddata
or 时仍然会出错rbf
。这些方法的正确语法是什么?
回答 0
免责声明:我在写这篇文章时主要是出于句法考虑和一般行为。我不熟悉所描述方法的内存和CPU方面,因此,我的答案是针对那些数据量较小的用户,因此,插值的质量可能是要考虑的主要方面。我知道,在处理非常大的数据集时,性能更好的方法(即griddata
和Rbf
)可能不可行。
我将比较三种多维插值方法(interp2d
/ splines griddata
和Rbf
)。我将对它们执行两种插值任务和两种基础功能(要从中插值的点)。具体示例将演示二维插值,但可行的方法适用于任意尺寸。每种方法都提供各种插值;在所有情况下,我都会使用三次插值(或接近1的东西)。重要的是要注意,每当使用插值时,都会引入与原始数据相比的偏差,并且所使用的特定方法会影响最终的工件。始终意识到这一点,并负责任地插值。
两个插值任务将是
- 上采样(输入数据在矩形网格上,输出数据在较密集的网格上)
- 将分散的数据插值到常规网格上
这两个函数(在上[x,y] in [-1,1]x[-1,1]
)将是
- 平稳,友好的功能:
cos(pi*x)*sin(pi*y)
; 范围[-1, 1]
- 邪恶的(尤其是非连续的)函数:
x*y/(x^2+y^2)
在原点附近具有0.5的值;范围[-0.5, 0.5]
它们的外观如下:
我将首先演示这三个方法在这四个测试下的行为,然后详细介绍这三个方法的语法。如果您知道对某个方法应该有什么期望,那么您可能不想浪费时间学习其语法(看着您interp2d
)。
测试数据
为了清楚起见,这是我用来生成输入数据的代码。虽然在这种特定情况下,我显然知道数据的基础功能,但我只会使用它为插值方法生成输入。为了方便起见,我使用了numpy(并且主要是用于生成数据),但仅scipy也足够。
import numpy as np
import scipy.interpolate as interp
# auxiliary function for mesh generation
def gimme_mesh(n):
minval = -1
maxval = 1
# produce an asymmetric shape in order to catch issues with transpositions
return np.meshgrid(np.linspace(minval,maxval,n), np.linspace(minval,maxval,n+1))
# set up underlying test functions, vectorized
def fun_smooth(x, y):
return np.cos(np.pi*x)*np.sin(np.pi*y)
def fun_evil(x, y):
# watch out for singular origin; function has no unique limit there
return np.where(x**2+y**2>1e-10, x*y/(x**2+y**2), 0.5)
# sparse input mesh, 6x7 in shape
N_sparse = 6
x_sparse,y_sparse = gimme_mesh(N_sparse)
z_sparse_smooth = fun_smooth(x_sparse, y_sparse)
z_sparse_evil = fun_evil(x_sparse, y_sparse)
# scattered input points, 10^2 altogether (shape (100,))
N_scattered = 10
x_scattered,y_scattered = np.random.rand(2,N_scattered**2)*2 - 1
z_scattered_smooth = fun_smooth(x_scattered, y_scattered)
z_scattered_evil = fun_evil(x_scattered, y_scattered)
# dense output mesh, 20x21 in shape
N_dense = 20
x_dense,y_dense = gimme_mesh(N_dense)
平滑的功能和上采样
让我们从最简单的任务开始。这是从形状网格[6,7]
到其中之一的向上采样如何[20,21]
实现平滑测试功能的方法:
尽管这是一项简单的任务,但输出之间已经存在细微的差异。乍一看,这三个输出都是合理的。根据我们对基础功能的先验知识,有两个功能需要注意:中间情况griddata
会使数据失真最大。注意图的y==-1
边界(最靠近x
标签):该函数应严格为零(因为y==-1
该平滑函数是节点线),但是并非如此griddata
。还要注意图的x==-1
边界(在左边,在后面):基础函数在处具有局部最大值(隐含边界附近的零梯度)[-1, -0.5]
,但griddata
输出在该区域中明显显示出非零梯度。效果是微妙的,但仍然存在偏见。(保真度Rbf
如果使用默认的径向函数,则更好multiquadratic
。
邪恶功能和上采样
更加艰巨的任务是对我们邪恶的函数执行升采样:
三种方法之间开始出现明显的差异。查看表面图,从的输出中会出现明显的虚假极值interp2d
(请注意所绘制表面的右侧的两个驼峰)。虽然griddata
并Rbf
似乎产生乍看类似的结果,后者似乎产生更深的最低附近[0.4, -0.4]
也就是从底层功能缺失。
但是,有一个至关重要的方面Rbf
要优越得多:它尊重基础函数的对称性(当然,这也可以通过样本网格的对称性来实现)。的输出griddata
破坏了采样点的对称性,在对称情况下,该对称性已经微弱可见。
功能流畅且数据分散
最常见的是要对分散的数据执行插值。因此,我希望这些测试更加重要。如上所示,在感兴趣的域中伪均匀地选择了采样点。在实际情况下,每次测量都可能会产生额外的噪声,因此您应该考虑对原始数据进行插值是否有意义。
平滑功能的输出:
现在已经有一些恐怖表演在继续。为了保留至少最少的信息,我将输出从剪辑interp2d
到两者之间,[-1, 1]
专门用于绘图。显然,虽然存在一些潜在的形状,但是在巨大的嘈杂区域中,该方法完全失效了。第二种情况griddata
很好地再现了形状,但请注意轮廓图边界处的白色区域。这是由于这样的事实,它griddata
仅在输入数据点的凸包内部有效(换句话说,它不执行任何外推)。我保留了凸包外部的输出点的默认NaN值。2考虑到这些功能,Rbf
似乎表现最佳。
邪恶功能和分散数据
我们一直在等待的时刻:
interp2d
放弃并不奇怪。实际上,在调用期间,interp2d
您应该期望一些友善RuntimeWarning
的人抱怨无法构造样条线。至于其他两种方法,Rbf
即使在推断结果的域边界附近,似乎也能产生最佳输出。
因此,让我以优先顺序从高到低的顺序对这三种方法说几句话(这样一来,最坏的情况就是每个人阅读的可能性最小)。
scipy.interpolate.Rbf
该Rbf
级代表“径向基函数”。老实说,直到开始研究这篇文章之前,我从未考虑过这种方法,但是我敢肯定,将来我会使用这些方法。
就像基于样条的方法(请参阅后面)一样,用法分为两个步骤:首先Rbf
根据输入数据创建一个可调用的类实例,然后为给定的输出网格调用此对象以获得插值结果。平滑升采样测试的示例:
import scipy.interpolate as interp
zfun_smooth_rbf = interp.Rbf(x_sparse, y_sparse, z_sparse_smooth, function='cubic', smooth=0) # default smooth=0 for interpolation
z_dense_smooth_rbf = zfun_smooth_rbf(x_dense, y_dense) # not really a function, but a callable class instance
请注意,在这种情况下,输入点和输出点均为2d数组,并且输出z_dense_smooth_rbf
具有相同的形状,x_dense
而y_dense
无需付出任何努力。另请注意,它Rbf
支持插值的任意尺寸。
所以, scipy.interpolate.Rbf
- 即使对于疯狂的输入数据也能产生良好的输出
- 支持更大尺寸的插值
- 在输入点的凸包之外进行外推(当然,外推总是一场赌博,您通常根本不应该依赖它)
- 首先创建一个插值器,因此在各个输出点对其进行评估会减少额外的工作量
- 可以具有任意形状的输出点(而不是局限于矩形网格,请参阅下文)
- 倾向于保持输入数据的对称性
- 支持多种径向函数关键字
function
:multiquadric
,inverse
,gaussian
,linear
,cubic
,quintic
,thin_plate
和用户自定义的任意
scipy.interpolate.griddata
我以前最喜欢的,griddata
是用于任意尺寸插值的通用工具。除了为节点点的凸包之外的点设置单个预设值外,它不会执行外推,但是由于外推是非常易变且危险的事情,因此不一定是不利条件。用法示例:
z_dense_smooth_griddata = interp.griddata(np.array([x_sparse.ravel(),y_sparse.ravel()]).T,
z_sparse_smooth.ravel(),
(x_dense,y_dense), method='cubic') # default method is linear
请注意略显晦涩的语法。输入点具有在形状上的阵列,以指定[N, D]
在D
尺寸。为此,我们首先必须展平2D坐标数组(使用ravel
),然后将数组连接起来并转置结果。有多种方法可以执行此操作,但是它们似乎都很庞大。输入z
数据也必须展平。关于输出点,我们有更多的自由度:由于某些原因,它们也可以指定为多维数组的元组。请注意,help
of griddata
具误导性,因为这表明输入点也是如此(至少对于版本0.17.0):
griddata(points, values, xi, method='linear', fill_value=nan, rescale=False)
Interpolate unstructured D-dimensional data.
Parameters
----------
points : ndarray of floats, shape (n, D)
Data point coordinates. Can either be an array of
shape (n, D), or a tuple of `ndim` arrays.
values : ndarray of float or complex, shape (n,)
Data values.
xi : ndarray of float, shape (M, D)
Points at which to interpolate data.
简而言之, scipy.interpolate.griddata
- 即使对于疯狂的输入数据也能产生良好的输出
- 支持更大尺寸的插值
- 不执行外推,可以为输入点的凸包外部的输出设置单个值(请参阅参考资料
fill_value
) - 在一次调用中计算插值,因此从头开始探测多组输出点
- 可以具有任意形状的输出点
- 支持任意尺寸的最近邻和线性插值,立方在1d和2d中。最近邻和线性内插使用
NearestNDInterpolator
和LinearNDInterpolator
引擎盖下,分别。一维三次插值使用样条曲线,二维三次插值用于CloughTocher2DInterpolator
构造连续可微分分段三次插值器。 - 可能会违反输入数据的对称性
scipy.interpolate.interp2d
/scipy.interpolate.bisplrep
我正在interp2d
与其亲戚讨论的唯一原因是它的名称具有欺骗性,人们可能会尝试使用它。剧透警告:请勿使用(自scipy版本0.17.0起)。它已经比以前的主题更特别了,因为它专门用于二维插值,但是我怀疑这是迄今为止多元插值最常见的情况。
就语法而言,interp2d
这类似于Rbf
它首先需要构造一个插值实例,可以调用该插值实例以提供实际的插值。但是有一个问题:输出点必须位于矩形网格上,因此输入到内插器的输入的输入必须是跨输出网格的一维向量,就像来自numpy.meshgrid
:
# reminder: x_sparse and y_sparse are of shape [6, 7] from numpy.meshgrid
zfun_smooth_interp2d = interp.interp2d(x_sparse, y_sparse, z_sparse_smooth, kind='cubic') # default kind is 'linear'
# reminder: x_dense and y_dense are of shape [20, 21] from numpy.meshgrid
xvec = x_dense[0,:] # 1d array of unique x values, 20 elements
yvec = y_dense[:,0] # 1d array of unique y values, 21 elements
z_dense_smooth_interp2d = zfun_smooth_interp2d(xvec,yvec) # output is [20, 21]-shaped array
使用时最常见的错误之一interp2d
是将完整的2d网格放入插值调用中,这会导致大量的内存消耗,并希望仓促行事MemoryError
。
现在,最大的问题interp2d
是它通常不起作用。为了理解这一点,我们必须深入研究。事实证明,这interp2d
是较低级函数bisplrep
+的bisplev
包装,而后者又是FITPACK例程(用Fortran编写)的包装。对上一个示例的等效调用为
kind = 'cubic'
if kind=='linear':
kx=ky=1
elif kind=='cubic':
kx=ky=3
elif kind=='quintic':
kx=ky=5
# bisplrep constructs a spline representation, bisplev evaluates the spline at given points
bisp_smooth = interp.bisplrep(x_sparse.ravel(),y_sparse.ravel(),z_sparse_smooth.ravel(),kx=kx,ky=ky,s=0)
z_dense_smooth_bisplrep = interp.bisplev(xvec,yvec,bisp_smooth).T # note the transpose
现在,这里的事儿interp2d
:(在SciPy的版本0.17.0或更新版本)有一个很好的注释interpolate/interpolate.py
为interp2d
:
if not rectangular_grid:
# TODO: surfit is really not meant for interpolation!
self.tck = fitpack.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0)
确实在中interpolate/fitpack.py
,bisplrep
有一些设置,最终
tx, ty, c, o = _fitpack._surfit(x, y, z, w, xb, xe, yb, ye, kx, ky,
task, s, eps, tx, ty, nxest, nyest,
wrk, lwrk1, lwrk2)
就是这样。底层的例程interp2d
并不是真正要执行插值。它们可能足以满足足够良好的数据需求,但是在实际情况下,您可能需要使用其他内容。
总结一下, interpolate.interp2d
- 即使数据经过良好调整也会导致伪像
- 专为双变量问题(尽管
interpn
在网格上定义的输入点有限) - 执行外推
- 首先创建一个插值器,因此在各个输出点对其进行评估会减少额外的工作量
- 只能在矩形网格上产生输出,对于分散的输出,您将不得不在循环中调用内插器
- 支持线性,三次和五次插值
- 可能会违反输入数据的对称性
1我相当确定,的基函数cubic
和linear
种类Rbf
不完全对应于同名的其他插值器。
2这些NaN也是表面图看起来如此奇怪的原因:从历史上看,matplotlib难以绘制具有适当深度信息的复杂3d对象。数据中的NaN值会混淆渲染器,因此应将应在背面的表面部分绘制在正面。这是可视化的问题,而不是插值。