Python 碾压混凝土的有效方法
我有一个1-D NumPy数组,我在其中创建一个滚动窗口,然后计算Python 碾压混凝土的有效方法,python,numpy,Python,Numpy,我有一个1-D NumPy数组,我在其中创建一个滚动窗口,然后计算np.nanstd: import numpy as np def rolling_window(a, window): a = np.asarray(a) shape = a.shape[:-1] + (a.shape[-1] - window + 1, window) strides = a.strides + (a.strides[-1],) return np.lib.stride_tr
np.nanstd
:
import numpy as np
def rolling_window(a, window):
a = np.asarray(a)
shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
strides = a.strides + (a.strides[-1],)
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
if __name__ == "__main__":
n = 100_000_000
nan_indices = np.random.choice(np.arange(n), size=1000, replace=False)
T = np.random.rand(n)
T[nan_indices] = np.nan
m = 50
np.nanstd(rolling_window(T, m), axis=T.ndim)
然而,我注意到这不仅非常耗时,而且占用了大量内存。有没有办法同时提高内存和速度性能(Numba是一个选项)?NumPy矢量化
经过艰苦的数学计算后,以下是我最终得到的一些np.convolve
和一些掩蔽,以得到一个矢量化的NumPy解-
def nanstd(a, W):
k = np.ones(W, dtype=int)
m = ~np.isnan(a)
a0 = np.where(m, a,0)
n = np.convolve(m,k,'valid')
c1 = np.convolve(a0, k,'valid')
f2 = c1**2
p2 = f2/n**2
f1 = np.convolve((a0**2)*m,k,'valid')+n*p2
out = np.sqrt((f1 - (2/n)*f2)/n)
return out
- 完整的解释在这篇文章的末尾
import pandas as pd
def pdroll(T,m):
return pd.Series(T).rolling(m).std(ddof=0).values[m-1:]
标杆管理
使用包(打包在一起的一些基准测试工具;免责声明:我是它的作者)对建议的解决方案进行基准测试
def setup(n):
nan_indices = np.random.choice(np.arange(n), size=10, replace=False)
T = np.random.rand(n)
T[nan_indices] = np.nan
return T
import benchit
f = {'rolling': lambda T,m: np.nanstd(rolling_window(T, m), axis=T.ndim),
'pdroll': pdroll, 'conv':nanstd}
in_={(n,w):(setup(n),w) for n in 10**np.arange(2,6) for w in [5,10,20,50,80,100]}
t = benchit.timings(f, in_, multivar=True)
t.plot(logx=True, sp_ncols=2, save='timings.png', dpi=200)
NumPy one适合较小的窗户尺寸,而熊猫则适合较大的窗户尺寸
NumPy矢量化:关于NumPy版本的说明
nanstd
基本上,np.nanstd
正在计算std
忽略NaNs
。现在,std
可以基于mean
计算
因此,对于没有NAN的数组a
,它将是:
np.sqrt(np.mean((a-np.mean(a))**2)) # (1)
In [48]: np.nanstd(a)
Out[48]: 1.5811388300841898
In [89]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0))
Out[89]: 10.0
In [90]: c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2
Out[90]: array([10. , 4.66666667, 4.66666667, 10. , 8.75 ])
让我们证明一下:
In [43]: a = np.arange(1,6).astype(float)
In [44]: np.nanstd(a)
Out[44]: 1.4142135623730951
In [45]: np.sqrt(np.mean((a-np.mean(a))**2))
Out[45]: 1.4142135623730951
现在,假设我们有一个NaN
:
In [46]: a[2] = np.nan
In [47]: a
Out[47]: array([ 1., 2., nan, 4., 5.])
带有nanstd
的std
将是:
np.sqrt(np.mean((a-np.mean(a))**2)) # (1)
In [48]: np.nanstd(a)
Out[48]: 1.5811388300841898
In [89]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0))
Out[89]: 10.0
In [90]: c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2
Out[90]: array([10. , 4.66666667, 4.66666667, 10. , 8.75 ])
让我们根据(1)
计算出等效值
让我们从(a-np.mean(a))**2开始
这个:
In [72]: (a-np.mean(a))**2
Out[72]: array([nan, nan, nan, nan, nan])
In [73]: (a0 - np.sum(a0)/n)**2
Out[73]: array([4., 1., 9., 1., 4.])
In [75]: m*((a0 - np.sum(a0)/n)**2)
Out[75]: array([4., 1., 0., 1., 4.])
不
这个:
In [72]: (a-np.mean(a))**2
Out[72]: array([nan, nan, nan, nan, nan])
In [73]: (a0 - np.sum(a0)/n)**2
Out[73]: array([4., 1., 9., 1., 4.])
In [75]: m*((a0 - np.sum(a0)/n)**2)
Out[75]: array([4., 1., 0., 1., 4.])
不!因为a
是:
In [76]: a
Out[76]: array([ 1., 2., nan, 4., 5.])
In [79]: (2/n)*np.sum(a0*np.sum(a0))
Out[79]: 72.0
我们需要使NaN
位置一0
这个:
In [72]: (a-np.mean(a))**2
Out[72]: array([nan, nan, nan, nan, nan])
In [73]: (a0 - np.sum(a0)/n)**2
Out[73]: array([4., 1., 9., 1., 4.])
In [75]: m*((a0 - np.sum(a0)/n)**2)
Out[75]: array([4., 1., 0., 1., 4.])
对!
那么,什么是np.意思((a-np.意思(a))**2)
?它将是,[75]
中的总和除以n
:
In [77]: np.sum(m*((a0-np.sum(a0)/n)**2))/n
Out[77]: 2.5
因此,最终标准值:
In [78]: np.sqrt(np.sum(m*((a0-np.sum(a0)/n)**2))/n)
Out[78]: 1.5811388300841898
总结:
In [55]: m = ~np.isnan(a) # (2)
...: a0 = np.where(m, a,0)
...: n = m.sum()
...: out0 = np.sqrt(np.sum(m*((a0-np.sum(a0)/n)**2))/n)
In [56]: out0
Out[56]: 1.5811388300841898
下一部分是结合滑动性质。因此,我们需要以滑动的方式执行(2)
。因此,前两个步骤仍然存在
因此,它从以下内容开始:
m = ~np.isnan(a)
a0 = np.where(m, a,0)
但最后两个会改变,让我们看看如何
让我们关注计算out0
的最后一步。我们有:
m*((a0-np.sum(a0)/n)**2)
然后,我们计算总和:
np.sum(m*((a0-np.sum(a0)/n)**2))
我们有:(a-b)**2=a**2+b**2-2*a*b
。所以,前面的步骤变得更加简单
np.sum(m*(a0**2 + (np.sum(a0)/n)**2 - 2*a0*np.sum(a0)/n))
进一步重新安排导致:
np.sum(m*(a0**2 + (np.sum(a0)/n)**2) - np.sum(2*a0*np.sum(a0)/n))
np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - np.sum(2*a0*np.sum(a0)/n)
np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - 2*np.sum(a0*np.sum(a0))/n
np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0)) # (3)
让我们集中在总结的前两部分
也让我们拿一个样本来让事情具体化。我们将设置两个数据集-一个用于完整阵列设置,另一个用于完整阵列的窗口版本
设置:
#=========================== 1. Complete setup
a = np.arange(1,10).astype(float)
a[[2,5]] = np.nan
W = 5
k = np.ones(W, dtype=int)
m_comp = ~np.isnan(a)
a0_comp = np.where(m_comp, a,0)
n_comp = np.convolve(m_comp,k,'valid')
c1 = np.convolve(a0_comp, k,'valid')
c2 = np.convolve((a0_comp**2)*m_comp,k,'valid')
#=========================== 2. Windowed setup
a1 = np.arange(1,6).astype(float)
a1[2] = np.nan
m = ~np.isnan(a1)
a0 = np.where(m, a1,0)
n = m.sum()
out0 = np.sqrt(np.sum(m*((a0-np.sum(a0)/n)**2))/n)
通过窗口设置,我们有:
In [51]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2))
Out[51]: 82.0
In [52]: np.sum(m*(a0**2) + m*((np.sum(a0)/n)**2))
Out[52]: 82.0
In [53]: np.sum(m*(a0**2)) + np.sum(m*((np.sum(a0)/n)**2))
Out[53]: 82.0
第一个总结部分:
In [86]: np.sum(m*(a0**2))
Out[86]: 46.0
# complete setup version :
In [87]: c2
Out[87]: array([ 46., 45., 90., 154., 219.])
In [54]: np.sum(m*((np.sum(a0)/n)**2))
Out[54]: 36.0
# complete setup version :
In [55]: n_comp*(c1/n_comp)**2
Out[55]:
array([ 36. , 40.33333333, 85.33333333, 144. ,
210.25 ])
第二部分总结:
In [86]: np.sum(m*(a0**2))
Out[86]: 46.0
# complete setup version :
In [87]: c2
Out[87]: array([ 46., 45., 90., 154., 219.])
In [54]: np.sum(m*((np.sum(a0)/n)**2))
Out[54]: 36.0
# complete setup version :
In [55]: n_comp*(c1/n_comp)**2
Out[55]:
array([ 36. , 40.33333333, 85.33333333, 144. ,
210.25 ])
n(3)
中剩下的一块拼图是:
In [76]: a
Out[76]: array([ 1., 2., nan, 4., 5.])
In [79]: (2/n)*np.sum(a0*np.sum(a0))
Out[79]: 72.0
让我们把重点放在它的实质上:
In [80]: np.sum(a0*np.sum(a0))
Out[80]: 144.0
在完整设置中,它将对应于:
In [81]: c1**2
Out[81]: array([144., 121., 256., 576., 841.])
因此,对于整个剩余部分:
In [82]: (2/n)*np.sum(a0*np.sum(a0))
Out[82]: 72.0
# complete setup version :
In [83]: (2/n_comp)*c1**2
Out[83]:
array([ 72. , 80.66666667, 170.66666667, 288. ,
420.5 ])
因此,(3)
及其完整版本对应物应为:
np.sqrt(np.mean((a-np.mean(a))**2)) # (1)
In [48]: np.nanstd(a)
Out[48]: 1.5811388300841898
In [89]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0))
Out[89]: 10.0
In [90]: c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2
Out[90]: array([10. , 4.66666667, 4.66666667, 10. , 8.75 ])
要获得最终的std值,我们需要除以每个窗口的有效值计数,然后应用sqrt
:
In [99]: np.sqrt((c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2)/n_comp)
Out[99]: array([1.58113883, 1.24721913, 1.24721913, 1.58113883, 1.47901995])
因此,通过一些清理,我们最终得到了最终的nanstd
版本。NumPy矢量化
经过艰苦的数学计算后,以下是我最终得到的一些np.convolve
和一些掩蔽,以得到一个矢量化的NumPy解-
def nanstd(a, W):
k = np.ones(W, dtype=int)
m = ~np.isnan(a)
a0 = np.where(m, a,0)
n = np.convolve(m,k,'valid')
c1 = np.convolve(a0, k,'valid')
f2 = c1**2
p2 = f2/n**2
f1 = np.convolve((a0**2)*m,k,'valid')+n*p2
out = np.sqrt((f1 - (2/n)*f2)/n)
return out
- 完整的解释在这篇文章的末尾
熊猫当量
这是相当于熊猫的版本,性能还不错-
import pandas as pd
def pdroll(T,m):
return pd.Series(T).rolling(m).std(ddof=0).values[m-1:]
标杆管理
使用包(打包在一起的一些基准测试工具;免责声明:我是它的作者)对建议的解决方案进行基准测试
def setup(n):
nan_indices = np.random.choice(np.arange(n), size=10, replace=False)
T = np.random.rand(n)
T[nan_indices] = np.nan
return T
import benchit
f = {'rolling': lambda T,m: np.nanstd(rolling_window(T, m), axis=T.ndim),
'pdroll': pdroll, 'conv':nanstd}
in_={(n,w):(setup(n),w) for n in 10**np.arange(2,6) for w in [5,10,20,50,80,100]}
t = benchit.timings(f, in_, multivar=True)
t.plot(logx=True, sp_ncols=2, save='timings.png', dpi=200)
NumPy one适合较小的窗户尺寸,而熊猫则适合较大的窗户尺寸
NumPy矢量化:关于NumPy版本的说明nanstd
基本上,np.nanstd
正在计算std
忽略NaNs
。现在,std
可以基于mean
计算
因此,对于没有NAN的数组a
,它将是:
np.sqrt(np.mean((a-np.mean(a))**2)) # (1)
In [48]: np.nanstd(a)
Out[48]: 1.5811388300841898
In [89]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0))
Out[89]: 10.0
In [90]: c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2
Out[90]: array([10. , 4.66666667, 4.66666667, 10. , 8.75 ])
让我们证明一下:
In [43]: a = np.arange(1,6).astype(float)
In [44]: np.nanstd(a)
Out[44]: 1.4142135623730951
In [45]: np.sqrt(np.mean((a-np.mean(a))**2))
Out[45]: 1.4142135623730951
现在,假设我们有一个NaN
:
In [46]: a[2] = np.nan
In [47]: a
Out[47]: array([ 1., 2., nan, 4., 5.])
带有nanstd
的std
将是:
np.sqrt(np.mean((a-np.mean(a))**2)) # (1)
In [48]: np.nanstd(a)
Out[48]: 1.5811388300841898
In [89]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0))
Out[89]: 10.0
In [90]: c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2
Out[90]: array([10. , 4.66666667, 4.66666667, 10. , 8.75 ])
让我们根据(1)
计算出等效值
让我们从(a-np.mean(a))**2开始
这个:
In [72]: (a-np.mean(a))**2
Out[72]: array([nan, nan, nan, nan, nan])
In [73]: (a0 - np.sum(a0)/n)**2
Out[73]: array([4., 1., 9., 1., 4.])
In [75]: m*((a0 - np.sum(a0)/n)**2)
Out[75]: array([4., 1., 0., 1., 4.])
不
这个:
In [72]: (a-np.mean(a))**2
Out[72]: array([nan, nan, nan, nan, nan])
In [73]: (a0 - np.sum(a0)/n)**2
Out[73]: array([4., 1., 9., 1., 4.])
In [75]: m*((a0 - np.sum(a0)/n)**2)
Out[75]: array([4., 1., 0., 1., 4.])
不!因为a
是:
In [76]: a
Out[76]: array([ 1., 2., nan, 4., 5.])
In [79]: (2/n)*np.sum(a0*np.sum(a0))
Out[79]: 72.0
我们需要使NaN
位置一0
这个:
In [72]: (a-np.mean(a))**2
Out[72]: array([nan, nan, nan, nan, nan])
In [73]: (a0 - np.sum(a0)/n)**2
Out[73]: array([4., 1., 9., 1., 4.])
In [75]: m*((a0 - np.sum(a0)/n)**2)
Out[75]: array([4., 1., 0., 1., 4.])
对!
那么,什么是np.意思((a-np.意思(a))**2)
?它将是,[75]
中的总和除以n
:
In [77]: np.sum(m*((a0-np.sum(a0)/n)**2))/n
Out[77]: 2.5
因此,最终标准值:
In [78]: np.sqrt(np.sum(m*((a0-np.sum(a0)/n)**2))/n)
Out[78]: 1.5811388300841898
总结:
In [55]: m = ~np.isnan(a) # (2)
...: a0 = np.where(m, a,0)
...: n = m.sum()
...: out0 = np.sqrt(np.sum(m*((a0-np.sum(a0)/n)**2))/n)
In [56]: out0
Out[56]: 1.5811388300841898
下一部分是结合滑动性质。因此,我们需要以滑动的方式执行(2)
。因此,前两个步骤仍然存在
因此,它从以下内容开始:
m = ~np.isnan(a)
a0 = np.where(m, a,0)
但最后两个会改变,让我们看看如何
让我们关注计算out0
的最后一步。我们有:
m*((a0-np.sum(a0)/n)**2)
然后,我们计算总和:
np.sum(m*((a0-np.sum(a0)/n)**2))
我们有:(a-b)**2=a**2+b**2-2*a*b
。所以,前面的步骤变得更加简单
np.sum(m*(a0**2 + (np.sum(a0)/n)**2 - 2*a0*np.sum(a0)/n))
进一步重新安排导致:
np.sum(m*(a0**2 + (np.sum(a0)/n)**2) - np.sum(2*a0*np.sum(a0)/n))
np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - np.sum(2*a0*np.sum(a0)/n)
np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - 2*np.sum(a0*np.sum(a0))/n
np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0)) # (3)
让我们集中在总结的前两部分
也让我们拿一个样本来让事情具体化。我们将设置两个数据集-一个用于完整阵列设置,另一个用于完整阵列的窗口版本
设置:
#=========================== 1. Complete setup
a = np.arange(1,10).astype(float)
a[[2,5]] = np.nan
W = 5
k = np.ones(W, dtype=int)
m_comp = ~np.isnan(a)
a0_comp = np.where(m_comp, a,0)
n_comp = np.convolve(m_comp,k,'valid')
c1 = np.convolve(a0_comp, k,'valid')
c2 = np.convolve((a0_comp**2)*m_comp,k,'valid')
#=========================== 2. Windowed setup
a1 = np.arange(1,6).astype(float)
a1[2] = np.nan
m = ~np.isnan(a1)
a0 = np.where(m, a1,0)
n = m.sum()
out0 = np.sqrt(np.sum(m*((a0-np.sum(a0)/n)**2))/n)
通过窗口设置,我们有:
In [51]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2))
Out[51]: 82.0
In [52]: np.sum(m*(a0**2) + m*((np.sum(a0)/n)**2))
Out[52]: 82.0
In [53]: np.sum(m*(a0**2)) + np.sum(m*((np.sum(a0)/n)**2))
Out[53]: 82.0
第一个总结部分:
In [86]: np.sum(m*(a0**2))
Out[86]: 46.0
# complete setup version :
In [87]: c2
Out[87]: array([ 46., 45., 90., 154., 219.])
In [54]: np.sum(m*((np.sum(a0)/n)**2))
Out[54]: 36.0
# complete setup version :
In [55]: n_comp*(c1/n_comp)**2
Out[55]:
array([ 36. , 40.33333333, 85.33333333, 144. ,
210.25 ])
第二部分总结:
In [86]: np.sum(m*(a0**2))
Out[86]: 46.0
# complete setup version :
In [87]: c2
Out[87]: array([ 46., 45., 90., 154., 219.])
In [54]: np.sum(m*((np.sum(a0)/n)**2))
Out[54]: 36.0
# complete setup version :
In [55]: n_comp*(c1/n_comp)**2
Out[55]:
array([ 36. , 40.33333333, 85.33333333, 144. ,
210.25 ])
n(3)
中剩下的一块拼图是:
In [76]: a
Out[76]: array([ 1., 2., nan, 4., 5.])
In [79]: (2/n)*np.sum(a0*np.sum(a0))
Out[79]: 72.0
让我们把重点放在它的实质上:
In [80]: np.sum(a0*np.sum(a0))
Out[80]: 144.0
在完整设置中,它将对应于:
In [81]: c1**2
Out[81]: array([144., 121., 256., 576., 841.])
因此,对于整个剩余部分:
In [82]: (2/n)*np.sum(a0*np.sum(a0))
Out[82]: 72.0
# complete setup version :
In [83]: (2/n_comp)*c1**2
Out[83]:
array([ 72. , 80.66666667, 170.66666667, 288. ,
420.5 ])
因此,(3)
及其完整版本对应物应为:
np.sqrt(np.mean((a-np.mean(a))**2)) # (1)
In [48]: np.nanstd(a)
Out[48]: 1.5811388300841898
In [89]: np.sum(m*(a0**2 + (np.sum(a0)/n)**2)) - (2/n)*np.sum(a0*np.sum(a0))
Out[89]: 10.0
In [90]: c2 + n_comp*(c1/n_comp)**2 - (2/n_comp)*c1**2
Out[90]: array([10. , 4.66666667, 4.66666667, 10. , 8.75 ])
为了得到最终的std值,我们需要除以有效的std值的计数