質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

2回答

2509閲覧

ArtistAnimationでアニメーションを作成する際、グラフの初期化が必要ない理由について

MF0524

総合スコア48

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/10/23 10:42

Qiitaの記事を参照すると、FuncAnimationを用いてグラフを描画する場合、グラフの初期化を行ってから描画しています。グラフを上書きしないためには、ArtistAnimationでアニメーションを作成する場合も同じ操作をする必要があると思いました。
しかし、例えば以下のコードを実行した場合、1枚のプロット画像のみがgif画像になってしまいます。

python

1import numpy as np 2import matplotlib.pyplot as plt 3import matplotlib.animation as animation 4 5fig, ax = plt.subplots(figsize=(8, 6)) 6 7ims = [] 8 9for i in range(10): 10 plt.cla() 11 rand = np.random.randn(100) # 100個の乱数を生成 12 im = ax.plot(rand) # 乱数をグラフにする 13 ims.append(im) # グラフを配列 ims に追加 14plt.close() 15 16# 10枚のプロットを 100ms ごとに表示 17ani = animation.ArtistAnimation(fig, ims, interval=100) 18ani.save("result.gif", writer="pillow") 19Image('./result.gif', format='png')

上記のコードはgoogle colabにて確認しています。このコードで初期化を行うplt.cla() をコメントアウトした場合は10個のデータが上書きされずに正しく描画されているように見えます。なぜplt.cla()はFuncAnimationでは必要なのに、ArtistAnimationでは必要ないのでしょうか?
どなたか教えていただけると幸いです、どうぞよろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

回答ではないのですが、matplotlib の公式ドキュメントのアニメーションの説明ページや、そこからリンクされている FuncAnimation のサンプルプログラムを見ていると、plt.cla() を行なっているプログラムはひとつもありません。
それでよくよくサンプルプログラムを見てみると、質問文で引用されている Qiita の FuncAnimation のプログラムは、FuncAnimation の使い方としては問題があるのではないでしょうか。

Qiita のプログラムは関数の中で plt.cla() と plt.plot() をしていて、つまり関数が実行されるたびに Axes オブジェクトを初期化して再度プロットをし直す ~~ Axes オブジェクトを削除して再度 Axes オブジェクトを生成する~~ ということを繰り返しているのですが、公式ドキュメントのプログラムを読むと、Axes オブジェクトの初期化は Axes オブジェクトを生成するのは 関数の外で1回のみで、関数の中ではプロットのためのデータを変更しているだけのように読めます。

公式ドキュメントのサンプルプログラムを参考にして、Qiita のプログラムを書き換えてみました。

python

1import numpy as np 2import matplotlib.pyplot as plt 3import matplotlib.animation as animation 4 5fig, ax = plt.subplots() 6rand = np.random.randn(100) 7line, = ax.plot(rand) 8 9def plot(data): 10 rand = np.random.randn(100) 11 line.set_ydata(rand) 12 return line, 13 14ani = animation.FuncAnimation(fig, plot, interval=100) 15plt.show()

投稿2021/10/23 15:55

編集2021/10/24 02:49
etherbeg

総合スコア1195

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MF0524

2021/10/24 02:05 編集

etherbeg様 ご回答ありがとうございます。 確かに教えて頂いたコードの場合、初期化はしなくても正しく描画されているように見えます。 一方で以下の記事ではax.cla()を行わないと図が上書きされているようです。恐らく基本的にはax.cla()を実施した方が良さそうだと思っています。 https://qiita.com/osanshouo/items/3c66781f41884694838b ちなみにetherbeg様の教えてくださったコードでは、FuncAnimationの引数に繰り返し回数を指定するものが無いように思えるのですが、アニメーション作成に使用される画像の枚数はどうやって指定しているのでしょうか?教えて頂けると幸いです。よろしくお願い致します。
MF0524

2021/10/23 17:35

すみません。()が邪魔してうまくリンク先にいけないので注意してください。
jbpb0

2021/10/23 21:17

> ()が邪魔してうまくリンク先にいけない コメントを編集して()を消したらいいのでは?
MF0524

2021/10/24 02:06

jbpb0様 ありがとうございます。修正いたしました。
etherbeg

2021/10/24 02:50

※ plt.cla() の挙動についてこれまで誤解していたらしい(Axesオブジェクトそのものを削除すると思い込んでいたが、Axesオブジェクトを初期化するだけのようだ)ということがわかったので、回答文を訂正しました。 私の回答の趣旨は、FuncAnimationを使ったプログラムにおいて、FuncAnimationに渡す関数の中でプロットを行うのは、matplotlibの開発者が想定しているFuncAnimationの主要な使い方からは逸脱するのではないか? という疑問でした。ですので私が書いて示したコード例も、関数の中ではプロットは行なっていません。 一方コメントで言及されているQiitaの記事のコードは、すべて関数の中でプロットを行っています。 関数の中でプロットする場合は、Axes自体を初期化するかAxes上に描かれたプロットを削除する作業を、プログラマが明示的に行わないといけないらしい、ということはそれらのコード例から確かに言えます。 それをもって「基本的にはax.cla()を実施した方が良」いと主張していただくのは構わないのですが、私の回答に対するコメントでそれを言われると、私の回答の趣旨を理解していただけたのかどうか不安に思ってしまいます。 関数内でプロットを行った場合と行わなかった場合の違いについては、正直理解しておりません。 「FuncAnimationの引数に繰り返し回数を指定するもの」とは何でしょうか。frames引数のことでしょうか? frames引数を指定しなかった場合は itertools.count を指定したのと同じになるようです。つまり終わりがないということかと思います。
MF0524

2021/10/24 06:08

etherbeg様 質問の意図を読み取った返信ができず、申し訳ありません。また、frames引数については承知致しました。納得しました。 教えて頂いたコードについて、理解があやふやな点があるので確認させてください。。 etherbeg様のコードでは、最初に「line, = ax.plot(rand)」でlineに値を持たせたのち、定義したplot関数でlineに持たせた値を更新していくコードと理解しました。そして、etherbeg様のご回答を読む限り、載せて頂いたコードは「Axes自体を初期化する」コードではなく、「Axes上に描かれたプロットを削除する」コードと理解しました。 lineの値の更新が、プロットの削除もできる理由は、lineにはxy平面ならx, y1対の値しか持たせることができないので、値の更新が同時に直前のプロットの削除にも相当する、という理解でよろしいでしょうか? あと細かい点になりますが、plot関数を定義する際に、引数にdataを取るように設定していますが、dataはなぜ必要なのでしょうか?関数内での処理を示すコードにはdataに関連するコードは無いように思えますが、実際実行するとエラーになります。 お手数ですが、上記2点について確認とご回答をお願い致します。
etherbeg

2021/10/24 10:51 編集

> etherbeg様のコードでは、最初に「line, = ax.plot(rand)」でlineに値を持たせたのち、定義したplot関数でlineに持たせた値を更新していくコードと理解しました 私もそう思っています。line.set_ydata(rand) の部分がその値の更新をしている部分である、と思っています。 > etherbeg様のご回答を読む限り、載せて頂いたコードは「Axes自体を初期化する」コードではなく、「Axes上に描かれたプロットを削除する」コードと理解しました これはちょっとわからないですね。というのもその部分は表の(私が書いた)コードには現れていなくて、mattplotlib というライブラリが裏で行なっている部分だからです。 ユーザにはその労を取らせていないだけで、裏では Axes自体を初期化しているかもしれませんし、あるいは Axes上に描かれたプロットだけを削除しているかもしれません。あるいはもっとそれ以外の方法をとっているかもしれません。この部分はドキュメントに説明が書いてなければ、matplotlib のソースコードを読まないと分かりません。少しソースコードを見てみましたが、読み解くには時間がかかりそうでしたので、これ以上する気はありません。 > lineの値の更新が、プロットの削除もできる理由は、lineにはxy平面ならx, y1対の値しか持たせることができないので、値の更新が同時に直前のプロットの削除にも相当する、という理解でよろしいでしょうか これは上で書いた通り、ソースコードを見てみないと実際の機序は分かりません。しかしそういうわけではないと思います。 > plot関数を定義する際に、引数にdataを取るように設定していますが、dataはなぜ必要なのでしょうか これは元の Qiita の記事のコードにあったものをそのまま残してあるだけです。削除したらエラーになったので残してあります。使っても使わなくても必要なようです。元の Qiita の記事のコードでもこの data は関数の中で使われていません。ドキュメントによればこの引数は the next value in frames とされています。フレームの更新に関数の外部からの値が必要な場合はこの引数を介して受け渡す、ぐらいに私は理解しています。
etherbeg

2021/10/25 02:49 編集

気になったので引き続きドキュメントを見ていたところ、もう少し正確な理解に近づけたかと思うので補足します。 > plot関数を定義する際に、引数にdataを取るように設定していますが、dataはなぜ必要なのでしょうか 私が回答で書いた FuncAnimation のインスタンス化部分は次のようになっていました。  ani = animation.FuncAnimation(fig, plot, interval=100) frames 引数を指定しないときは itertools.count を指定したのと同じですので、これは次のように書いたのと同じです。  ani = animation.FuncAnimation(fig, plot, frames=itertools.count, interval=100) このとき frames で指定した値やシーケンス、ジェネレータ等に基づいて interval ミリ秒間隔で繰り返し値の取得が行われ、繰り返しごとに plot 関数が呼び出され、plot 関数の引数に frames から取得した値が渡されます。plot 関数内部では次のフレームの描画のために必要なユーザ処理が行われます。 上のコードでは frames は itertools.count でしたので、次のようなコードと同様の処理が行われることになります。 for d in itertools.count():  plot(d)  sleep(0.1) もし  ani = animation.FuncAnimation(fig, plot, frames=128, interval=100) の場合は次のようなコードと同様の処理が行われることになります。 for d in range(128):  plot(d)  sleep(0.1) 私の書いたコードでは(Qiitaのコードでも)、plot 関数は data 引数で itertools.count からの値を受け取っていましたが、その値は関数の内部では使われていませんでした。しかしframesに指定するオブジェクト次第では、そこから得られる値を関数の中で使用するような処理を書くことも可能です。 このような構造で動作するよう FuncAnimation クラスが書かれているために、FuncAnimation に渡す関数を定義する際には、使っても使わなくても、位置引数をひとつ取るように定義する必要があるということです。
etherbeg

2021/10/25 03:23 編集

FuncAnimation の動きを説明するための擬似的なコードとして、ドキュメントでは次のようなコードが使われています(上のコメントで私が示したコードもこれと同じ構造のものです)。 for d in frames:  artists = func(d, *fargs)  fig.canvas.draw_idle()  fig.canvas.start_event_loop(interval) ここで fig.canvas.draw_idle() というメソッドが使われていますが、これは実際のソースコードの中でも使われていることを確認しました。 draw_idle() というメソッドについて、ドキュメントでは次のように説明されています。  Request a widget redraw once control returns to the GUI event loop. これだけではちょっと分かりにくいので、matplotlib.pyplot.draw() メソッドの説明も参考にします。  Redraw the current figure.  This is used to update a figure that has been altered, but not automatically re-drawn. (...)  This is equivalent to calling fig.canvas.draw_idle(), where fig is the current figure. つまり Figure 自体を描き直しているということですね。 FuncAnimation では(ArtistAnimation でも)フレームの更新の際に内部的にこの draw_idle() が呼ばれていることが、ユーザのコードで明示的に plt.cla() (あるいは ax.cla())を行わなくてもいいことと関係しているのではないか、と思っています。 関数の内部でプロットを行なった場合は plt.cla() が必要になるので、なぜそうなるかも合わせた理解が必要になるのですが、そこまでは理解できていません。 公式ドキュメントのサンプルコードでは関数の中でプロットを行なっていないと述べましたが、必ず return 文があり、関数内部での処理結果を返しています。一方で Qiita のコードは関数の中でプロットを行なっていて、return 文はありません。この辺りの違いも理解の手がかりになりそうな気がします。
MF0524

2021/10/26 09:06

etherbeg様 ご回答ありがとうございます。 そして返信が遅くなってしまい申し訳ありません。 頂いた回答を今理解しようとしているところです。今しばらくお待ちください。
etherbeg

2021/10/26 09:58 編集

私も完全に理解した上で書き込んでいるのではなく、理解できた範囲ではだいたいこんな感じではないでしょうか?程度の大雑把なものですので、一字一句厳密に文字通り解釈しようとされると困ります。あくまで正確な理解に至るまでの暫定的な仮説のようなものとお考えください。 返信は無理にしようとしなくても、必要ならしてもらったらそれでいいですよ。
MF0524

2021/10/28 03:10

etherbeg様 承知致しました。 ありがとうございます。頂いたコメント、有難く参考にさせていただきます。
guest

0

matplotlib でアニメーションを作るに書かれている通りです。

FuncAnimationhは、「アニメーションの1フレームごとに関数を実行する。」ですから、毎回axをclearする必要があります。

ArtistAnimationは、「あらかじめ全てのグラフを配列の形で用意しておき、それを1枚ずつ流すというものである。」ですから、途中でaxをclearするとそれまでの画像は消えてしまい、最後の一枚だけが保存されることになります。

投稿2021/10/23 13:10

ppaul

総合スコア24666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MF0524

2021/10/23 13:52

ppaul様 ご回答ありがとうございます。ご回答頂いた内容には納得致しました。申し訳ありませんが、もう1点質問させてください。 Qiitaの記事「matplotlib でアニメーションを作る」において、ArtistAnimationに渡す画像のリストの作成はfor文を使用して同じaxに上書きしているように見えるのですが、実際には上書きされていません。 ax.plot(rand)でグラフを作成しても、変数に代入してしまえば元のaxには何も書かれていない状態が維持されるのでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問