似乎没有简单计算numpy / scipy移动平均线的函数,导致convoluted solutions。
我的问题是双重的:
答案 0 :(得分:126)
如果您只想要一个简单的非加权移动平均线,您可以使用np.cumsum
轻松实现它,可能 比基于FFT的方法更快:
编辑更正了代码中Bean发现的一个错误的索引。的修改
def moving_average(a, n=3) :
ret = np.cumsum(a, dtype=float)
ret[n:] = ret[n:] - ret[:-n]
return ret[n - 1:] / n
>>> a = np.arange(20)
>>> moving_average(a)
array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.,
12., 13., 14., 15., 16., 17., 18.])
>>> moving_average(a, n=4)
array([ 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5,
10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5])
所以我猜答案是:它实现起来非常简单,而且numpy可能已经变得有点臃肿了。
答案 1 :(得分:71)
NumPy缺乏特定的特定领域功能可能是由于核心团队的纪律和对NumPy主要指令的忠诚:提供N维数组类型,以及用于创建的功能,以及索引这些数组。像许多基本目标一样,这个目标并不小,NumPy非常出色。
(更大) SciPy 包含更大的特定于域的库集合(称为 子包 作者:SciPy devs) - 例如,数值优化(优化),信号处理(信号)和积分微积分(积分)。 / p>
我的猜测是你所追求的功能至少在一个SciPy子包中(或许是 scipy.signal );但是,我会首先看一下 SciPy scikits 的集合,找出相关的scikit(s)并在那里寻找感兴趣的功能。
Scikits是基于NumPy / SciPy独立开发的软件包,并针对特定的技术学科(例如, scikits-image , scikits-learn 等。)在选择居住在相对较新的 scikits 标题之前,其中一些(特别是用于数值优化的令人敬畏的OpenOpt)是备受推崇的成熟项目。 Scikits 主页上面列出了大约30个这样的 scikits ,尽管其中至少有几个不再处于活跃开发状态。
遵循此建议会引导您进入 scikits-timeseries ;但是,这个包裹不再处于积极发展之中;实际上,Pandas已成为AFAIK,基于事实上 NumPy 的时间序列库。
Pandas 有几个可用于计算移动平均值的函数;其中最简单的可能是 rolling_mean ,您可以这样使用:
>>> # the recommended syntax to import pandas
>>> import pandas as PD
>>> import numpy as NP
>>> # prepare some fake data:
>>> # the date-time indices:
>>> t = PD.date_range('1/1/2010', '12/31/2012', freq='D')
>>> # the data:
>>> x = NP.arange(0, t.shape[0])
>>> # combine the data & index into a Pandas 'Series' object
>>> D = PD.Series(x, t)
现在,只需调用传递Series对象的函数 rolling_mean 和窗口大小,在我下面的示例中 10天。
>>> d_mva = PD.rolling_mean(D, 10)
>>> # d_mva is the same size as the original Series
>>> d_mva.shape
(1096,)
>>> # though obviously the first w values are NaN where w is the window size
>>> d_mva[:3]
2010-01-01 NaN
2010-01-02 NaN
2010-01-03 NaN
验证它是否有效 - 例如,比较原始系列中的值10 - 15与使用滚动平均值平滑的新系列
>>> D[10:15]
2010-01-11 2.041076
2010-01-12 2.041076
2010-01-13 2.720585
2010-01-14 2.720585
2010-01-15 3.656987
Freq: D
>>> d_mva[10:20]
2010-01-11 3.131125
2010-01-12 3.035232
2010-01-13 2.923144
2010-01-14 2.811055
2010-01-15 2.785824
Freq: D
函数rolling_mean以及大约十几个其他函数在rubric 移动窗口函数下的Pandas文档中非正式地分组; Pandas中第二个相关的函数组称为指数加权函数(例如, ewma ,它计算指数移动的加权平均值)。第二组未包含在第一组(移动窗口函数)中的事实可能是因为指数加权变换不依赖于固定长度的窗口
答案 2 :(得分:13)
一种简单的方法是使用np.convolve
。
其背后的想法是利用discrete convolution的计算方式,并使用它来返回滚动平均值。这可以通过对长度等于我们想要的滑动窗口长度的np.ones
序列进行卷积来完成。
为此,我们可以定义以下函数:
def moving_average(x, w):
return np.convolve(x, np.ones(w), 'valid') / w
此函数将对序列x
和长度为w
的序列进行卷积。请注意,所选的mode
是valid
,因此仅对序列完全重叠的点给出卷积。
用例
一些例子:
x = np.array([5,3,8,10,2,1,5,1,0,2])
对于窗口长度为2
的移动平均值,我们将具有:
moving_average(x, 2)
# array([4. , 5.5, 9. , 6. , 1.5, 3. , 3. , 0.5, 1. ])
对于长度为4
的窗口:
moving_average(x, 4)
# array([6.5 , 5.75, 5.25, 4.5 , 2.25, 1.75, 2. ])
详细信息
让我们更深入地了解离散卷积的计算方式。
以下函数旨在复制np.convolve
计算输出值的方式:
def mov_avg(x, w):
for m in range(len(x)-(w-1)):
yield sum(np.ones(w) * x[m:m+w]) / w
对于上面的相同示例,这也会产生:
list(mov_avg(x, 2))
# [4.0, 5.5, 9.0, 6.0, 1.5, 3.0, 3.0, 0.5, 1.0]
因此,每个步骤要做的是取一个1与当前窗口之间的内积。在这种情况下,假设我们直接取序列的np.ones(w)
,那么sum
的乘法是多余的。
下面是一个示例,该示例说明了如何计算第一个输出,以便使其更加清晰。假设我们想要一个w=4
的窗口:
[1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*5 + 1*3 + 1*8 + 1*10) / w = 6.5
以下输出将计算为:
[1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*3 + 1*8 + 1*10 + 1*2) / w = 5.75
依次类推,一旦执行了所有重叠操作,就返回序列的移动平均值。
答案 3 :(得分:5)
由于rolling_mean
已不再是熊猫的一部分,因此该使用熊猫的答案已从上方改编
# the recommended syntax to import pandas
import pandas as pd
import numpy as np
# prepare some fake data:
# the date-time indices:
t = pd.date_range('1/1/2010', '12/31/2012', freq='D')
# the data:
x = np.arange(0, t.shape[0])
# combine the data & index into a Pandas 'Series' object
D = pd.Series(x, t)
现在,只需在数据框上使用窗口大小调用函数rolling
,在我的示例中,该窗口大小为10天。
d_mva10 = D.rolling(10).mean()
# d_mva is the same size as the original Series
# though obviously the first w values are NaN where w is the window size
d_mva10[:11]
2010-01-01 NaN
2010-01-02 NaN
2010-01-03 NaN
2010-01-04 NaN
2010-01-05 NaN
2010-01-06 NaN
2010-01-07 NaN
2010-01-08 NaN
2010-01-09 NaN
2010-01-10 4.5
2010-01-11 5.5
Freq: D, dtype: float64
答案 4 :(得分:5)
这里有多种方法以及一些基准。最好的方法是使用来自其他库的优化代码的版本。 bottleneck.move_mean
方法可能是最好的方法。 scipy.convolve
方法也非常快速,可扩展,并且在语法和概念上都很简单,但是对于很大的窗口值来说,缩放效果不好。如果您需要纯numpy.cumsum
方法,那么numpy
方法是很好的。
注意:其中一些(例如bottleneck.move_mean
)没有居中,会移动数据。
import numpy as np
import scipy as sci
import scipy.signal as sig
import pandas as pd
import bottleneck as bn
import time as time
def rollavg_direct(a,n):
'Direct "for" loop'
assert n%2==1
b = a*0.0
for i in range(len(a)) :
b[i]=a[max(i-n//2,0):min(i+n//2+1,len(a))].mean()
return b
def rollavg_comprehension(a,n):
'List comprehension'
assert n%2==1
r,N = int(n/2),len(a)
return np.array([a[max(i-r,0):min(i+r+1,N)].mean() for i in range(N)])
def rollavg_convolve(a,n):
'scipy.convolve'
assert n%2==1
return sci.convolve(a,np.ones(n,dtype='float')/n, 'same')[n//2:-n//2+1]
def rollavg_convolve_edges(a,n):
'scipy.convolve, edge handling'
assert n%2==1
return sci.convolve(a,np.ones(n,dtype='float'), 'same')/sci.convolve(np.ones(len(a)),np.ones(n), 'same')
def rollavg_cumsum(a,n):
'numpy.cumsum'
assert n%2==1
cumsum_vec = np.cumsum(np.insert(a, 0, 0))
return (cumsum_vec[n:] - cumsum_vec[:-n]) / n
def rollavg_cumsum_edges(a,n):
'numpy.cumsum, edge handling'
assert n%2==1
N = len(a)
cumsum_vec = np.cumsum(np.insert(np.pad(a,(n-1,n-1),'constant'), 0, 0))
d = np.hstack((np.arange(n//2+1,n),np.ones(N-n)*n,np.arange(n,n//2,-1)))
return (cumsum_vec[n+n//2:-n//2+1] - cumsum_vec[n//2:-n-n//2]) / d
def rollavg_roll(a,n):
'Numpy array rolling'
assert n%2==1
N = len(a)
rolling_idx = np.mod((N-1)*np.arange(n)[:,None] + np.arange(N), N)
return a[rolling_idx].mean(axis=0)[n-1:]
def rollavg_roll_edges(a,n):
# see https://stackoverflow.com/questions/42101082/fast-numpy-roll
'Numpy array rolling, edge handling'
assert n%2==1
a = np.pad(a,(0,n-1-n//2), 'constant')*np.ones(n)[:,None]
m = a.shape[1]
idx = np.mod((m-1)*np.arange(n)[:,None] + np.arange(m), m) # Rolling index
out = a[np.arange(-n//2,n//2)[:,None], idx]
d = np.hstack((np.arange(1,n),np.ones(m-2*n+1+n//2)*n,np.arange(n,n//2,-1)))
return (out.sum(axis=0)/d)[n//2:]
def rollavg_pandas(a,n):
'Pandas rolling average'
return pd.DataFrame(a).rolling(5, center=True, min_periods=1).mean().to_numpy()
def rollavg_bottlneck(a,n):
'bottleneck.move_mean'
return bn.move_mean(a, window=n, min_count=1)
N = 10**6
a = np.random.rand(N)
functions = [rollavg_direct, rollavg_comprehension, rollavg_convolve,
rollavg_convolve_edges, rollavg_cumsum, rollavg_cumsum_edges,
rollavg_pandas, rollavg_bottlneck, rollavg_roll, rollavg_roll_edges]
print('Small window (n=3)')
%load_ext memory_profiler
for f in functions :
print('\n'+f.__doc__+ ' : ')
%timeit b=f(a,3)
print('\nLarge window (n=1001)')
for f in functions[0:-2] :
print('\n'+f.__doc__+ ' : ')
%timeit b=f(a,1001)
print('\nMemory\n')
print('Small window (n=3)')
N = 10**7
a = np.random.rand(N)
%load_ext memory_profiler
for f in functions[2:] :
print('\n'+f.__doc__+ ' : ')
%memit b=f(a,3)
print('\nLarge window (n=1001)')
for f in functions[2:-2] :
print('\n'+f.__doc__+ ' : ')
%memit b=f(a,1001)
定时,小窗口(n = 3)
Direct "for" loop :
4.14 s ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
List comprehension :
3.96 s ± 27.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
scipy.convolve :
1.07 ms ± 26.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
scipy.convolve, edge handling :
4.68 ms ± 9.69 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
numpy.cumsum :
5.31 ms ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
numpy.cumsum, edge handling :
8.52 ms ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Pandas rolling average :
9.85 ms ± 9.63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
bottleneck.move_mean :
1.3 ms ± 12.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Numpy array rolling :
31.3 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Numpy array rolling, edge handling :
61.1 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
定时,大窗口(n = 1001)
Direct "for" loop :
4.67 s ± 34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
List comprehension :
4.46 s ± 14.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
scipy.convolve :
103 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
scipy.convolve, edge handling :
272 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
numpy.cumsum :
5.19 ms ± 12.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
numpy.cumsum, edge handling :
8.7 ms ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Pandas rolling average :
9.67 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
bottleneck.move_mean :
1.31 ms ± 15.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
内存,小窗口(n = 3)
The memory_profiler extension is already loaded. To reload it, use:
%reload_ext memory_profiler
scipy.convolve :
peak memory: 362.66 MiB, increment: 73.61 MiB
scipy.convolve, edge handling :
peak memory: 510.24 MiB, increment: 221.19 MiB
numpy.cumsum :
peak memory: 441.81 MiB, increment: 152.76 MiB
numpy.cumsum, edge handling :
peak memory: 518.14 MiB, increment: 228.84 MiB
Pandas rolling average :
peak memory: 449.34 MiB, increment: 160.02 MiB
bottleneck.move_mean :
peak memory: 374.17 MiB, increment: 75.54 MiB
Numpy array rolling :
peak memory: 661.29 MiB, increment: 362.65 MiB
Numpy array rolling, edge handling :
peak memory: 1111.25 MiB, increment: 812.61 MiB
内存,大窗口(n = 1001)
scipy.convolve :
peak memory: 370.62 MiB, increment: 71.83 MiB
scipy.convolve, edge handling :
peak memory: 521.98 MiB, increment: 223.18 MiB
numpy.cumsum :
peak memory: 451.32 MiB, increment: 152.52 MiB
numpy.cumsum, edge handling :
peak memory: 527.51 MiB, increment: 228.71 MiB
Pandas rolling average :
peak memory: 451.25 MiB, increment: 152.50 MiB
bottleneck.move_mean :
peak memory: 374.64 MiB, increment: 75.85 MiB
答案 5 :(得分:2)
从 Numpy 1.20
开始,sliding_window_view
提供了一种滑动/滚动元素窗口的方法。然后您可以单独平均的 Windows。
例如,对于一个 4
元素的窗口:
from numpy.lib.stride_tricks import sliding_window_view
# values = np.array([5, 3, 8, 10, 2, 1, 5, 1, 0, 2])
np.average(sliding_window_view(values, window_shape = 4), axis=1)
# array([6.5, 5.75, 5.25, 4.5, 2.25, 1.75, 2])
注意 sliding_window_view
的中间结果:
# values = np.array([5, 3, 8, 10, 2, 1, 5, 1, 0, 2])
sliding_window_view(values, window_shape = 4)
# array([[ 5, 3, 8, 10],
# [ 3, 8, 10, 2],
# [ 8, 10, 2, 1],
# [10, 2, 1, 5],
# [ 2, 1, 5, 1],
# [ 1, 5, 1, 0],
# [ 5, 1, 0, 2]])
答案 6 :(得分:1)
如果您要小心处理边缘条件(仅从边缘的可用元素计算平均值),则可以使用以下功能。
import numpy as np
def running_mean(x, N):
out = np.zeros_like(x, dtype=np.float64)
dim_len = x.shape[0]
for i in range(dim_len):
if N%2 == 0:
a, b = i - (N-1)//2, i + (N-1)//2 + 2
else:
a, b = i - (N-1)//2, i + (N-1)//2 + 1
#cap indices to min and max indices
a = max(0, a)
b = min(dim_len, b)
out[i] = np.mean(x[a:b])
return out
>>> running_mean(np.array([1,2,3,4]), 2)
array([1.5, 2.5, 3.5, 4. ])
>>> running_mean(np.array([1,2,3,4]), 3)
array([1.5, 2. , 3. , 3.5])
答案 7 :(得分:1)
我认为可以使用bottleneck
轻松解决此问题请参见下面的基本示例:
import numpy as np
import bottleneck as bn
a = np.random.randint(4, 1000, size=(5, 7))
mm = bn.move_mean(a, window=2, min_count=1)
这给出了沿每个轴的移动平均值。
“ mm”是“ a”的移动平均值。
“窗口”是移动平均值要考虑的最大条目数。
“ min_count”是移动平均值(例如,第一个元素或数组具有nan值)要考虑的最小条目数。
好的部分是Bottleneck有助于处理nan值,而且效率很高。
答案 8 :(得分:1)
for i in range(len(Data)):
Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback
尝试这段代码。我认为这比较简单,可以完成工作。 回溯是移动平均线的窗口。
在Data[i-lookback:i, 0].sum()
中,我放置了0
来引用数据集的第一列,但是如果有多个列,则可以放置任何您喜欢的列。
答案 9 :(得分:1)
移动平均线
迭代器方法
将i处的数组取反,只需将i的均值取为n。
使用列表推导来即时生成小型阵列。
x = np.random.randint(10, size=20)
def moving_average(arr, n):
return [ (arr[:i+1][::-1][:n]).mean() for i, ele in enumerate(arr) ]
d = 5
moving_average(x, d)
张量卷积
moving_average = np.convolve(x, np.ones(d)/d, mode='valid')
答案 10 :(得分:0)
我实际上想要的行为与接受的答案略有不同。我正在为sklearn
管道构建移动平均特征提取器,因此我要求移动平均的输出必须具有与输入相同的维度。我想要的是使移动平均线假设序列保持恒定,即,{2}与窗口2的移动平均数将得到[1,2,3,4,5]
。
对于列向量(我的用例),我们得到
[1.5,2.5,3.5,4.5,5.0]
对于数组
def moving_average_col(X, n):
z2 = np.cumsum(np.pad(X, ((n,0),(0,0)), 'constant', constant_values=0), axis=0)
z1 = np.cumsum(np.pad(X, ((0,n),(0,0)), 'constant', constant_values=X[-1]), axis=0)
return (z1-z2)[(n-1):-1]/n
当然,不必假定填充的常量值,但在大多数情况下这样做就足够了。
答案 11 :(得分:0)
talib包含一个简单的移动平均工具以及其他类似的平均工具(即指数移动平均)。下面将方法与其他一些解决方案进行了比较。
%timeit pd.Series(np.arange(100000)).rolling(3).mean()
2.53 ms ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit talib.SMA(real = np.arange(100000.), timeperiod = 3)
348 µs ± 3.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit moving_average(np.arange(100000))
638 µs ± 45.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
一个警告是,实数必须具有dtype = float
的元素。否则会引发以下错误
例外:实数不是两倍
答案 12 :(得分:0)
这是使用numba的快速实现(请注意类型)。请注意,它确实包含移位的nan。
import numpy as np
import numba as nb
@nb.jit(nb.float64[:](nb.float64[:],nb.int64),
fastmath=True,nopython=True)
def moving_average( array, window ):
ret = np.cumsum(array)
ret[window:] = ret[window:] - ret[:-window]
ma = ret[window - 1:] / window
n = np.empty(window-1); n.fill(np.nan)
return np.concatenate((n.ravel(), ma.ravel()))
答案 13 :(得分:0)
我使用accepted answer的解决方案,对其进行了稍微修改以使其具有与输入相同的输出长度,或者使用pandas
'的版本,如在另一个答案的注释中所述。我在这里总结了两个示例,以供将来参考:
import numpy as np
import pandas as pd
def moving_average(a, n):
ret = np.cumsum(a, dtype=float)
ret[n:] = ret[n:] - ret[:-n]
return ret / n
def moving_average_centered(a, n):
return pd.Series(a).rolling(window=n, center=True).mean().to_numpy()
A = [0, 0, 1, 2, 4, 5, 4]
print(moving_average(A, 3))
# [0. 0. 0.33333333 1. 2.33333333 3.66666667 4.33333333]
print(moving_average_centered(A, 3))
# [nan 0.33333333 1. 2.33333333 3.66666667 4.33333333 nan ]
答案 14 :(得分:0)
通过将以下解决方案与使用numpy cumsum的解决方案进行比较,该解决方案几乎花费了一半的时间。这是因为它不需要遍历整个数组来求和,然后进行所有减法。此外,如果数组很大且数量很大(可能的溢出),则总和可能是“ 危险”。当然,这里也存在危险,但至少仅将基本数字加在一起。
def moving_average(array_numbers, n):
if n > len(array_numbers):
return []
temp_sum = sum(array_numbers[:n])
averages = [temp_sum / float(n)]
for first_index, item in enumerate(array_numbers[n:]):
temp_sum += item - array_numbers[first_index]
averages.append(temp_sum / float(n))
return averages
答案 15 :(得分:0)
所有答案似乎都集中在预先计算列表的情况上。对于实际运行的用例,数字一个一个进来,这里有一个简单的类,提供对最后 N 个值求平均值的服务:
import numpy as np
class RunningAverage():
def __init__(self, stack_size):
self.stack = [0 for _ in range(stack_size)]
self.ptr = 0
self.full_cycle = False
def add(self,value):
self.stack[self.ptr] = value
self.ptr += 1
if self.ptr == len(self.stack):
self.full_cycle = True
self.ptr = 0
def get_avg(self):
if self.full_cycle:
return np.mean(self.stack)
else:
return np.mean(self.stack[:self.ptr])
用法:
N = 50 # size of the averaging window
run_avg = RunningAverage(N)
for i in range(1000):
value = <my computation>
run_avg.add(value)
if i % 20 ==0: # print once in 20 iters:
print(f'the average value is {run_avg.get_avg()}')