snovaのブログ

プログラミングとか、日常のこととか、アウトプットしたほうがよいと聞いたので

matplotlibでグラフのスムージング



【スポンサーリンク】



イントロダクション

gnuplotではスムージングという機能があります。
pythonのグラフ描画ライブラリのmatplotlibには、残念ながら、直接スムージングできる機能はありません。
そのため、numpyやscipyを使って、実現する必要があります。

線形補間

matplotlibの設定を変更しなかったら、グラフは線形補間(linear interpolation)されます。
いわゆる、折れ線グラフになります。
以下はプログラム

# -*- coding: utf-8 -*- #

import numpy as np
import matplotlib.pyplot as plt

def make_func(in_x):
    np.random.seed(0)
    out_y = np.exp(-in_x) + 0.4*(np.random.rand(in_x.size) - 0.5) # exp(-x)の式にランダムな誤差を入れる
    return out_y

def main():
    x1 = np.linspace(0, 10, 10)
    y1 = make_func(x1)

    plt.plot(x1, y1, color='b', label='linear', alpha=0.7)
    plt.legend()
    plt.show()

if __name__ == '__main__':
    main()

結果

f:id:snova301:20181007134712p:plain

スプライン補間

離散データを非線形補間する方法として、スプライン補間(spline interpolation)というものがあります。
これは離散データを区間ごとに分けて、それぞれを多項式で補間することです。
代表的なスプライン補間は、3次のスプライン補間(cubic spline)です。
詳しくはこちらのサイトを見てください。
以下はプログラム

# -*- coding: utf-8 -*- #

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d # scipyのモジュールを使う

def make_func(in_x):
    np.random.seed(0)
    out_y = np.exp(-in_x) + 0.4*(np.random.rand(in_x.size) - 0.5) # exp(-x)の式にランダムな誤差を入れる
    return out_y

def spline_interp(in_x, in_y):
    out_x = np.linspace(np.min(in_x), np.max(in_x), np.size(in_x)*100) # もとのxの個数より多いxを用意
    func_spline = interp1d(in_x, in_y, kind='cubic') # cubicは3次のスプライン曲線
    out_y = func_spline(out_x) # func_splineはscipyオリジナルの型

    return out_x, out_y

def main():
    x1 = np.linspace(0, 10, 10)
    y1 = make_func(x1)

    x2, y2 = spline_interp(x1, y1)

    plt.plot(x1, y1, color='b', label='linear', alpha=0.7)
    plt.plot(x2, y2, color='r', label='spline', alpha=0.7)
    plt.legend()
    plt.show()

if __name__ == '__main__':
    main()

結果

f:id:snova301:20181007134714p:plain

移動平均

時系列データを扱う場合、スプライン補間だけではなく、移動平均(moving average)も使われます。
移動平均とは、一定区間ごとに平均値を出すことです。
移動平均はフィルターや深層学習で有名な畳込みニューラルネット(convolutional neural network)に応用されています。
こちらや、こちらに詳しい説明があります。

プログラム上ではnumpyのconvolve関数を使っています。
今回は重みが平等になるように設定しています。
convolve関数の使い方については、公式を見てください。
3項で移動平均したときのプログラムは以下

# -*- coding: utf-8 -*- #

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d # scipyのモジュールを使う

def make_func(in_x):
    np.random.seed(0)
    out_y = np.exp(-in_x) + 0.4*(np.random.rand(in_x.size) - 0.5) # exp(-x)の式にランダムな誤差を入れる
    return out_y

def spline_interp(in_x, in_y):
    out_x = np.linspace(np.min(in_x), np.max(in_x), np.size(in_x)*100) # もとのxの個数より多いxを用意
    func_spline = interp1d(in_x, in_y, kind='cubic') # cubicは3次のスプライン曲線
    out_y = func_spline(out_x) # func_splineはscipyオリジナルの型

    return out_x, out_y

def moving_avg(in_x, in_y):
    np_y_conv = np.convolve(in_y, np.ones(3)/float(3), mode='valid') # 畳み込む
    out_x_dat = np.linspace(np.min(in_x), np.max(in_x), np.size(np_y_conv))

    return out_x_dat, np_y_conv

def main():
    x1 = np.linspace(0, 10, 10)
    y1 = make_func(x1)

    x2, y2 = spline_interp(x1, y1)

    x3, y3 = moving_avg(x1, y1)
    x4, y4 = spline_interp(x3, y3)

    plt.plot(x1, y1, color='b', label='linear', alpha=0.7)
    plt.plot(x2, y2, color='r', label='spline', alpha=0.7)
    plt.plot(x4, y4, color='g', label='average + spline', alpha=0.7)
    plt.legend()
    plt.show()

if __name__ == '__main__':
    main()

結果

f:id:snova301:20181007134644p:plain

オリジナルのデータ点を増やし、5項で移動平均した場合

f:id:snova301:20181007134709p:plain

補足

同じxに対して複数のyがあるデータをgnuplotで3次のスプライン補間すると、スプライン曲線がデータ点を通っておらず、補間だけでは実現できないなめらかさを持っています。
公式サイトで調べてみると、同一のxに対して複数のyがある場合、yの値を平均した値を補間しているとの記述がありました。
いずれ挑戦したいと思います。

参考文献