移動平均または移動平均


192

特定のウィンドウを指定して1D配列の移動平均を計算するPythonのSciPy関数またはNumPy関数またはモジュールはありますか?

回答:


24

依存関係なしで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)

44
速い?!このソリューションは、Numpyのソリューションよりも桁違いに遅いです。
Bart

3
このネイティブソリューションはクールですが、OPはnumpy / scipy関数を要求しました-おそらくそれらはかなり高速になるでしょう。
デミス

254

UPD:Alleojasaarimにより、より効率的なソリューションが提案されています。


あなたはそれのために使うことができます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ます。

エッジ

mode引数はnp.convolve、エッジの処理方法を指定します。私が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()

実行平均畳み込みモード


4
このソリューションは、クリーン(1行)で比較的効率的(numpy内で行われる作業)であるため、気に入っています。しかし、Alleoの「効率的なソリューション」を使用するnumpy.cumsumと、より複雑になります。
Ulrich Stern

2
@denfromufa、ドキュメントには実装が十分にカバーされていると思います。また、数学を説明するWikipediaにもリンクしています。質問の焦点を考えると、この答えはそれらをコピーする必要があると思いますか?
ラピス

@lapis移動平均のたたみ込みの使用法は非常に珍しく、自明ではありません。これが私が見つけた最高の視覚的説明です:matlabtricks.com/post-11/moving-average-by-convolution
denfromufa

プロットおよび関連するタスクの場合は、None値を入力すると便利です。私の(それほど美しくはないが短い)提案: `` `def moving_average(x、N、fill = True):return np.concatenate([x for x in [[None] *(N // 2 + N%2) * fill、np.convolve(x、np.ones((N、))/ N、mode = 'valid')、[None] *(N // 2)* fill、] if len(x)]) ` ``コードがSOコメントxDで見苦しいように見える非常に多くあるので、別の回答を追加したくありませんでしたが、それをコピーしてIDEに貼り付けるだけかもしれません。
Chaoste

143

効率的なソリューション

たたみ込みは単純なアプローチよりもはるかに優れていますが、(おそらく)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が大きいほど、時間差が大きくなります。

警告:cumsumの方が高速ですが、浮動小数点エラーが増加し、結果が無効/不適切/許容できないものになる可能性があります

コメントはこの浮動小数点エラーの問題をここで指摘しましたが、私は答えでそれをより明白にしています。

# demonstrate loss of precision with only 100,000 points
np.random.seed(42)
x = np.random.randn(100000)+1e6
y1 = running_mean_convolve(x, 10)
y2 = running_mean_cumsum(x, 10)
assert np.allclose(y1, y2, rtol=1e-12, atol=0)
  • 蓄積するポイントが多いほど、浮動小数点エラーが大きくなります(1e5ポイントが顕著で、1e6ポイントがより重要で、1e6よりも多く、アキュムレータをリセットする必要がある場合があります)
  • を使用してカンニングすることができnp.longdoubleますが、浮動小数点エラーは比較的多数のポイント(1e5を超えますが、データに依存します)でも重大になります
  • あなたはエラーをプロットし、それが比較的速く増加するのを見ることができます
  • 畳み込み解は遅いですが、この浮動小数点の精度の損失はありません
  • uniform_filter1dソリューションは、このcumsumソリューションよりも高速であり、この浮動小数点の精度の損失はありません

3
素敵な解決策!私の直感はnumpy.convolveO(mn)です。そのドキュメントscipy.signal.fftconvolveはFFT を使用することを述べています。
Ulrich Stern

3
このメソッドは配列のエッジを処理しませんか?
JoVe 2016

6
良い解決策ですが、配列の終わりに近づくと、2つの大きな数値を減算して小さな結果を得る可能性があるため、大きな配列では数値エラーが発生する可能性があることに注意してください。
Bas Swinckels 2017年

1
これは、浮動小数点除算の代わりに整数除算を使用しrunning_mean([1,2,3], 2)ますarray([1, 2])。交換x[float(value) for value in x]トリックを行います。
ChrisW 2017年

4
このソリューションの数値的な安定性は、x浮動小数点を含む場合に問題になる可能性があります。例:期待どおりにrunning_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2戻ります。詳細:en.wikipedia.org/wiki/Loss_of_significance0.0031250.0
ミラノ

79

更新:以下の例は、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配列も返します。

rolling_meanカスタムの純粋なPython実装でパフォーマンスを打ち負かすことは困難です。以下は、提案された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

エッジ値の扱い方についても素晴らしいオプションがあります。


6
Pandas rolling_meanは、この仕事に適したツールですが、ndarraysでは非推奨となっています。将来のPandasリリースでは、Pandasシリーズでのみ機能します。現在、Pandas以外の配列データはどこにありますか?
マイク

5
@Mike rolling_mean()は非推奨になりましたが、ローリングと平均を別々に使用できるようになりましたdf.rolling(windowsize).mean()。代わりに機能します(すぐに追加する可能性があります)。6,000行の場合、シリーズ%timeit test1.rolling(20).mean()1000ループを
Vlox

5
@Vlox df.rolling()は十分に機能しますが、問題は、このフォームでも将来的にndarrayをサポートしないことです。これを使用するには、最初にデータをPandas Dataframeにロードする必要があります。この機能がnumpyまたはに追加されてみたいscipy.signalです。
マイク

1
@マイクは完全に同意します。特に自分の配列のパンダの.ewm()。mean()速度に一致させるために特に苦労しています(最初にdfにロードする必要はありません)。つまり、高速であることは素晴らしいことですが、データフレームに頻繁に出入りするのが少し不格好だと感じています。
Vlox 2017年

6
%timeit bottleneck.move_mean(x, N)私のPCのcumsumおよびpandasメソッドより3〜15倍高速です。リポジトリのREADMEでベンチマークを確認してください。
mab 2017

50

次の方法で移動平均を計算できます。

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には、速度を上げるために使用できるたたみ込み関数が含まれています。移動平均は、すべてのメンバーがに等しい、長いxベクトルでのたたみ込みに相当N1/Nます。convolveの派手な実装には、開始過渡が含まれるため、最初のN-1ポイントを削除する必要があります。

def runningMeanFast(x, N):
    return np.convolve(x, np.ones((N,))/N)[(N-1):]

私のマシンでは、入力ベクトルの長さと平均化ウィンドウのサイズに応じて、高速バージョンは20〜30倍高速です。

convolveには'same'、開始時の一時的な問題に対処する必要があるように見えるモードが含まれていますが、開始と終了の間で分割することに注意してください。


最初のN-1ポイントを削除しても、最後のポイントに境界効果が残ることに注意してください。問題を解決する簡単な方法は、使用することですmode='valid'convolve任意の後処理を必要としません。
ラピス

1
@Psycho- mode='valid'両端からトランジェントを削除しますよね?との場合len(x)=10N=4現在の平均では10件の結果が必要ですvalidが7を返します
mtrw

1
トランジェントを最後から削除し、最初にはトランジェントがありません。まあ、それは優先順位の問題だと思います。データにないゼロへの勾配を取得することを犠牲にして、同じ数の結果は必要ありません。ところで、ここにモード間の違いを表示するコマンドがあります:modes = ('full', 'same', 'valid'); [plot(convolve(ones((200,)), ones((50,))/50, mode=m)) for m in modes]; axis([-10, 251, -.1, 1.1]); legend(modes, loc='lower center')(pyplotとnumpyをインポートした場合)。
ラピス

runningMean配列のx[ctr:(ctr+N)]右側を使用して配列から出るときに、ゼロで平均化するという副作用があります。
mrgloom

runningMeanFastこの境界効果の問題もあります。
mrgloom 2018年

21

または計算する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

ここに画像の説明を入力してください


NameError: name 'info' is not defined。このエラーが発生しています。
Md。Rezwanul Haque

1
平滑化後に時系列がずれているように見えますが、望ましい効果ですか?
mrgloom 2018年

@mrgloomはい、視覚化の目的で。それ以外の場合は、チャート上で1本の線として表示されます。Md。Rezwanul Haqueでは、PAIRとinfoへのすべての参照を削除できます。これらは、現在は機能しなくなったtradewave.netの内部サンドボックスメソッドでした
litepresence

21

すぐに使用できるソリューションについては、https://scipy-cookbook.readthedocs.io/items/SignalSmooth.htmlを参照してください。これは、flatウィンドウタイプの移動平均を提供します。これは、単純なdo-it-yourself convolve-methodよりも少し洗練されていることに注意してください。これは、データの最初と最後でそれを反映することによって問題を処理しようとするためです(場合によっては機能しない場合もあります)。 ..)。

最初に、あなたは試すことができます:

a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)

1
このメソッドはnumpy.convolve、シーケンスの変更のみが異なるに依存しています。
Alleo

10
入力と出力の両方が同じ性質である場合(たとえば、両方の時間信号)、入力信号とは異なる形状の出力信号を返す信号処理機能にいつも悩まされています。これは、関連する独立変数(例:時間、周波数)との対応を壊し、プロットまたは比較を直接の問題にしない...とにかく、感じを共有する場合、提案された関数の最後の行をy = npとして変更することができます。 .convolve(w / w.sum()、s、mode = 'same'); return y [window_len-1 :-( window_len-1)]
クリスチャンオライリー

@ ChristianO'Reilly、それを別の回答として投稿する必要があります-プロットなどのために平滑化されたデータの長さに一致する必要のある他の2つの配列があるため、それはまさに私が探していたものです。正確にどのようにしたか- wウィンドウサイズとsデータですか?
Demis

@Demis喜んでコメントが役に立った。numpy たたみ込み関数の詳細については、ここdocs.scipy.org/doc/numpy-1.15.0/reference/generated/…たたみ込み関数(en.wikipedia.org/wiki/Convolution)が2つの信号を互いにたたみ込みます。この場合、それはあなたの信号を正規化された(すなわち単一領域)ウィンドウ(w / w.sum())でたたみ込みます。
クリスチャンオライリー

18

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)

uniform_filter1d

  • 同じでこぼこの形状(つまり、ポイント数)で出力を与えます
  • 'reflect'デフォルトである境界線を処理するための複数の方法を許可しますが、私の場合、私はむしろ望んだ'nearest'

また、かなり高速です(上記の累積合計アプローチよりも約50倍np.convolve、2〜5倍高速です)。

%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

さまざまな実装のエラー/速度を比較できる3つの関数を次に示します。

from __future__ import division
import numpy as np
import scipy.ndimage.filters as ndif
def running_mean_convolve(x, N):
    return np.convolve(x, np.ones(N) / float(N), 'valid')
def running_mean_cumsum(x, N):
    cumsum = np.cumsum(np.insert(x, 0, 0))
    return (cumsum[N:] - cumsum[:-N]) / float(N)
def running_mean_uniform_filter1d(x, N):
    return ndif.uniform_filter1d(x, N, mode='constant', origin=-(N//2))[:-(N-1)]

1
これは、境界線の問題を考慮しているように思われる唯一の答えです(特にプロットする場合は、かなり重要です)。ありがとうございました!
ガブリエル

1
私は長方形でプロファイリングしuniform_filter1d、続いて。私の結果:(1.)畳み込みが最も遅い。(2.)合計/減算は約20〜30倍高速です。(3.)uniform_filter1dは、cumsum / subtractよりも約2〜3倍高速です。勝者は間違いなくuniform_filter1dです。np.convolvenp.cumsumnp.subtract
Trevor Boyd Smith

使用uniform_filter1dソリューションよりも高速ですcumsum(約2〜5倍)。そして、uniform_filter1d のような大規模な浮動小数点エラーを取得していないcumsum解決策がありません。
Trevor Boyd Smith

15

私はこれが古い質問であることを知っていますが、ここに余分なデータ構造またはライブラリを使用しないソリューションがあります。入力リストの要素の数は線形であり、それをより効率的にする他の方法は考えられません(実際に結果を割り当てるためのより良い方法を誰かが知っている場合は、私に知らせてください)。

注:リストの代わりにnumpy配列を使用すると、これははるかに速くなりますが、すべての依存関係を排除する必要がありました。マルチスレッド実行によりパフォーマンスを向上させることも可能です

この関数は、入力リストが1次元であることを前提としているため、注意が必要です。

### 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

私たちは、リストがあるとdata = [ 1, 2, 3, 4, 5, 6 ]私たちは3の期間とローリング平均を計算したいが、あなたはまた、(最もよくあるケースです)入力1の同じサイズで出力リストを望んでいること。

最初の要素のインデックスは0なので、ローリング平均はインデックス-2、-1、0の要素で計算する必要があります。明らかに、data [-2]とdata [-1]はありません(特別な境界条件)、したがって、これらの要素は0であると想定します。これは、実際にパディングしないことを除いて、リストのゼロパディングと同等です。パディングが必要なインデックス(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]が、それはすべてのためにも真実であるi >= N、減算の前に、あるためsumですdata[i-N] + ... + data[i-2] + data[i-1]


12

これはボトルネックを使用してエレガントに解決できると思います

以下の基本的なサンプルを参照してください。

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値がある場合)について検討するエントリの最小数です。

良い点は、ボトルネックがナンの値を処理するのに役立ち、非常に効率的でもあることです。


このlibは本当に高速です。純粋なPython移動平均関数は低速です。BootleneckはPyDataライブラリーであり、安定しており、Pythonコミュニティから継続的なサポートを得ることができると思います。
GoingMyWay

6

これがどれほど速いかはまだ確認していませんが、試すことができます。

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)

1
これは私がやろうとしていたことです。なぜこれが悪い方法なのか、誰でも批評できますか?
staggart 2015

1
この単純なpythonソリューションは、numpyを必要とせずにうまく機能しました。私はそれを再利用のためにクラスにまとめました。
Matthew Tschiegg

6

この回答には、3つの異なるシナリオでPython 標準ライブラリを使用するソリューションが含まれています。


移動平均 itertools.accumulate

これは、を活用して反復可能な値の移動平均を計算する、メモリ効率の高いPython 3.2+ソリューションitertools.accumulateです。

>>> 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場合、またはインクリメンタルに呼び出す場合に発行できます。
(もちろん、暗黙的に呼び出さrolling_avgれるforループで反復することもできます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リストに似たデータ構造であるを使用しますが、両方のエンドポイントでの高速変更(popappend)に最適化されています

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

5

パーティーには少し遅れましたが、端をラップしない、またはゼロでパッドを埋めない独自の小さな関数を作成しました。これを使用して、平均を求めます。さらなる扱いとして、線形間隔のポイントで信号を再サンプリングすることもあります。他の機能を得るために、コードを自由にカスタマイズしてください。

この方法は、正規化されたガウスカーネルを使用した単純な行列乗算です。

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

正規分布ノイズが追加された正弦波信号の簡単な使用法: ここに画像の説明を入力してください


これは私には使えません(python 3.6)。 1という名前の関数がありませんsum使用して、np.sum代わりに2@オペレータ(何であるか全く分からない)エラーがスローされますが。後で調べるかもしれませんが、今は時間が足りません
バスティアン

これ@は、np.matmulを実装する行列乗算演算子です。あなたのy_in配列が問題のある配列であるかどうかを確認してください。
xyzzyqed 2018

5

numpyやscipyの代わりに、パンダにこれをより迅速に行うことをお勧めします:

df['data'].rolling(3).mean()

これは、列「データ」の3つの期間の移動平均(MA)をとります。また、シフトされたバージョンを計算することもできます。たとえば、現在のセルを除外したもの(1つ後ろにシフトされたもの)は、次のように簡単に計算できます。

df['data'].shift(periods=1).rolling(3).mean()

これは2016年に提案されたソリューションとどのように異なりますか?
T氏

2
2016年に提案されたソリューションはpandas.rolling_mean、私が使用しているときに使用しpandas.DataFrame.rollingます。移動min(), max(), sum()などもmean()簡単に計算できます。
Gursel Karacor

前者では、pandas.rolling_min, pandas.rolling_maxetcのような別の方法を使用する必要があります。
Gursel Karacor

4

この方法を持つ上記の回答の 1つに埋め込まれたmabによるコメントがあります。 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はに等しくなりwindowwindowポイントまでのすべてがnan


3

numpy、panda 使用せずに移動平均を見つける別のアプローチ

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]


itertools.accumulateはpython 2.7には存在しませんが、python 3.4には存在します
grayaii

3

この質問は今、NeXuSが先月それを書いたときよりもさらに古くなっていますが、私は彼のコードがエッジケースをどのように扱うかが好きです。ただし、これは「単純移動平均」であるため、その結果は、適用するデータよりも遅れています。私はnumpyののモードよりも満足な方法でエッジケースを扱うことを考えてvalidsamefullにも同様のアプローチを適用することによって、達成することができた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によってかなりの速度で増加する可能性がありますが、この考えは有効だと思います。


3

移動平均の計算については、上記の多くの答えがあります。私の答えは、2つの追加機能を追加します。

  1. ナンの値を無視します
  2. 対象の値自体を含まないN個の隣接値の平均を計算します

この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を変更することで、奇数に調整できます。

出力例(未加工の黒、movavgの青): 各値の周囲の10ポイントの生データ(黒)と移動平均(青)。その値は含まれません。 nan値は無視されます。

このコードは、カットオフ未満の3つの非ナン値から計算されたすべての移動平均値を削除するように簡単に調整できます。

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)

生データ(黒)と移動平均(青)で、non-nan値が3未満のウィンドウは無視されます。


2

Python標準ライブラリのみを使用(メモリ効率が良い)

標準ライブラリdequeのみを使用する別のバージョンを指定してください。ほとんどの回答がpandasor を使用していることは、私にとってかなり驚きです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 docsに別の実装を見つけました

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ドキュメントになければなりません、誰かが私の実装と標準のドキュメントについてコメントできますか?


2
反復ごとにウィンドウメン​​バーを合計し続ける大きな違いの1つは、合計を効率的に更新することです(1つのメンバーを削除して、別のメンバーを追加します)。複雑さの点であなたがやっている O(n*d) 計算(dウィンドウのサイズで、n反復可能なの大きさ)を、彼らはやっているO(n)
Iftah

@イフタ、いいね、説明ありがとう、あなたは正しい。
MaThMaX

2

@Aikudeの変数を使用して、ワンライナーを作成しました。

import numpy as np

mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3

mean = [np.mean(mylist[x:x+N]) for x in range(len(mylist)-N+1)]
print(mean)

>>> [2.0, 3.0, 4.0, 5.0, 6.0]

1

この質問に対する解決策はここにありますが、私の解決策を見てください。それは非常にシンプルでうまく機能しています。

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)

1

他の答えを読んで、これは質問が求めたものだとは思いませんが、サイズが大きくなった値のリストの移動平均を維持する必要があるため、ここに行きました。

したがって、どこか(サイト、測定デバイスなど)から取得している値のリストと、最後に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()

それは与える:

値と値の関数としての平均値#


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

1

教育目的のために、さらに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_stridedadd.reduceat


1

前述のソリューションはすべて欠けているため、貧弱です

  • 派手なベクトル化された実装ではなく、ネイティブPythonによる速度、
  • の不十分な使用による数値の安定性numpy.cumsum、または
  • O(len(x) * w)畳み込みとしての実装による速度。

与えられた

import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000

x_[:w].sum()等しいことに注意してくださいx[:w-1].sum()。したがって、最初の平均では、numpy.cumsum(...)加算x[w] / w(を介してx_[w+1] / w)と減算0(からx_[0] / w)を行います。これはx[0:w].mean()

合計を介して、さらに加算x[w+1] / wおよび減算することにより2番目の平均を更新しx[0] / w、結果としてになりx[1:w+1].mean()ます。

これx[-w:].mean()は到達するまで続きます。

x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w

このソリューションは、ベクトル化され、O(m)読みやすく、数値的に安定しています。


1

どの程度移動平均フィルタ?これはワンライナーでもあり、長方形以外のものが必要な場合にウィンドウタイプを簡単に操作できるという利点があります。配列のNロングの単純移動平均a:

lfilter(np.ones(N)/N, [1], a)[N:]

そして、三角形のウィンドウが適用されています:

lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]

注:通常は最初のN個のサンプルを偽物として破棄するため[N:]、最後に破棄しますが、これは必須ではなく、個人的な選択の問題のみです。


-7

既存のライブラリを使用するのではなく、独自にロールすることを選択した場合は、浮動小数点エラーに注意して、その影響を最小限に抑えてください。

class SumAccumulator:
    def __init__(self):
        self.values = [0]
        self.count = 0

    def add( self, val ):
        self.values.append( val )
        self.count = self.count + 1
        i = self.count
        while i & 0x01:
            i = i >> 1
            v0 = self.values.pop()
            v1 = self.values.pop()
            self.values.append( v0 + v1 )

    def get_total(self):
        return sum( reversed(self.values) )

    def get_size( self ):
        return self.count

すべての値がほぼ同じ大きさである場合、これは常にほぼ同じ大きさの値を追加することで精度を維持するのに役立ちます。


15
これはひどく不明確な答えです。少なくともコード内のコメントまたはこれが浮動小数点エラーに役立つ理由の説明がいいでしょう。
Gabe 2014

私の最後の文では、浮動小数点エラーを改善する理由を示しようとしました。2つの値の大きさがほぼ同じである場合、非常に大きな数を非常に小さな値に追加した場合よりも、それらを追加すると精度が低下します。このコードは、浮動小数点エラーを最小限に抑えるために、中間の合計でも常に適度に大きさが近くなるように、「隣接する」値を組み合わせます。間違いのないものは何もありませんが、この方法により、実稼働環境での実装が不十分ないくつかのプロジェクトを節約できました。
Mayur Patel 14

1.元の問題に適用されると、これはひどく遅くなる(計算平均)ので、これは単に無関係です。等しい数。
Alleo

@Alleo:値ごとに1つの加算を行う代わりに、2つの加算を行います。証明はビットフリッピング問題と同じです。ただし、この回答のポイントは必ずしもパフォーマンスではなく、精度です。64ビット値を平均化するためのメモリ使用量は、キャッシュ内の64要素を超えないため、メモリ使用量にも適しています。
Mayur Patel 14

はい、これは単純な合計よりも2倍多くの演算を必要とすることは正しいですが、元の問題は合計だけでなく計算実行平均です。これはO(n)で実行できますが、答えはO(mn)を必要とします。ここで、mはウィンドウのサイズです。
Alleo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.