こんな人にオススメ
plotly
でアニメーションができるらしいけどどうやるの?
Plotly公式の「Intro to Animations in Python」を見てもよくわからん。
ということで、今回はplotly.graph_objects
、通称go
を使用してアニメーション機能を持つグラフを作成する。アニメーションってのはグラフ上のボタンを押すと自動でプロット内容が変わるグラフのこと。
go
じゃなくてplotly.express
、通称px
を使用する方法もあるけど、正直こっちの方が作成は簡単。その代わりにグラフのカスタム性は低い。
px
で描いたアニメーショングラフについては以下参照。
-
-
【px&animation】plotly.expressでアニメーションのグラフを作成
続きを見る
アニメーション機能付きのグラフは作成が面倒なことが多いけど、本記事では各部分を分解して解説するので、少しでも理解していただけると幸いだ。
なお、アニメーション機能を使うにはPlotly
のボタンを機能を使う。ボタン機能については以下の記事参照。
-
-
【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加
続きを見る
Python環境は以下。
- Python 3.10.1
- pandas 1.3.5
- plotly 5.4.0
- plotly-orca 3.4.2
作成したコード全文
下準備
import pandas as pd import plotly.graph_objects as go import plotly.io as pio import plotly.express as px
まずは下準備のimport
関連。pandas
はデータを読み込む際に使用。pandas
じゃなくてもいいけど、pandas
が一番データの処理が楽だと思う。
今回はgo
を使用するけど、px
での作成方法も軽く解説するのでpx
もimport
。pioはグラフ保存用に使用する。詳しくは以下の記事参照。
-
-
【plotly&orca】plotlyで静止画保存(orca)
続きを見る
再生ボタンだけのアニメーショングラフ
まずはPlotly
公式の「Intro to Animations in Python」で作成されている「Simple Play Button」のグラフから。このグラフには再生ボタンの「Play
」が設置されている。
公式のコードにコメントを追記したのが以下だが、ちょっとわかりにくい。ということで、次の項目でこのコードを分解して解説する。
なお、公式のグラフのままだとフォントサイズが小さいので、fig
作成後にfig.update_layout
でフォントサイズを変更している。fig.update
シリーズについては以下の記事参照。
-
-
【plotly&fig作成と更新】add_traceやupdate_layoutの使い方
続きを見る
# 再生ボタンだけのアニメーション fig = go.Figure( # データの作成 data=[go.Scatter(x=[0, 1], y=[0, 1])], # レイアウトの設定 layout=go.Layout( # 横軸・縦軸の範囲設定 xaxis=dict(range=[0, 5], autorange=False), yaxis=dict(range=[0, 5], autorange=False), # グラフタイトル title="Start Title", # 再生用のボタン作成 updatemenus=[dict( type="buttons", # ボタンの形式 buttons=[dict(label="Play", # ボタンのラベル method="animate", # ボタンの役割はアニメーション # ボタンを押して変更する内容(今回はなし) args=[None])])] ), # アニメーションの各フレームの設定 frames=[go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]), go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]), # 最後のフレームが終わればグラフタイトルを変更 go.Frame(data=[go.Scatter(x=[3, 4], y=[3, 4])], layout=go.Layout(title_text="End Title"))] ) # グラフ全体とホバーのフォントサイズ変更(追記) fig.update_layout(font_size=20, hoverlabel_font_size=20) # グラフの表示 fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_play" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
Simple Play Buttonのコードを分解
先程のコードを書く部分に分解してグラフを作成。コードを分解しただけだから同じグラフができる。
グラフの作成手順は以下の10ステップ。
- データの作成(
data
) - プロットに変換(
plot
) - ボタンの挙動変更(
buttons
) - buttonsをレイアウトに反映させる設定(
updatemenus
) - レイアウトの設定(
layout
) - アニメーションフレーム作成(
frames
) - グラフ作成(
fig
) - フォントサイズ変更(追記分、
fig.update_layout
) - グラフ表示(
fig.show()
) - グラフ保存(
prefix
以下)
複雑に見えるけど、分解してみるとシンプル。データ作成はgo.Scatter
で散布図を設定、ボタン作成では単にボタンをレイアウトに設定するだけ。
レイアウトではグラフの表示範囲をrange
で指定、autorange
で自動調節を無効にしている。 autorange=True
にするとアニメーションの各フレームごとにグラフ範囲が変更される可能性がある。
-
-
【plotly&go.Scatter】plotlyの散布図グラフの描き方
続きを見る
# さっきのグラフを分解する data = go.Scatter(x=[0, 1], y=[0, 1]) # 初期表示データ plot = [data] # listにする # ボタンの挙動 buttons = dict( label='Play', # ボタンのラベル method='animate', # ボタンの形式 # ボタンを押した時のプロット・レイアウトの変更点(今回はなし) args=[None] ) # レイアウトにボタンを配置 updatemenus = dict( type='buttons', # ボタンの形式はボタン式に buttons=[buttons] # ボタンの設定はここに ) # レイアウトの設定 layout = go.Layout( # 横軸・縦軸の表示範囲 # autorange=Trueにすると各フレームで自動で表示範囲が調整されてしまう xaxis=dict(range=[0, 5], autorange=False), yaxis=dict(range=[0, 5], autorange=False), title='Start Title', # 初期からフレーム終了までのグラフタイトル updatemenus=[updatemenus], # ボタンのレイアウト設定はここに ) # アニメーションの各フレームの設定 frames = [ # (x1, y1)=(1, 1), (x2, y2)=(2, 2)の直線 go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]), # (x1, y1)=(1, 1), (x2, y2)=(4, 4)の直線 go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]), # (x1, y1)=(3, 3), (x2, y2)=(4, 4)の直線 go.Frame( data=[go.Scatter(x=[3, 4], y=[3, 4])], # アニメーション終了でグラフタイトルを変更 layout=go.Layout(title_text='End Title'), ) ] fig = go.Figure( data=plot, # データ部分 layout=layout, # レイアウト部分 frames=frames, # アニメーション部分 ) # グラフ全体とホバーのフォントサイズ変更(追記) fig.update_layout(font_size=20, hoverlabel_font_size=20) fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_play_divide" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
go.Frame
でフレームを作成
# アニメーションの各フレームの設定 frames = [ # (x1, y1)=(1, 1), (x2, y2)=(2, 2)の直線 go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]), # (x1, y1)=(1, 1), (x2, y2)=(4, 4)の直線 go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]), # (x1, y1)=(3, 3), (x2, y2)=(4, 4)の直線 go.Frame( data=[go.Scatter(x=[3, 4], y=[3, 4])], # アニメーション終了でグラフタイトルを変更 layout=go.Layout(title_text='End Title'), ) ]
で、重要なのが変数frames
のアニメーションの各フレームの設定。list
形式で各フレームで表現したいグラフの内容やレイアウトの設定を格納する。今回は3種類。
go.Frame
の引数data
でデータ内容を、layout
でレイアウト内容を設定可能。frames
を設定できたら、最後にこれをgo.Figure
の引数frames
に入れるだけ。
なお、今回は最後のフレームを処理する際にtitle_text='End Title'
でグラフタイトルを変更している。layout
を指定しない場合はレイアウトの変更はない。
初期プロットもアニメーション化
気づいた方もいるかもしれないが、さっきのアニメーショングラフだと、初期プロットは「Play
」ボタンを押しても表示されない。あくまでもgo.Frame
で指定したプロットのみ表示される。
本記事に載せているグラフはサイトを開いた時点で自動的にアニメーションが動いてしまうからわからんが、Plotly
公式の「Intro to Animations in Python」の「Simple Play Button」だと初期プロットの存在が確認できる。
初期プロットもアニメーションに組み込みたいって場合は、frames
のgo.Frame
に初期プロットの設定を追記したらいい。ただし、初期プロット→フレームの順番になるから、アニメーションが一瞬遅くなる。
# 初期グラフもアニメーションに fig = go.Figure( # データの作成 data=[go.Scatter(x=[0, 1], y=[0, 1])], # レイアウトの設定 layout=go.Layout( # 横軸・縦軸の範囲設定 xaxis=dict(range=[0, 5], autorange=False), yaxis=dict(range=[0, 5], autorange=False), # グラフタイトル title="Start Title", # 再生用のボタン作成 updatemenus=[dict( type="buttons", # ボタンの形式 buttons=[dict(label="Play", # ボタンのラベル method="animate", # ボタンの役割はアニメーション # ボタンを押して変更する内容(今回はなし) args=[None])])] ), # アニメーションの各フレームの設定 frames=[ # 初期グラフ分をアニメーションに組み込む go.Frame(data=[go.Scatter(x=[0, 1], y=[0, 1])]), go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]), go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]), # 最後のフレームが終わればグラフタイトルを変更 go.Frame(data=[go.Scatter(x=[3, 4], y=[3, 4])], layout=go.Layout(title_text="End Title"))] ) # アニメーションの各フレームの設定 frames=[ # 初期グラフ分をアニメーションに組み込む go.Frame( data=[go.Scatter(x=[0, 1], y=[0, 1])], layout=go.Layout(title_text='Start Title')), go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]), go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]), # 最後のフレームが終わればグラフタイトルを変更 go.Frame(data=[go.Scatter(x=[3, 4], y=[3, 4])], layout=go.Layout(title_text='End Title'))] ) # グラフ全体とホバーのフォントサイズ変更(追記) fig.update_layout(font_size=20, hoverlabel_font_size=20) # グラフの表示 fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_play_initial" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
px
でもアニメーショングラフを作成
ここまでgo
を使ってアニメーショングラフを作成したが、px
を使っても作成可能。詳しくは以下の記事にて解説しているが、px
の方が楽に作成できる。
-
-
【px&animation】plotly.expressでアニメーションのグラフを作成
続きを見る
px
の場合は単にデータを配列で指定するのではなく、pandas
のデータフレームを使用するのが一般的であり簡単。ということで、ここでもpandas
のデータフレームを使うことにする。
なお、作成されたグラフには自動で再生/一時停止のボタンやスライダーが表示される。取り付ける手間がないのが魅力的。
# pxでもアニメーションを作成 data = { 'x': [ 0, 1, # フレーム1 1, 2, # フレーム2 1, 4, # フレーム3 3, 4, # フレーム4 ], 'y': [ 0, 1, # フレーム1 1, 2, # フレーム2 1, 4, # フレーム3 3, 4, # フレーム4 ], 'frame': [ 0, 0, # フレーム1 1, 1, # フレーム2 2, 2, # フレーム3 3, 3, # フレーム4 ], } # 同じframe列の値が1つのフレームのプロット df = pd.DataFrame(data) print(df) # x y frame # 0 0 0 0 # 1 1 1 0 # 2 1 1 1 # 3 2 2 1 # 4 1 1 2 # 5 4 4 2 # 6 3 3 3 # 7 4 4 3 # グラフの作成 fig = px.line( markers=True, # マーカーの追加 data_frame=df, x='x', y='y', animation_frame='frame', # フレームを区別する列名 range_x=[0, 5], # 横軸の表示範囲 range_y=[0, 5], # 縦軸の表示範囲 ) fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_px" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
duration
とeasing
で遅延と動作を変更
ボタンの引数args
で、各フレームの遅延とフレーム間の動き方を変更することが可能。詳しくは以下の記事で解説している。
-
-
【plotly&animation】動き方のeasingと遅延のduration
続きを見る
上のグラフでは各フレーム間の遅延を長くして動作をゆっくりに、さらにフレーム間の動き方をbounce-in-out
でバウンスするように変更した。
duration
やeasing
についてはPlotly
公式でもあまり解説されていないので、手探りで情報をかき集めるしかないのがネックな点だ。
# easingとdurationを変更 data = go.Scatter(x=[0, 1], y=[0, 1]) # 初期表示データ plot = [data] # listにする # ボタンの挙動 buttons = dict( label='Play', # ボタンのラベル method='animate', # ボタンの形式 # ボタンを押した時のプロット・レイアウトの変更点 args=[ None, # プロットの内容は変更なし # フレームの遅延と再描画のオフ {'frame': {'duration': 1500, 'redraw': False}, 'fromcurrent': True, # 現在のフレームから再生 # フレーム間の繊維の設定 'transition': { 'duration': 300, # フレーム間の遅延 'easing': 'bounce-in-out' # フレーム間の移動方法 }} ] ) # レイアウトにボタンを配置 updatemenus = dict( type='buttons', # ボタンの形式はボタン式に buttons=[buttons] # ボタンの設定はここに ) # レイアウトの設定 layout = go.Layout( # 横軸・縦軸の表示範囲 # autorange=Trueにすると各フレームで自動で表示範囲が調整されてしまう xaxis=dict(range=[0, 5], autorange=False), yaxis=dict(range=[0, 5], autorange=False), title='Start Title', # 初期からフレーム終了までのグラフタイトル updatemenus=[updatemenus], # ボタンのレイアウト設定はここに ) # アニメーションの各フレームの設定 frames = [ # (x1, y1)=(1, 1), (x2, y2)=(2, 2)の直線 go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]), # (x1, y1)=(1, 1), (x2, y2)=(4, 4)の直線 go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]), # (x1, y1)=(3, 3), (x2, y2)=(4, 4)の直線 go.Frame( data=[go.Scatter(x=[3, 4], y=[3, 4])], # アニメーション終了でグラフタイトルを変更 layout=go.Layout(title_text='End Title'), ) ] fig = go.Figure( data=plot, # データ部分 layout=layout, # レイアウト部分 frames=frames, # アニメーション部分 ) # グラフ全体とホバーのフォントサイズ変更(追記) fig.update_layout(font_size=20, hoverlabel_font_size=20) fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_args" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
各フレームでレイアウトを変更
今度は各フレームでレイアウトを変更してみた。やっていることは簡単で、frames
の各go.Frame
のlayout
の内容を変更するだけ。
ここではグラフタイトルやグラフテーマ(テンプレート)、軸ラベルを変更した。グラフテーマ(テンプレート)については以下の記事で解説している。
-
-
【plotly&グラフテーマ】plotly既存のthemasをグラフ化
続きを見る
注意点は一度設定したdata
, layout
の内容は上書きしない限り残り続けるということ。2つ目のフレームではxaxis_title='second frame'
で横軸ラベルを追加しているが、一度表示されると再度Play
しても消えることはない。
一方で、title_text='first frame’
やtemplate='ggplot2’
は最後のフレームでそれぞれtitle_text='End Title’
、template='plotly’
に変更しているので表示が変わる。
なので、途中で変更した設定を消したい(非表示にしたい)場合はその旨を設定する必要がある。
# 各フレームでレイアウトを変更 data = go.Scatter(x=[0, 1], y=[0, 1]) # 初期表示データ plot = [data] # listにする # ボタンの挙動 buttons = dict( label='Play', # ボタンのラベル method='animate', # ボタンの形式 # ボタンを押した時のプロット・レイアウトの変更点(今回はなし) args=[None] ) # レイアウトにボタンを配置 updatemenus = dict( type='buttons', # ボタンの形式はボタン式に buttons=[buttons] # ボタンの設定はここに ) # レイアウトの設定 layout = go.Layout( # 横軸・縦軸の表示範囲 # autorange=Trueにすると各フレームで自動で表示範囲が調整されてしまう xaxis=dict(range=[0, 5], autorange=False), yaxis=dict(range=[0, 5], autorange=False), title='Start Title', # 初期からフレーム終了までのグラフタイトル updatemenus=[updatemenus], # ボタンのレイアウト設定はここに ) # アニメーションの各フレームの設定 frames = [ # (x1, y1)=(1, 1), (x2, y2)=(2, 2)の直線 go.Frame( data=[go.Scatter(x=[1, 2], y=[1, 2])], layout=go.Layout( title_text='first frame', # グラフタイトル template='ggplot2', # グラフのテンプレート(テーマ) ) ), # (x1, y1)=(1, 1), (x2, y2)=(4, 4)の直線 go.Frame( data=[go.Scatter(x=[1, 4], y=[1, 4])], layout=go.Layout(xaxis_title='second frame') # 横軸ラベル ), # (x1, y1)=(3, 3), (x2, y2)=(4, 4)の直線 go.Frame( data=[go.Scatter(x=[3, 4], y=[3, 4])], # アニメーション終了でグラフタイトルを変更 layout=go.Layout( title_text='End Title', template='plotly', # グラフのテンプレート(テーマ) ), ) ] fig = go.Figure( data=plot, # データ部分 layout=layout, # レイアウト部分 frames=frames, # アニメーション部分 ) # グラフ全体とホバーのフォントサイズ変更(追記) fig.update_layout(font_size=20, hoverlabel_font_size=20) fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_layout" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
アニメーショングラフにスライダーとボタンを追加
最後はPlotly
公式の「Intro to Animations in Python」の「Using a Slider and Buttons」で作成されているグラフについて。
このグラフはアニメーションってだけじゃなくて、「Play
」「Pause
」ボタンにスライダーも追加されている。px
だと簡単に追加できたが、go
の場合はかなり大変。
以下にPlotly
公式のコードにコメントなどを追加したコードを示す。かなり長いのでコードを確認する際には注意。
なお、上のグラフを作成するためのステップは大まかには以下。
- データの読み込み
- アニメーションのフレームと大陸名の定義
fig
設定用のdict
作成- レイアウト設定
- ボタンの作成
- スライダーの作成
- 初期表示用のプロット作成
- アニメーションのフレーム作成
- グラフ表示と保存
以下より各部分の解説を行う。
使用するデータ
url = "<https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv>" dataset = pd.read_csv(url) print(dataset) # country year pop continent lifeExp gdpPercap # 0 Afghanistan 1952 8425333.0 Asia 28.801 779.445314 # 1 Afghanistan 1957 9240934.0 Asia 30.332 820.853030 # 2 Afghanistan 1962 10267083.0 Asia 31.997 853.100710 # 3 Afghanistan 1967 11537966.0 Asia 34.020 836.197138 # 4 Afghanistan 1972 13079460.0 Asia 36.088 739.981106 # ... ... ... ... ... ... ... # 1699 Zimbabwe 1987 9216418.0 Africa 62.351 706.157306 # 1700 Zimbabwe 1992 10704340.0 Africa 60.377 693.420786 # 1701 Zimbabwe 1997 11404948.0 Africa 46.809 792.449960 # 1702 Zimbabwe 2002 11926563.0 Africa 39.989 672.038623 # 1703 Zimbabwe 2007 12311143.0 Africa 43.487 469.709298 # [1704 rows x 6 columns]
まずは使用するデータについて。使用するのはGapminderの国名・年・人口・大陸・寿命・GDPのデータ。Gapminderは「FACTFULNESS」で有名な財団。
このブログでも以下の記事でデータを使用している。このデータをpandas
のpd.read_csv
で読み込んだ。
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く
続きを見る
-
-
【plotly&アニメーション】plotlyで各国の収入と平均寿命の時代変化をバブルチャート&アニメーションで描く
続きを見る
アニメーションフレームの年と大陸を定義
# アニメーションのフレームとなるのは年 years = ["1952", "1962", "1967", "1972", "1977", "1982", "1987", "1992", "1997", "2002", "2007"] # 各大陸名を入れたlistを作成 continents = [] for continent in dataset["continent"]: if continent not in continents: continents.append(continent) print(continents) # ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']
アニメーションのフレームの基準となる年を変数years
で定義。データフレームから取ってきてもいい気がする。
また、変数continents
で各大陸名称を取ってきている。continents
の型はlist
。なお、以下のように.unique
を使うことでも取り出せる。この時は型が<class 'numpy.ndarray'>
となる。
print(dataset['continent'].unique()) # ['Asia' 'Europe' 'Africa' 'Americas' 'Oceania']
figure
作成用のdict
を定義
# figure用のdictを作成 fig_dict = { 'data': [], 'layout': {}, 'frames': [] } # レイアウトを編集 # 横軸の表示範囲をと軸ラベルを設定 fig_dict['layout']['xaxis'] = {'range': [30, 85], 'title': 'Life Expectancy'} # 縦軸ラベルを設定し、縦軸をlogに fig_dict['layout']['yaxis'] = {'title': 'GDP per Capita', 'type': 'log'} # マウスホバー時にはカーソルに一番近い点を表示 fig_dict['layout']['hovermode'] = 'closest'
続いてはfig
を作成するためのdict
を定義。後に解説するが、この変数fig_dict
をgo.Figure
でfig
に変換する。 dict
から作成する方が使い勝手がいいからdict
を使用すると思う。
dict
の中身はデータのdata
、レイアウトのlayout
、アニメーションのフレームのframes
の3つ。そして、data
とframes
はlist
にして各データ・各フレームを格納。レイアウトはそのままkeys
とvalues
で設定。
また、上記コードの最後の部分でレイアウトの設定を変更している。軸ラベルやhovermode
は好みだが、軸の表示範囲は設定しておかないと、表示範囲がプロットの最小・最大値に応じて自動で変わって見づらい。
# レイアウトを編集 # 横軸の表示範囲をと軸ラベルを設定 fig_dict['layout']['xaxis'] = {'range': [30, 85], 'title': 'Life Expectancy'} # 縦軸ラベルを設定し、縦軸をlogに fig_dict['layout']['yaxis'] = {'title': 'GDP per Capita', 'type': 'log'} # マウスホバー時にはカーソルに一番近い点を表示 fig_dict['layout']['hovermode'] = 'closest'
この設定で変更しているのは以下の3種類。
xaxis
: 表示範囲と軸ラベルyaxis
: 軸ラベルと軸の種類hovermode
: マウスオーバー時の挙動
ボタンの設定
# ボタンの設定 fig_dict['layout']['updatemenus'] = [ { 'buttons': [ # 再生用ボタンを作成 { 'args': [ None, # プロットの内容は変更なし # フレームの遅延と再描画のオフ {'frame': {'duration': 500, 'redraw': False}, 'fromcurrent': True, # 現在のフレームから再生 # フレーム間の繊維の設定 'transition': { 'duration': 300, # フレーム間の遅延 'easing': 'quadratic-in-out' # フレーム間の移動方法 }} ], 'label': 'Play', # ボタン名 'method': 'animate' # ボタンの挙動の種類 }, # 停止用ボタンを作成 { 'args': [ [None], # プロットの内容は変更なし # フレームの遅延と再描画のオフ {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate', # フレーム間の繊維の設定 'transition': {'duration': 0}} # フレーム間の遅延 ], 'label': 'Pause', # ボタン名 'method': 'animate' # ボタンの挙動の種類 } ], 'direction': 'left', # 再生・一時停止ボタンを並べる向き 'pad': {'r': 10, 't': 87}, # プロット領域からの余白の量 'showactive': False, # 再生・一時停止ボタンを押した時にハイライトしない 'type': 'buttons', # ボタンの種類は独立したボタン 'x': 0.1, # ボタンを設置するxの値 'xanchor': 'right', # xの位置の基準 'y': 0, # ボタンを設置するyの値 'yanchor': 'top' # yの位置の基準 } ]
アニメーショングラフの肝となるボタンの設定。作成するボタンは再生の「Play
」と停止の「Pause
」ボタン。fig_dict['layout']['updatemenus']
のlist
の中で以下の9種類の引数を設定している。
buttons
: ボタンの内容設定direction
: ボタンの向きpad
: グラフのプロット部分とボタンの間隔(余白)showactive
: 再生/一時停止の時にボタンをハイライトするか否かtype
: ボタンの形式x
: ボタンの横位置xanchor
: ボタンの横位置の基準y
: ボタンの縦位置yanchor
: ボタンの縦位置の基準
2から9についてはコメントのままで理解しやすいと思う。pad
やx
, y
については各人で値を変更してみて挙動を確認するのもいいだろう。
1についてはややこしいので以下で取り出して解説する。buttons
の中にはさらにdict
が2つあり、それぞれがPlay
とPause
に対応。
各dict
のargs
がボタンの内容、label
がボタンのラベル、そしてmethod
がボタンの種類に対応する。
# 再生用ボタンを作成 { 'args': [ None, # プロットの内容は変更なし # フレームの遅延と再描画のオフ {'frame': {'duration': 500, 'redraw': False}, 'fromcurrent': True, # 現在のフレームから再生 # フレーム間の繊維の設定 'transition': { 'duration': 300, # フレーム間の遅延 'easing': 'quadratic-in-out' # フレーム間の移動方法 }} ], 'label': 'Play', # ボタン名 'method': 'animate' # ボタンの挙動の種類 }, # 停止用ボタンを作成 { 'args': [ [None], # プロットの内容は変更なし # フレームの遅延と再描画のオフ {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate', # フレーム間の繊維の設定 'transition': {'duration': 0}} # フレーム間の遅延 ], 'label': 'Pause', # ボタン名 'method': 'animate' # ボタンの挙動の種類 }
各args
の内容については既に上で解説したが、初めのNone
はプロットの内容は変更しないってことを意味する。
ただ、Play
ではNone
はNone
のままでPause
は[None]
であることに注意。Pause
の[None]
はNone
にしても問題がないが、Play
のNone
は[None]
にするとアニメーションが動かない。
None
の下のdict
がレイアウトやアニメーションの設定で、ここではアニメーション間の遅延やフレーム間の遷移の方法を設定している。
スライダーの設定
# スライダーの設定 sliders_dict = { 'active': 0, # 初期表示の際にアクティブにする位置 'yanchor': 'top', # yの位置の基準 'xanchor': 'left', # xの位置の基準 # 各スライダーのフレームごとのラベル(今回は年)の設定 'currentvalue': { 'font': {'size': 20}, # フォントサイズ 'prefix': 'Year:', # 接頭辞 'visible': True, # ラベルを表示する 'xanchor': 'right' # xの位置の基準 }, # スライダーの遷移の遅延と遷移の動き方(プロットとは独立して設定) 'transition': {'duration': 300, 'easing': 'cubic-in-out'}, 'pad': {'b': 10, 't': 50}, # プロット領域からの余白の量 'len': 0.9, # スライダーの長さ 'x': 0.1, # スライダーを設置するxの値 'y': 0, # スライダーを設置するyの値 'steps': [] # 各スライダーのフレームの設定はここに }
続いてはスライダーの設定。ここではスライダーの各種設定とスライダー内容を入れるためのlist(’steps’
)を設定。詳しいスライダーの解説については以下の記事参照。
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
スライダーの設定もfig
の場合と同じようにsliders_dict
でdict
を定義して内容を設定、その後にレイアウトのスライダーの項目に入れることでスライダーの設定を行う。
ここでの設定内容は以下。
active
: 初期表示でスライダーを置くインデックスyanchor
: スライダーの縦位置の基準xanchor
: スライダーの横位置の基準currentvalue
: 各スライダーのフレームの設定transition
: 各スライダーのフレームの遷移の設定pad
: グラフのプロット部分とスライダーの間隔(余白)len
: スライダーの長さx
: スライダーの横位置y
: スライダーの縦位置steps
: 各スライダーのフレームの設定
最後のsteps
に各スライダーのフレームの内容(どのデータをプロットするのかなど)を設定する。ここの設定は後述。アニメーションのフレーム作成と同時に行う。
初期表示用のプロット作成
# 初期表示用のプロットの作成 year = 1952 for continent in continents: # 1952年のデータを抽出 dataset_by_year = dataset[dataset['year'] == year] # 1952年のデータから各大陸のデータを抽出 dataset_by_year_and_cont = dataset_by_year[ dataset_by_year['continent'] == continent] # 初期表示用のプロットを作成 data_dict = { 'x': list(dataset_by_year_and_cont['lifeExp']), # xの値 'y': list(dataset_by_year_and_cont['gdpPercap']), # yの値 'mode': 'markers', # プロットはマーカーのみ 'text': list(dataset_by_year_and_cont['country']), # ホバーに国名を追加 'marker': { 'sizemode': 'area', # マーカーサイズの反映のさせ方 'sizeref': 200000, # マーカーサイズをスケーリングする引数 'size': list(dataset_by_year_and_cont['pop']) # マーカーサイズ }, 'name': continent # プロットの名称 } # figのデータに格納 fig_dict['data'].append(data_dict)
続いては初期表示用のプロット作成。アニメーションプロットでは、初期表示用のプロットとアニメーション中のプロットで分けないといけない。
今回は初期表示は一番古い1952年のデータを使いたいので、pandas
のデータフレームdataset
からdataset[dataset['year'] == year]
で1952年のデータフレーム内容を抽出。
1952年だけのデータフレームdataset_by_year
からさらにdataset_by_year['continent'] == continent]
で各大陸のデータを抽出している。dataset_by_year
、Oceaniaの場合のdataset_by_year_and_cont
は以下。
print(dataset_by_year) # country year pop continent lifeExp gdpPercap # 0 Afghanistan 1952 8425333.0 Asia 28.801 779.445314 # 12 Albania 1952 1282697.0 Europe 55.230 1601.056136 # 24 Algeria 1952 9279525.0 Africa 43.077 2449.008185 # 36 Angola 1952 4232095.0 Africa 30.015 3520.610273 # 48 Argentina 1952 17876956.0 Americas 62.485 5911.315053 # ... ... ... ... ... ... ... # 1644 Vietnam 1952 26246839.0 Asia 40.412 605.066492 # 1656 West Bank and Gaza 1952 1030585.0 Asia 43.160 1515.592329 # 1668 Yemen, Rep. 1952 4963829.0 Asia 32.548 781.717576 # 1680 Zambia 1952 2672000.0 Africa 42.038 1147.388831 # 1692 Zimbabwe 1952 3080907.0 Africa 48.451 406.884115 # [142 rows x 6 columns] print(dataset_by_year_and_cont) # Oceaniaの場合 # Oceania country year pop continent lifeExp gdpPercap # 60 Australia 1952 8691212.0 Oceania 69.12 10039.59564 # 1092 New Zealand 1952 1994794.0 Oceania 69.39 10556.57566
data_dict
もsliders_dict
などと同じように、 fig
に入れるために一時的にdictにしている。
で、ここが面白いところだが、data_dict
でx
やy
を設定するだけで自動でgo.Scatter
が設定される。わざわざgo.Scatter
と書いて散布図を設定する必要がない。
go.Scatter
とpx
での散布図px.scatter
については以下の記事で詳しく解説している。
-
-
【plotly&go.Scatter】plotlyの散布図グラフの描き方
続きを見る
-
-
【plotly&px.scatter】pxでの散布図の描き方
続きを見る
# 初期表示用のプロットを作成 data_dict = { 'x': list(dataset_by_year_and_cont['lifeExp']), # xの値 'y': list(dataset_by_year_and_cont['gdpPercap']), # yの値 'mode': 'markers', # プロットはマーカーのみ 'text': list(dataset_by_year_and_cont['country']), # ホバーに国名を追加 'marker': { 'sizemode': 'area', # マーカーサイズの反映のさせ方 'sizeref': 200000, # マーカーサイズをスケーリングする引数 'size': list(dataset_by_year_and_cont['pop']) # マーカーサイズ }, 'name': continent # プロットの名称 } # figのデータに格納 fig_dict['data'].append(data_dict)
設定内容はx
, y
で横軸・縦軸の値、mode
でプロットの種類、text
でマウスオーバー時の追加表示項目、marker
でマーカーの設定、最後にname
で各プロット(ここでは大陸名)の名前を設定している。
data_dict
の設定が終わったら、fig_dict
のdata
部分であるfig_dict[’data’]
に追加する。
アニメーションのフレームの設定
# アニメーションのフレーム作成 for year in years: # 各年のフレームを入れるためのdict frame = {'data': [], 'name': str(year)} for continent in continents: # years, yearはstrだからdatasetで検索する際にはintに変換 dataset_by_year = dataset[dataset['year'] == int(year)] # 各大陸のデータを抽出 dataset_by_year_and_cont = dataset_by_year[ dataset_by_year['continent'] == continent] # データの作成 data_dict = { 'x': list(dataset_by_year_and_cont['lifeExp']), 'y': list(dataset_by_year_and_cont['gdpPercap']), 'mode': 'markers', 'text': list(dataset_by_year_and_cont['country']), 'marker': { 'sizemode': 'area', 'sizeref': 200000, 'size': list(dataset_by_year_and_cont['pop']) }, 'name': continent } # frameのデータに各都市・各大陸のデータを格納 frame['data'].append(data_dict) # figのframeに各年・全大陸のframeを追加 fig_dict['frames'].append(frame) # スライダーの作成 slider_step = {'args': [ # スライダーとアニメーションを紐づけるのはyear [year], # listにしないとスライダーが動かない # frameの設定 {'frame': {'duration': 300, 'redraw': False}, 'mode': 'immediate', 'transition': {'duration': 300}} ], 'label': year, # スライダーのラベル 'method': 'animate'} # 作成したスライダーのstepに格納 sliders_dict['steps'].append(slider_step) # figのlayoutのslider設定に作成したスライダー内容を格納 fig_dict['layout']['sliders'] = [sliders_dict]
メインであるアニメーションのフレームの設定。各フレームは年で分けられているのでyearsをforで回している。で、各年のフレームのデータの設定と年の情報を変数frame
で定義している。
年毎にfor continent
で大陸をさらにfor
で回す。dataset_by_year
からframe['data'].append(data_dict)
までは初期表示プロット作成の時と同じ。単に1952年が各年も含めて作成されているだけ。
ここでの追加事項はframe['data'].append(data_dict)
の後のfig_dict['frames'].append(frame)
から後の部分。フレーム内容の設定と各フレームでのスライダーの設定。
# figのframeに各年・全大陸のframeを追加 fig_dict['frames'].append(frame) # スライダーの作成 slider_step = {'args': [ # スライダーとアニメーションを紐づけるのはyear [year], # listにしないとスライダーが動かない # frameの設定 {'frame': {'duration': 300, 'redraw': False}, 'mode': 'immediate', 'transition': {'duration': 300}} ], 'label': year, # スライダーのラベル 'method': 'animate'} # 作成したスライダーのstepに格納 sliders_dict['steps'].append(slider_step) # figのlayoutのslider設定に作成したスライダー内容を格納 fig_dict['layout']['sliders'] = [sliders_dict]
fig_dict['frames'].append(frame)
はシンプルにfig_dict
のフレーム部分である fig_dict['frames']
に変数frame
を追加。
slider_step
はスライダー設定の時に変数sliders_dict
で空のlist
の’steps’
を作成したが、ここに入れる内容の設定。スライダーの各設定については以下の記事で解説している。
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
ざっくり解説すると、以下の設定を行なっている。
args
: スライダーの設定label
: スライダーに表示するラベルmethod
: スライダーの種類
args
について注意することが、スライダーの[year]
とアニメーションのフレームframe = {'data': [], 'name': str(year)}
のname
が同じでないといけない点。
ここの値が異なっている場合はアニメーションは動くがスライダーは停止したままだったり、そもそもアニメーションが動かない可能性がある。
もしアニメーションがうまくいかないならこの2点を確認すると良いだろう。
fig
への変換と表示・保存
# fig_dictをgo.Figureに変換 fig = go.Figure(fig_dict) # グラフ全体とホバーのフォントサイズ変更(追記) fig.update_layout(font_size=20, hoverlabel_font_size=20) fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_slider_buttons" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
最後は作成したfig_dict
をgo.Figure
に変換し、追記分のフォントサイズ変更、そしてグラフの表示と保存。グラフの保存については以下の記事参照。
-
-
【plotly&orca】plotlyで静止画保存(orca)
続きを見る
長いコードは分解する
ということで作成されるグラフが上のグラフになるんだが、ここまで来るのにかなり苦労する。go
だと各設定の自由度が高い分、それぞれ設定しないといけない。
一方でpx
の場合は以下の記事でも書いたように、同様のグラフを簡単に作成可能。以下の記事で解説したpx
の方法で作成したグラフは次に紹介する。
-
-
【px&animation】plotly.expressでアニメーションのグラフを作成
続きを見る
px
でGapminderのグラフを作成
上で紹介したpx
でのアニメーションのグラフ作成方法を使って、今回作成したgo
のグラフを再現してみた。多少細かい部分は異なるが、これはレイアウトの設定次第でどうにでもなる。
px
での作成だとデータフレームが予め出来ていればグラフ化自体のコードはかなり短く済む。サクッと簡単にグラフができるのがpx
の魅力。
なお、px
の場合は設定出来る引数が少ないため、軸ラベルなどは別途fig.update_layout
などで設定する必要がある。
-
-
【plotly&fig作成と更新】add_traceやupdate_layoutの使い方
続きを見る
# pxでもアニメーショングラフを作成 df = px.data.gapminder() print(df) # country continent year ... gdpPercap iso_alpha iso_num # 0 Afghanistan Asia 1952 ... 779.445314 AFG 4 # 1 Afghanistan Asia 1957 ... 820.853030 AFG 4 # 2 Afghanistan Asia 1962 ... 853.100710 AFG 4 # 3 Afghanistan Asia 1967 ... 836.197138 AFG 4 # 4 Afghanistan Asia 1972 ... 739.981106 AFG 4 # ... ... ... ... ... ... ... ... # 1699 Zimbabwe Africa 1987 ... 706.157306 ZWE 716 # 1700 Zimbabwe Africa 1992 ... 693.420786 ZWE 716 # 1701 Zimbabwe Africa 1997 ... 792.449960 ZWE 716 # 1702 Zimbabwe Africa 2002 ... 672.038623 ZWE 716 # 1703 Zimbabwe Africa 2007 ... 469.709298 ZWE 716 # [1704 rows x 8 columns] fig = px.scatter( df, # 使用するデータフレーム x='lifeExp', y='gdpPercap', # 横軸・縦軸とする列名 animation_frame='year', # アニメーションにする列名 animation_group='country', # アニメーション時に同じグループにする列名 size='pop', color='continent', # サイズと色分けの基準とする列名 hover_name='country', # ホバーに表示する情報 log_y=True, # 縦軸をlogに size_max=55, # sizeの最大値 range_x=[30, 85], # 横軸の表示範囲 ) # 横軸・縦軸ラベルの変更 fig.update_layout(xaxis_title='Life Expectancy', yaxis_title='GDP per Capita') # グラフ全体とホバーのフォントサイズ変更 fig.update_layout(font_size=20, hoverlabel_font_size=20) # fig.show() # グラフ保存 prefix = 'go-animation' # 保存ファイル名の接頭辞 save_name = f"{prefix}_px_animation" pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html") pio.write_image(fig, f"{save_name}.png")
複雑なgo
のアニメーション
ということで、今回はPlotly
のgo
を使ったアニメーショングラフの作成方法について解説した。
go
でのアニメーショングラフは、再生ボタン作成だけならそこまで難しくない。しかし一時停止ボタンやスライダーをつけるとなるとかなり面倒。
したがって、アニメーションについてはgo
ではなくpx
を使うのが楽だろうという本末転倒な結論で本記事を締める。
関連記事
-
-
【plotly&マーカー】plotlyのマーカーのシンボル
続きを見る
-
-
【plotly&config】グラフのツールバーを編集する
続きを見る
-
-
【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加
続きを見る
-
-
【plotly&ボタン】plotlyのupdatemenusに2回押し対応のbuttonsを追加
続きを見る
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く
続きを見る
-
-
【plotly&animation】動き方のeasingと遅延のduration
続きを見る
-
-
【px&バブルチャート】plotly.expressで各国の収入と平均寿命の時代変化をアニメーションで
続きを見る
-
-
【plotly&rangeslider/rangeselector】レンジスライダーとレンジセレクターで時系列を見やすく
続きを見る