特定のウィンドウを与えられた1次元配列の移動平均を計算する、python用のscipy関数、numpy関数、またはモジュールはありますか?
依存関係なしに1つのループ内ですべてのことを実行する短くて高速な解決策の場合は、以下のコードが最適です。
mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3
cumsum, moving_aves = [0], []
for i, x in enumerate(mylist, 1):
cumsum.append(cumsum[i-1] + x)
if i>=N:
moving_ave = (cumsum[i] - cumsum[i-N])/N
#can do stuff with moving_ave here
moving_aves.append(moving_ave)
UPD:より効率的な解決策が Alleo と によって提案されましたjasaarim .
そのためには np.convolve
を使うことができます。
np.convolve(x, np.ones((N,))/N, mode='valid')
移動平均は、 畳み込み の数学演算の場合です。移動平均については、入力に沿ってウィンドウをスライドさせ、ウィンドウの内容の平均を計算します。離散1D信号の場合、畳み込みは、任意の線形結合を計算する平均ではなく、各要素に対応する係数を掛けて結果を合計することを除いて、同じことです。これらの係数は、ウィンドウ内の各位置に1つずつあり、コンボリューションカーネルと呼ばれることがあります。さて、N個の値の算術平均は(x_1 + x_2 + ... + x_N) / N
なので、対応するカーネルは(1/N, 1/N, ..., 1/N)
であり、それはまさにnp.ones((N,))/N
を使って得られるものです。
np.convolve
のmode
引数は、エッジの処理方法を指定します。私はここでvalid
モードを選択しました。これは、ほとんどの人がランニング手段が機能することを期待している方法だと思うからです。これは、モード間の違いを説明するプロットです。
import numpy as np
import matplotlib.pyplot as plt
modes = ['full', 'same', 'valid']
for m in modes:
plt.plot(np.convolve(np.ones((200,)), np.ones((50,))/50, mode=m));
plt.axis([-10, 251, -.1, 1.1]);
plt.legend(modes, loc='lower center');
plt.show()
畳み込みは直接的なアプローチよりもはるかに優れていますが、FFTを使用しているため、かなり遅いです。しかし、特にランニングを計算するためには、次のアプローチがうまく機能することを意味します
def running_mean(x, N):
cumsum = numpy.cumsum(numpy.insert(x, 0, 0))
return (cumsum[N:] - cumsum[:-N]) / float(N)
確認するコード
In[3]: x = numpy.random.random(100000)
In[4]: N = 1000
In[5]: %timeit result1 = numpy.convolve(x, numpy.ones((N,))/N, mode='valid')
10 loops, best of 3: 41.4 ms per loop
In[6]: %timeit result2 = running_mean(x, N)
1000 loops, best of 3: 1.04 ms per loop
numpy.allclose(result1, result2)
はTrue
であり、2つのメソッドは同等です。 Nが大きいほど、時間差が大きくなります。
更新:以下の例は、最近のバージョンのパンダで削除された古いpandas.rolling_mean
関数を示しています。以下の関数呼び出しと現代的に等価なものは、
In [8]: pd.Series(x).rolling(window=N).mean().iloc[N-1:].values
Out[8]:
array([ 0.49815397, 0.49844183, 0.49840518, ..., 0.49488191,
0.49456679, 0.49427121])
パンダ はNumPyやSciPyよりもこれに適しています。その関数 rolling_mean は便利に仕事をします。入力が配列の場合もNumPy配列を返します。
カスタムの純粋なPython実装でrolling_mean
を性能面で上回るのは困難です。提案された2つのソリューションに対するパフォーマンスの例は次のとおりです。
In [1]: import numpy as np
In [2]: import pandas as pd
In [3]: def running_mean(x, N):
...: cumsum = np.cumsum(np.insert(x, 0, 0))
...: return (cumsum[N:] - cumsum[:-N]) / N
...:
In [4]: x = np.random.random(100000)
In [5]: N = 1000
In [6]: %timeit np.convolve(x, np.ones((N,))/N, mode='valid')
10 loops, best of 3: 172 ms per loop
In [7]: %timeit running_mean(x, N)
100 loops, best of 3: 6.72 ms per loop
In [8]: %timeit pd.rolling_mean(x, N)[N-1:]
100 loops, best of 3: 4.74 ms per loop
In [9]: np.allclose(pd.rolling_mean(x, N)[N-1:], running_mean(x, N))
Out[9]: True
Edgeの値の扱い方についてもNiceオプションがあります。
移動平均は次のように計算できます。
import numpy as np
def runningMean(x, N):
y = np.zeros((len(x),))
for ctr in range(len(x)):
y[ctr] = np.sum(x[ctr:(ctr+N)])
return y/N
しかし遅いです。
幸いなことに、numpyには 畳み込み関数 が含まれています。移動平均は、すべてのメンバが1/N
に等しい、長さがx
のベクトルとN
を畳み込むことと同じです。 convolveの派手な実装には開始トランジェントが含まれるため、最初のN-1点を削除する必要があります。
def runningMeanFast(x, N):
return np.convolve(x, np.ones((N,))/N)[(N-1):]
私のマシンでは、高速バージョンは、入力ベクトルの長さと平均化ウィンドウのサイズに応じて、20〜30倍高速です。
Convolveには、開始時の一時的な問題に対処する必要があるように思われる'same'
モードが含まれていますが、開始と終了の間で分割されています。
またはPython用の計算モジュール
tradewave.netでの私のテストでは、TA-libは常に勝ちます:
import talib as ta
import numpy as np
import pandas as pd
import scipy
from scipy import signal
import time as t
PAIR = info.primary_pair
PERIOD = 30
def initialize():
storage.reset()
storage.elapsed = storage.get('elapsed', [0,0,0,0,0,0])
def cumsum_sma(array, period):
ret = np.cumsum(array, dtype=float)
ret[period:] = ret[period:] - ret[:-period]
return ret[period - 1:] / period
def pandas_sma(array, period):
return pd.rolling_mean(array, period)
def api_sma(array, period):
# this method is native to Tradewave and does NOT return an array
return (data[PAIR].ma(PERIOD))
def talib_sma(array, period):
return ta.MA(array, period)
def convolve_sma(array, period):
return np.convolve(array, np.ones((period,))/period, mode='valid')
def fftconvolve_sma(array, period):
return scipy.signal.fftconvolve(
array, np.ones((period,))/period, mode='valid')
def tick():
close = data[PAIR].warmup_period('close')
t1 = t.time()
sma_api = api_sma(close, PERIOD)
t2 = t.time()
sma_cumsum = cumsum_sma(close, PERIOD)
t3 = t.time()
sma_pandas = pandas_sma(close, PERIOD)
t4 = t.time()
sma_talib = talib_sma(close, PERIOD)
t5 = t.time()
sma_convolve = convolve_sma(close, PERIOD)
t6 = t.time()
sma_fftconvolve = fftconvolve_sma(close, PERIOD)
t7 = t.time()
storage.elapsed[-1] = storage.elapsed[-1] + t2-t1
storage.elapsed[-2] = storage.elapsed[-2] + t3-t2
storage.elapsed[-3] = storage.elapsed[-3] + t4-t3
storage.elapsed[-4] = storage.elapsed[-4] + t5-t4
storage.elapsed[-5] = storage.elapsed[-5] + t6-t5
storage.elapsed[-6] = storage.elapsed[-6] + t7-t6
plot('sma_api', sma_api)
plot('sma_cumsum', sma_cumsum[-5])
plot('sma_pandas', sma_pandas[-10])
plot('sma_talib', sma_talib[-15])
plot('sma_convolve', sma_convolve[-20])
plot('sma_fftconvolve', sma_fftconvolve[-25])
def stop():
log('ticks....: %s' % info.max_ticks)
log('api......: %.5f' % storage.elapsed[-1])
log('cumsum...: %.5f' % storage.elapsed[-2])
log('pandas...: %.5f' % storage.elapsed[-3])
log('talib....: %.5f' % storage.elapsed[-4])
log('convolve.: %.5f' % storage.elapsed[-5])
log('fft......: %.5f' % storage.elapsed[-6])
結果:
[2015-01-31 23:00:00] ticks....: 744
[2015-01-31 23:00:00] api......: 0.16445
[2015-01-31 23:00:00] cumsum...: 0.03189
[2015-01-31 23:00:00] pandas...: 0.03677
[2015-01-31 23:00:00] talib....: 0.00700 # <<< Winner!
[2015-01-31 23:00:00] convolve.: 0.04871
[2015-01-31 23:00:00] fft......: 0.22306
すぐに使えるソリューションについては、 https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html を参照してください。それはflat
ウィンドウタイプで移動平均を提供します。データの最初と最後の問題をそれを反映することで処理しようとするので(これはあなたの場合にはうまくいくかもしれないし、うまくいかないかもしれないので)、これは単純な日曜大工の畳み込み法よりも少し複雑です。 ..)。
まず始めに、試すことができます:
a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)
私はこれが古い質問であることを知っています、しかしここに余分なデータ構造やライブラリを使わない解決策があります。それは入力リストの要素の数において線形であり、私はそれをより効率的にするための他の方法を考えることはできません(実際に誰かが結果を割り当てるより良い方法を知っているなら、教えてください)。
注:これはリストの代わりに派手な配列を使った方がはるかに速いでしょうが、私はすべての依存関係を排除したいと思いました。マルチスレッド実行によってパフォーマンスを向上させることも可能です。
この関数は入力リストが一次元であると仮定しているので注意してください。
### Running mean/Moving average
def running_mean(l, N):
sum = 0
result = list( 0 for x in l)
for i in range( 0, N ):
sum = sum + l[i]
result[i] = sum / (i+1)
for i in range( N, len(l) ):
sum = sum - l[i-N] + l[i]
result[i] = sum / N
return result
例
期間3のローリング平均を計算するリストdata = [ 1, 2, 3, 4, 5, 6 ]
があり、入力サイズと同じサイズの出力リストも必要であるとします(ほとんどの場合)。
最初の要素のインデックスは0なので、ローリング平均はインデックス-2、-1、および0の要素で計算する必要があります。明らかに、data [-2]とdata [-1]はありません(特殊を使用しない場合)。これはリストをゼロでパディングすることと同じですが、実際にはパディングを行わず、パディングを必要とするインデックス(0からN-1まで)を追跡するだけです。
そのため、最初のN個の要素については、アキュムレータで要素を足し合わせます。
result[0] = (0 + 0 + 1) / 3 = 0.333 == (sum + 1) / 3
result[1] = (0 + 1 + 2) / 3 = 1 == (sum + 2) / 3
result[2] = (1 + 2 + 3) / 3 = 2 == (sum + 3) / 3
要素N + 1から単純積算はうまくいきません。 result[3] = (2 + 3 + 4)/3 = 3
を期待していますが、これは(sum + 4)/3 = 3.333
とは異なります。
正しい値を計算する方法は、data[0] = 1
からsum+4
を引くことです。したがって、sum + 4 - 1 = 9
が得られます。
これは現在sum = data[0] + data[1] + data[2]
が原因で発生しますが、減算前はsum
がi >= N
であるため、すべてのdata[i-N] + ... + data[i-2] + data[i-1]
にも当てはまります。
(畳み込みの'valid'
領域に出力を制限するのではなく)入力の大きさを保つことが重要な場合は、 scipy.ndimage.filters.uniform_filter1d を使用できます。
import numpy as np
from scipy.ndimage.filters import uniform_filter1d
N = 1000
x = np.random.random(100000)
y = uniform_filter1d(x, size=N)
y.shape == x.shape
>>> True
uniform_filter1d
は'reflect'
がデフォルトである境界線を扱うための複数の方法を可能にします、しかし私の場合、私はむしろ'nearest'
を望みました。
それはまたかなり速いです(np.convolve
よりおよそ50倍速い):
%timeit y1 = np.convolve(x, np.ones((N,))/N, mode='same')
100 loops, best of 3: 9.28 ms per loop
%timeit y2 = uniform_filter1d(x, size=N)
10000 loops, best of 3: 191 µs per loop
私はこれがどれくらい速いかまだ確認していません、しかしあなたは試みることができます:
from collections import deque
cache = deque() # keep track of seen values
n = 10 # window size
A = xrange(100) # some dummy iterable
cum_sum = 0 # initialize cumulative sum
for t, val in enumerate(A, 1):
cache.append(val)
cum_sum += val
if t < n:
avg = cum_sum / float(t)
else: # if window is saturated,
cum_sum -= cache.popleft() # subtract oldest value
avg = cum_sum / float(n)
でこぼこや荒々しいのではなく、私はパンダがこれをより迅速に行うことをお勧めします:
df['data'].rolling(3).mean()
これは、列「データ」の3つの期間の移動平均(MA)を取ります。現在のセルを除外したもの(後ろに1つずらしたもの)は、次のように簡単に計算できます。
df['data'].shift(periods=1).rolling(3).mean()
パーティーには少し時間がかかりますが、私は自分自身の小さな関数を作りました。それは両端を覆い隠したりゼロで埋めたりすることはしません。その後、平均を見つけるのにも使われます。さらなる扱いとして、線形に間隔を置いた点で信号を再サンプリングすることもできます。他の機能を利用するために自由にコードをカスタマイズしてください。
この方法は、正規化ガウスカーネルを使った単純行列乗算です。
def running_mean(y_in, x_in, N_out=101, sigma=1):
'''
Returns running mean as a Bell-curve weighted average at evenly spaced
points. Does NOT wrap signal around, or pad with zeros.
Arguments:
y_in -- y values, the values to be smoothed and re-sampled
x_in -- x values for array
Keyword arguments:
N_out -- NoOf elements in resampled array.
sigma -- 'Width' of Bell-curve in units of param x .
'''
N_in = size(y_in)
# Gaussian kernel
x_out = np.linspace(np.min(x_in), np.max(x_in), N_out)
x_in_mesh, x_out_mesh = np.meshgrid(x_in, x_out)
gauss_kernel = np.exp(-np.square(x_in_mesh - x_out_mesh) / (2 * sigma**2))
# Normalize kernel, such that the sum is one along axis 1
normalization = np.tile(np.reshape(sum(gauss_kernel, axis=1), (N_out, 1)), (1, N_in))
gauss_kernel_normalized = gauss_kernel / normalization
# Perform running average as a linear operation
y_out = gauss_kernel_normalized @ y_in
return y_out, x_out
移動平均を求める別のアプローチ なし numpyを使用したパンダ
import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a,
enumerate(itertools.accumulate(sample), 1)))
印刷します[2.0、4.0、6.0、6.5、7.4、7.833333333333333]
この質問は、NeXuSが先月書いたときよりも古くなっています、しかし彼のコードがEdgeのケースをどう扱うかが好きです。ただし、これは「単純移動平均」であるため、その結果は適用されるデータよりも遅れます。 NumPyのモードvalid
、same
、full
よりも満足のいく方法でEdgeのケースを扱うことはconvolution()
ベースのメソッドに同様のアプローチを適用することによって達成できると思いました。
私の貢献は、結果を彼らのデータと一致させるために中央移動平均を使用しています。フルサイズのウィンドウを使用するのに使用できるポイントが少なすぎる場合、移動平均は配列の端にある連続して小さいウィンドウから計算されます。 [実際には、連続して大きくなるウィンドウからですが、これは実装の詳細です。]
import numpy as np
def running_mean(l, N):
# Also works for the(strictly invalid) cases when N is even.
if (N//2)*2 == N:
N = N - 1
front = np.zeros(N//2)
back = np.zeros(N//2)
for i in range(1, (N//2)*2, 2):
front[i//2] = np.convolve(l[:i], np.ones((i,))/i, mode = 'valid')
for i in range(1, (N//2)*2, 2):
back[i//2] = np.convolve(l[-i:], np.ones((i,))/i, mode = 'valid')
return np.concatenate([front, np.convolve(l, np.ones((N,))/N, mode = 'valid'), back[::-1]])
それはconvolve()
を使っているので比較的遅く、そして本当のPythonistaにはかなり跳ね上がる可能性がありますが、私はその考えが正しいと思います。
私はこれを ボトルネック を使ってエレガントに解決できると感じます。
下記の基本サンプルを参照してください。
import numpy as np
import bottleneck as bn
a = np.random.randint(4, 1000, size=100)
mm = bn.move_mean(a, window=5, min_count=1)
"mm"は "a"の移動平均です。
「ウィンドウ」は、移動平均のために考慮すべきエントリの最大数です。
「min_count」は、移動平均(例えば、最初のいくつかの要素について、またはアレイがナノ値を有する場合)について考慮すべきエントリの最小数である。
良い部分は、ボトルネックがnanの値を処理するのに役立ち、それはまた非常に効率的です。
この回答には、3つの異なるシナリオでPython標準ライブラリを使用した解決策が含まれています。
itertools.accumulate
による移動平均これは、itertools.accumulate
を利用することによって、反復可能な値の移動平均を計算する、メモリ効率の良いPython 3.2+ソリューションです。
>>> from itertools import accumulate
>>> values = range(100)
values
は、ジェネレータやその場で値を生成するその他のオブジェクトなど、任意の反復可能オブジェクトにすることができます。
まず、値の累積和を遅延的に構築します。
>>> cumu_sum = accumulate(value_stream)
次に、enumerate
(1から始まる)累積合計を計算し、累積値の分数と現在の列挙インデックスを生成するジェネレータを構築します。
>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))
一度にメモリ内のすべての値が必要な場合はmeans = list(rolling_avg)
を発行するか、next
を少しずつ呼び出してください。
(もちろん、for
ループを使用してrolling_avg
を反復することもできます。これにより、next
が暗黙的に呼び出されます。)
>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0
この解は以下のように関数として書くことができます。
from itertools import accumulate
def rolling_avg(iterable):
cumu_sum = accumulate(iterable)
yield from (accu/i for i, accu in enumerate(cumu_sum, 1))
このコルーチンはあなたがそれを送る値を消費し、これまでに見られた値の移動平均を保ちます。
反復可能な値を持っていなくても、プログラムの存続期間中に異なる時間に1つずつ平均化されるように値を取得すると便利です。
def rolling_avg_coro():
i = 0
total = 0.0
avg = None
while True:
next_value = yield avg
i += 1
total += next_value
avg = total/i
コルーチンは次のように機能します。
>>> averager = rolling_avg_coro() # instantiate coroutine
>>> next(averager) # get coroutine going (this is called priming)
>>>
>>> averager.send(5) # 5/1
>>> 5.0
>>> averager.send(3) # (5 + 3)/2
>>> 4.0
>>> print('doing something else...')
doing something else...
>>> averager.send(13) # (5 + 3 + 13)/3
>>> 7.0
N
のスライディングウィンドウにわたる平均の計算このジェネレータ関数はイテラブルとウィンドウサイズN
を取り、ウィンドウ内の現在の値の平均を求めます。これは deque
を使用します。これはリストに似たデータ構造ですが、両方のエンドポイントで高速変更(pop
、append
)に最適化されています.
from collections import deque
from itertools import islice
def sliding_avg(iterable, N):
it = iter(iterable)
window = deque(islice(it, N))
num_vals = len(window)
if num_vals < N:
msg = 'window size {} exceeds total number of values {}'
raise ValueError(msg.format(N, num_vals))
N = float(N) # force floating point division if using Python 2
s = sum(window)
while True:
yield s/N
try:
nxt = next(it)
except StopIteration:
break
s = s - window.popleft() + nxt
window.append(nxt)
これが実行中の関数です。
>>> values = range(100)
>>> N = 5
>>> window_avg = sliding_avg(values, N)
>>>
>>> next(window_avg) # (0 + 1 + 2 + 3 + 4)/5
>>> 2.0
>>> next(window_avg) # (1 + 2 + 3 + 4 + 5)/5
>>> 3.0
>>> next(window_avg) # (2 + 3 + 4 + 5 + 6)/5
>>> 4.0
移動平均の計算については、上記の多くの回答があります。私の答えは2つの追加機能を追加します。
この2番目の機能は、どの値が一般的な傾向と一定量異なるかを判断するのに特に役立ちます。
私はnumpy.cumsumを使っています。それは最も時間効率の良い方法だからです( 上記のAlleoの答えを見てください )。
N=10 # number of points to test on each side of point of interest, best if even
padded_x = np.insert(np.insert( np.insert(x, len(x), np.empty(int(N/2))*np.nan), 0, np.empty(int(N/2))*np.nan ),0,0)
n_nan = np.cumsum(np.isnan(padded_x))
cumsum = np.nancumsum(padded_x)
window_sum = cumsum[N+1:] - cumsum[:-(N+1)] - x # subtract value of interest from sum of all values within window
window_n_nan = n_nan[N+1:] - n_nan[:-(N+1)] - np.isnan(x)
window_n_values = (N - window_n_nan)
movavg = (window_sum) / (window_n_values)
このコードはNsだけで動作します。 padded_xとn_nanのnp.insertを変更することで、奇数に調整できます。
このコードは、cutoff = 3未満のnon-nan値から計算されたすべての移動平均値を削除するように簡単に適応できます。
window_n_values = (N - window_n_nan).astype(float) # dtype must be float to set some values to nan
cutoff = 3
window_n_values[window_n_values<cutoff] = np.nan
movavg = (window_sum) / (window_n_values)
Python標準ライブラリのみを使用する(メモリ効率が良い)
標準ライブラリdeque
のみを使用する別のバージョンを与えてください。ほとんどの答えがpandas
またはnumpy
を使っているのは私にとって非常に驚きです。
def moving_average(iterable, n=3):
d = deque(maxlen=n)
for i in iterable:
d.append(i)
if len(d) == n:
yield sum(d)/n
r = moving_average([40, 30, 50, 46, 39, 44])
assert list(r) == [40.0, 42.0, 45.0, 43.0]
実際に私はPythonのドキュメントで別の 実装を見つけました
def moving_average(iterable, n=3):
# moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
# http://en.wikipedia.org/wiki/Moving_average
it = iter(iterable)
d = deque(itertools.islice(it, n-1))
d.appendleft(0)
s = sum(d)
for elem in it:
s += elem - d.popleft()
d.append(elem)
yield s / n
しかし、実装は私が思うよりも少し複雑です。しかし、それは何らかの理由で標準のPythonドキュメントに含まれている必要があります。私の実装と標準ドキュメントについて誰かがコメントできますか?
mab によるコメントが、上記の 答え の1つに埋め込まれています。この方法があります。 bottleneck
は、単純な移動平均であるmove_mean
を持ちます。
import numpy as np
import bottleneck as bn
a = np.arange(10) + np.random.random(10)
mva = bn.move_mean(a, window=2, min_count=1)
min_count
は、基本的に配列のその時点までの移動平均を取るのに便利なパラメータです。 min_count
を設定しないと、window
と等しくなり、window
ポイントまでのすべてがnan
になります。
ここにこの質問に対する解決策がありますが、私の解決策を見てください。それはとてもシンプルでうまく機能しています。
import numpy as np
dataset = np.asarray([1, 2, 3, 4, 5, 6, 7])
ma = list()
window = 3
for t in range(0, len(dataset)):
if t+window <= len(dataset):
indices = range(t, t+window)
ma.append(np.average(np.take(dataset, indices)))
else:
ma = np.asarray(ma)
他の答えを読むことから、私はこの質問が求めていたことではないと思いますが、私はここで、サイズが大きくなっていった値のリストの移動平均を維持する必要がありました。
あなたがどこかから取得している値のリスト(サイト、測定装置など)と更新された最後のn
値の平均を保持したいのであれば、以下のコードを使用することができます。新しい要素
class Running_Average(object):
def __init__(self, buffer_size=10):
"""
Create a new Running_Average object.
This object allows the efficient calculation of the average of the last
`buffer_size` numbers added to it.
Examples
--------
>>> a = Running_Average(2)
>>> a.add(1)
>>> a.get()
1.0
>>> a.add(1) # there are two 1 in buffer
>>> a.get()
1.0
>>> a.add(2) # there's a 1 and a 2 in the buffer
>>> a.get()
1.5
>>> a.add(2)
>>> a.get() # now there's only two 2 in the buffer
2.0
"""
self._buffer_size = int(buffer_size) # make sure it's an int
self.reset()
def add(self, new):
"""
Add a new number to the buffer, or replaces the oldest one there.
"""
new = float(new) # make sure it's a float
n = len(self._buffer)
if n < self.buffer_size: # still have to had numbers to the buffer.
self._buffer.append(new)
if self._average != self._average: # ~ if isNaN().
self._average = new # no previous numbers, so it's new.
else:
self._average *= n # so it's only the sum of numbers.
self._average += new # add new number.
self._average /= (n+1) # divide by new number of numbers.
else: # buffer full, replace oldest value.
old = self._buffer[self._index] # the previous oldest number.
self._buffer[self._index] = new # replace with new one.
self._index += 1 # update the index and make sure it's...
self._index %= self.buffer_size # ... smaller than buffer_size.
self._average -= old/self.buffer_size # remove old one...
self._average += new/self.buffer_size # ...and add new one...
# ... weighted by the number of elements.
def __call__(self):
"""
Return the moving average value, for the lazy ones who don't want
to write .get .
"""
return self._average
def get(self):
"""
Return the moving average value.
"""
return self()
def reset(self):
"""
Reset the moving average.
If for some reason you don't want to just create a new one.
"""
self._buffer = [] # could use np.empty(self.buffer_size)...
self._index = 0 # and use this to keep track of how many numbers.
self._average = float('nan') # could use np.NaN .
def get_buffer_size(self):
"""
Return current buffer_size.
"""
return self._buffer_size
def set_buffer_size(self, buffer_size):
"""
>>> a = Running_Average(10)
>>> for i in range(15):
... a.add(i)
...
>>> a()
9.5
>>> a._buffer # should not access this!!
[10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]
Decreasing buffer size:
>>> a.buffer_size = 6
>>> a._buffer # should not access this!!
[9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
>>> a.buffer_size = 2
>>> a._buffer
[13.0, 14.0]
Increasing buffer size:
>>> a.buffer_size = 5
Warning: no older data available!
>>> a._buffer
[13.0, 14.0]
Keeping buffer size:
>>> a = Running_Average(10)
>>> for i in range(15):
... a.add(i)
...
>>> a()
9.5
>>> a._buffer # should not access this!!
[10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]
>>> a.buffer_size = 10 # reorders buffer!
>>> a._buffer
[5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
"""
buffer_size = int(buffer_size)
# order the buffer so index is zero again:
new_buffer = self._buffer[self._index:]
new_buffer.extend(self._buffer[:self._index])
self._index = 0
if self._buffer_size < buffer_size:
print('Warning: no older data available!') # should use Warnings!
else:
diff = self._buffer_size - buffer_size
print(diff)
new_buffer = new_buffer[diff:]
self._buffer_size = buffer_size
self._buffer = new_buffer
buffer_size = property(get_buffer_size, set_buffer_size)
そして、あなたはそれをテストすることができます、例えば:
def graph_test(N=200):
import matplotlib.pyplot as plt
values = list(range(N))
values_average_calculator = Running_Average(N/2)
values_averages = []
for value in values:
values_average_calculator.add(value)
values_averages.append(values_average_calculator())
fig, ax = plt.subplots(1, 1)
ax.plot(values, label='values')
ax.plot(values_averages, label='averages')
ax.grid()
ax.set_xlim(0, N)
ax.set_ylim(0, N)
fig.show()
これは、
教育目的のために、さらに2つのNumpyソリューションを追加してみましょう(これはcumsumソリューションよりも遅いです)。
import numpy as np
from numpy.lib.stride_tricks import as_strided
def ra_strides(arr, window):
''' Running average using as_strided'''
n = arr.shape[0] - window + 1
arr_strided = as_strided(arr, shape=[n, window], strides=2*arr.strides)
return arr_strided.mean(axis=1)
def ra_add(arr, window):
''' Running average using add.reduceat'''
n = arr.shape[0] - window + 1
indices = np.array([0, window]*n) + np.repeat(np.arange(n), 2)
arr = np.append(arr, 0)
return np.add.reduceat(arr, indices )[::2]/window
使用される機能: as_strided 、 add.reduceat
移動平均フィルタはどうですか。これはワンライナーでもあり、長方形以外のものが必要な場合はウィンドウタイプを簡単に操作できるという利点があります。配列のN倍長単純移動平均
lfilter(np.ones(N)/N, [1], a)[N:]
そして三角ウィンドウを適用して:
lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]
標準ライブラリとdequeを使用したもう1つの解決策:
from collections import deque
import itertools
def moving_average(iterable, n=3):
# http://en.wikipedia.org/wiki/Moving_average
it = iter(iterable)
# create an iterable object from input argument
d = deque(itertools.islice(it, n-1))
# create deque object by slicing iterable
d.appendleft(0)
s = sum(d)
for elem in it:
s += elem - d.popleft()
d.append(elem)
yield s / n
# example on how to use it
for i in moving_average([40, 30, 50, 46, 39, 44]):
print(i)
# 40.0
# 42.0
# 45.0
# 43.0