假设我有一座山上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()
很好,所以我们的温度是这样的:
对于相同的高度,对所有时间进行插值:
我认为这一点非常简单。假设我每次都要把温度调到1000度。我可以使用内置的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。也就是说,与沉重的提升是“建立线性插值函数”的想法一致,step...which在第二个例子中发生了1000次,但在第一个例子中只发生了一次。
那么,问题是:有没有更好的方法来解决第二个问题?例如,有没有一个好的方法来设置它,用2-dimensinoal插值(它也许可以处理这样的情况:已知徒步旅行队位置的时间并不是采样温度的时间)?或者,有没有一种特别巧妙的方式来处理这里的事情?或者其他的?
发布于 2015-10-29 06:33:54
对于固定的时间点,您可以使用以下插值函数:
g(a) = cc[0]*abs(a-aa[0]) + cc[1]*abs(a-aa[1]) + cc[2]*abs(a-aa[2])
其中a
是徒步旅行者的高度,aa
是具有3个测量值altitudes
的向量,cc
是具有系数的向量。有三点需要注意:
对于给定的温度(alltemps
),对应于aa
,可以通过求解线性矩阵方程来确定cc
对于(N,)维样条核( a
)和(N,3)维样条核(包括respectively).
g(a)
称为一阶一元样条核(对于三点),使用np.linalg.solve()
很容易矢量化。使用abs(a-aa[i])**(2*d-1)
会将样条线顺序更改为d
。这种方法可以解释为Gaussian Process in Machine Learning.的简化版本
所以代码应该是:
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
更新:i替换了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
这表明对于大型N
,这种方法比interp_checked
方法具有更好的可扩展性。
发布于 2015-10-26 02:03:46
位置x1
和x2
处的两个值y1
、y2
相对于点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]
与最快的方法相比,Scipy的interpn
在速度上有所下降,但由于它的通用性和易用性,它绝对是可行的。
发布于 2015-10-12 04:05:49
我将提供一点进展。在第二种情况下(沿路径插值),我们制作了许多不同的插值函数。我们可以尝试的一件事是只制作一个插值函数(像上面的第一个例子一样,在所有时间内在高度维度上进行插值),并反复计算该函数(以矢量化的方式)。这会给我们提供比我们想要的更多的数据(它会给我们一个1000 x 1000的矩阵,而不是1000个元素的向量)。但是然后我们的目标结果就会沿着对角线。所以问题是,在更复杂的参数中调用单个函数比创建许多函数并使用简单的参数调用它们更快吗?
答案是肯定的!
关键是scipy.interpolate.interp1d
返回的插值函数能够接受numpy.ndarray
作为其输入。因此,您可以通过输入向量输入,以C速度有效地多次调用插值函数。也就是说,这比一次又一次地在标量输入上调用插值函数的for循环要快得多。因此,当我们计算许多最终丢弃的数据点时,我们通过不构造许多我们很少使用的不同插值函数来节省更多的时间。
old_way = interped_along_path = np.array([interp1d(altitudes, finaltemps.values[i, :])(loc)
for i, loc in enumerate(location)])
# look ma, no for loops!
new_way = np.diagonal(interp1d(altitudes, finaltemps.values)(location))
# note, `location` is a vector!
abs(old_way - new_way).max()
#-> 0.0
然而:
%%timeit
res = np.diagonal(interp1d(altitudes, finaltemps.values)(location))
#-> 100 loops, best of 3: 16.7 ms per loop
因此,这种方法使我们的性能提高了10倍!有人能做得更好吗?或者建议一种完全不同的方法?
https://stackoverflow.com/questions/33069366
复制相似问题