Matplotlibでグラフを作図-01 複数のアニメーションを実行する

Matplotlibでグラフを作図-01 複数のアニメーションを実行する

モモノキ&ナノネと一緒にMatplotlibを使ってグラフの作図方法を学習していきます。

モモノキ&ナノネと一緒にPythonとMatplotlibで複数のアニメーションを実行してみよう




Matplotlibで複数アニメーション

今回はMatplotlibを使って、グラフのアニメーション練習をやってみるよ。

データ分析とアニメーションは、なにか関係あるの?

特にはないけど...グラフ作成の練習にはなるよ。作ったグラフが動いたらちょっと楽しいいし、gifファイルにも簡単に出力できる。あとで何かに使えるかもよ。

データ分析でグラフは重要だから、練習になるならやってみる。

まずはアニメーションの準備から。NumpyとMatplotlibはいつも通りにインポート。グラフアニメーションを使うときは、追加でMatplotlibのanimationをインポートしてね。名前は慣例でanimationとしてね。

あと、Jupyter Notebook上でアニメーションを動かすには、マジックコマンドで『%matplotlib nbagg』を実行してね。いつもの『%matlib inline』では動かないから注意だよ。

In [16]:
# Jupyter notebook上でのインタラクティブな画像を表示する
# (通常よく利用する%matplotlib inlineでは再生不可)
%matplotlib nbagg

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

アニメーションの準備できた。

次はアニメーションの実行方法について。アニメーションさせる方法が二通りあるから、両方とも練習してみよう。

Matplotlibでアニメーションさせる方法には『ArtistAnimation』と『FuncAnimation』があるよ。ArtistAnimationは画像をあらかじめ全部準備してから、パラパラマンガみたいに再生。FuncAnimationはグラフの情報を逐次更新して再生するみたいなイメージだよ。

ArtistAnimationとFuncAnimationでは、できることが違うの?

たぶん、どっちでも同じようことできそうだけど。FuncAnimationの方がコーティングの自由度は高そうかな。そのかわり、再生中にグラフの更新処理が必要だからPC負荷も高めだよ。

二つの違いはともかくとして、最初は『ArtistAnimation』を使う方法からやってみよう。

In [17]:
%matplotlib nbagg

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x_min = -2 * np.pi
x_max = 2 * np.pi
x = np.arange(x_min, x_max, 0.1)

fig = plt.figure(figsize=(4, 2))
fig.patch.set_alpha(1.0)

plt.xlim(x_min, x_max)
plt.grid()

frames = 50 # フレーム数
images = [] # 再生画ストック用リスト

for i in range(frames):
    y = np.sin(x + 2*np.pi*(i / frames))
    image_line = plt.plot(x, y, c='g') # 正弦波プロット
    images.append(image_line) # 再生画追加ストック

ani_artist1 = animation.ArtistAnimation(fig, images, interval=100) # アニメーション
plt.show()

上のコードは、ArtistAnimationを使ったアニメーションだよ。このブログ上だとPythonコードは実行できないから、一旦gifファイルに保存するね。

アニメーション画像をgifファイルに書き出すには『ImageMagick』というのを使うよ。OSがLinuxだとImageMagickはプリインストールされているケースが多いみたいだから、特に事前準備は不要そう。MacやWindowsの場合は環境に合わせてImageMagickのインストールとMatplotlibでImageMagickを使えるように設定(matplotlibrcファイルのanimation.convert_pathを設定)が必要だよ。詳しくはネットで検索してみてね。

ImageMagickダウンロード先(外部サイト)
http://www.imagemagick.org/script/download.php

In [18]:
# gifファイルで保存
ani_artist1.save("ani_artist1.gif", writer='imagemagick')

# fpsで一秒当たりの再生フレーム数も指定可能
# ani_artist1.save("ani_artist99.gif", writer='imagemagick', fps=10)

保存したgif画像を表示すると、こんな感じだよ。

正弦波が右から左に動いてる。

コード短いから簡単そうだけど、ArtistAnimationを使うポイントは?

事前に再生画を全部準備しておく必要があるんだ。フレーム数分のグラフ画をまとめてリスト形式で変数にストックしておけばOK。コードの流れ的には、フレーム数分のループ処理を実行。ループの中でplot()したときの戻り値を再生に使うリスト変数にappend()で追加する。当然だけどループの中でプロットデータの更新も忘れずに。更新しないとただ静止画になっちゃうよ。

再生用のリスト変数が準備できたら、あとはanimation.ArtistAnimation()を呼び出すだけ。第1引数がグラフFigure、第2引数が再生画をまとめたList変数、オプション名前付き引数intervalに再生間隔(単位はms)を指定すれば、とりあえずアニメーションとして動くよ。

わかった、それくらいだったらできそう。gifファイルへの出力は?

出力方法はいくつかあるみたいなので、gif形式でファイル保存する一例ね。animation.ArtistAnimation()の戻り値を受け取った変数に、変数.save('ファイル名.gif', writer='imagemagick')とすれば、gifファイルで保存できるよ。

ArtistAnimationはOK。もう一つのFuncAnimationの使い方は?

FuncAnimationは次のコードを参考にしてみて。

In [25]:
%matplotlib nbagg

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x_min = -2 * np.pi
x_max = 2 * np.pi
x = np.arange(x_min, x_max, 0.1)

fig, ax = plt.subplots(figsize=(4, 2))
fig.patch.set_alpha(1.0)
ax.set_xlim(x_min, x_max)
ax.grid()

line, = ax.plot(x, np.sin(x)) # 正弦波:unpackで,をつける

frames = 50 # フレーム数

# アニメーション更新関数
def animate(frames_cnt):
    line.set_ydata(np.sin(x + 2*np.pi*(frames_cnt / frames)))  # 正弦波y座標更新

# アニメーション実行
ani_func1 = animation.FuncAnimation(fig, animate, frames=frames, interval=100)

# plt.show()
In [ ]:
ani_func1.save("ani_func1.gif", writer='imagemagick')

保存したgif画像を表示すると、こんな感じ。
ArtistAnimationと同じように再生できてるね。

FuncAnimationの使い方は、グラフ描画更新用の関数を定義しておいてFuncAnimation()から一定間隔で更新用関数を呼び出すイメージだよ。コーバック関数の使い方を知ってれば簡単かも。

コールバック関数はJavaScriptでもよく使うから大丈夫。設定しておくと後でその処理が必要になったとき、自動で関数を呼び出してくれる便利やつね。

そんな感じだね。
コードの書き方としては、アニメーションさせたいグラフオブジェクトを先に登録して変数に保持しておく。FuncAnimation()から呼び出させる描画更新用関数の中で、保持したグラフオブジェクトの情報を書き換える操作をするとアニメーションとして機能するよ。

FuncAnimation()の第1引数はグラフFigure、第2引数がグラフ描画更新用の関数名、名前付きオプション引数framesにフレーム数、あとintervalはArtistAnimationと一緒だよ。

そしてグラフを書き換えるときに重要な情報。描画更新用関数の第1仮引数で受け取る値は処理毎にカウントアップしていくからグラフの更新条件として活用できるよ。仮にframes値50設定の場合、変更される値の範囲は0〜49になるよ。

FuncAnimation()のオプション引数は他にもいくつかあるよ。今回は利用してないけど、更新用関数にオブジェクトを渡せたり、描画の高速化などもあるみたい。興味があったらドキュメントなどを調べてみてね。

なんとか使えるかな...少しずつ練習してみるよ。

ところで、上の例だと正弦波を一つ表示してアニメーションさせてたけど、複数のグラフ要素を同時に動かすこともできるの?

うん、コードを少し足すだけで複数個でも同時に動かせるよ。(あまりよくばると処理が重くなるけど)

次のコードはArtistAnimationを使って複数対象のアニメーションを実行した例だよ。正弦波とマーカーを同時に動かしているよ。

In [20]:
%matplotlib nbagg

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x_min = -2 * np.pi
x_max = 2 * np.pi
x = np.arange(x_min, x_max, 0.1)

fig = plt.figure(figsize=(4, 2))
fig.patch.set_alpha(1.0)

plt.xlim(x_min, x_max)
plt.grid()

frames = 50 # フレーム数
images = [] # 再生画ストック用リスト

for i in range(frames):
    y = np.sin(x + 2*np.pi*(i / frames))
    image_line = plt.plot(x, y, c='g') # 正弦波プロット
    image_marker = plt.plot(0, np.sin(2*np.pi*(i / frames)),
                            c='r', marker='o', markersize=12) # マーカープロット
    images.append(image_line + image_marker) # 再生画追加ストック (対象が複数の場合は+演算子を利用)

ani_artist2 = animation.ArtistAnimation(fig, images, interval=100) # アニメーション
# plt.show()
In [ ]:
ani_artist2.save("ani_artist2.gif", writer='imagemagick')

保存したgif画像を表示すると、こんな感じだよ。
ArtistAnimation

ただの正弦波だけより動きあっていいね!複数を対象するときのポイントは?

ArtistAnimationで複数のグラフ要素を扱うには、appendするとき対象の変数を+で連結するとうまくいくよ。再生画像用リスト変数.apeend(対象A + 対象B)みたいにね。

続いて、FuncAnimationで複数対象のアニメーションも。

In [23]:
%matplotlib nbagg

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

x_min = -2 * np.pi
x_max = 2 * np.pi
x = np.arange(x_min, x_max, 0.1)

fig, ax = plt.subplots(figsize=(4, 2))
fig.patch.set_alpha(1.0)
ax.set_xlim(x_min, x_max)
ax.grid()

line, = ax.plot(x, np.sin(x)) # 正弦波:unpackで,をつける
marker, = ax.plot(0, 0, c='r', marker='o', markersize=12) # マーカー:unpackで,をつける

frames = 50 # フレーム数

# アニメーション更新関数
def animate(frames_cnt):
    line.set_ydata(np.sin(x + 2*np.pi*(frames_cnt / frames)))  # 正弦波y座標更新
    marker.set_ydata(np.sin(0 + 2*np.pi*(frames_cnt / frames))) # マーカーy座標更新

# アニメーション実行
ani_func2 = animation.FuncAnimation(fig, animate, frames=frames, interval=100)

plt.show()
In [24]:
ani_func2.save("ani_func2.gif", writer='imagemagick')

保存したgif画像を表示すると、こんな感じだよ。
FuncAnimation

FuncAnimationも同じような感じ。アニメーション対象のグラフオブジェクト登録して、FuncAnimationから呼び出させる関数内に更新処理を追加していけばOK。

複数のアニメーションも簡単だね。アニメーションの要素をもっと増やしてみたら?

処理が重いからやめとく。(10年以上前のPCで32ビット・1コア、サクサク動かない...)

たしかに、動きが...冷却ファンが唸ってるし。

> <。。

またね。







スポンサーリンク

0 件のコメント :

コメントを投稿