logo

ジュリアでアートスタイルを作る方法 📂ジュリア

ジュリアでアートスタイルを作る方法

概要

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

例では、時系列データをそのまま折れ線チャートで描いてみる。

  • @userplotplot()関数の性質を継承する関数の名前を指定する。ここでは大文字と小文字の区別があるが、完成する関数は小文字のみ使うことができることに注意しろ。
  • @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次元配列を直接図に描くとしても、ラベルはy1y2などと自動的に与えられる。これを防ぐために、上記のようにデータフレームの列名を取得して別々にラベルを与える。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)

1.png

データフレームを入れてもエラーなく図がうまく描かれていることを確認できる。折れ線の透明度は0.5で、ラベルもデータフレームにあった列名をそのまま引き継いでいる。

timeevolution(df, legend = :left)

2.png

任意に凡例の位置を調整してみた。fを定義する時にlegendについて何の言及もなかったにも関わらず、うまく適用されていることを確認できる。これは、timeevolution()plot()の残りの要素を継承しているためだ。

環境

  • OS: Windows
  • julia: v1.6.3