カテゴリー

go

【plotly&Scattergl】大量のデータをplotlyで軽くグラフ化

2021年8月8日

こんな人にオススメ

plotlyを使ってグラフを描いているんだけど、データが大量にあるからグラフがモッサリしたり何も表示されなくなってしまう。

データを減らさずにplotlyのグラフを軽くする方法を教えて!

ということで、今回はplotlyで大量のグラフを描く方法について解説する。plotlyはグラフをグリグリ動かせるからかなり重宝するんだけど、そのせいか重い。しかもデータが増えるとその分もっと重くなる。

かといって機能を省いて貧相なグラフにすると負けた感じがある。それに一旦グラフを作成しただけでPCのファンが爆速で回るのもの負けた感。

そこで使えるがWebGLなるもの。WebGLってのはどうやらブラウザで3D表示を高速にするための仕様のようで、GPUを呼び出したりして高速にしているよう。これをplotlyでも使用することで大量のデータを軽く扱える。

python環境は以下。

  • Python 3.9.6
  • numpy 1.21.1
  • plotly 5.1.0

運営者のメガネです。YouTubeTwitterInstagramも運営してます。

自己紹介はこちらから、お問い合わせはこちら。

運営者メガネ

下準備

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import time

import functions

まずは下準備としてのimport関連。今回はWevGLを使用することでどれくらい処理時間が短縮されるのかも測りたい。よって、timeimport

さらに、共通して使用するような処理を一括管理するために以下のコードがあるfunctions.pyも作成。同じディレクトリにある場合はそのままimportできる。

import sys
import numpy as np

sys.path.append('../../')
import plotly_layout_template as template

def set_func(func, x: np.array, y: np.array, name: str):
    d = func(
        mode='markers',
        x=x, y=y, name=name,
    )
    return d

def config():
    return template.plotly_config()

一方で、異なるディレクトリに読みたいファイルがある場合はいつも通りsysでディレクトリを移動して読み込んでいる。plotly_layout_templateは自作テンプレートで以下に詳しい内容が書いてある。

【随時更新 備忘録】plotlyのグラフ即席作成コード

こんな人にオススメ plotlyっていじれる項目が多くて設定が面倒。何かいいテンプレー| ...

続きを見る

ファイル構造

├── functions.py
├── heatmap.py
├── heatmapgl.py
├── passed_time.py
├── scatter.py
└── scattergl.py

本記事で使用するファイルは上のような構造になっている。今回は処理時間を測りたいので、それぞれの処理ごとにファイルを分けて、それぞれのファイルごとにtimeライブラリの関数で時間を計測。

  • functions.py: 共通して使用する処理が存在
  • heatmap.py: go.Heatmapを使用して2次元マップを作成
  • heatmapgl.py: go.Heatmapglを使用してWebGL適用後の2次元マップを作成
  • passed_time.py: WebGLを使用する前後での処理時間計測用 scatter.py: go.Scatterを使用して散布図を作成
  • scattergl.py: go.Scatterglを使用してWebGL適用後の散布図を作成

要するに散布図と2次元マップでWebGLを使用するか否かを分けて、その処理時間を比較するということ。本当は他にもWebGL系はあるけど今回はこの2つに絞る。より詳しい内容は公式のここに載っている。

plotlyScatterHeatmapについてはそれぞれ以下参照。

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

こんな人にオススメ plotlyのとっかかりとして散布図(scatter)を描きたいけど、どうやっӗ ...

続きを見る

【plotly&heatmap】go.Heatmapで2次元配列をマップ化

こんな人にオススメ 以前、matplotlib.pyplot、pltのplt.imshowで2次元配列をマップ化したけど、plotlyでは一߮ ...

続きを見る

大量のデータをグラフ化するとモッサリ

そもそも本記事はなんで書かれたのかという点だが、データ数が多いとグラフが重くなる。データ数だけじゃなくて、ボタンなどの付加機能も重くなる大きな原因だろう。

だからと言ってデータを削減したり機能を省略するというのはもったいないし、作成した時間が無駄。という事で、現状維持のままどうにかグラフを軽くできないかということ。

そんな上手い話があるのかという事だがあった。それがWebGLを使用するという方法。

Scatterの場合はScattergl

まずは散布図から。上の画像のように大量にプロット点があるとかなりモッサリしてPCにも負担が大きいし何よりストレスが大きい。

go.Scatterを使用

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import time

import functions

start_time = time.perf_counter()

def set_plot(number: int):
    # 標準正規分布(平均0、分散1)の乱数を生成
    # data_num = 100000
    data_num = 10000  # 記事用
    plot = []
    for num in range(number):
        np.random.seed(num)
        x = np.random.randn(data_num)
        y = np.random.randn(data_num)

        d = functions.set_func(func=go.Scatter, x=x, y=y, name=f"data {num}")
        plot.append(d)
    return plot

plot_num = 10
fig = go.Figure(data=set_plot(plot_num))
fig.show()

execution_time = time.perf_counter() - start_time
print(execution_time)

name = f"scatter{plot_num}"
pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
pio.write_html(fig, f"{name}.html", config=functions.config(),)
pio.write_image(fig, f"{name}.png")

WebGLを使用せずに散布図を作成する場合は上のようなコードになる。計測時間はimportが終わってからプロットしたグラフを表示するまでとした。time.perf_counter()を使用するとより正確な時間となるらしい。

グラフを静止画で保存するときに認証とかがあるからそれを避けるためにグラフ表示までの時間計測にしている。plotlyのグラフ保存については以下。

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

こんな人にオススメplotlyで画像を保存するときに色々と警告とかエラーが出てきて保ढ ...

続きを見る

時間計測の結果は後ほど述べるとして、出来上がるグラフは以下の感じ。ただし、記事にする時の容量の都合上、データ数は1,000に制限した。処理時間計測時にはデータ数を100,000に増やして行う。

また、plot_num = 10でプロット種類は10にした。閲覧しているブラウザによって違いはあるだろうけど、多少のモッサリ感があるだろう。


go.Scatterglを使用

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import time

import functions

start_time = time.perf_counter()

def set_plot(number: int):
    # 標準正規分布(平均0、分散1)の乱数を生成
    # data_num = 100000
    data_num = 1000  # 記事用
    plot = []
    for num in range(number):
        np.random.seed(num)
        x = np.random.randn(data_num)
        y = np.random.randn(data_num)

        d = functions.set_func(func=go.Scattergl, x=x, y=y, name=f"data {num}")
        plot.append(d)
    return plot

plot_num = 10
fig = go.Figure(data=set_plot(plot_num))
fig.show()

execution_time = time.perf_counter() - start_time
print(execution_time)

name = f"scattergl{plot_num}"
pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
pio.write_html(fig, f"{name}.html", config=functions.config(),)
pio.write_image(fig, f"{name}.png")

さっきのgo.ScatterをWebGLを使用して軽くしたのが上のコード。変更点は使用する関数がgo.Scatterglになっただけ。あとはファイル名。こうするだけで以下のように軽い散布図を作成することができる。


たった1部分を変更するだけで結構軽くなるからオススメだ。

Heatmapの場合はHeatmapgl

Heatmapの場合も軽くすることができる。マップ状になっているので、データ数は多くなる傾向にあると思うから、これを軽くできるとだいぶと楽になる。

go.Heatmapを使用

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import time

import functions

start_time = time.perf_counter()

side = 300  # 1辺のデータ数
data_num = int(side ** 2)  # 全体のデータ数

np.random.seed(seed=1)
arr = np.random.randint(1, 100, data_num).astype("float")
arr2d = arr.reshape(side, -1)

plot = []
d = go.Heatmap(z=arr2d)
plot.append(d)

layout = go.Layout(yaxis=dict(scaleanchor='x', ),)

fig = go.Figure(data=plot, layout=layout)
fig.show()

execution_time = time.perf_counter() - start_time
print(execution_time)

name = f"heatmap{data_num}"
pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
pio.write_html(fig, f"{name}.html", config=functions.config(),)
pio.write_image(fig, f"{name}.png")

時間の計測はScatterと同様グラフ表示までとした。データ数についても記事にする際には数を減らしている。また、正方形にならないと個人的に嫌だったので、go.Layoutの部分でscaleanchorを設定している。

このコードでできるgo.Heatmapの結果が以下。Scatterよりかは軽い印象。気のせいか。