如何使 scipy.interpolate 给出超出输入范围的推断结果?

Posted

技术标签:

【中文标题】如何使 scipy.interpolate 给出超出输入范围的推断结果?【英文标题】:How to make scipy.interpolate give an extrapolated result beyond the input range? 【发布时间】:2011-02-14 06:39:46 【问题描述】:

我正在尝试移植一个使用手动插值器(由数学家学院开发)的程序,以使用 scipy 提供的插值器。我想使用或包装 scipy 插值器,使其具有尽可能接近旧插值器的行为。

这两个函数之间的一个关键区别在于,在我们的原始插值器中 - 如果输入值高于或低于输入范围,我们的原始插值器将推断结果。如果您使用 scipy 插值器尝试此操作,则会引发ValueError。以这个程序为例:

import numpy as np
from scipy import interpolate

x = np.arange(0,10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y)

print f(9)
print f(11) # Causes ValueError, because it's greater than max(x)

是否有一种明智的方法可以做到这一点,而不是崩溃,最后一行将简单地进行线性外推,将第一个和最后两个点定义的梯度延续到无穷大。

请注意,在真正的软件中,我实际上并没有使用 exp 函数 - 这只是为了说明!

【问题讨论】:

scipy.interpolate.UnivariateSpline 似乎推断没有问题。 【参考方案1】:

从 SciPy 版本 0.17.0 开始,scipy.interpolate.interp1d 有一个允许外推的新选项。只需在调用中设置 fill_value='extrapolate'。以这种方式修改您的代码:

import numpy as np
from scipy import interpolate

x = np.arange(0,10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y, fill_value='extrapolate')

print f(9)
print f(11)

输出是:

0.0497870683679
0.010394302658

【讨论】:

外插类与插值类类似吗?例如,我们可以用最近点外推进行线性插值吗? 如果 kind='cubic',fill_value='extrapolate' 不起作用。 @a.sam:我不确定你的意思......大概,如果你使用 kind='linear' 和 fill_value='interpolation' 那么你会得到一个线性插值,如果你将其与 fill_value='extrapolation' 一起使用,然后您会得到线性外推,不是吗? @vlmercado:你能解释一下它在什么方面不起作用吗?我尝试通过添加 kind='cubic' 来运行上面的示例,它对我来说很好。 @Moot,使用 scipy 0.18.1,我得到以下信息: ValueError: Extrapolation does not work with kind=spline【参考方案2】:

你可以看看InterpolatedUnivariateSpline

这里是一个使用它的例子:

import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline

# given values
xi = np.array([0.2, 0.5, 0.7, 0.9])
yi = np.array([0.3, -0.1, 0.2, 0.1])
# positions to inter/extrapolate
x = np.linspace(0, 1, 50)
# spline order: 1 linear, 2 quadratic, 3 cubic ... 
order = 1
# do inter/extrapolation
s = InterpolatedUnivariateSpline(xi, yi, k=order)
y = s(x)

# example showing the interpolation for linear, quadratic and cubic interpolation
plt.figure()
plt.plot(xi, yi)
for order in range(1, 4):
    s = InterpolatedUnivariateSpline(xi, yi, k=order)
    y = s(x)
    plt.plot(x, y)
plt.show()

【讨论】:

这是最好的答案。这就是我所做的。 I used k=1 (order),所以变成了线性插值,而I used bbox=[xmin-w, xmax+w] where w is my tolerance【参考方案3】:

1。不断外推

您可以使用 scipy 中的interp 函数,它将左右值外推为超出范围的常量:

>>> from scipy import interp, arange, exp
>>> x = arange(0,10)
>>> y = exp(-x/3.0)
>>> interp([9,10], x, y)
array([ 0.04978707,  0.04978707])

2。线性(或其他自定义)外推

您可以为处理线性外插的插值函数编写一个包装器。例如:

from scipy.interpolate import interp1d
from scipy import arange, array, exp

def extrap1d(interpolator):
    xs = interpolator.x
    ys = interpolator.y

    def pointwise(x):
        if x < xs[0]:
            return ys[0]+(x-xs[0])*(ys[1]-ys[0])/(xs[1]-xs[0])
        elif x > xs[-1]:
            return ys[-1]+(x-xs[-1])*(ys[-1]-ys[-2])/(xs[-1]-xs[-2])
        else:
            return interpolator(x)

    def ufunclike(xs):
        return array(list(map(pointwise, array(xs))))

    return ufunclike

extrap1d 接受一个插值函数并返回一个也可以外插的函数。你可以这样使用它:

x = arange(0,10)
y = exp(-x/3.0)
f_i = interp1d(x, y)
f_x = extrap1d(f_i)

print f_x([9,10])

输出:

[ 0.04978707  0.03009069]

【讨论】:

在 Python 3.6 中,我必须将 list 添加到返回:return array(list(map(pointwise, array(xs)))) 以解析迭代器。 此解决方案比 fill_value="extrapolate" 选项更灵活。我能够根据我的需要“逐点”调整内部函数,我支持上面的评论并在需要时插入列表。话虽如此,有时您可能只想拥有一个生成器。 请注意,不再推荐基于 scipy.interp 的第一个解决方案,因为它已被弃用,并将在 SciPy 2.0.0 中消失。他们建议改用numpy.interp,但正如问题中所述,它在这里不起作用【参考方案4】:

scipy.interpolate.splrep 怎么样(1 级且没有平滑):

>> tck = scipy.interpolate.splrep([1, 2, 3, 4, 5], [1, 4, 9, 16, 25], k=1, s=0)
>> scipy.interpolate.splev(6, tck)
34.0

它似乎做你想做的事,因为 34 = 25 + (25 - 16)。

【讨论】:

【参考方案5】:

这是一种仅使用 numpy 包的替代方法。它利用了 numpy 的数组函数,因此在插值/外插大数组时可能更快:

import numpy as np

def extrap(x, xp, yp):
    """np.interp function with linear extrapolation"""
    y = np.interp(x, xp, yp)
    y = np.where(x<xp[0], yp[0]+(x-xp[0])*(yp[0]-yp[1])/(xp[0]-xp[1]), y)
    y = np.where(x>xp[-1], yp[-1]+(x-xp[-1])*(yp[-1]-yp[-2])/(xp[-1]-xp[-2]), y)
    return y

x = np.arange(0,10)
y = np.exp(-x/3.0)
xtest = np.array((8.5,9.5))

print np.exp(-xtest/3.0)
print np.interp(xtest, x, y)
print extrap(xtest, x, y)

编辑:Mark Mikofski 对“额外”功能的建议修改:

def extrap(x, xp, yp):
    """np.interp function with linear extrapolation"""
    y = np.interp(x, xp, yp)
    y[x < xp[0]] = yp[0] + (x[x<xp[0]]-xp[0]) * (yp[0]-yp[1]) / (xp[0]-xp[1])
    y[x > xp[-1]]= yp[-1] + (x[x>xp[-1]]-xp[-1])*(yp[-1]-yp[-2])/(xp[-1]-xp[-2])
    return y

【讨论】:

+1 是一个实际示例,但您也可以使用 boolean indexing 和 here y[x &lt; xp[0]] = fp[0] + (x[x &lt; xp[0]] - xp[0]) / (xp[1] - xp[0]) * (fp[1] - fp[0])y[x &gt; xp[-1]] = fp[-1] + (x[x &gt; xp[-1]] - xp[-1]) / (xp[-2] - xp[-1]) * (fp[-2] - fp[-1]) 而不是 np.where,因为 False 选项 y不变。【参考方案6】:

布尔索引大型数据集一起使用可能会更快,因为该算法会检查每个点是否都在区间之外,而布尔索引允许更轻松、更快速比较。

例如:

# Necessary modules
import numpy as np
from scipy.interpolate import interp1d

# Original data
x = np.arange(0,10)
y = np.exp(-x/3.0)

# Interpolator class
f = interp1d(x, y)

# Output range (quite large)
xo = np.arange(0, 10, 0.001)

# Boolean indexing approach

# Generate an empty output array for "y" values
yo = np.empty_like(xo)

# Values lower than the minimum "x" are extrapolated at the same time
low = xo < f.x[0]
yo[low] =  f.y[0] + (xo[low]-f.x[0])*(f.y[1]-f.y[0])/(f.x[1]-f.x[0])

# Values higher than the maximum "x" are extrapolated at same time
high = xo > f.x[-1]
yo[high] = f.y[-1] + (xo[high]-f.x[-1])*(f.y[-1]-f.y[-2])/(f.x[-1]-f.x[-2])

# Values inside the interpolation range are interpolated directly
inside = np.logical_and(xo >= f.x[0], xo <= f.x[-1])
yo[inside] = f(xo[inside])

在我的例子中,对于 300000 个点的数据集,这意味着从 25.8 秒加速到 0.094 秒,这快了 250 倍以上

【讨论】:

这很好,但是如果 x0 是浮点数,如果 y[0] 是 np.nan,或者 y[-1] 是 np.nan,则它不起作用。【参考方案7】:

我通过在我的初始数组中添加一个点来做到这一点。这样我就避免了定义自制函数,而且线性外推​​(在下面的例子中:右外推)看起来还可以。

import numpy as np  
from scipy import interp as itp  

xnew = np.linspace(0,1,51)  
x1=xold[-2]  
x2=xold[-1]  
y1=yold[-2]  
y2=yold[-1]  
right_val=y1+(xnew[-1]-x1)*(y2-y1)/(x2-x1)  
x=np.append(xold,xnew[-1])  
y=np.append(yold,right_val)  
f = itp(xnew,x,y)  

【讨论】:

【参考方案8】:

据我所知,恐怕在 Scipy 中做到这一点并不容易。正如我相当确定你知道的那样,你可以关闭边界错误并用常数填充超出范围的所有函数值,但这并没有真正的帮助。有关更多想法,请参阅邮件列表上的 this question。也许您可以使用某种分段函数,但这似乎很痛苦。

【讨论】:

这是我得出的结论,至少在 scipy 0.7 中,但是这个 21 个月前写的教程表明 interp1d 函数有一个 high 和 low 属性,可以设置为“linear”,教程不清楚这适用于哪个版本的 scipy:projects.scipy.org/scipy/browser/branches/Interpolate1D/docs/… 看起来这是一个分支的一部分,还没有被同化到主版本中,所以它可能仍然存在一些问题。当前代码位于projects.scipy.org/scipy/browser/branches/interpolate/…,尽管您可能希望滚动到页面底部并单击以将其作为纯文本下载。我认为这看起来很有希望,虽然我自己还没有尝试过。【参考方案9】:

下面的代码为您提供了简单的外推模块。 k 是数据集 y 必须根据数据集 x 推断的值。 需要 numpy 模块。

 def extrapol(k,x,y):
        xm=np.mean(x);
        ym=np.mean(y);
        sumnr=0;
        sumdr=0;
        length=len(x);
        for i in range(0,length):
            sumnr=sumnr+((x[i]-xm)*(y[i]-ym));
            sumdr=sumdr+((x[i]-xm)*(x[i]-xm));

        m=sumnr/sumdr;
        c=ym-(m*xm);
        return((m*k)+c)

【讨论】:

【参考方案10】:

我没有足够的声誉发表评论,但如果有人正在寻找使用 scipy 进行线性 2d 插值的外插包装器,我已经调整了此处给出的用于 1d 插值的答案。

def extrap2d(interpolator):
xs = interpolator.x
ys = interpolator.y
zs = interpolator.z
zs = np.reshape(zs, (-1, len(xs)))
def pointwise(x, y):
    if x < xs[0] or y < ys[0]:
        x1_index = np.argmin(np.abs(xs - x))
        x2_index = x1_index + 1
        y1_index = np.argmin(np.abs(ys - y))
        y2_index = y1_index + 1
        x1 = xs[x1_index]
        x2 = xs[x2_index]
        y1 = ys[y1_index]
        y2 = ys[y2_index]
        z11 = zs[x1_index, y1_index]
        z12 = zs[x1_index, y2_index]
        z21 = zs[x2_index, y1_index]
        z22 = zs[x2_index, y2_index]

        return (z11 * (x2 - x) * (y2 - y) +
        z21 * (x - x1) * (y2 - y) +
        z12 * (x2 - x) * (y - y1) +
        z22 * (x - x1) * (y - y1)
       ) / ((x2 - x1) * (y2 - y1) + 0.0)


    elif x > xs[-1] or y > ys[-1]:
        x1_index = np.argmin(np.abs(xs - x))
        x2_index = x1_index - 1
        y1_index = np.argmin(np.abs(ys - y))
        y2_index = y1_index - 1
        x1 = xs[x1_index]
        x2 = xs[x2_index]
        y1 = ys[y1_index]
        y2 = ys[y2_index]
        z11 = zs[x1_index, y1_index]
        z12 = zs[x1_index, y2_index]
        z21 = zs[x2_index, y1_index]
        z22 = zs[x2_index, y2_index]#

        return (z11 * (x2 - x) * (y2 - y) +
        z21 * (x - x1) * (y2 - y) +
        z12 * (x2 - x) * (y - y1) +
        z22 * (x - x1) * (y - y1)
        ) / ((x2 - x1) * (y2 - y1) + 0.0)
    else:
        return interpolator(x, y)
def ufunclike(xs, ys):
    if  isinstance(xs, int) or isinstance(ys, int) or isinstance(xs, np.int32) or isinstance(ys, np.int32):
        res_array = pointwise(xs, ys)
    else:
        res_array = np.zeros((len(xs), len(ys)))
        for x_c in range(len(xs)):
            res_array[x_c, :] = np.array([pointwise(xs[x_c], ys[y_c]) for y_c in range(len(ys))]).T

    return res_array
return ufunclike

我没有发表太多评论,而且我知道代码不是很干净。如果有人看到任何错误,请告诉我。在我当前的用例中,它可以正常工作:)

【讨论】:

【参考方案11】:

标准插值+线性外插:

    def interpola(v, x, y):
        if v <= x[0]:
            return y[0]+(y[1]-y[0])/(x[1]-x[0])*(v-x[0])
        elif v >= x[-1]:
            return y[-2]+(y[-1]-y[-2])/(x[-1]-x[-2])*(v-x[-2])
        else:
            f = interp1d(x, y, kind='cubic') 
            return f(v)

【讨论】:

嘿费德里科!如果您想知道为什么被否决,请注意,在回答问题时,您需要实际解释它是如何解决问题的。事实上,这个答案只是一个代码转储,应该至少有几句话解释它为什么和/或如何有用。谢谢!

以上是关于如何使 scipy.interpolate 给出超出输入范围的推断结果?的主要内容,如果未能解决你的问题,请参考以下文章

使用scipy.interpolate.CubicSpline添加或乘以三次样条曲线

函数 scipy.interpolate.interpn() 太慢了

scipy 0.11.0 到 0.12.0 更改了线性 scipy.interpolate.interp1d,破坏了我不断更新的插值器

Python scipy.interpolate插值

使用 scipy interpolate griddata 方法重新网格化数据时出现意外的内存错误

差异 scipy interpolate vs mpl griddata