カテゴリー

px

【px&animation】plotly.expressでアニメーションのグラフを作成

2021年10月15日

こんな人にオススメ

plotlyでアニメーションができるらしいけどどうやるの?

Plotly公式の「Intro to Animations in Python」を見てもよくわからん。

ということで、今回はplotly.express、通称pxを使用してアニメーション機能を持つグラフを作成する。アニメーションってのはグラフ上のボタンを押すと自動でプロット内容が変わるグラフのこと。

pxじゃなくてplotly.graph_objects、通称goを使用する方法もあるけど、こっちはかなり複雑でコードが長くなってしまう。今回のpxだとかなり短く作成可能。

goで描いたアニメーショングラフについては以下参照。

【Plotly&animation】Plotlyのgoでアニメーションを作成

続きを見る

アニメーション機能付きのグラフは作成が面倒なことが多いけど、pxなら簡単に作成可能。なので、本記事でアニメーションの作成方法を知っていただけると幸いだ。

python環境は以下。

  • Python 3.10.1
  • pandas 1.3.5
  • plotly 5.4.0
  • plotly-orca 3.4.2

運営者のメガネです。YouTubeTwitterInstagram、自己紹介はこちら、お問い合わせはこちらから。

運営者メガネ

作成したコード全文

下準備(import

import pandas as pd
import plotly.express as px
import plotly.io as pio

まずは下準備としてのimport関連。pxpandasのデータフレームを使ってグラフを描くことに特化しているのでpandasimport

あとはメインのpxとグラフ保存用のpiopioによるPlotlyのグラフ保存については以下参照。

plotlyでグラフを静止画として保存する際に出るポップアップ
【plotly&orca】plotlyで静止画保存(orca)

続きを見る

アニメーションを使わずグラフ化

まずはアニメーションを使わずにグラフ化してみる。コードはPlotly公式の「Animated figures with Plotly Express」のアニメーショングラフから、アニメーション要素を取り除いた版。

この場合はアニメーションで動かすはずだったプロットも含めて全てのデータがプロットされている。これだと何が何やらわからん。

px.scatterについては以下の記事参照。

【plotly&px.scatter】pxでの散布図の描き方

続きを見る

このグラフをアニメーション化したグラフを次に解説する。

import pandas as pd
import plotly.express as px
import plotly.io as pio

# データ全体をグラフ化

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='gdpPercap', y='lifeExp',  # 横軸・縦軸とする列名
    size='pop', color='continent',  # サイズと色分けの基準とする列名
    hover_name='country',  # ホバーに表示する情報
    log_x=True,  # 横軸をlogに
    size_max=55,  # sizeの最大値
    range_x=[100, 100000], range_y=[25, 90]  # 横軸・縦軸の表示範囲
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_simple"
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")

animation_frameでアニメーショングラフ

pxでアニメーションのグラフを作成するには引数animation_frame, animation_groupを使用するだけ。本当は使用するデータがアニメーションに対応した構造じゃないといけないが。

ここではアニメーションをyear、どのデータでアニメーション各コマをグループするかを国で指定している。

pxのアニメーションの場合は列名で指定する必要がある。また、各列(各データ)は同じ種族で縦に並べないといけない。例えばcountryで言えば初めにAfghanistanのデータが並び、その後にAlbaniaのデータが続く。

import pandas as pd
import plotly.express as px
import plotly.io as pio

df = px.data.gapminder()
print(df.head(15))
#         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
# 5   Afghanistan      Asia  1977  ...   786.113360        AFG        4
# 6   Afghanistan      Asia  1982  ...   978.011439        AFG        4
# 7   Afghanistan      Asia  1987  ...   852.395945        AFG        4
# 8   Afghanistan      Asia  1992  ...   649.341395        AFG        4
# 9   Afghanistan      Asia  1997  ...   635.341351        AFG        4
# 10  Afghanistan      Asia  2002  ...   726.734055        AFG        4
# 11  Afghanistan      Asia  2007  ...   974.580338        AFG        4
# 12      Albania    Europe  1952  ...  1601.056136        ALB        8
# 13      Albania    Europe  1957  ...  1942.284244        ALB        8
# 14      Albania    Europe  1962  ...  2312.888958        ALB        8

# [15 rows x 8 columns]

縦に並んでいないデータフレームを使おうとするとアニメーションがうまくいかなかったりする可能性があるので面倒。

結局は上のように各種族ごとにデータを並べてデータフレームを作成するのが安定。

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='gdpPercap', y='lifeExp',  # 横軸・縦軸とする列名
    animation_frame='year',  # アニメーションにする列名
    animation_group='country',  # アニメーション時に同じグループにする列名
    size='pop', color='continent',  # サイズと色分けの基準とする列名
    hover_name='country',  # ホバーに表示する情報
    log_x=True,  # 横軸をlogに
    size_max=55,  # sizeの最大値
    range_x=[100, 100000], range_y=[25, 90]  # 横軸・縦軸の表示範囲
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_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")

range_x, range_yには注意

px.scatterには横軸と縦軸の表示範囲を変更するための引数range_x, range_yがある。このrange_x, range_yを指定しないと上のグラフのようにアニメーションの途中でプロットがはみ出てしまう。

1つ前のグラフのようにrange_x, range_yを調節する必要があることはPloty公式「Intro to Animations in Python」でも注意喚起がなされている。

Here is an example of an animated scatter plot creating using Plotly Express. Note that you should always fix the x_range  and y_range  to ensure that your data remains visible throughout the animation.

import pandas as pd
import plotly.express as px
import plotly.io as pio

# range_x, range_yを調整しないとはみ出る

df = px.data.gapminder()

fig = px.scatter(
    df,  # 使用するデータフレーム
    x='gdpPercap', y='lifeExp',  # 横軸・縦軸とする列名
    animation_frame='year',  # アニメーションにする列名
    animation_group='country',  # アニメーション時に同じグループにする列名
    size='pop', color='continent',  # サイズと色分けの基準とする列名
    hover_name='country',  # ホバーに表示する情報
    log_x=True,  # 横軸をlogに
    size_max=55,  # sizeの最大値
    # range_x=[100, 100000], range_y=[25, 90]  # 横軸・縦軸の表示範囲
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_animation_range"
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.barで棒グラフのアニメーション


px.scatterの散布図だけではなく、px.barを使うことで棒グラフも作成することが可能。引数は基本的に同じ。range_x, range_yの指定を忘れなければいい。

px.bargoでの棒グラフgo.Barについては以下の記事参照。

【plolty&棒グラフ】px.barでバーチャートを作成

続きを見る

【plotly&棒グラフ】go.Barでバーチャートを作成

続きを見る

import pandas as pd
import plotly.express as px
import plotly.io as pio

# px.barで棒グラフでもアニメーション

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.bar(
    df,  # 使用するデータフレーム
    x="continent", y="pop",  # 横軸・縦軸とする列名
    color="continent",  # 色分けの基準とする列名
    animation_frame="year",  # アニメーションにする列名
    animation_group="country",  # アニメーション時に同じグループにする列名
    # range_yを設定しておかないとグラフがはみ出る
    range_y=[0, 4000000000],  # 縦軸の表示範囲
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_animation_bar"
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にあるデータpx.data.stocks()を使用。これは各アメリカの大企業の株価の、データ初日を1とした時の推移。

px.lineはデフォルトでは線だけのグラフ。線だけでもいいけど、ここでは引数marker=Trueでマーカーをつけておいた。

また、1列目がdateで日付なので、実際にグラフ化する際にはdf.columns[1:7]で企業だけに絞っている。

import pandas as pd
import plotly.express as px
import plotly.io as pio

# アニメーションなしのグラフ

# 株価のデータ
df = px.data.stocks()
print(df)
#            date      GOOG      AAPL      AMZN        FB      NFLX      MSFT
# 0    2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 1    2018-01-08  1.018172  1.011943  1.061881  0.959968  1.053526  1.015988
# 2    2018-01-15  1.032008  1.019771  1.053240  0.970243  1.049860  1.020524
# 3    2018-01-22  1.066783  0.980057  1.140676  1.016858  1.307681  1.066561
# 4    2018-01-29  1.008773  0.917143  1.163374  1.018357  1.273537  1.040708
# ..          ...       ...       ...       ...       ...       ...       ...
# 100  2019-12-02  1.216280  1.546914  1.425061  1.075997  1.463641  1.720717
# 101  2019-12-09  1.222821  1.572286  1.432660  1.038855  1.421496  1.752239
# 102  2019-12-16  1.224418  1.596800  1.453455  1.104094  1.604362  1.784896
# 103  2019-12-23  1.226504  1.656000  1.521226  1.113728  1.567170  1.802472
# 104  2019-12-30  1.213014  1.678000  1.503360  1.098475  1.540883  1.788185
# [105 rows x 7 columns]

# pxで折れ線グラフを作成
fig = px.line(df, x='date', y=df.columns[1:7], markers=True,)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_line"
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.scatterのグラフでは推移をザッと見られれば良かったし、そもそも国数が多いから履歴を残してられない。

ただ、今回はデータ数が少ないから履歴を残したいし、株価の推移だから過去分も見ておきたい。ということでこのグラフは失敗作。

引数として単純にanimation_frame='date'を指定しただけ、animation_groupは列が1つじゃないから指定できない。例えば以下のようにanimation_group=df.columns[1:7]とするとエラー。

import pandas as pd
import plotly.express as px
import plotly.io as pio

fig = px.line(
    df, x='date', y=df.columns[1:7], markers=True,
    animation_frame='date',
    animation_group=df.columns[1:7],
    range_x=('2018-01-01', '2019-12-30'),
    range_y=[0.6, 2.1],
)
# ValueError: All arguments should have the same length. The length of argument `animation_group` is 6, whereas the length of  previously-processed arguments ['date'] is 105

単純にアニメーションを作成したいなら以下のコードで作成可能。

# シンプルにアニメーションにすると履歴が残らない

# 株価のデータ
df = px.data.stocks()
print(df)
#            date      GOOG      AAPL      AMZN        FB      NFLX      MSFT
# 0    2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 1    2018-01-08  1.018172  1.011943  1.061881  0.959968  1.053526  1.015988
# 2    2018-01-15  1.032008  1.019771  1.053240  0.970243  1.049860  1.020524
# 3    2018-01-22  1.066783  0.980057  1.140676  1.016858  1.307681  1.066561
# 4    2018-01-29  1.008773  0.917143  1.163374  1.018357  1.273537  1.040708
# ..          ...       ...       ...       ...       ...       ...       ...
# 100  2019-12-02  1.216280  1.546914  1.425061  1.075997  1.463641  1.720717
# 101  2019-12-09  1.222821  1.572286  1.432660  1.038855  1.421496  1.752239
# 102  2019-12-16  1.224418  1.596800  1.453455  1.104094  1.604362  1.784896
# 103  2019-12-23  1.226504  1.656000  1.521226  1.113728  1.567170  1.802472
# 104  2019-12-30  1.213014  1.678000  1.503360  1.098475  1.540883  1.788185
# [105 rows x 7 columns]

# pxで折れ線グラフを作成
fig = px.line(
    df, x='date', y=df.columns[1:7], markers=True,
    animation_frame='date',
    # animation_group=df.columns[1:7],  # ValueErrorになる
    range_x=('2018-01-01', '2019-12-30'),
    range_y=[0.6, 2.1],
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_animation_nohistory"
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")

軌跡(履歴)を残そうとしたアニメーショングラフ

軌跡を残すアニメーショングラフの考え方

続いて、履歴を残そうとしたグラフを作成。実はアニメーションの作成には工夫する点がある。上の図がその概略図。データフレームとして以下の構造を持っている。

  • 赤、緑、黄、青の塗りつぶし
    • アニメーションのフレームに対応
    • スライダーの各点のこと
  • 赤、紫、緑、青の文字
    • 各アニメーション内でグラフ化する日付
    • 各塗りつぶし(フレームに対応)でプロットする

イメージが難しいかもしれないけど、要するに各フレーム(グラフの各スライダー)で各塗りつぶしをグラフ化するだけ。これを最初、2番目、3番目、...のアニメーションフレームと繋げることで、軌跡を残すことができる。

ポイントは各フレームで過去のプロット(軌跡)までグラフ化する点。例えば「2番目のアニメーションフレーム」だと、新規の紫文字だけプロットすると2018/1/8のデータだけプロットされる。しかし赤文字のデータもプロットすることで過去分(2018/1/1)もプロットされる。

まだ、左右の図の違いは以下。

  • 左:各企業を1列で表現
  • 右:各企業を列ごとに表現

どっちが見やすいか・管理しやすいか・使いやすいかは人や状況それぞれ。本記事ではどちらのパターンも作成する。

上のグラフは赤文字の2018/1/1のデータと紫文字の2018/1/8のデータをプロットしたもの。さっきの表でいえば緑で塗りつぶされた「2番目のアニメーションフレーム」の時点。

もし、赤文字の2018/1/1をプロットしなかったら、紫文字の2018/1/8のプロットだけになる。これまでの履歴もいちいちプロットしないといけない。

軌跡を残そうとして失敗したアニメーショングラフ

このグラフもなぜか動かせるグラフを載せられなかったからgifで示す。見てもらえると分かるように、履歴が残らず各アニメーションの点だけがプロットされている。

作成したデータフレームは以下。データフレームの行の数だけforでループを回し、各行以前のデータを空のデータフレームdf2に追加していく方法をとった。

なお、ignore_index=Trueにすることで、 df2のインデックスをリセットして0から勘定するようにした。

import pandas as pd
import plotly.express as px
import plotly.io as pio

df = px.data.stocks()

# 空のデータフレームを作成
df2 = pd.DataFrame(index=[], columns=df.columns)

# データフレームへの追記はパフォーマンスが悪い
for num in range(len(df)):
    df2 = df2.append(
        df.iloc[:num + 1, :],
        ignore_index=True,
    )
print(df2)
#             date      GOOG      AAPL      AMZN        FB      NFLX      MSFT
# 0     2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 1     2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 2     2018-01-08  1.018172  1.011943  1.061881  0.959968  1.053526  1.015988
# 3     2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 4     2018-01-08  1.018172  1.011943  1.061881  0.959968  1.053526  1.015988
# ...          ...       ...       ...       ...       ...       ...       ...
# 5560  2019-12-02  1.216280  1.546914  1.425061  1.075997  1.463641  1.720717
# 5561  2019-12-09  1.222821  1.572286  1.432660  1.038855  1.421496  1.752239
# 5562  2019-12-16  1.224418  1.596800  1.453455  1.104094  1.604362  1.784896
# 5563  2019-12-23  1.226504  1.656000  1.521226  1.113728  1.567170  1.802472
# 5564  2019-12-30  1.213014  1.678000  1.503360  1.098475  1.540883  1.788185

# [5565 rows x 7 columns]

このデータフレームで言えば0行目が1フレーム目、1, 2行目が2フレーム目3, 4, 5行目が3フレーム目になる。

プロットのためのコードは以下。animation_groupは指定してもしなくても結果は変わらない。履歴は残らない。ただし、animation_groupを指定しない場合は各フレーム間が滑らかに遷移するようになる。

# pxで折れ線グラフを作成
fig = px.line(
    df2, x='date', y=df.columns[1:7],
    markers=True,
    animation_frame='date',
    animation_group='date',
    range_x=('2018-01-01', '2019-12-30'),
    range_y=[0.6, 2.1],
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_animation_mistake"
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")

ここでの全体コードを以下に記載しておく。一括で見たい方は開いてみてほしい。

フレームの指定方法が間違っている

じゃあなんで履歴が残らないのかっていうと、恐らくフレームの指定方法(animation_frame)が間違っているから。

上の画像のように、さっきのコード(上の図の右側)だとフレームが各塗りつぶしでバラバラになっている。一方で、後で解説する上手くいく方法では各フレームでanimation_frameが同じ。これが原因。

このフレームの指定ズレを修正したグラフを次に解説する。

軌跡(履歴)を残せたアニメーショングラフ

最後に軌跡を残すことができたアニメーショングラフの作成方法を解説する。さっきの図であったように、データフレームの各列に各企業を配置する方法と、データフレームの1列に全企業を置く方法の2つがある。

ここではそのどちらの方法も解説するから、状況に応じて使い分けてほしい。厄介だけどどちらの考え方も頭に入れておくとアニメーションでつまづくことがグンと減るだろう。

各列に企業を配置した場合のアニメーショングラフ

ということで奇跡が残るように修正したグラフを作成した。全企業を乗せると重すぎて記事に載せられなかったのでgif。pxはブログと相性が悪い。

空のデータフレームdf3に、各行以前の日付と株価を追加。さらに各行の最新の日付=各フレームの日付を、軌跡の数だけ追加する。例えば2018/1/8は1/1と1/8の2データ分だけ2018/1/8というフレーム用のデータをframe列に追加する。1/15なら1/1, 1/8. 1/15の3データ。

df = px.data.stocks()

# 空のデータフレームを作成
df3 = pd.DataFrame(index=[], columns=df.columns)
frame_lst = []  # 各フレームの名称を入れるための配列

# データフレームへの追記はパフォーマンスが悪い
for num in range(len(df)):
    df3 = df3.append(
        df.iloc[:num + 1, :],
        ignore_index=True,
    )

    # 各フレームの日付を軌跡の数だけ作成
    date = [df['date'][num]] * (num + 1)
    frame_lst.extend(date)

print(df3)
#             date      GOOG      AAPL      AMZN        FB      NFLX      MSFT
# 0     2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 1     2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 2     2018-01-08  1.018172  1.011943  1.061881  0.959968  1.053526  1.015988
# 3     2018-01-01  1.000000  1.000000  1.000000  1.000000  1.000000  1.000000
# 4     2018-01-08  1.018172  1.011943  1.061881  0.959968  1.053526  1.015988
# ...          ...       ...       ...       ...       ...       ...       ...
# 5560  2019-12-02  1.216280  1.546914  1.425061  1.075997  1.463641  1.720717
# 5561  2019-12-09  1.222821  1.572286  1.432660  1.038855  1.421496  1.752239
# 5562  2019-12-16  1.224418  1.596800  1.453455  1.104094  1.604362  1.784896
# 5563  2019-12-23  1.226504  1.656000  1.521226  1.113728  1.567170  1.802472
# 5564  2019-12-30  1.213014  1.678000  1.503360  1.098475  1.540883  1.788185

# [5565 rows x 7 columns]

あとはdf3pandaspd.Seriesに変換したframe_lstpd.concatで横に結合するとデータフレームの完成。pd.concatは上書きではなく新たに変数を作成するので、df3 = pd.concatにしないとdf3に反映されないことに注意。

データフレームができたらpx.lineでグラフ化するのみ。各企業を各列に置いているのでpx.lineydf.columns[1:7]で指定する。

# pxで折れ線グラフを作成
fig = px.line(
    df3, x='date', y=df.columns[1:7],
    markers=True,
    animation_frame='frame',
    animation_group='date',
    range_x=('2018-01-01', '2019-12-30'),
    range_y=[0.6, 2.1],
)

# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_animation_rowcompany"
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")

まとめたコードは以下。

1列に企業を配置した場合のアニメーショングラフ

一方でデータフレームの1列に全ての企業を配置する方法も可能。むしろこっちの方がpxでは使われるような気もする。ただ、この場合はなぜかマーカーが消える。

データフレームのイメージは下の画像の左側。company列に全ての企業が羅列されており、全企業分のデータが終わるまではdate列やframe列は同じ値を取る。

データフレームの構造が元のdf = px.data.stocks()と異なるから、データフレームは初めから作成する。作成のイメージは以下。

  • 日付、株価、企業、フレームそれぞれで配列を作成
  • 各行以前の上記データをそれぞれ格納
  • それぞれのデータをpandaspd.Seriesに変換
  • Seriespd.concatで結合

まずは各データの軌跡を作成。

import pandas as pd
import plotly.express as px
import plotly.io as pio

df = px.data.stocks()

date_lst = []  # 日付データ
stock_lst = []  # 株価データ
company_lst = []  # 企業データ
# frame=2の時に初めの人から始まるから、履歴がバグる
frame_lst = []  # 各アニメーションのラベル

# 各行までの日付でデータを作成
companies = df.columns[1:]  # 企業名
for num in range(len(df)):
    # 各行の日付をアニメーションのラベルにする
    # 個数は各行までの日付分 x 企業の数
    frame_lst.extend([df.iloc[num, 0]] * len(companies) * (num + 1))
    # 各企業ごとでデータを作成
    for company in companies:
        # 企業の数だけ、各業の日付までの日付・株価・企業名を取得・追加
        date_lst.extend(df.iloc[:num + 1, 0])
        stock_lst.extend(df[company][:num + 1])
        company_lst.extend([company] * (num + 1))

続いては各データをpandaspd.Seriesに変換。ここで引数nameを使うことでpd.concat後のデータフレームに自動で適用される。

# pandasのシリーズに変換
series_date = pd.Series(date_lst, name='date')
series_stock = pd.Series(stock_lst, name='stock')
series_company = pd.Series(company_lst, name='company')
series_frame = pd.Series(frame_lst, name='frame')

最後にpd.concatで結合。frame列とdate列でズレている行がわかるだろう。このズレている行が次のアニメーションフレームに移行するタイミング。

各フレームごとにまとめて同じ値にしておかないと軌跡が残らない。

# シリーズを横に結合
df3 = pd.concat(
    [series_date, series_stock, series_company, series_frame], axis=1
)
print(df3)
#              date     stock company       frame
# 0      2018-01-01  1.000000    GOOG  2018-01-01
# 1      2018-01-01  1.000000    AAPL  2018-01-01
# 2      2018-01-01  1.000000    AMZN  2018-01-01
# 3      2018-01-01  1.000000      FB  2018-01-01
# 4      2018-01-01  1.000000    NFLX  2018-01-01
# ...           ...       ...     ...         ...
# 33385  2019-12-02  1.720717    MSFT  2019-12-30
# 33386  2019-12-09  1.752239    MSFT  2019-12-30
# 33387  2019-12-16  1.784896    MSFT  2019-12-30
# 33388  2019-12-23  1.802472    MSFT  2019-12-30
# 33389  2019-12-30  1.788185    MSFT  2019-12-30

# [33390 rows x 4 columns]

print(df3.head(10))
#          date     stock company       frame
# 0  2018-01-01  1.000000    GOOG  2018-01-01
# 1  2018-01-01  1.000000    AAPL  2018-01-01
# 2  2018-01-01  1.000000    AMZN  2018-01-01
# 3  2018-01-01  1.000000      FB  2018-01-01
# 4  2018-01-01  1.000000    NFLX  2018-01-01
# 5  2018-01-01  1.000000    MSFT  2018-01-01
# 6  2018-01-01  1.000000    GOOG  2018-01-08 ここが1/1になるからframe列は必要
# 7  2018-01-08  1.018172    GOOG  2018-01-08
# 8  2018-01-01  1.000000    AAPL  2018-01-08
# 9  2018-01-08  1.011943    AAPL  2018-01-08

あとはpx.lineでグラフ化するのみ。今回は1列に全企業を入れたのでy='stock'する。また、animation_frame='frame’、animation_group='company’と指定することが可能。1列にすると使える引数が増える。

# pxで折れ線グラフを作成
fig = px.line(
    df3, x='date', y='stock',
    markers=True,
    animation_frame='frame',
    animation_group='company',
    color='company',
    range_x=('2018-01-01', '2019-12-30'),
    range_y=[0.6, 2.1],
)
# グラフ全体とホバーのフォントサイズ変更
fig.update_layout(font_size=20, hoverlabel_font_size=20)
fig.show()

# グラフ保存
prefix = 'px-animation'  # 保存ファイル名の接頭辞
save_name = f"{prefix}_animation_columncompany"
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のアニメーションは比較的簡単

ということで、今回はPlotlypxを使ったアニメーションの描き方を解説した。軌跡を残さない方法だとサクッとできるのがpxの魅力。

goのアニメーションだともっと複雑になるのでとっつきにくい。goのアニメーションについては以下参照。

【Plotly&animation】Plotlyのgoでアニメーションを作成

続きを見る

本記事でpxのアニメーションの描き方を知り、使いこなせていただけると幸いだ。

関連記事

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

続きを見る

【plotly&animation】動き方のeasingと遅延のduration

続きを見る

【px&バブルチャート】plotly.expressで各国の収入と平均寿命の時代変化をアニメーションで

続きを見る

ubblechart_animation1820_2020_50_latest
【plotly&アニメーション】plotlyで各国の収入と平均寿命の時代変化をバブルチャート&アニメーションで描く

続きを見る

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

続きを見る

【plotly&config】グラフのツールバーを編集する

続きを見る

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

続きを見る

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

続きを見る

【plotly&fill】goで領域を塗りつぶし

続きを見る

【plotly&3D】goで3Dグラフを作成

続きを見る

ガジェット

2023/9/18

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

完全ワイヤレスイヤホン(TWS)

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/8/17

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

ベストバイ

2023/9/18

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/9/16

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(30万円) x Galaxy S22 Ultra(17万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa

-px
-, , , ,