matplotlibでアニメーション

# 概要

python の matplotlib を使えば手軽に見栄えのいいグラフが作成できます。グラフのアニメーションを作成する機能も matplotlib 標準で提供されているので、ここではその使い方を網羅的にまとめておきます。

# アニメーションの作成

大きく次の2つの方法が提供されています。

- あらかじめ作成したグラフのオブジェクトのリストを渡してアニメーションする（ArtistAnimation）
- グラフ更新用の関数を定義してそれによりアニメーションする（FuncAnimation）

前者がなぜArtistAnimationかというと、matplotlib ではグラフオブジェクトを artist と呼んでいるからです。ここでもその呼び方を使います。ある程度性能が要求される場合や、アニメーションの中でタイトルを変更したり細かい制御が必要な場合は後者を選択することになります。

それでは、それぞれをコード例とともに紹介します。

## ArtistAnimation

ArtistAnimationに渡すのはartistのリストのリスト[^1]です。アニメーションの各フレームをフレーム1, フレーム2, ... であらわすと、

```json

[
  [
    フレーム1で表示するartist_1, フレーム1で表示するartist_2, ...
  ],
  [
    フレーム2で表示するartist_1, フレーム2で表示するartist_2, ...
  ],
  ...
]
```

となります。matplotlibのグラフの種類や関数ごとにartistを返すものやartistのリストを返すものが混在しているので注意が必要です。たとえば以下の例では、plot関数から戻ってきたartistのリストをそのままappendしています。

```py3
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
import numpy as np
fig, ax = plt.subplots()
artists = []
x = np.arange(10)
for i in range(10):
    y = np.random.rand(10)
    im = ax.plot(x, y)
    artists.append(im)
anim = ArtistAnimation(fig, artists, interval=1000)
fig.show()
```

scatterなどartistを返す関数の場合はリストにラッピングしたものをappendします。

```py3
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
import numpy as np
fig, ax = plt.subplots()
artists = []
x = np.arange(10)
for i in range(10):
    y = np.random.rand(10)
    im = ax.scatter(x, y)
    artists.append([im])
anim = ArtistAnimation(fig, artists, interval=1000)
fig.show()
```

## FuncAnimation

アニメーションのフレームごとに関数が呼ばれます。処理は次の2つのパターンがありえます。

- 毎回新規にartistを作成する（前回作成したartistを保持して明示的に消してあげる必要がある）
- 作成済みのartistのデータを更新する

それぞれのコードを紹介します。

### 作成パターン

```py3
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
ims = []
x = np.arange(10)

ax.set_xlim(0, 9)
ax.set_ylim(0, 1)

def update_anim(i):
    y = np.random.rand(10)
    if len(ims) > 0:
        im = ims.pop()
        im.remove()
    im, = ax.plot(x, y)
    ims.append(im)

anim = FuncAnimation(fig, update_anim, interval=1000)
fig.show()
```

### データ更新パターン

```py3
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
artists = []
im, = ax.plot([], [])
x = np.arange(10)
ax.set_xlim(0, 9)
ax.set_ylim(0, 1)

def update_anim(frame):
    y = np.random.rand(10)
    im.set_data(x, y)
    
anim = FuncAnimation(fig, update_anim, blit=False)
fig.show()
```

当然データ更新の方が効率的です。

### blit=True

また、blitというオプション引数をTrueに指定すれば、コンピュータグラフィックで用いられるblittingという手法をつかってさらに描画を高速化できるようです。この場合関数のなかからblitting対象のartistのリストをリターンする必要があります。

```py3
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
artists = []
im, = ax.plot([], [])
x = np.arange(10)
ax.set_xlim(0, 9)
ax.set_ylim(0, 1)

def update_anim(frame):
    y = np.random.rand(10)
    im.set_data(x, y)
    return im,
    
anim = FuncAnimation(fig, update_anim, blit=True)
fig.show()
```

# アニメーションの保存・共有

作成したアニメーションは以下のようにファイルに保存することができます。

gifに書き出し

```py3
anim.save('anim.gif', writer='imagemagick', fps=4)
```

mp4に書き出し

```py3
anim.save('anim.mp4', writer='ffmpeg', fps=4)
```

またjupyter notebook上での作業の場合、 `%matplotlib inline`のかわりに`%matplotlib nbagg`と指定すればユーザがインタラクティブに操作することができるグラフがインラインで表示されます。アニメーションも実行されます。ただし、重いし、nbviewerでは表示されないので、むしろインラインで表示したいだけなら以下のように動画を埋めこむのがおすすめです。

```py3
from IPython.display import HTML
HTML(anim.to_html5_video())
```

# 脚注

[^1]: 一般的にはforでループ可能なiterableなオブジェクトです。ここでは一般的な意味でリストと呼びます。