matplotlibでは、アニメーションを実装する方法として、ArtistAnimationとFuncAnimationの大きく2種類の方法があります。
ArtistAnimationの方が実装は簡単である反面、メモリ等のコンピュータリソースを大きく使ってしまいます。一方、今回解説するFuncAnimationは効率的にアニメーションを作成することができますが、インターフェースが少しだけ複雑で、実装するのに慣れが必要です。
今回は、matplotlibでアニメーションを実装する方法として、FuncAnimationを利用した方法を紹介します。
本記事の内容
- FuncAnimationの仕組みとインターフェース
- サイン波のアニメーション実装
- 散布図のアニメーション実装
- GIFファイルへの保存方法
FuncAnimationの仕組み
FuncAnimationのインターフェースは以下のようになっています。
from matplotlib.animation import FuncAnimation
# FuncAnimation(fig, animate, frames=range(8), interval=1000)
| 引数 | 説明 |
|---|---|
fig |
アニメーションの描画領域(Figure オブジェクト) |
animate |
各フレームで呼ばれる更新関数。Artistオブジェクトを返す |
frames |
アニメーションのフレーム数またはイテラブル |
interval |
フレーム間隔(ミリ秒) |
init_func |
初期化関数(オプション) |
blit |
True にすると描画が高速化される |
全体の仕組みをまとめると、FuncAnimationは frames で指定された回数だけ animate 関数を繰り返し呼び出し、各フレームの描画を更新することでアニメーションを実現します。
サイン波のアニメーション
最も基本的な例として、サイン波が時間とともに移動するアニメーションを作成しましょう。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# 描画領域の準備
fig, ax = plt.subplots(figsize=(10, 4))
x = np.linspace(0, 4 * np.pi, 200)
line, = ax.plot([], [], 'b-', linewidth=2)
ax.set_xlim(0, 4 * np.pi)
ax.set_ylim(-1.5, 1.5)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title("Traveling Sine Wave")
ax.grid(True, alpha=0.3)
def init():
"""初期化関数: 空のプロットを返す"""
line.set_data([], [])
return line,
def animate(frame):
"""更新関数: 各フレームで呼ばれる"""
phase = frame * 0.1 # 位相をフレームごとにずらす
y = np.sin(x - phase)
line.set_data(x, y)
return line,
# アニメーション作成
anim = FuncAnimation(fig, animate, init_func=init,
frames=100, interval=50, blit=True)
plt.tight_layout()
plt.show()
animate 関数が各フレームで呼ばれ、サイン波の位相を少しずつずらすことで、波が右方向に移動するアニメーションが実現されます。
振幅が変化するサイン波
もう少し複雑な例として、振幅が時間とともに減衰するサイン波のアニメーションを作りましょう。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots(figsize=(10, 5))
x = np.linspace(0, 4 * np.pi, 300)
line, = ax.plot([], [], 'b-', linewidth=2)
fill = None
ax.set_xlim(0, 4 * np.pi)
ax.set_ylim(-2, 2)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True, alpha=0.3)
time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=12)
def init():
line.set_data([], [])
time_text.set_text('')
return line, time_text
def animate(frame):
t = frame / 20.0
amplitude = np.exp(-0.1 * t) * 1.5
y = amplitude * np.sin(x - t)
line.set_data(x, y)
time_text.set_text(f't = {t:.2f}, A = {amplitude:.2f}')
ax.set_title(f"Damped Sine Wave (amplitude = {amplitude:.2f})")
return line, time_text
anim = FuncAnimation(fig, animate, init_func=init,
frames=200, interval=50, blit=True)
plt.tight_layout()
plt.show()
散布図のアニメーション
散布図でもFuncAnimationを利用できます。ランダムウォークする点群のアニメーションを作成してみましょう。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
np.random.seed(42)
n_points = 50
n_frames = 150
# ランダムウォークの軌跡を事前計算
positions = np.zeros((n_frames, n_points, 2))
positions[0] = np.random.randn(n_points, 2)
for t in range(1, n_frames):
positions[t] = positions[t-1] + np.random.randn(n_points, 2) * 0.1
fig, ax = plt.subplots(figsize=(8, 8))
scat = ax.scatter([], [], c='steelblue', s=30, alpha=0.7)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_aspect('equal')
ax.set_title("Random Walk Particles")
ax.grid(True, alpha=0.3)
frame_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=12)
def init():
scat.set_offsets(np.empty((0, 2)))
frame_text.set_text('')
return scat, frame_text
def animate(frame):
scat.set_offsets(positions[frame])
# 色を距離で変化させる
distances = np.sqrt(positions[frame][:, 0]**2 + positions[frame][:, 1]**2)
scat.set_array(distances)
frame_text.set_text(f'Frame: {frame}')
return scat, frame_text
anim = FuncAnimation(fig, animate, init_func=init,
frames=n_frames, interval=50, blit=True)
plt.tight_layout()
plt.show()
散布図の場合は set_offsets メソッドで座標を更新し、set_array メソッドで色を更新しています。
複数のサブプロットでのアニメーション
複数のサブプロットを同時にアニメーションすることも可能です。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
x = np.linspace(0, 2 * np.pi, 200)
line1, = ax1.plot([], [], 'b-', linewidth=2)
line2, = ax2.plot([], [], 'r-', linewidth=2)
ax1.set_xlim(0, 2 * np.pi)
ax1.set_ylim(-1.5, 1.5)
ax1.set_title("sin(x - t)")
ax1.grid(True, alpha=0.3)
ax2.set_xlim(0, 2 * np.pi)
ax2.set_ylim(-1.5, 1.5)
ax2.set_title("cos(x - t)")
ax2.grid(True, alpha=0.3)
def init():
line1.set_data([], [])
line2.set_data([], [])
return line1, line2
def animate(frame):
t = frame * 0.1
line1.set_data(x, np.sin(x - t))
line2.set_data(x, np.cos(x - t))
return line1, line2
anim = FuncAnimation(fig, animate, init_func=init,
frames=100, interval=50, blit=True)
plt.tight_layout()
plt.show()
GIFファイルへの保存
作成したアニメーションをGIFファイルとして保存できます。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots(figsize=(8, 4))
x = np.linspace(0, 4 * np.pi, 200)
line, = ax.plot([], [], 'b-', linewidth=2)
ax.set_xlim(0, 4 * np.pi)
ax.set_ylim(-1.5, 1.5)
ax.set_title("Sine Wave Animation")
ax.grid(True, alpha=0.3)
def init():
line.set_data([], [])
return line,
def animate(frame):
y = np.sin(x - frame * 0.1)
line.set_data(x, y)
return line,
anim = FuncAnimation(fig, animate, init_func=init,
frames=60, interval=50, blit=True)
# GIFとして保存(pillowライターを使用)
# anim.save('sine_wave.gif', writer='pillow', fps=20)
# MP4として保存(ffmpegが必要)
# anim.save('sine_wave.mp4', writer='ffmpeg', fps=20)
plt.tight_layout()
plt.show()
anim.save() の writer パラメータで保存形式を指定します。GIF保存には pillow、MP4保存には ffmpeg が必要です。
まとめ
本記事では、matplotlibのFuncAnimationを使ったアニメーション作成方法を解説しました。
- FuncAnimationは
animate関数を繰り返し呼ぶことでアニメーションを実現する init_funcで初期化、animateで各フレームの更新を行うblit=Trueで描画を高速化できる- 線グラフだけでなく散布図など様々なプロットに適用可能
anim.save()でGIFやMP4として保存できる
ArtistAnimationと比べて、FuncAnimationはメモリ効率が良く、フレーム数が多いアニメーションに適しています。