こんな人にオススメ
plotly
ってgo
とpx
があるけど、それぞれでボタン・スライダーを作成するのはどうしたらいいの?
書き方とかって異なる?
ということで、今回はplotly
のgo
(plotly.graph_objects
)とpx
(plotly.express
)でボタンつき、スライダーつきのグラフを作成する。
go
, px
それぞれでグラフを描く方法は異なるけど、ボタンについてはかなり似ている。スライダーはかなり異なる。
python環境は以下。
- Python 3.9.7
- pandas 1.3.3
- numpy 1.21.2
- plotly 5.3.1
- plotly-orca 3.4.2
作成したコード全文
下準備
import numpy as np import pandas as pd import plotly.graph_objects as go import plotly.express as px import plotly.io as pio
まずは下準備のimport
関連。今回は自作テンプレートは使用しない。シンプルにデフォルトのグラフを作成する。
-
-
【随時更新 備忘録】plotlyのグラフ即席作成コード
続きを見る
使用するデータ
# 横軸は-2から+2までを0.1刻み x = np.arange(-2, 2 + 0.1, 0.1) print(x) # [-2.00000000e+00 -1.90000000e+00 -1.80000000e+00 -1.70000000e+00 # -1.60000000e+00 -1.50000000e+00 -1.40000000e+00 -1.30000000e+00 # -1.20000000e+00 -1.10000000e+00 -1.00000000e+00 -9.00000000e-01 # -8.00000000e-01 -7.00000000e-01 -6.00000000e-01 -5.00000000e-01 # -4.00000000e-01 -3.00000000e-01 -2.00000000e-01 -1.00000000e-01 # 1.77635684e-15 1.00000000e-01 2.00000000e-01 3.00000000e-01 # 4.00000000e-01 5.00000000e-01 6.00000000e-01 7.00000000e-01 # 8.00000000e-01 9.00000000e-01 1.00000000e+00 1.10000000e+00 # 1.20000000e+00 1.30000000e+00 1.40000000e+00 1.50000000e+00 # 1.60000000e+00 1.70000000e+00 1.80000000e+00 1.90000000e+00 # 2.00000000e+00]
今回使用するデータは-2から2までを0.1刻みにした配列を横軸に、この横軸を1から5次関数として計算した配列を縦軸とする。
各○次関数はどのデータかがわかりやすいようにdict
に格納しておく。これで配列にラベルをつけることができる。
# 縦軸は1次関数から5次関数まで data = {} for order in range(1, 6): data[f"order{order}"] = x ** order print(data) # {'order1': array([-2.00000000e+00, -1.90000000e+00, -1.80000000e+00, -1.70000000e+00, # -1.60000000e+00, -1.50000000e+00, -1.40000000e+00, -1.30000000e+00, # -1.20000000e+00, -1.10000000e+00, -1.00000000e+00, -9.00000000e-01, # -8.00000000e-01, -7.00000000e-01, -6.00000000e-01, -5.00000000e-01, # -4.00000000e-01, -3.00000000e-01, -2.00000000e-01, -1.00000000e-01, # 1.77635684e-15, 1.00000000e-01, 2.00000000e-01, 3.00000000e-01, # 4.00000000e-01, 5.00000000e-01, 6.00000000e-01, 7.00000000e-01, # 8.00000000e-01, 9.00000000e-01, 1.00000000e+00, 1.10000000e+00, # 1.20000000e+00, 1.30000000e+00, 1.40000000e+00, 1.50000000e+00, # 1.60000000e+00, 1.70000000e+00, 1.80000000e+00, 1.90000000e+00, # 2.00000000e+00]), 'order2': array([4.00000000e+00, 3.61000000e+00, 3.24000000e+00, 2.89000000e+00, # 2.56000000e+00, 2.25000000e+00, 1.96000000e+00, 1.69000000e+00, # 1.44000000e+00, 1.21000000e+00, 1.00000000e+00, 8.10000000e-01, # 6.40000000e-01, 4.90000000e-01, 3.60000000e-01, 2.50000000e-01, # 1.60000000e-01, 9.00000000e-02, 4.00000000e-02, 1.00000000e-02, # 3.15544362e-30, 1.00000000e-02, 4.00000000e-02, 9.00000000e-02, # 1.60000000e-01, 2.50000000e-01, 3.60000000e-01, 4.90000000e-01, # 6.40000000e-01, 8.10000000e-01, 1.00000000e+00, 1.21000000e+00, # 1.44000000e+00, 1.69000000e+00, 1.96000000e+00, 2.25000000e+00, # 2.56000000e+00, 2.89000000e+00, 3.24000000e+00, 3.61000000e+00, # 4.00000000e+00]), 'order3': array([-8.00000000e+00, -6.85900000e+00, -5.83200000e+00, -4.91300000e+00, # -4.09600000e+00, -3.37500000e+00, -2.74400000e+00, -2.19700000e+00, # -1.72800000e+00, -1.33100000e+00, -1.00000000e+00, -7.29000000e-01, # -5.12000000e-01, -3.43000000e-01, -2.16000000e-01, -1.25000000e-01, # -6.40000000e-02, -2.70000000e-02, -8.00000000e-03, -1.00000000e-03, # 5.60519386e-45, 1.00000000e-03, 8.00000000e-03, 2.70000000e-02, # 6.40000000e-02, 1.25000000e-01, 2.16000000e-01, 3.43000000e-01, # 5.12000000e-01, 7.29000000e-01, 1.00000000e+00, 1.33100000e+00, # 1.72800000e+00, 2.19700000e+00, 2.74400000e+00, 3.37500000e+00, # 4.09600000e+00, 4.91300000e+00, 5.83200000e+00, 6.85900000e+00, # 8.00000000e+00]), 'order4': array([1.60000000e+01, 1.30321000e+01, 1.04976000e+01, 8.35210000e+00, # 6.55360000e+00, 5.06250000e+00, 3.84160000e+00, 2.85610000e+00, # 2.07360000e+00, 1.46410000e+00, 1.00000000e+00, 6.56100000e-01, # 4.09600000e-01, 2.40100000e-01, 1.29600000e-01, 6.25000000e-02, # 2.56000000e-02, 8.10000000e-03, 1.60000000e-03, 1.00000000e-04, # 9.95682444e-60, 1.00000000e-04, 1.60000000e-03, 8.10000000e-03, # 2.56000000e-02, 6.25000000e-02, 1.29600000e-01, 2.40100000e-01, # 4.09600000e-01, 6.56100000e-01, 1.00000000e+00, 1.46410000e+00, # 2.07360000e+00, 2.85610000e+00, 3.84160000e+00, 5.06250000e+00, # 6.55360000e+00, 8.35210000e+00, 1.04976000e+01, 1.30321000e+01, # 1.60000000e+01]), 'order5': array([-3.20000000e+01, -2.47609900e+01, -1.88956800e+01, -1.41985700e+01, # -1.04857600e+01, -7.59375000e+00, -5.37824000e+00, -3.71293000e+00, # -2.48832000e+00, -1.61051000e+00, -1.00000000e+00, -5.90490000e-01, # -3.27680000e-01, -1.68070000e-01, -7.77600000e-02, -3.12500000e-02, # -1.02400000e-02, -2.43000000e-03, -3.20000000e-04, -1.00000000e-05, # 1.76868732e-74, 1.00000000e-05, 3.20000000e-04, 2.43000000e-03, # 1.02400000e-02, 3.12500000e-02, 7.77600000e-02, 1.68070000e-01, # 3.27680000e-01, 5.90490000e-01, 1.00000000e+00, 1.61051000e+00, # 2.48832000e+00, 3.71293000e+00, 5.37824000e+00, 7.59375000e+00, # 1.04857600e+01, 1.41985700e+01, 1.88956800e+01, 2.47609900e+01, # 3.20000000e+01])}
このdict
データでgo
のプロットはできるが、px
についてはこのままだと使いにくいのでpandas
のデータフレームに変換しておく。
px
用にデータフレームに変換
px_data = {'x': x.tolist() * len(data)} # データをまとめる用のdict px_y = [] # px用のyの値 order = [] # px用のオーダーの値 # 各データごとにdictに格納 for num, val in enumerate(data.values(), 1): px_y.append(val.tolist()) # listに変換して後から展開 order.append([f"{num}"] * len(val)) # 各データの数だけオーダーの値も複製
データフレームの列は横軸のx
、縦軸のy
、そして各次数のorder
を名称とする。order
は1次関数のデータが続いている間はずっと1
を示し続ける。
order
を設定しておくことで、どこまでが1次関数なのかというような判断が可能となる。ただし、order
は文字列ではなく数値にしておくと、凡例作成時に自動でカラーバーができてしまうので注意。
また、上のコードだとpx_y
とorder
はそれぞれ2次元配列になっている。
# 現状だと2次元配列になっているから展開して1次元配列に print(order) # [['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'], ['2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2'], ['3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3'], ['4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4'], ['5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5']]
なので1次元配列に変換する。そうしないとデータフレームにするのが面倒になる。
px_y = sum(px_y, []) # yの値を展開 order = sum(order, []) # オーダーの値を展開 print(order) # ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5']
あとはできたデータをdict
に入れてからデータフレームに変換。これでpx
用のデータフレームが完成。
# データをdictに入れる px_data['y'] = px_y px_data['order'] = order # データフレームに変換 df = pd.DataFrame(px_data) print(df) # x y order # 0 -2.0 -2.00000 1 # 1 -1.9 -1.90000 1 # 2 -1.8 -1.80000 1 # 3 -1.7 -1.70000 1 # 4 -1.6 -1.60000 1 # .. ... ... ... # 200 1.6 10.48576 5 # 201 1.7 14.19857 5 # 202 1.8 18.89568 5 # 203 1.9 24.76099 5 # 204 2.0 32.00000 5 # [205 rows x 3 columns]
諸関数の定義
ここではグラフ作成に使用する関数を定義する。以下の2つを定義する。
- ボタン・スライダーの内容作成の関数
- グラフ保存用の関数
ボタン・スライダーの内容作成の関数
# ボタン・スライダー作成 def set_menus(): # ボタン・スライダーの内容を作成 length = len(data) # ボタン・スライダーは全プロット表示と各プロット表示で2つに分けて作成 # 全プロット表示用のボタン・スライダー作成 visible = [True] * length menu = dict( label='All', method='update', args=[dict(visible=visible), dict(title='All')] ) menus = [menu] # ボタン・スライダー全体の設定のlistに格納 # 各プロットのみを表示するためのボタン・スライダー作成 for num, order in enumerate(data): # 一旦全てのプロットを非表示に visible = [False] * length # 該当するボタン・スライダーのプロット部分だけ表示にして、ラベルを作成 visible[num] = True menu = dict( label=order, method='update', args=[dict(visible=visible), dict(title=order)] ) menus.append(menu) return menus
設定内容としてはボタンもスライダーも共通。以下の2条件を満たせばいい。
- 全プロット表示
- 各プロット表示、その他のプロットは非表示
ボタンやスライダーについて詳しくは以下を参照してほしいが、今回だとvisible
で指定したbool
がTrue
だと表示、False
だと非表示。
-
-
【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加
続きを見る
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
また、args
の初めのdict
がプロットに関する内容、2つ目のdict
がレイアウトに関する内容を示している。ここを逆にしてもエラーとはならず何も起きないだけ。
グラフ保存用の関数
# グラフ保存用の関数 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にした。plotly
のグラフ保存については以下で解説している。
-
-
【plotly&orca】plotlyで静止画保存(orca)
続きを見る
また、config
はグラフ右上のツールバーだが、今回はごちゃごちゃするのでNone
と指定して無効にする。
ボタンとスライダー設定
set_menus
関数でボタン・スライダーの内容の設定はできるので、ここではこれらの設定をグラフに反映させる設定を行う。
ボタン(updatemenus
)とスライダー(sliders
)では引数が異なるのでここでは分けて考えることにする。
ボタンの設定
updatemenus = [ dict( active=0, buttons=set_menus(), type='buttons', direction="right", x=0.5, y=1.01, xanchor='center', yanchor='bottom', )]
まずはボタンから。各引数は以下。
active
: グラフの初期表示プロットのインデックス(0は0番目のプロットを表示)buttons
: ボタン設定type
:buttons
でボタンを表示、dropdown
でドロップダウン形式にできるdirecton
: ボタンを並べる方向x
,y
: どの位置にボタンを並び始めるかxanchor
,yanchor
:x
,y
の位置の基準
注意点はlist
の中でdict
を作成しないといけない点。list
の中で作成するということは、裏を返せば複数ボタン作成可能ということ。
今回でいえばどのプロットを表示するかって話だったけど、別の位置に背景色を変更するとかのボタンも作成可能。
-
-
【plotly&ボタン】グラフに複数種のボタンを追加
続きを見る
スライダーの設定
sliders = [ dict( active=0, currentvalue=dict(prefix='now: '), steps=set_menus() ) ]
スライダーも同じように設定する。ボタンの時と異なる引数は以下。
currentvalue
: スライダーの現在の値に関する設定prefix
: 現在の値に接頭辞をつけるsteps
: スライダー設定
こちらも同じようにlist
の中にdict
を入れないといけない。
go
でボタンとスライダーを作成
# 各プロットの作成 plot = [] for order, y in data.items(): d = go.Scatter(x=x, y=y, name=f"go-{order}", showlegend=True) plot.append(d)
ということで準備が終わったのでgo
でのボタンとスライダーのグラフを作成。の前にプロットデータを作成、レイアウトの関数を定義しておく。
プロットデータとレイアウトの関数はgo
でしか使用しないのでここで定義しておく。プロットデータのshowlegend
をTrue
としておくとプロットが1つでも凡例が出るようになる。
レイアウトに関しては初期グラフタイトルと横・縦軸の表示範囲、そしてボタンとスライダーの設定ができるように追加設定用のkwargs
を入れた。
# レイアウトの作成 def set_layout(**kwargs): layout = go.Layout( title=dict(text='ALL',), # 初期グラフタイトル xaxis_range=(-2.1, 2.1), yaxis_range=(-35, 35), **kwargs, # 追加設定 ) return layout
軸の表示範囲を固定しておかないと各プロットごとに自動で範囲が調節されたり、プロットがはみ出たりしてキレイではない。
go
x ボタン
まずはgo
とボタンの組み合わせ。fig
のdata
にプロットデータのplot
を、レイアウト部分にボタン情報を含んだset_layout
関数を入れたらいい。
ボタンを押すことでプロットが変わることがわかるだろう。
# goでボタン fig = go.Figure(data=plot, layout=set_layout(updatemenus=updatemenus)) fig.show() save(fig=fig, config=None, save_name='go_buttons')
go
x スライダー
スライダーも同じようにレイアウトにスライダーを入れたら完成。
# goでスライダー fig = go.Figure(data=plot, layout=set_layout(sliders=sliders)) fig.show() save(fig=fig, config=None, save_name='go_sliders')
go
x ボタン&スライダー
やる意味はないけど、ボタンとスライダーを同時に設定することも可能。しかし、ボタンとスライダーは同期されておらず、片方を動かしてももう片方は止まったまま。
なので、同期するように設定しないと誤解を生みそうだ。というより、そもそもこのグラフでは両方はいらない。
# ボタンとスライダーを両方 layout = set_layout(updatemenus=updatemenus, sliders=sliders) fig = go.Figure(data=plot, layout=layout) fig.show() save(fig=fig, config=None, save_name='go_buttons_sliders')
px
でボタンとスライダーを作成
最後にpx
でも同じようにボタンとスライダーを作成する。ボタンについてはgo
と似ているけど、スライダーに関してはちょっとトリッキーな方法で作成。
px
x ボタン
px
のボタンの場合はすぐにできるというわけではなく、figを作成した後にupdatemenus
を設定する。要するに後付け。
px.scatter
の引数でボタン関連がないから仕方なくこのようにするしかない。また、デフォルトでは点のプロットとなる。
# pxのボタンの場合はレイアウトに後付けしないといけない fig = px.scatter(df, x='x', y='y', color='order') fig.update_layout(updatemenus=updatemenus) fig.show() save(fig=fig, config=None, save_name='px_buttons')
px
x スライダー
スライダーについては後付けではなく、後削りの戦法。これはかなりトリッキーだが賢いと感じた。
どういうことかというと、スライダーというのがアニメーションから再生ボタンを削除したものという考えから、アニメーションを作成後に再生ボタンを削除する。
例えば以下のようのアニメーションを作成したとする。
# pxのスライダーはアニメーションから再生ボタンを消す fig = px.scatter( df, x='x', y='y', color='order', animation_frame="order", # アニメーションはオーダーごと range_x=(-2.1, 2.1), range_y=(-35, 35), )
この時のfigの中身は以下。
ここから再生ボタン部分を削除する。再生ボタンはupdatemenus
でしか使用しないから、実質updatemunus
を削除したのと同じ。
fig["layout"].pop("updatemenus") # 再生ボタンを削除
あとはシンプルにfig
を表示してグラフを保存すればいい。まとめると以下。
# pxのスライダーはアニメーションから再生ボタンを消す fig = px.scatter( df, x='x', y='y', color='order', animation_frame="order", # アニメーションはオーダーごと range_x=(-2.1, 2.1), range_y=(-35, 35), ) fig["layout"].pop("updatemenus") # 再生ボタンを削除 fig.show() save(fig=fig, config=None, save_name='px_sliders')
px
x ボタン&スライダー
最後にpx
とボタンとスライダーのグラフを作成したが、スライダーでupdatemenus
を消さなければいけないのでとりあえずアニメーションにボタンをつけた。
するとバグった。全プロット表示とorder1
がそれぞれ再生ボタンと停止ボタンとなってしまい、使用できない。さらにボタンを押すとプロットが消える。
これだと実用性がないので、使用する際にはうまく両立できるように工夫しないといけない。
# pxでボタンとスライダーを同時するとバグる fig = px.scatter( df, x='x', y='y', color='order', animation_frame="order", # アニメーションはオーダーごと range_x=(-2.1, 2.1), range_y=(-35, 35), ) fig.update_layout(updatemenus=updatemenus) # fig["layout"].pop("updatemenus") # 再生ボタンを削除 fig.show() save(fig=fig, config=None, save_name='px_buttons_sliders')
なお、updatemenus
を設定した後に削除すると、普通にスライダーとなる。そりゃそうだ。消したもん
ボタンは共通点多し、スライダーは結構異なる
今回はplotly
のgo
とpx
を使用してボタンを描く方法とスライダーを描く方法について解説した。
それぞれでグラフを描く方法は異なるけど、ボタンの指定方法は同じだったので、ボタンについては好みになりそう。
一方でスライダーに関しては大きく異なるのでスライダーはデータの作成方法や指定のしやすさで決めるのが良さそうだ。
関連記事
-
-
【px&animation】plotly.expressでアニメーションのグラフを作成
続きを見る
-
-
【Plotly&animation】Plotlyのgoでアニメーションを作成
続きを見る
-
-
【px&facet】plotly.expressでsubplotsを描く
続きを見る
-
-
【plotly&ボタン】plotlyのupdatemenusに2回押し対応のbuttonsを追加
続きを見る