Numpy / Scipy中的快速线性插值“沿着路径”

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (93)

假设我有一座山上3(已知)高度的气象站的数据。

每隔一分钟,每个站都会在其位置记录一次温度测量。

所以让我们设置一些数据:

import numpy as np
from scipy.interpolate import interp1d
import pandas as pd
import seaborn as sns

np.random.seed(0)
N, sigma = 1000., 5

basetemps = 70 + (np.random.randn(N) * sigma)
midtemps = 50 + (np.random.randn(N) * sigma)
toptemps = 40 + (np.random.randn(N) * sigma)
alltemps = np.array([basetemps, midtemps, toptemps]).T # note transpose!
trend = np.sin(4 / N * np.arange(N)) * 30
trend = trend[:, np.newaxis]

altitudes = np.array([500, 1500, 4000]).astype(float)

finaltemps = pd.DataFrame(alltemps + trend, columns=altitudes)
finaltemps.index.names, finaltemps.columns.names = ['Time'], ['Altitude']
finaltemps.plot()

我们的温度是这样的:

将所有时间内插到同一高度:

假设我想每次在海拔1,000度的地方获得温度。我可以用内置的scipy插值方法:

interping_function = interp1d(altitudes, finaltemps.values)
interped_to_1000 = interping_function(1000)

fig, ax = plt.subplots(1, 1, figsize=(8, 5))
finaltemps.plot(ax=ax, alpha=0.15)
ax.plot(interped_to_1000, label='Interped')
ax.legend(loc='best', title=finaltemps.columns.name)

看看速度:

%%timeit
res = interp1d(altitudes, finaltemps.values)(1000)
#-> 1000 loops, best of 3: 207 µs per loop

插入“沿着一条路径”:

假设我知道一个远足派对的高度随时间的变化,我想通过线性地插值我的时间数据来计算他们(移动)位置的温度。特别是,我知道远足聚会地点的时间是我知道我气象站的温度的时间。

location = np.linspace(altitudes[0], altitudes[-1], N)
interped_along_path = np.array([interp1d(altitudes, finaltemps.values[i, :])(loc) 
                                             for i, loc in enumerate(location)])

fig, ax = plt.subplots(1, 1, figsize=(8, 5))
finaltemps.plot(ax=ax, alpha=0.15)
ax.plot(interped_along_path, label='Interped')
ax.legend(loc='best', title=finaltemps.columns.name)

上面的重点是使用列表理解来隐藏大量的工作。在前一个案例中,scipy是为我们创建一个插值函数,并对大量数据进行一次评估。在这种情况下,scipy实际上是在构造N个别的插值函数,并对每一次对少量数据进行评估。这就让人感觉到了内在的效率。这里隐藏着一个for循环(在列表理解中),而且,这感觉很松弛。

这比上一次的情况慢得多:

%%timeit
res = np.array([interp1d(altitudes, finaltemps.values[i, :])(loc) 
                            for i, loc in enumerate(location)])
#-> 10 loops, best of 3: 145 ms per loop

所以第二个例子比第一个慢了1000。也就是说,与“做线性插值函数”的概念相一致,在第二个例子中,这个步骤发生了1000次,但在第一个例子中只发生过一次。

因此,问题是:是否有更好的方法来处理第二个问题?例如,是否有一种好的方法来建立它的2-酒窝内插(这也许可以处理的情况下,远足聚会地点是已知的时间是。温度取样的时间)?还是有一种特别巧妙的方法来处理这里的事情,在这里,时代的潮流?还是其他的?

提问于
用户回答回答于

对于不动点,可以使用以下插值函数:

g(a) = cc[0]*abs(a-aa[0]) + cc[1]*abs(a-aa[1]) + cc[2]*abs(a-aa[2])

何地a是徒步旅行者的高度,aa具有三度测量的矢量altitudescc是带系数的向量。有三件事要注意:

  1. 对于给定的温度(alltemps)对应于aa,决定cc可以通过求解线性矩阵方程来实现np.linalg.solve()...
  2. g(a)对于(N,)维数很容易矢量化a(N,3)维数cc(包括np.linalg.solve()分别)。
  3. g(a)称为一阶单变量样条核(用于三点)。使用abs(a-aa[i])**(2*d-1)将样条顺序更改为d.

所以守则是:

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

# generate temperatures
np.random.seed(0)
N, sigma = 1000, 5
trend = np.sin(4 / N * np.arange(N)) * 30
alltemps = np.array([tmp0 + trend + sigma*np.random.randn(N)
                     for tmp0 in [70, 50, 40]])

# generate attitudes:
altitudes = np.array([500, 1500, 4000]).astype(float)
location = np.linspace(altitudes[0], altitudes[-1], N)


def doit():
    """ do the interpolation, improved version for speed """
    AA = np.vstack([np.abs(altitudes-a_i) for a_i in altitudes])
    # This is slighty faster than np.linalg.solve(), because AA is small:
    cc = np.dot(np.linalg.inv(AA), alltemps)

    return (cc[0]*np.abs(location-altitudes[0]) +
            cc[1]*np.abs(location-altitudes[1]) +
            cc[2]*np.abs(location-altitudes[2]))


t_loc = doit()  # call interpolator

# do the plotting:
fg, ax = plt.subplots(num=1)
for alt, t in zip(altitudes, alltemps):
    ax.plot(t, label="%d feet" % alt, alpha=.5)
ax.plot(t_loc, label="Interpolation")
ax.legend(loc="best", title="Altitude:")
ax.set_xlabel("Time")
ax.set_ylabel("Temperature")
fg.canvas.draw()

测量时间可得出:

In [2]: %timeit doit()
10000 loops, best of 3: 107 µs per loop

最新情况:我替换了最初的列表理解doit()将进口速度提高30%(用于N=1000)

此外,按照比较的要求,@moarningsun在我的机器上的基准代码块:

10 loops, best of 3: 110 ms per loop  
interp_checked
10000 loops, best of 3: 83.9 µs per loop
scipy_interpn
1000 loops, best of 3: 678 µs per loop
Output allclose:
[True, True, True]

请注意N=1000是一个相对较小的数字。使用N=100000产生的结果:

interp_checked
100 loops, best of 3: 8.37 ms per loop

%timeit doit()
100 loops, best of 3: 5.31 ms per loop

这表明这种方法对于大型应用来说更适合。Ninterp_checked接近。

用户回答回答于

两值间的线性插值y1,,,y2在地点x1x2,关于点xi简单地说:

yi = y1 + (y2-y1) * (xi-x1) / (x2-x1)

使用一些向量化的Numpy表达式,我们可以从数据集中选择相关的点,并应用上述函数:

I = np.searchsorted(altitudes, location)

x1 = altitudes[I-1]
x2 = altitudes[I]

time = np.arange(len(alltemps))
y1 = alltemps[time,I-1]
y2 = alltemps[time,I]

xI = location

yI = y1 + (y2-y1) * (xI-x1) / (x2-x1)

某些点位于已知范围的边界(甚至超出),这应该考虑到:

I = np.searchsorted(altitudes, location)
same = (location == altitudes.take(I, mode='clip'))
out_of_range = ~same & ((I == 0) | (I == altitudes.size))
I[out_of_range] = 1  # Prevent index-errors

x1 = altitudes[I-1]
x2 = altitudes[I]

time = np.arange(len(alltemps))
y1 = alltemps[time,I-1]
y2 = alltemps[time,I]

xI = location

yI = y1 + (y2-y1) * (xI-x1) / (x2-x1)
yI[out_of_range] = np.nan

Scipy已经提供了ND插值,这也同样容易处理不匹配的时间,例如:

from scipy.interpolate import interpn

time = np.arange(len(alltemps))

M = 150
hiketime = np.linspace(time[0], time[-1], M)
location = np.linspace(altitudes[0], altitudes[-1], M)
xI = np.column_stack((hiketime, location))

yI = interpn((time, altitudes), alltemps, xI)

这里有一个基准代码(没有任何基准代码)pandas实际上,我确实包含了另一个答案的解决方案):

import numpy as np
from scipy.interpolate import interp1d, interpn

def original():
    return np.array([interp1d(altitudes, alltemps[i, :])(loc)
                                for i, loc in enumerate(location)])

def OP_self_answer():
    return np.diagonal(interp1d(altitudes, alltemps)(location))

def interp_checked():
    I = np.searchsorted(altitudes, location)
    same = (location == altitudes.take(I, mode='clip'))
    out_of_range = ~same & ((I == 0) | (I == altitudes.size))
    I[out_of_range] = 1  # Prevent index-errors

    x1 = altitudes[I-1]
    x2 = altitudes[I]

    time = np.arange(len(alltemps))
    y1 = alltemps[time,I-1]
    y2 = alltemps[time,I]

    xI = location

    yI = y1 + (y2-y1) * (xI-x1) / (x2-x1)
    yI[out_of_range] = np.nan

    return yI

def scipy_interpn():
    time = np.arange(len(alltemps))
    xI = np.column_stack((time, location))
    yI = interpn((time, altitudes), alltemps, xI)
    return yI

N, sigma = 1000., 5

basetemps = 70 + (np.random.randn(N) * sigma)
midtemps = 50 + (np.random.randn(N) * sigma)
toptemps = 40 + (np.random.randn(N) * sigma)
trend = np.sin(4 / N * np.arange(N)) * 30
trend = trend[:, np.newaxis]
alltemps = np.array([basetemps, midtemps, toptemps]).T + trend
altitudes = np.array([500, 1500, 4000], dtype=float)
location = np.linspace(altitudes[0], altitudes[-1], N)

funcs = [original, interp_checked, scipy_interpn]
for func in funcs:
    print(func.func_name)
    %timeit func()

from itertools import combinations
outs = [func() for func in funcs]
print('Output allclose:')
print([np.allclose(out1, out2) for out1, out2 in combinations(outs, 2)])

在我的系统上有以下结果:

original
10 loops, best of 3: 184 ms per loop
OP_self_answer
10 loops, best of 3: 89.3 ms per loop
interp_checked
1000 loops, best of 3: 224 µs per loop
scipy_interpn
1000 loops, best of 3: 1.36 ms per loop
Output allclose:
[True, True, True, True, True, True]

西皮氏interpn与最快的方法相比,它在速度上有些困难,但由于它的通用性和易用性,它肯定是可行的。

扫码关注云+社区