web-dev-qa-db-ja.com

NumPyを使用して移動平均を計算する方法は?

Numpy/scipyの移動平均を単純に計算する関数はないようで、 畳み込み解 になります。

私の質問は2つあります。

  • Numpyで移動平均を(正しく)実装する最も簡単な方法は何ですか?
  • これは簡単ではなく、エラーが発生しやすいように思えるので、この場合 バッテリーを含む を持たない正当な理由はありますか?
79
goncalopp

単純な非加重移動平均が必要な場合は、np.cumsumを使用して簡単に実装できます。 多分 is FFTベースの方法よりも高速:

EDITコード内でBeanによって検出された1つずつ間違ったインデックスを修正しました。 編集

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はすでに特殊な機能を備えて少し肥大化しています。

130
Jaime

特定のドメイン固有の機能がNumPyにないのは、おそらく、コアチームの規律とNumPyの主なディレクティブに対する忠実性によるものです:N次元配列型を提供し、作成するための関数、それらの配列のインデックス付け。多くの基本的な目標と同様に、これも小さくはなく、NumPyは見事に実行します。

(はるかに)大きいSciPyには、ドメイン固有のライブラリ(subpackagesby SciPy devs)---たとえば、数値最適化(optimize)、信号処理(signal)、および積分計算(integrate)。

私の推測では、あなたが求めている関数は少なくとも1つのSciPyサブパッケージ(おそらくscipy.signal)にあります。ただし、SciPy scikitsのコレクションを最初に見て、関連するscikitを識別します目的の機能を探します。

Scikits はNumPy/SciPyに基づいて個別に開発されたパッケージであり、特定の技術分野に向けられています(例:scikits-image scikits-learnなど)これらのいくつかは(特に、数値最適化のためのすごい OpenOpt )は、比較的新しいscikitsルーブリック。上記のようなScikitsホームページには、約30のscikitsがリストされていますが、それらの少なくともいくつかは現在活発に開発されていません。

このアドバイスに従うと、scikits-timeseries;ただし、そのパッケージは現在活発に開発されていません。実際には、 Pandas は、知る限り、de factoNumPyベースの時系列ライブラリ。

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)

ここで、関数rolling_meanを呼び出して、Seriesオブジェクトとwindow sizeを渡します。以下の例は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は、約1ダースほどの関数とともに、Pandasドキュメントのルーブリックmoving window関数の下に非公式にグループ化されています。 Pandasの2番目の関連する関数グループは、指数加重関数と呼ばれます(たとえば、指数移動加重平均を計算するewma)。この2番目のグループが最初の(移動ウィンドウ関数)に含まれていないという事実は、おそらく指数加重変換が固定長ウィンドウに依存していないためです。

72
doug

これを実現する簡単な方法は、 np.convolve を使用することです。この背後にある考え方は、 discrete convolution の計算方法を活用し、それを使用して ローリング平均。これは、希望するスライディングウィンドウの長さに等しい長さの np.ones のシーケンスで畳み込むことで実行できます。

そのためには、次の関数を定義できます。

def moving_average(x, w):
    return np.convolve(x, np.ones(w), 'valid') / w

この関数は、シーケンスxと長さwのシーケンスの畳み込みを取ります。選択されたmodevalidであることに注意してください。したがって、畳み込み積は、シーケンスが完全にオーバーラップするポイントに対してのみ与えられます。


使用事例

いくつかの例:

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の配列と現在の積の間の内積をとることです 。この場合、シーケンスのsumを直接取得しているので、np.ones(w)による乗算は不要です。

以下は、最初の出力がどのように計算されるかの例であり、少しわかりやすくなっています。 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

など、すべてのオーバーラップが実行されると、シーケンスの移動平均を返します。

22
yatu

Pandasを使用したこの回答は、rolling_meanがPandasの一部ではなくなったため、上から適応されています

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

これは 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値がある場合)を検討するエントリの最小数です。

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

3
Anthony Anyanwu

エッジの条件を注意深く管理したい場合(エッジで利用可能な要素のみから平均を計算)、次の関数がトリックを行います。

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])
1
Peixiang Zhong

私は実際、受け入れられた答えとは少し異なる振る舞いを望んでいました。 sklearnパイプライン用の移動平均フィーチャエクストラクターを構築していたため、移動平均の出力が入力と同じ次元を持つ必要がありました。私が望むのは、移動平均がシリーズが一定であると仮定することです。つまり、ウィンドウ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

そしてアレイ用

def moving_average_array(X, n):
  z2 = np.cumsum(np.pad(X, (n,0), 'constant', constant_values=0))
  z1 = np.cumsum(np.pad(X, (0,n), 'constant', constant_values=X[-1]))
  return (z1-z2)[(n-1):-1]/n

もちろん、パディングに定数値を仮定する必要はありませんが、そうすることはほとんどの場合適切です。

0
cbartondock