指数移動平均(ema)について考察する

指数移動平均(Exponential Moving Average 以下 ema)の仕組みや動作がよくわからないので、計算式をPythonで実装し、関数に適用してみます。
emaの計算式は以下のWikipediaのページを参考にしました。

https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average



これによると、emaの式は以下のように表されるようです。
SとYをtの関数とし、SをYのemaとすると以下のように表せます。

・t = 1 のとき
 S(t) =Y(1)

・t > 1 のとき
 S(t) = a * Y(t) + (1 - a) * S(t-1)

a の取りうる値は 0.0 〜 1.0 で、
ema は a の値が 1.0 のとき現在の Y(t) の値そのものに、 0.0 のとき Y(0) の値そのものになります。

ここでは、 a の値を変化させたときの ema の値を見てみます。さらに、過去のデータを直近のものに限定した場合のemaの値も見てみます。

ソースコード

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

def St_a(y):
    return y

def St_b(alpha, St_minus, Yt):
    return alpha * Yt + (1.0 - alpha) * St_minus

def S(alpha, y):
    ret = []
    for i in range(150):
        if i==0:
            ret.append(St_a(y[i]))
        else:
            ret.append(St_b(alpha, ret[i-1], y[i]))
    return ret

N = 3
alpha_0 = 0.5
x = np.arange(150)
y = 2*np.sin(N*2*np.pi/150*x) + np.random.randn(150)
s = S(alpha_0, y)

fig, (ax, ax_slider_1, ax_slider_2) = plt.subplots(3, 1, gridspec_kw = {'height_ratios':[14, 1, 1]})
ax.plot(x, y, linestyle='--')
ax.plot(x, 2*np.sin(N*2*np.pi/150*x), linestyle=':')
ax_s, = ax.plot(x, s)

sldr_1 = Slider(ax_slider_1, 'alpha', 0.0, 1.0, valinit=alpha_0)
sldr_2 = Slider(ax_slider_2, 'start_index', 0, 149, valinit=0, valfmt="%d")

def update_s(alpha_val):
    ax_s.set_xdata(x)
    ax_s.set_ydata(S(alpha_val, y))
    sldr_2.set_val(0)
    fig.canvas.draw_idle()

def update_s_start(s_start_index):
    s_start_index = int(s_start_index)
    tmp_y = y.copy()
    tmp_y[:s_start_index] = tmp_y[s_start_index]
    ax_s.set_xdata(x[s_start_index:])
    ax_s.set_ydata(S(sldr_1.val, tmp_y)[s_start_index:])
    fig.canvas.draw_idle()

sldr_1.on_changed(update_s)
sldr_2.on_changed(update_s_start)
    
plt.show()


実行結果:

f:id:keimina:20180825220145g:plain

xが横軸, yが縦軸です。

オレンジ色の点線はSin波を表しています。
ema を適用する前の オリジナルの関数は,このオレンジ色のSin波にランダムな値を加えたもので、青色の点線です。
それに ema を適用したのが緑色の線になります。

グラフの下にある スライダーの alpha が 数式の a を表しています。
スライダーの start_index は ema の初期値を表しています。このスライダーは過去のデータを直近のものに限定するために使用します。

まず alpha を動かして見ます。
・a = 0 のときは ema はx軸に関係なく常に初期値を維持し続けます。
・a = 1.0 のときは、青い点線と緑色の線が重なりますので、オリジナルの関数と同じになることがわかります。

次に start_index を動かしてみます。
・a = 0.0 にして start_index をプラス方向に動かすと緑色の線がy軸方向に大きく変動することがわかります。
・a = 1.0 にして start_index をプラス方向に動かしても緑色の線がy軸方向に変動することはないことがわかります。
・a = 0.4 にして start_index をプラス方向に動かすと緑色の線がy軸方向に少しだけ変動します。
・a = 0.05 にして start_index をプラス方向に動かすと緑色の線がy軸方向に大きく変動します。

このことから、 a が 1.0 に近ければ 、大雑把にいうと単純移動平均のように動作することがわかりました。
また、start_index を動かした時のグラフの変化を見ると、データ量が少ない場合はa が0に近いと初期値の影響を大きく受けてしまうため、ノイズの除去をして低周波成分を見たいときなどにはデータ量が必要だということがわかりました。そのためema を 単純移動平均と同じような場面で使うときには a を 1.0 に近付けて使うのが良いということがわかりました。emaは同一のシンプルなアルゴリズムでデータ数に関係なく過去の全ての情報を含む値を算出することができるので便利だと思いました。

以上。