daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

【解説】matplotlib:基本的な内容、設定、各ケーススタディ(アニメーションで潰れた感じになってしまう)

Pythonで、グラフや、画像、アニメーションを出力するとき、matplotlibを使う場合が多いと思います。

しかし、matplotlibは意外に難しいです。

Web検索すると、たくさんヒットして、大変ありがたいのですが、知りたいことが全て書かれてることは少なく、だいたいは複数のサイトを見ることになります。

そこで、ここでは、簡単なことは書かず、複数のサイトを見なければならなくなったものだけ書いていこうと思います。

参考文献

参考サイト

参考文献「指標・特徴量の設計から始めるデータ可視化学入門」の付録コードのGitHub

github.com

基本的なこと

matplotlibがとっつきにくいのは、グラフを書くとき、二通りの書き方があるためです。

一つは簡易的な書き方で、plt.xxx()だけでやりきる方法(pyplotインタフェースを使う方法)です。

もう一つは、fig, ax = plt.subplots()などで、FigureオブジェクトとAxesオブジェクトを適切に使っていく方法(オブジェクト指向インタフェースを使う方法)です。

参考文献の「指標・特徴量の設計から始めるデータ可視化学入門」の付録コードで例を示します。

pyplotインタフェースを使う方法

import matplotlib.pyplot as plt

# データ生成
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 4, 9, 16, 25]

# プロット作成
plt.plot(x, y)

# タイトルと軸ラベル
plt.title("Using plt.plot()")
plt.xlabel("x")
plt.ylabel("y")

# グラフ表示
plt.show()

オブジェクト指向インタフェースを使う方法

import matplotlib.pyplot as plt

# データ生成
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 4, 9, 16, 25]

# フィギュアとサブプロットの生成
fig, ax = plt.subplots()

# プロット作成
ax.plot(x, y)

# タイトルと軸ラベル
ax.set_title("Using ax.plot()")
ax.set_xlabel("x")
ax.set_ylabel("y")

# グラフ表示
plt.show()

前者は、少ない実装で実現できるため、細かい見栄えにこだわらないときに重宝します。

後者は、細かい見栄えを調整できるので、他の方に見せる必要がある場合に使うといいと思います。

設定

matplotlibの設定とは、matplotlibrcのことです。

matplotlibrcの場所を知る方法

Linuxなら、site-packagesの場所は、何とかたどり着けるので、その下のmatplotlib/mpl-data/matplotlibrcを見つけることが出来ますが、Windowsの場合は、さっぱり分かりません。

matplotlibrcの場所を直接表示する方法

このやり方は初めて知ったのでメモしておきます。

import matplotlib
print( matplotlib.matplotlib_fname() )

表示結果(あくまで一例です)

'C:\\Users\\xxx\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_xxxxxxxx\\LocalCache\\local-packages\\Python311\\site-packages\\matplotlib\\mpl-data\\matplotlibrc'
sys.pathから見つける方法

Pythonのモジュール検索パスを表示してくれます。

今回の用途以外にも、よく使う汎用的な方法です。覚えるならこっちの方がいいと思います。

たくさん出力される場合がありますが、site-packagesを探せばOKです。

import sys
print( sys.path )

表示結果(一例)

['', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.11_3.11.2288.0_x64__xxxxxxxx\\python311.zip', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.11_3.11.2288.0_x64__xxxxxxxx\\DLLs', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.11_3.11.2288.0_x64__xxxxxxxx\\Lib', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.11_3.11.2288.0_x64__xxxxxxxx', 'C:\\Users\\muni0\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_xxxxxxxx\\LocalCache\\local-packages\\Python311\\site-packages', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.11_3.11.2288.0_x64__xxxxxxxx\\Lib\\site-packages']

バックエンドの設定

デフォルトは以下のように、コメントアウトされた状態でした。

## backend: Agg

この状態だと、以下のようにインタラクティブモード(グラフなどがウィンドウで表示されるモード)になります。

Backend TkAgg is interactive backend. Turning interactive mode on.

一方、コメントアウトを外してみます。

backend: Agg

インタラクティブモードではなくなり、グラフなどのウィンドウは表示されなくなります。

複数のグラフや画像を出力すると、たくさんウィンドウが出てしまうので、インタラクティブモードはオフにしておき、必要に応じて、savefig()して、ファイル保存するやり方にしておきます。

ケーススタディ

ArtistAnimationで、画像が潰れた感じになってしまう

まず、正常な場合は以下です。

ArtistAnimationの正常
ArtistAnimationの正常

問題の潰れた感じとは、以下のようになります。

ArtistAnimationで画像が潰れる
ArtistAnimationで画像が潰れる

ArtistAnimationに渡すArtistsの中身を確認する

まず、正常な場合は以下のように、Artistsリストの中に、5枚の画像が、それぞれ構成要素をリストで持っている状態です。

[[Text(0, 3.1, 'step=0'), <matplotlib.collections.QuadMesh object at 0x00000147E301ECD0>, Text(0.4, 2.85, '        0.03'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.10'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=1'), <matplotlib.collections.QuadMesh object at 0x00000147E30925D0>, Text(0.4, 2.85, '        0.81'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.90'), Text(1.55, 2.5, '→'), Text(2.4, 2.85, '        1.00'), Text(2.5500000000000003, 2.5, '→'), Text(3.1, 2.1, 'R 1.0 (GOAL)'), ...],
 [Text(0, 3.1, 'step=2'), <matplotlib.collections.QuadMesh object at 0x00000147E30C5E50>, Text(0.4, 2.85, '        0.81'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.90'), Text(1.55, 2.5, '→'), Text(2.4, 2.85, '        1.00'), Text(2.5500000000000003, 2.5, '→'), Text(3.1, 2.1, 'R 1.0 (GOAL)'), ...],
 [Text(0, 3.1, 'step=3'), <matplotlib.collections.QuadMesh object at 0x00000147E30F1290>, Text(0.4, 2.85, '        0.81'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.90'), Text(1.55, 2.5, '→'), Text(2.4, 2.85, '        1.00'), Text(2.5500000000000003, 2.5, '→'), Text(3.1, 2.1, 'R 1.0 (GOAL)'), ...],
 [Text(0, 3.1, 'step=4'), <matplotlib.collections.QuadMesh object at 0x00000147E511FE50>, Text(0.4, 2.85, '        0.81'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.90'), Text(1.55, 2.5, '→'), Text(2.4, 2.85, '        1.00'), Text(2.5500000000000003, 2.5, '→'), Text(3.1, 2.1, 'R 1.0 (GOAL)'), ...]]

一方、問題の潰れた感じでは、

[[Text(0, 3.1, 'step=0 phase=0 state=(0, 0)'), <matplotlib.collections.QuadMesh object at 0x000002155B716C10>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(0, 1)'), <matplotlib.collections.QuadMesh object at 0x000002155B785CD0>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(0, 2)'), <matplotlib.collections.QuadMesh object at 0x000002155B799450>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(0, 3)'), <matplotlib.collections.QuadMesh object at 0x000002155D8356D0>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(1, 0)'), <matplotlib.collections.QuadMesh object at 0x000002155D8B7950>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(1, 1)'), <matplotlib.collections.QuadMesh object at 0x000002155B786910>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(1, 2)'), <matplotlib.collections.QuadMesh object at 0x000002155D979450>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(1, 3)'), <matplotlib.collections.QuadMesh object at 0x000002155B714ED0>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(2, 0)'), <matplotlib.collections.QuadMesh object at 0x000002155DC4F350>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(2, 1)'), <matplotlib.collections.QuadMesh object at 0x000002155DC4FD50>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(2, 2)'), <matplotlib.collections.QuadMesh object at 0x000002155DD28A10>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...],
 [Text(0, 3.1, 'step=0 phase=0 state=(2, 3)'), <matplotlib.collections.QuadMesh object at 0x000002155DD6DA10>, Text(0.4, 2.85, '        0.00'), Text(0.45, 2.6, '↑'), Text(0.45, 2.4, '↓'), Text(0.35, 2.5, '←'), Text(0.55, 2.5, '→'), Text(1.4, 2.85, '        0.00'), Text(1.45, 2.6, '↑'), ...]]

特に問題なさそうです。

正常な方とフレーム数を合わせてみる

正常な方は、stepごとに、計5枚のフレームでアニメーション化しています。

一方、異常な方は、stateごとに、計12枚のフレームでアニメーション化しています。

では、異常な方を5枚のフレームでアニメーション化してみます。

具体的には、Visual Source Codeでデバッグし、5枚たまった時点で、デバッグコンソールを使って、アニメーション化します。

以下は作業イメージです。

VSCodeでデバッグ
VSCodeでデバッグ

すると、5枚の時点では正常なアニメーション画像になった。枚数の問題ということでしょうか?

以下が正常なアニメーションになった画像です。

異常な方を、5枚にしたら正常になった
異常な方を、5枚にしたら正常になった

次は8枚でやってみたが、同様に正常なアニメーションになった。さらに12枚でやっても正常なアニメーションになった。

異常な方を、12枚にしても正常になった
異常な方を、12枚にしても正常になった

ここまでやって、やっと分かりました。12枚のフレームがたまった時点でアニメーション化すれば問題ないが、その後に、stepごとの描画が入っており、それにより、画像が混ざったような感じになったということだでした(ただのバグでした)。

以下が、正常に全step、全stateの計384フレームでアニメーション化したものです。

不具合を修正して正常にアニメーション化できた
不具合を修正して正常にアニメーション化できた