📊

Python と同様のシンタックスで使える Plotly の Julia インターフェースを作りました.

2022/02/04に公開
1

本日は

PyPlotly.jl

を作りましたの話

作成の背景

Plotly という対話的な操作が得意なグラフ描画ライブラリがあります. Dash というWeb 上でのダッシュボードを作るアプリにおいてグラフを描画する際に重宝されます.

JS(=JavaScript) はもちろん Python, R, Julia をはじめとするさまざまなインターフェースをサポートしています.

Julia の場合は PlotlyJS.jl によって
機能を使えます.

# https://plotly.com/julia/2d-histogram-contour/ から抜粋
using PlotlyJS, Distributions

x = rand(Uniform(-1,1), 500)
y = rand(Uniform(-1,1), 500)

plot(histogram2dcontour(
        x = x,
        y = y,
        colorscale = "Jet",
        contours = attr(
            showlabels = true,
            labelfont = attr(
                family = "Raleway",
                color = "white"
            )
        ),
        hoverlabel = attr(
            bgcolor = "white",
            bordercolor = "black",
            font = attr(
                family = "Raleway",
                color = "black"
            )
        )

))

内部では JS の API に直接アクセスするためかなり似た書き方ができます.

// https://plotly.com/javascript/2d-histogram-contour/#styled-2d-histogram-contour から抜粋
var x = [];
var y = [];
for (var i = 0; i < 500; i ++) {
	x[i] = Math.random();
	y[i] = Math.random() + 1;
}

var data = [
  {
    x: x,
    y: y,
    colorscale: 'Blues',
    type: 'histogram2dcontour',
    contours: {
      showlabels: true,
      labelfont: {
        family: 'Raleway',
        color: 'white'
      }
    },
    hoverlabel: {
      bgcolor: 'white',
      bordercolor: 'black',
      font: {
        family: 'Raleway',
        color: 'black'
      }
    }
  }
];
Plotly.newPlot('myDiv', data);

一方で Python の場合はどうでしょう?

# https://plotly.com/python/2d-histogram-contour/ から抜粋
import plotly.graph_objects as go

import numpy as np

x = np.random.uniform(-1, 1, size=500)
y = np.random.uniform(-1, 1, size=500)

fig = go.Figure(go.Histogram2dContour(
        x = x,
        y = y,
        colorscale = 'Jet',
        contours = dict(
            showlabels = True,
            labelfont = dict(
                family = 'Raleway',
                color = 'white'
            )
        ),
        hoverlabel = dict(
            bgcolor = 'white',
            bordercolor = 'black',
            font = dict(
                family = 'Raleway',
                color = 'black'
            )
        )

))

fig.show()

大体似てるといえば似てますが, go = plotly.graph_objects を使ってたり二次元のヒストグラムの名前が Python のクラスのコード規約に則した upper camel case になっています. また plotly.express モジュールという独自のインターフェースも持っています.

これは鶏 vs. 卵な話に近く, プログラマーが最初に覚えた言語が Python でそのAPIが自然見えるとすればば JS 側 Julia 側が独特となってしまうわけです. そして plotly で検索すると多くの場合 Python から使う方法がよく出てしまう.

自分も Python を主に使っていたのもあり Pythonで書かれたものを JS の書き方に持っていくのはちょっとしんどいわけです. このクラスは JS/Julia だとどう書くのか? API リファレンスと睨めっこする必要があります.グッグっても一発でヒットしない (^^;)

そんなわけで

そんなわけで,Python から Julia に移行する場合にもその辺の課題があったのでそれを解消するために
PyPlotly.jl を作りました.

名前に PyPlotly としているのは Plotly.jl が既にあるからです. これは

Plotting functions provided by this package are identical to PlotlyJS. Please consult its documentation. In fact, the package depends on PlotlyJS.jl and reexports all the methods.

とあるように Plolty のクラウドサービスのためのAPIに向けたものです.

使い方

今のところ野良パッケージなので下記のように手動で add かクローンする必要があります.

$ pip3 install numpy pandas plotly
$ git clone https://github.com/AtelierArith/PyPlotly.jl.git
$ cd PyPlotly
$ julia --project=@. -e 'using Pkg; Pkg.isinstance()'

コードは下記のようになります.

using PyPlotly # これをすると `go`, `px` が使える 
go.Figure(
    go.Histogram2dContour(
        x = x,
        y = y,
        colorscale = "Jet",
        contours = Dict(
            :showlabels => true,
            :labelfont => Dict(:family => "Raleway", :color => "white"),
        ),
        hoverlabel = Dict(a)
    ),
)

パット見た目 Python の書き方と似ていることに気づくと思います. Dict の部分を調節する必要がありますが, ほぼほぼ機械的に変換できるでしょう.

README に書いてある例であればほとんど同じ書き方ができることがわかるでしょう.

# これは Python
import plotly.graph_objects as go

# Create random data with numpy
import numpy as np
np.random.seed(1)

N = 100
random_x = np.linspace(0, 1, N)
random_y0 = np.random.randn(N) + 5
random_y1 = np.random.randn(N)
random_y2 = np.random.randn(N) - 5

fig = go.Figure()

# Add traces
fig.add_trace(go.Scatter(x=random_x, y=random_y0,
                    mode='markers',
                    name='markers'))
fig.add_trace(go.Scatter(x=random_x, y=random_y1,
                    mode='lines+markers',
                    name='lines+markers'))
fig.add_trace(go.Scatter(x=random_x, y=random_y2,
                    mode='lines',
                    name='lines'))

fig.show()
# こっちは Julia
using PyPlotly # this exports `go` and `px`

using Random
Random.seed!(1)

N = 100
random_x = range(0, 1, length=N)
random_y0 = randn(N) .+ 5
random_y1 = randn(N)
random_y2 = randn(N) .- 5

fig = go.Figure()

# Add traces
fig.add_trace(go.Scatter(x=random_x, y=random_y0,
                    mode="markers",
                    name="markers"))
fig.add_trace(go.Scatter(x=random_x, y=random_y1,
                    mode="lines+markers",
                    name="lines+markers"))
fig.add_trace(go.Scatter(x=random_x, y=random_y2,
                    mode="lines",
                    name="lines"))

fig

px(=plotly.express) による例ならただのコピペで Python <-> Julia の変換が即時対応できます.

# using PyPlotly <--- Julia
# import plotly.express as px <--- Python

df = px.data.iris()
fig = px.scatter(
    df,
    x="sepal_width",
    y="sepal_length",
    color="species",
    size="petal_length",
    hover_data=["petal_width"],
)
fig

動作原理は先日説明した Qulacs を例に Julia から Python ライブラリを呼べるようにする話 と同様です.

この記事が出る2日前に開発を開始しJuliaLang Live Coding (Making PyPlotly.jl) with a silent cat では PyPlotly.jl のメイキング動画を撮っていました. このあとバグが見つかったり, 機能拡張をするために試行錯誤でちょっと変わっていますが,思想は動画に含んでいる実装と同じです.

PlotlyJS.jl への変換

下記のような変換関数を作成すればOKですこれは PyPlotly.jl でも使っています.

function create_plotlyjs(fig::GraphObjects.Figure)
    jsonobj = JSON.parse(fig.to_json())
    traces = PlotlyJS.GenericTrace.(jsonobj["data"])
    layout = PlotlyJS.Layout(jsonobj["layout"])
    return PlotlyJS.plot(traces, layout)
end

これによって IJulia 上などで描画するときに PlotlyJS.jl の display 関数を利用し自分では実装する必要がないようにできます.

まとめ

Python ユーザーに優しい Julia から Plotly を使う方法を紹介しました.

GitHubで編集を提案

Discussion