今回は matplotlib を使って動的にグラフを生成する方法について。 ここでいう動的というのは、データを逐次的に作って、それを随時グラフに反映していくという意味を指す。 例えば機械学習のモデルを学習させるときに、その過程 (損失の減り方とか) を眺める用途で便利だと思う。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.13.5 BuildVersion: 17F77 $ python -V Python 3.6.5 $ pip list --format=columns | egrep -i "(matplotlib|pillow)" matplotlib 2.2.2 Pillow 5.2.0
下準備
まずは今回使うパッケージをインストールしておく。
$ pip install matplotlib pillow
静的にグラフを生成する
動的な生成について説明する前に、まずは静的なグラフの生成から説明する。 といっても、これは一般的な matplotlib のグラフの作り方そのもの。 あらかじめ必要なデータを全て用意しておいて、それをグラフとしてプロットする。
この場合、当たり前だけどプロットする前に全てのデータが揃っていないといけない。 例えば機械学習なら、モデルの学習を終えて各エポックなりラウンドごとの損失が出揃っている状態まで待つ必要がある。
次のサンプルコードではサイン波のデータをあらかじめ作った上で、それを折れ線グラフにしている。
#!/usr/bin/env python # -*- coding: utf-8 -*- import math import numpy as np from matplotlib import pyplot as plt def main(): # 描画領域 fig = plt.figure(figsize=(10, 6)) # 描画するデータ x = np.arange(0, 10, 0.1) y = [math.sin(i) for i in x] # グラフを描画する plt.plot(x, y) # グラフを表示する plt.show() if __name__ == '__main__': main()
上記を適当な名前でファイルに保存して実行してみよう。
$ python sin.py
すると、次のようなグラフが表示される。
これが静的なグラフ生成の場合。
動的にグラフを生成する
続いて動的にグラフを生成する方法について。
これには matplotlib.animation
パッケージを使う。
特に FuncAnimation
を使うと作りやすい。
animation — Matplotlib 2.2.2 documentation
次のサンプルコードでは、先ほどの例と同じサイン波を動的に生成している。
ポイントは、グラフの再描画を担当する関数をコールバックとして FuncAnimation
に登録すること。
そうすれば、あとは FuncAnimation
が一定間隔でその関数を呼び出してくれる。
呼び出されるコールバック関数の中でデータを生成したりグラフを再描画する。
#!/usr/bin/env python # -*- coding: utf-8 -*- import math import numpy as np from matplotlib import pyplot as plt from matplotlib import animation def _update(frame, x, y): """グラフを更新するための関数""" # 現在のグラフを消去する plt.cla() # データを更新 (追加) する x.append(frame) y.append(math.sin(frame)) # 折れ線グラフを再描画する plt.plot(x, y) def main(): # 描画領域 fig = plt.figure(figsize=(10, 6)) # 描画するデータ (最初は空っぽ) x = [] y = [] params = { 'fig': fig, 'func': _update, # グラフを更新する関数 'fargs': (x, y), # 関数の引数 (フレーム番号を除く) 'interval': 10, # 更新間隔 (ミリ秒) 'frames': np.arange(0, 10, 0.1), # フレーム番号を生成するイテレータ 'repeat': False, # 繰り返さない } anime = animation.FuncAnimation(**params) # グラフを表示する plt.show() if __name__ == '__main__': main()
先ほどと同じようにファイルに保存したら実行する。
$ python sin.py
すると、次のように動的にグラフが描画される。
ちなみに、上記のような GIF 画像や動画は次のようにすると保存できる。
#!/usr/bin/env python # -*- coding: utf-8 -*- import math import numpy as np from matplotlib import pyplot as plt from matplotlib import animation def _update(frame, x, y): """グラフを更新するための関数""" # 現在のグラフを消去する plt.cla() # データを更新 (追加) する x.append(frame) y.append(math.sin(frame)) # 折れ線グラフを再描画する plt.plot(x, y) def main(): # 描画領域 fig = plt.figure(figsize=(10, 6)) # 描画するデータ (最初は空っぽ) x = [] y = [] params = { 'fig': fig, 'func': _update, # グラフを更新する関数 'fargs': (x, y), # 関数の引数 (フレーム番号を除く) 'interval': 10, # 更新間隔 (ミリ秒) 'frames': np.arange(0, 10, 0.1), # フレーム番号を生成するイテレータ 'repeat': False, # 繰り返さない } anime = animation.FuncAnimation(**params) # グラフを保存する anime.save('sin.gif', writer='pillow') if __name__ == '__main__': main()
グラフを延々と描画し続ける
先ほどの例では frames
オプションに渡すイテレータに終わりがあった。
具体的には 0 ~ 10 の範囲を 0.1 区切りで分割した 100 のデータに対してグラフを生成した。
また repeat
オプションに False
を指定することで繰り返し描画することも抑制している。
続いては、先ほどとは異なり frames
オプションに終わりのないイテレータを渡してみよう。
こうすると、手動で止めるかメモリなどのリソースを食いつぶすまでは延々とデータを生成してグラフを描画することになる。
#!/usr/bin/env python # -*- coding: utf-8 -*- import itertools import math import numpy as np from matplotlib import pyplot as plt from matplotlib import animation def _update(frame, x, y): """グラフを更新するための関数""" # 現在のグラフを消去する plt.cla() # データを更新 (追加) する x.append(frame) y.append(math.sin(frame)) # 折れ線グラフを再描画する plt.plot(x, y) def main(): # 描画領域 fig = plt.figure(figsize=(10, 6)) # 描画するデータ x = [] y = [] params = { 'fig': fig, 'func': _update, # グラフを更新する関数 'fargs': (x, y), # 関数の引数 (フレーム番号を除く) 'interval': 10, # 更新間隔 (ミリ秒) 'frames': itertools.count(0, 0.1), # フレーム番号を無限に生成するイテレータ } anime = animation.FuncAnimation(**params) # グラフを表示する plt.show() if __name__ == '__main__': main()
上記をファイルに保存して実行してみよう。
$ python sin.py
ずーーーっとグラフが生成され続けるはず。
Jupyter Notebook 上で動的にグラフを生成する
Jupyter Notebook 上で動的なグラフ生成をするときは、次のように %matplotlib nbagg
マジックコマンドを使う。
また、意図的に pyplot.show()
を呼び出す必要はない。
%matplotlib nbagg import itertools import math import numpy as np from matplotlib import pyplot as plt from matplotlib import animation def _update(frame, x, y): """グラフを更新するための関数""" # 現在のグラフを消去する plt.cla() # データを更新 (追加) する x.append(frame) y.append(math.sin(frame)) # 折れ線グラフを再描画する plt.plot(x, y) # 描画領域 fig = plt.figure(figsize=(10, 6)) # 描画するデータ x = [] y = [] params = { 'fig': fig, 'func': _update, # グラフを更新する関数 'fargs': (x, y), # 関数の引数 (フレーム番号を除く) 'interval': 10, # 更新間隔 (ミリ秒) 'frames': np.arange(0, 10, 0.1), # フレーム番号を生成するイテレータ 'repeat': False, # 繰り返さない } anime = animation.FuncAnimation(**params)
めでたしめでたし。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る