こんな人にオススメ
plotly
のgo
でサブプロットしながらアニメーションを描きたいんだけど、どうしたらいけそう?
go
の場合だとアニメーションがごちゃごちゃするから書き方がわからん。
ということで、今回はplotly
のplotly.graph_objects
、go
を使ってサブプロットを描きつつ、各グラフをアニメーションにする方法を解説する。
以下の記事でsubplots
やanimation
単体については解説している。今回はこれらの合体版。
-
-
【Plotly&animation】Plotlyのgoでアニメーションを作成
続きを見る
-
-
【plotly&make_subplots】pythonのplotlyで複数グラフを1つの画像に描く
続きを見る
本記事で作成するグラフは9種類の関数の3 x 3プロットだが、もちろんデータを適用してもいい。自分で5 x 5とかにもできるから適したグラフを作成してほしい。
python環境は以下。
- Python 3.9.7
- numpy 1.21.3
- plotly 5.3.1
- plotly-orca 3.4.2
作成したコード全文
下準備
import numpy as np from plotly.subplots import make_subplots import plotly.graph_objects as go import plotly.io as pio
まずは下準備としてのimport
関連。goのsubplotには2種類の作成方法があるが、今回は自動で位置調節をしてくれるmake_subplotsを使用。
domain
ってのもあるけど、こっちは各グラフのサイズを指定したりしないといけないので自由度は高いが面倒。
使用データ
# -πから+πまでの-0.03141(π * 0.01)刻みのデータ x = np.arange(-np.pi, np.pi * 1.04, np.pi * 0.04) print(x) # [-3.14159265e+00 -3.01592895e+00 -2.89026524e+00 -2.76460154e+00 # -2.63893783e+00 -2.51327412e+00 -2.38761042e+00 -2.26194671e+00 # -2.13628300e+00 -2.01061930e+00 -1.88495559e+00 -1.75929189e+00 # -1.63362818e+00 -1.50796447e+00 -1.38230077e+00 -1.25663706e+00 # -1.13097336e+00 -1.00530965e+00 -8.79645943e-01 -7.53982237e-01 # -6.28318531e-01 -5.02654825e-01 -3.76991118e-01 -2.51327412e-01 # -1.25663706e-01 4.44089210e-15 1.25663706e-01 2.51327412e-01 # 3.76991118e-01 5.02654825e-01 6.28318531e-01 7.53982237e-01 # 8.79645943e-01 1.00530965e+00 1.13097336e+00 1.25663706e+00 # 1.38230077e+00 1.50796447e+00 1.63362818e+00 1.75929189e+00 # 1.88495559e+00 2.01061930e+00 2.13628300e+00 2.26194671e+00 # 2.38761042e+00 2.51327412e+00 2.63893783e+00 2.76460154e+00 # 2.89026524e+00 3.01592895e+00 3.14159265e+00]
まずは今回使用するデータ。今回は三角関数を使うのでπを基準に横軸のデータを作成した。範囲は-πから+πで0.04π刻み。
大雑把な刻みにするとグラフになめらかさがなくなるが、細かくするとデータが重くなるのでいい塩梅のところを選択。
この横軸の値から以下のように9種類の縦軸の値を作成。順番はグラフ化した時の関係でちょっとイレギュラーになっている。
- 直線(1次関数)
- 無理関数(ルート)
- 対数関数
- 2次関数
- sin関数
- cos関数
- 3次関数
- 指数関数
- sin関数と指数関数の組み合わせで減衰系のプロットを再現
# yとして使用する関数 ys = { 'linear': x, # 1次関数 'unreasonable': np.sqrt(x), # 無理関数 'log': np.log(x), # log関数 'quad': x ** 2, # 2次関数 'sin': np.sin(x), # sin関数 'cos': np.cos(x), # cos関数 'cubic': x ** 3, # 3次関数 'exponential': np.exp(x), # 指数関数 # めんどい関数 '5sin(8x)*e<sup>-0.5x</sup>': 5 * np.sin(8 * x) * np.exp(-0.5 * x), }
また、アニメーションの場合はグラフの表示範囲を設定する必要がある。横軸はx
の最小値・最大値から調節するが縦軸はys
の値から以下のように設定した。
# 各関数の表示範囲 ranges = { 'linear': (-4, 4), 'unreasonable': (-4, 4), 'log': (-4, 4), 'quad': (-2, 10), 'sin': (-2, 10), 'cos': (-2, 10), 'cubic': (-35, 35), 'exponential': (-35, 35), '5sin(8x)*e<sup>-0.5x</sup>': (-35, 35), }
ys
の関数の順番が変なのはこの表示範囲に関係している。今回は3 x 3のグラフを作成するが、ys
の上から3つずつで同じくらいの縦軸の値にしている。
こうすることで、軸を共有した時に見た目がキレイになる。一方で全てのグラフでスケールが一緒というわけではないので比較するという目的では適さない。
全ys
を1つのグラフにしたのが以下。save
関数はこの後で解説する自作の関数。グラフの保存を担う。
data = [ go.Scatter( x=x, y=y, name=name, mode='lines+markers') for name, y in ys.items() ] fig = go.Figure(data=data) # fig.show() save(fig=fig, config=None, save_name='all_plot')
グラフを作成するための関数たち
ここでは予め定義しておく関数を紹介しておく。先程のsave
関数のようなもの。予め関数を定義しておくとメインの部分でコードがごちゃごちゃしなくなってスッキリする。
また、複数箇所で同じコードを使用するときも関数があればその関数を使えばいい。修正するときも関数だけ修正したら全てで反映される。
-
-
【plotly&工夫】楽にグラフを描くためのplotlyの関数化
続きを見る
サブプロット作成用の関数
# subplotの雛形 def set_subplots(rows, cols): # sibplot用のfig作成 fig = make_subplots( rows=rows, cols=cols, # 行数と列数 shared_xaxes=True, # 横軸を共有 shared_yaxes=True, # 縦軸を共有 subplot_titles=list(ys), # 各グラフのタイトル ) return fig
まずはサブプロット作成用の関数。make_subplots
を使用する際には予めサブプロットをすることを明記しておかないといけない。
rows
とcols
で行数と列数を指定する。今回は3 x 3にする。shared_xaxes
, shared_yaxes
は横軸・縦軸を他のグラフを共有するか否か。共有すると軸ラベルと目盛が左端と下端だけになりスッキリする。
subplot_title
は各サブプロットのグラフのタイトル。ここで設定しないといけない。今回はys
のkeys
を使用し、各関数の名称をタイトルとした。
ボタン作成用の関数
# ボタン内容の設定 def set_buttons(): # ボタン内容の設定 transition = {'duration': 100, 'easing': 'linear'} animation = { 'frame': {'duration': 20, 'redraw': False}, 'transition': transition, } # ボタンの細かい設定 args = [None, animation] buttons = [dict(label='Play', method='animate', args=args,)] return buttons
アニメーションの開始ボタンを作成するためにはボタンの設定が必要。ということで、ボタン用の関数を定義。
duration
はコマごとの遅延。easing
はコマが進むときの進み方。スッと進むのかバウンスしながら進むのかなど設定可能。
-
-
【plotly&animation】動き方のeasingと遅延のduration
続きを見る
redraw
はコマごとにプロットの再描画をするかどうか。Falseにするとプロットの追加のみを行う。
グラフ保存用の関数
# グラフ保存用の関数 def save(fig, config, save_name): pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html", config=config,) pio.write_image(fig, f"{save_name}.png")
まずはグラフ保存用の関数。保存形式はhtmlとpng。config
はグラフの右上に存在するツールバーのようなもの。
config
を設定しておくとこのツールバーの内容を追加したり削除したりできる。htmlで使用することで、保存後のhtmlファイルでもツールバーの設定が引き継がれる。
-
-
【plotly&config】グラフのツールバーを編集する
続きを見る
今回はサクッと作りたいだけなのでconfigは使用しない。なのでconfig=None
として引数を処理することにする。
アニメーションのsubplot
グラフを作成
ということで、アニメーションのsubplotグラフを作成。大まかな流れは以下。
- サブプロットのベース作成
- 各サブプロットの行と列を指定
- 初期表示のプロット作成
- サブプロットのどのグラフに描画するか設定
- 初期値作成のついでに各グラフの表示範囲も設定
- ボタンを作成+ボタンをレイアウトに配置
- 各
x
ごとに全ys
のプロットでそのx以前までのデータをプロット(軌跡の描画) - データをフレームに格納
- フレームをグラフに適用
- グラフの表示と保存
項目としては多いけど、内容的には1行で済むものも多いので意外とスッキリする。
サブプロットの各点はxごとにそれまでのxで描かれた各ysのプロットを描くことで軌跡のようにプロットすることができる。
なお、アニメーションのプロットは左上から降ってくるようなので、途中までプロットされない無理関数(unreasonable)と対数関数(log)では左上に謎の点が出現する。
この謎の点の動きが知りたい場合は、mode='text'
にして好きなテキスト(以下のコードでは各関数名)を指定すると動きがよくわかるようになる。
def animation_subplot(save_name): fig = set_subplots(rows=3, cols=3,) # 初期データの作成 num = 0 for num, (name, y) in enumerate(ys.items()): row = num // 3 + 1 # 行の位置 col = num % 3 + 1 # 列の位置 # 初期表示のプロットは各関数の最初の値 d = go.Scatter(x=(x[0],), y=(y[0],), name=name) fig.append_trace(d, row, col) # グラフをグリッドに配置 # 各グリッドの表示範囲を決める dct = dict(row=row, col=col) fig.update_xaxes(range=(-3.5, 3.5), **dct) fig.update_yaxes(range=ranges[name], **dct) # ボタン全体の設定 updatemenus = dict(type='buttons', buttons=set_buttons(),) # レイアウトにボタンを配置する fig.update_layout(showlegend=False, updatemenus=[updatemenus]) # アニメーションの設定 data = [] # 各xごとにそれ以前の各関数の値を含めたプロットを格納 frames = [] for num, _ in enumerate(x): plot = [] # 各xごとの各関数のプロット点を格納 for name, y in ys.items(): d = go.Scatter( x=x[:num + 1], y=y[:num + 1], # 1点ずつプロットが増えるようにする name=name, mode='lines+markers', # mode='text', text=name # 関数名をテキストにしたい場合 ) plot.append(d) # 過去の分も含めてフレームに入れる data.append(go.Frame(data=plot, name=num)) frames.append(data) # フレームをfigに適用 fig['frames'] = frames[0] # ムダにlistがあるから外す # グラフの表示と保存 fig.show() save(fig=fig, config=None, save_name=save_name) animation_subplot(save_name='animation_subplot')
複数グラフの動きを見るには良い
今回はplotly
のgo
を使用して、アニメーション機能を持ったサブプロットを作成した。これで変化を気にしなければいけないような複数グラフを一気に閲覧することができる。
また、シンプルに移動した先の点だけをプロットするのではなく、それまでのプロットも軌跡としてプロットすることで、さらに変化がわかりやすいだろう。
今回のグラフを強化したいなら、一時停止ボタンや各フレームのスライダーの追加だろう。ここを設定するとなるとかなり面倒だができると機能性が爆上げするので是非とも挑戦していただきたい。
関連記事
-
-
【plotly&go.Scatter】plotlyの散布図グラフの描き方
続きを見る
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
-
-
【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加
続きを見る
-
-
【plotly&レーダーチャート】plotlyのRadar Chartの使い方とか設定とか
続きを見る