关于python:如何使用scipy执行二维插值? 您所在的位置:网站首页 scipy二维插值 关于python:如何使用scipy执行二维插值?

关于python:如何使用scipy执行二维插值?

2023-08-11 05:24| 来源: 网络整理| 查看: 265

This Q&A is intended as a canonical(-ish) concerning two-dimensional (and multi-dimensional) interpolation using scipy. There are often questions concerning the basic syntax of various multidimensional interpolation methods, I hope to set these straight too.

我有一组分散的二维数据点,我想将它们绘制为一个漂亮的表面,最好使用matplotlib.pyplot中的contourf或plot_surface之类的东西。 如何使用scipy将二维或多维数据插值到网格?

我已经找到了scipy.interpolate子软件包,但是在使用interp2d或bisplrep或griddata或rbf时,我总是收到错误消息。 这些方法的正确语法是什么?

免责声明:我在写这篇文章时主要是出于句法考虑和一般行为。我不熟悉所描述方法的内存和CPU方面,因此,我的答案是针对那些数据量较小的用户,因此,插值的质量可能是要考虑的主要方面。我知道在处理非常大的数据集时,性能更好的方法(即griddata和Rbf)可能不可行。

我将比较三种多维插值方法(interp2d /样条线,griddata和Rbf)。我将对它们执行两种插值任务和两种基础函数(要从中插值的点)。具体示例将演示二维插值,但可行的方法适用于任意尺寸。每种方法都提供各种插值;在所有情况下,我都将使用三次插值(或close1)。重要的是要注意,每当使用插值时,都会引入与原始数据相比的偏差,并且所使用的特定方法会影响最终的工件。始终意识到这一点,并负责任地插值。

两个插值任务将是

上采样(输入数据在矩形网格上,输出数据在较密集的网格上) 将分散的数据插值到常规网格上

这两个函数(在域[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也足够。

123456789101112131415161718192021222324252627282930313233import 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类实例,然后为给定的输出网格调用此对象以获取插值结果。平滑升采样测试的示例:

123import 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是用于任意尺寸插值的通用工具。除了为节点点的凸包之外的点设置单个预设值外,它不会执行外推,但是由于外推是非常易变和危险的事情,因此不一定是不利条件。用法示例:

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

请注意略显晦涩的语法。输入点必须在尺寸为D的形状为[N, D]的数组中指定。为此,我们首先必须展平二维坐标数组(使用ravel),然后将数组连接起来并转置结果。有多种方法可以执行此操作,但它们似乎都很庞大。输入z数据也必须展平。在输出点方面,我们有更多的自由度:由于某些原因,它们也可以指定为多维数组的元组。请注意,griddata的help具有误导性,因为这表明输入点也是如此(至少对于版本0.17.0):

123456789101112griddata(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相似,因为它首先需要构造一个插值实例,可以调用该插值实例以提供实际的插值。但是有一个陷阱:输出点必须位于矩形网格上,因此进入内插器调用的输入必须是跨输出网格的1d向量,就像来自numpy.meshgrid一样:

123456# 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编写)的包装。对上一个示例的等效调用为

12345678910kind = '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有一个很好的注释:

123if 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中进行了一些设置,最终

123tx, 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我相当确定Rbf的cubic和linear类型的基函数不完全对应于同名的其他插值器。 2这些NaN也是表面图看起来如此奇怪的原因:从历史上看,matplotlib难以绘制具有适当深度信息的复杂3d对象。数据中的NaN值会混淆渲染器,因此应将应在背面的表面部分绘制在正面。这是可视化的问题,而不是插值。

好。

相关讨论 取决于数据点的数量和维数,Rbf可能比griddata消耗更多的内存。网格数据还具有基础对象LinearNDInterpolator,可以像Rbf一样在2个步骤中使用它。 @denfromufa感谢您的输入,我怀疑这是内存方面的情况(来自其他帖子的评论)。至于网格数据中的基础对象,只有线性插值才是这种情况。但是无论如何,在三次方情况下,CloughTocher2DInterpolator是同样好调用的低级对象。但是,您提出了一个很好的观点:创建一次之后,这同样可以被调用。谢谢:) 很遗憾,griddata没有提供二次或三次插值器,也没有外推法。 Rbf并不是真正的插值-它是点的加权。 @denfromufa我的印象是griddata比基于B样条的插值器稳定得多的原因是它使用了非常局部的插值(基于三角剖分,并且在每个插值上进行插值)。我认为这种差异类似于Hermite插值与1d中简单的高阶多项式拟合之间的差异。这意味着用griddata进行的外推比平时更具误导性,仅在输入点的恰好边界上反映了数据的行为。 griddata确实提供了三次插值器,请参见上文。 Griddatas三次插值限制为2(?)维。对于更高尺寸的基于chebfun的smolyak稀疏网格值得考虑。 @denfromufa aah,我知道了。是的,仅1d和2d。我认为,随着尺寸的增加,内存需求将急剧增加,这可能就是这些插值器最多只能处理2d的原因。并且感谢稀疏的网格提示,我还没有听说过:) 让我以此链接结束我的评论,在该链接中,我研究了所有分散的插值选项:scicomp.stackexchange.com/questions/19137/ griddata线性插值是局部的,griddata三次插值是全局的。不支持外推法,因为我没有时间弄清楚如何保持连续性/可微性。 Rbf适用于小型数据集,但要对n个数据点进行插值,则需要对n x n矩阵求逆,这在n> 5000之后最终变得不可能。 Rbf对数据的分布也可能很敏感,您可能需要手动微调其参数。可以对大型数据集执行Rbf,但这不是在scipy中实现的。 这是大型数据集的rbf:github.com/scipy/scipy/issues/5180



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有