カテゴリー

go

【plotly&subplot】goでアニメーションのサブプロット

2021年11月13日

こんな人にオススメ

plotlygoでサブプロットしながらアニメーションを描きたいんだけど、どうしたらいけそう?

goの場合だとアニメーションがごちゃごちゃするから書き方がわからん。

ということで、今回はplotlyplotly.graph_objectsgoを使ってサブプロットを描きつつ、各グラフをアニメーションにする方法を解説する。

以下の記事でsubplotsanimation単体については解説している。今回はこれらの合体版。

【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
スポンサーリンク
スポンサーリンク

運営者のメガネと申します。TwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

作成したコード全文

下準備

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. 直線(1次関数)
  2. 無理関数(ルート)
  3. 対数関数
  4. 2次関数
  5. sin関数
  6. cos関数
  7. 3次関数
  8. 指数関数
  9. 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を使用する際には予めサブプロットをすることを明記しておかないといけない。

rowscolsで行数と列数を指定する。今回は3 x 3にする。shared_xaxes, shared_yaxesは横軸・縦軸を他のグラフを共有するか否か。共有すると軸ラベルと目盛が左端と下端だけになりスッキリする。

subplot_titleは各サブプロットのグラフのタイトル。ここで設定しないといけない。今回はyskeysを使用し、各関数の名称をタイトルとした。

ボタン作成用の関数

# ボタン内容の設定
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グラフを作成。大まかな流れは以下。

  1. サブプロットのベース作成
  2. 各サブプロットの行と列を指定
  3. 初期表示のプロット作成
  4. サブプロットのどのグラフに描画するか設定
  5. 初期値作成のついでに各グラフの表示範囲も設定
  6. ボタンを作成+ボタンをレイアウトに配置
  7. xごとに全ysのプロットでそのx以前までのデータをプロット(軌跡の描画)
  8. データをフレームに格納
  9. フレームをグラフに適用
  10. グラフの表示と保存

項目としては多いけど、内容的には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')

複数グラフの動きを見るには良い

今回はplotlygoを使用して、アニメーション機能を持ったサブプロットを作成した。これで変化を気にしなければいけないような複数グラフを一気に閲覧することができる。

また、シンプルに移動した先の点だけをプロットするのではなく、それまでのプロットも軌跡としてプロットすることで、さらに変化がわかりやすいだろう。

今回のグラフを強化したいなら、一時停止ボタンや各フレームのスライダーの追加だろう。ここを設定するとなるとかなり面倒だができると機能性が爆上げするので是非とも挑戦していただきたい。

関連記事

【plotly&go.Scatter】plotlyの散布図グラフの描き方

続きを見る

【plotly&スライダー】plotlyのslidersにスライダーを追加

続きを見る

【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加

続きを見る

【plotly&レーダーチャート】plotlyのRadar Chartの使い方とか設定とか

続きを見る

関連コンテンツ

スポンサーリンク

Amazonのお買い物で損したない人へ

1回のチャージ金額通常会員プライム会員
¥90,000〜2.0%2.5%
¥40,000〜1.5%2.0%
¥20,000〜1.0%1.5%
¥5,000〜0.5%1.0%

Amazonギフト券にチャージすることでお得にお買い物できる。通常のAmazon会員なら最大2.0%、プライム会員なら2.5%還元なのでバカにならない。

ゲットしたポイントは通常のAmazonでのお買い物に使えるからお得だ。一度チャージしてしまえば、好きなタイミングでお買いものできる。

なお、有効期限は10年だから安心だ。いつでも気軽にAmazonでお買い物できる。

Amazonチャージはここから出来るで

もっとお得なAmazon Prime会員はこちらから

30日間無料登録

執筆者も便利に使わせてもらってる

スポンサーリンク

  • この記事を書いた人

メガネ

独学でpythonを学び天文学系の大学院を修了。 ガジェット好きでMac×Android使い。色んなスマホやイヤホンを購入したいけどお金がなさすぎて困窮中。 元々、人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂ったために今も人生めちゃくちゃ楽しい。 pythonとガジェットをメインにブログを書いていますので、興味を持たれましたらちょこちょこ訪問してくだされば幸いです🥰。 自己紹介→変わって楽しいの繰り返し

-go
-, ,