ジュリアでアートスタイルを作る方法
概要
RecipesBase.jl
は、ユーザーが新しい図のスタイルを自分で作れるパッケージだ。Rプログラミング言語でのggplot
がそうであるように、元のジュリアとはまた別の独自の文法1がある。例を通して覚えよう。
コード
using Plots
using DataFrames
df = DataFrame(x = 1:10, y = rand(10))
plot(df)
@userplot TimeEvolution
@recipe function f(te::TimeEvolution)
df = te.args[1]
linealpha --> 0.5
column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
@series begin
label --> column_name
df[:,column_index]
end
end
end
timeevolution(df); png("1")
timeevolution(df, legend = :left); png("2")
最初に、上記のコードを実行すると、次のようなエラーが発生する。
julia> df = DataFrame(x = 1:10, y = rand(10))
10×2 DataFrame
Row │ x y
│ Int64 Float64
─────┼──────────────────
1 │ 1 0.636532
2 │ 2 0.463764
3 │ 3 0.604559
4 │ 4 0.654089
5 │ 5 0.883409
6 │ 6 0.91667
7 │ 7 0.0609783
8 │ 8 0.602259
9 │ 9 0.460372
10 │ 10 0.479944
julia> plot(df)
ERROR: Cannot convert DataFrame to series data for plotting
これは、基本的にplot()
がデータフレームを受け取って図を描く方法が定義されていないためだ。
@userplot
と@recipe
@userplot TimeEvolution
@recipe function f(te::TimeEvolution)
...
end
例では、時系列データをそのまま折れ線チャートで描いてみる。
@userplot
:plot()
関数の性質を継承する関数の名前を指定する。ここでは大文字と小文字の区別があるが、完成する関数は小文字のみ使うことができることに注意しろ。@recipe
:具体的に図のスタイルを指定する。これの後にくる関数の名前は、通常慣習的にf
を使用する。
f(te::TimeEvolution)
df = te.args[1]
linealpha --> 0.5
column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
@series begin
label --> column_name
df[:,column_index]
end
end
では、上記のコードを一行一行理解していこう。
df = te.args[1]
受け取った引数の中で最初のものをdf
とみなす。この例では、与える引数がデータフレームであると仮定されているため、その略称であるdf
を使った。このように、f
は私たちが使用する関数でもなく、その引数te
も直接使用されないことに注意せよ。
linealpha --> 0.5
この図で描かれる線の透明度を0.5に設定する。オプションを与えるようにlinealpha = 0.5
ではなく、-->
で値を与えることに注意せよ。これは、通常のジュリアの文法と完全に異なる。
column_names = names(df)
for (column_index, column_name) ∈ enumerate(column_names)
...
end
plot()
が2次元配列を直接図に描くとしても、ラベルはy1
、y2
などと自動的に与えられる。これを防ぐために、上記のようにデータフレームの列名を取得して別々にラベルを与える。enumerate()
関数を使って、そのインデックスと列名を同時に巡回する。詳しくはenumerate()
に関する説明を参照せよ。
@series
for ...
@series begin
label --> column_name
df[:,column_index]
end
end
@series
マクロを通じて、繰り返し描かれるデータとそのスタイルを指定する。label --> column_name
を通じて列名をラベルとして与え、一番下の行にdf[:,column_index]
と書くことで、その列名のデータが描かれることになる。この時、描かれるデータは一番下の行になければならないことに注意しろ。
結果
この結果によって、timeevolution()
またはtimeevolution!()
で、私たちが指定したスタイルで図を描くことができるようになった。
timeevolution(df)
データフレームを入れてもエラーなく図がうまく描かれていることを確認できる。折れ線の透明度は0.5で、ラベルもデータフレームにあった列名をそのまま引き継いでいる。
timeevolution(df, legend = :left)
任意に凡例の位置を調整してみた。f
を定義する時にlegend
について何の言及もなかったにも関わらず、うまく適用されていることを確認できる。これは、timeevolution()
がplot()
の残りの要素を継承しているためだ。
環境
- OS: Windows
- julia: v1.6.3