此Q& A旨在作为使用scipy进行二维(和多维)插值的规范(-ish)。关于各种多维插值方法的基本语法经常有问题,我希望也能直接设置这些。
我有一组分散的二维数据点,我想将它们绘制成一个漂亮的表面,最好使用contourf
中的plot_surface
或matplotlib.pyplot
。如何使用scipy将我的二维或多维数据插入网格?
我找到了scipy.interpolate
子包,但在使用interp2d
或bisplrep
或griddata
或rbf
时,我一直收到错误消息。这些方法的正确语法是什么?
答案 0 :(得分:140)
免责声明:我主要是在写这篇文章时考虑到语法因素和一般行为。我不熟悉所描述方法的内存和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
,{{1 },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]
数组中指定输入点。为此,我们首先必须展平我们的2d坐标数组(使用D
),然后连接数组并转置结果。有多种方法可以做到这一点,但所有这些方法看起来都很笨重。输入ravel
数据也必须展平。当涉及输出点时,我们有更多的自由:由于某种原因,这些也可以被指定为多维数组的元组。请注意,z
的{{1}}具有误导性,因为它表明输入点(至少版本为0.17.0)也是如此:
help
简而言之,griddata
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版本0.17.0开始)。它比以前的主题更特殊,因为它专门用于二维插值,但我怀疑这是多变量插值最常见的情况。
就语法而言,scipy.interpolate.bisplrep
类似于interp2d
,因为它首先需要构造一个插值实例,可以调用它来提供实际的插值。然而,有一个问题:输出点必须位于矩形网格上,因此进入内插器调用的输入必须是跨越输出网格的1d向量,就好像来自interp2d
:
Rbf
使用numpy.meshgrid
时最常见的错误之一就是将完整的2d网格放入插值调用,这会导致内存消耗殆尽,并希望仓促# 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
的最大问题在于它通常无法正常工作。为了理解这一点,我们必须深入了解。事实证明,MemoryError
是较低级函数bisplrep
+ bisplev
的包装器,它们又是FITPACK例程的包装器(用Fortran编写)。对前一个示例的等效调用将是
interp2d
现在,关于interp2d
的问题:(在scipy版本0.17.0中)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
有一个很好的comment in interpolate/interpolate.py
:
interp2d
确实在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
) 1 我相当确定interpolate.interp2d
的{{1}}和interpn
种基函数并不完全对应于其他插值器同名。
2 这些NaN也是为什么表面图看起来如此奇怪的原因:matplotlib历史上很难用复杂的深度信息绘制复杂的3d对象。数据中的NaN值会混淆渲染器,因此应该在后面的曲面部分被绘制在前面。这是可视化问题,而不是插值问题。