📊

Plots.jl入門

2021/11/01に公開
1

はじめに

juliapackages.comによると, Juliaの最もポピュラーな可視化パッケージはPlots.jlです. しかし, 公式のチュートリアルサンプルがあまり分かりやすいとは感じられませんでした. そこで, さらにシンプルに, できるだけ簡単な例を通して, Plots.jlの最初の一歩を踏み出すためのノートを作成しました.

インストール

Juliaのパッケージモードadd Plotsを実行し, 事前にPlots.jlをインストールしておく必要がある. また, ノート上ではusing Plotsを宣言する.

# using Pkg
# Pkg.add("Plots")
using Plots

Hello World!

plot()に関数名を渡すことで描写できる. 特に指定しなければ(かなり滑らかな)折れ線グラフになる.

plot(sin)

自作関数を渡すこともできる. 関数の宣言方法はこちらを参考にするとよい.

f(x) = x^2
plot(f)

1行で書きたい時は無名関数(Anonymous Function)を渡す.

plot(x->x^2)

2つの配列を渡して, それぞれx軸とy軸の値として折れ線グラフや散布図を描くこともできる.

X = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Y = [0, 1, 0, 2, 0, 4, 0, 8, 0, 4, 4]

plot(X, Y)

追加オプション

タイトルや軸ラベル, マーカーや線の色などを変更したい場合は以下のオプションを書き加えればよい. 以下の紹介するものはごく一部で, こちらに全て載っている.

文字列

説明 省略形 オプション 引数例
タイトル title "", "hello"
凡例 label "", "hello"
x軸ラベル xlabel "", "hello"
y軸ラベル ylabel "", "hello"
タイトルのフォントサイズ titlefontsize 12, 18
凡例のフォントサイズ legendfontsize 12, 18
軸の数字のフォントサイズ tickfontsize 12, 18
軸ラベルのフォントサイズ guidefont 12, 18
x軸ラベルのフォントサイズ xguidefont 12, 18
y軸ラベルのフォントサイズ yguidefont 12, 18

説明 省略形 オプション 引数例
枠線と軸 framestyle :box, :semi, :origin, :zerolines, :grid :none
x軸の表示 xshowaxis true, false
y軸の表示 yshowaxis true, false
x軸の描写範囲 xlims (-1,1), (0,2π)
y軸の描写範囲 ylims (-1,1), (0,2π)
x軸のスケール xscale :log10, :ln, :log2
y軸のスケール yscale :log10, :ln, :log2
x軸の目盛り位置 xticks 0:0.1:2, [1,3,4]
y軸の目盛り位置 yticks 0:0.1:2, [1,3,4]
x軸のグリッド線 xgrid true, false
y軸のグリッド線 ygrid true, false
x軸のマイナーグリッド線 xminorgrid true, false
y軸のマイナーグリッド線 yminorgrid true, false

デザイン

説明 省略形 オプション 引数例
グラフの種類 st seriestype :line, :scatter, :steppre, etc.
線の種類(実線, 破線等) ls linestyle :solid, :dash, :dot, etc.
線の透明度 la linealpha 0.0, 0.2, 0.5, 1.0
線の太さ lw linewidth 1, 2, 4
線の色 lc linecolor :red, :green, :blue, "#FF0000"
塗潰しの透明度 fa fillalpha 0.0, 0.2, 0.5, 1.0
塗潰しの色 fc fillcolor :red, :green, :blue, "#FF0000"
マーカーの形 markershape :circle, :square, :hexagon, etc.
マーカーの大きさ ms markersize 20
マーカーの透明度 markeralpha 0.0, 0.2, 0.5, 1.0
マーカーの色 mc markercolor :red, :green, :blue, "#FF0000"
マーカーの囲み線の太さ msw markerstrokewidth 1, 2, 4
マーカーの囲み線の透明度 markerstrokealpha 0.0, 0.2, 0.5, 1.0
マーカーの囲み線の色 msc markerstrokecolor :red, :green, :blue, "#FF0000"
マーカーの囲み線の種類(実線, 破線等) markerstrokestyle :solid, :dot
凡例の位置 legend :bottomleft, :topright, ect.
凡例の背景の色 background_color_legend :red, "#FF0000", nothing
凡例の枠線の色 foreground_color_legend :red, "#FF0000", nothing

基本的な文字列の指定と, 配色の指定について例を示す.

plot(sin, title="title", label="label", xlabel="x", ylabel="y", lw=10, lc="#00C8AF", foreground_color_legend=:red)

定義域と描写範囲

関数を描写する場合 の定義域とサンプル間隔は-1:0.01:1のように指定する. 描写範囲についてはxlims=(min,max)ylims=(min,max)をそれぞれ指定する. 片方だけ指定してもよい. 配列を描写する場合, サンプル数の指定は不要なのでplot(X, Y, xlims=(-2*pi,2*pi), ylims=(-0.5,1))のように指定すればよい.

plot(-5:0.01:5, sin, xlims=(-2*pi,2*pi), ylims=(-0.5,1))

散布図

散布図はplot()st=:scatterというオプションを追加するか, scatter()を用いる. 基本的な使い方はplot()と同じである.

plot(sin, st=:scatter)

scatter(sin)

ヒストグラム

配列を渡すことで要素についてのヒストグラムを描くことができる. 下記ではnormed=trueのオプションによって正規化された(総面積が1の)ヒストグラムを描いている.

X = [1, 2, 3, 3, 4, 5, 5]

histogram(X, bins=range(0, 6, step=0.2), normed=true)

一様分布の例:

X = rand(10000)

histogram(X, bins=range(0, 1, step=0.02), normed=true)

正規分布の例:

X = randn(10000)

histogram(X, bins=range(-5, 5, step=0.2), normed=true)

2つの配列を渡して, 2次元ヒストグラムを描写することもできる. c=:heatのように配色を指定できる. 配色についてはこちらを参照されたい.

X = randn(1000)
Y = randn(1000)

histogram2d(X, Y, nbins=20, c=:heat)

ヒートマップ

x軸とy軸それぞれの定義域と, 2変数関数fを渡せばよい. c=:thermalのように配色を指定できる. 配色についてはこちらを参照されたい.

f(x,y) = x^2 + y^2

heatmap(-3:0.01:3, -2:0.01:2, f, c=:thermal)

2次元配列を渡すこともでてきる.

Z = randn(20,20)

heatmap(Z)

X軸とY軸に相当する1次元配列を渡すこともできる.

X = [0.1*i for i in 0:20]
Y = [0.1*i for i in 0:20]
Z = randn(21,21)

heatmap(X, Y, Z)

3Dグラフ

基本的な使い方は上記のheatmapsurfaceに置き換えるだけである.

f(x,y) = x^2 + y^2

surface(-3:0.01:3, -2:0.01:2, f, c=:thermal)

Z = randn(21,21)

surface(Z)

多変数関数

多変数関数のプロットでエラーが出て躓くことは多々あると思います. 2変数関数ならヒートマップや3Dで描写すればよいですが, それ以上となると難しくなります. 以下のように射影し, 1変数関数を作ってプロットしましょう.

f(x,y,z) = x^2 + y^2 + z^2

plot(x->f(x,0,0))
f(x,y,z) = x^2 + y^2 + z^2
g(x) = f(x,0,0)

plot(g)

グラフを並べる

等価な書き方を4通り列挙する. それぞれ一長一短があるので, 適宜使い分けてほしい.

plt1 = plot(sin, label="")
plt2 = plot(cos, label="")
plt3 = plot(exp, label="")
plt4 = plot(abs, label="")

plot(plt1, plt2, plt3, plt4, layout = (2, 2))

次の記法ではループが使いやすい.

plt = []

push!(plt, plot(sin, label=""))
push!(plt, plot(cos, label=""))
push!(plt, plot(exp, label=""))
push!(plt, plot(abs, label=""))

plot(plt..., layout = (2, 2))

plot(
    plot(sin, label=""),
    plot(cos, label=""),
    plot(exp, label=""),
    plot(abs, label=""),
    layout = (2, 2)
)

plot(
    plot(x->sin(x), label=""),
    plot(x->cos(x), label=""),
    plot(x->exp(x), label=""),
    plot(x->abs(x), label=""),
    layout = (2, 2)
)

無名関数も同様

plot(
    plot(x->x^1, label=""),
    plot(x->x^2, label=""),
    plot(x->x^3, label=""),
    plot(x->x^4, label=""),
    layout = (2, 2)
)

グラフを重ねる

重ねる場合は, plot!()を使う.

plt = plot(sin, label="sin")
plot!(plt, cos, label="cos")

折れ線グラフと散布図を重ねることもできる.

X = [i for i in 0:10]
Y = sin.(X)

plt = plot(0:0.1:10, sin, label="function")
scatter!(plt, X, Y, label="array")

文字列

title, label, xlabel, ylabelなどとは別に, 座標を指定して文字列を描写したい場合がある. グラフを重ねるのと同じ要領で, plot!(annotations=(x座標, y座標, ("文字列", フォントサイズ(整数), 角度(浮動小数点数), 位置)))として文字列を描写することができる. フォントの色の指定はうまくいかないことが多いので, 推奨しない(おそらくバックエンドに依存している). また, xticks!([0,1,3], ["A", "B", "C"])のようにして目盛りの数値に好きな文字列を与えることもできる.

plot(-3:0.1:3, sin, title="example", label="label", xlabel="xlabel", ylabel="ylabel")

# 1つずつ指定する例:
plot!(annotations=(-pi/2, -1, ("min", 8, 0.0, :bottom)))
plot!(annotations=(0, 0, ("origin", 8, 0.0, :center)))
plot!(annotations=(pi/2, 1, ("max", 8, 0.0, :top)))

# 1つずつ指定する例:
annotate!(-1, 0.5, "aaaaa")

# 配列として渡す例:
plot!(annotations=[
    (pi/2, 0.0, ("a", 8, 0.0, :center)),
    (pi/2, 0.1, ("b", 8, 45.0, :center)),
    (pi/2, 0.2, ("c", 8, 90.0, :center)),
])

# 軸の文字を変更する例:
xticks!([0,1,3], ["A", "B", "C"])

保存

ファイルに保存する場合は以下のようにサイズと拡張子を指定する. SVGはベクタ画像の中でも扱いやすく, PowerPointでもドラッグ&ドロップすれば貼り付けられる. Zennにアップロードする場合はGIFに変換すると劣化しにくい.

plt = plot(sin, size=(420,300), fmt=:svg)
savefig(plt, "plot.svg")
display(plt)

アニメーション

GIFアニメーションを出力することもできる.(FFmpegが必要なのでこちらに従ってインストールし, binフォルダを環境変数のパスに追加する必要がある. なお, Juliaのバージョンを上げたときにUndefVarError: ffmpeg not definedというエラーが出たが, パッケージモードで再度add Plotsbuild Plotsを実行すれば動くようになった(参考).)

anim = Animation()

for i in 1:10
    plt = plot(x->sin(i*x), title="i = "*string(i), label="")
    frame(anim, plt)
end

gif(anim, "plot.gif", fps = 2)

CSVファイル

CSVファイル等の外部ファイルを読み込んでプロットしたい場合, まずCSV.jlやDataFrames.jlを使って. データを配列として格納しましょう. 配列として得られてしまえば, あとは先ほど解説した通りにプロットできる.

Tips

凡例を非表示にする例
plot(sin, label="")

無名関数の描写の例
plot(x->x*sin(x^2), label="")

片対数プロット
plot(0.1:0.1:10, x->x, yscale=:log10, label="")

グラフをループで重ねる
plt = plot()
for i in 1:10
    plot!(plt, x->exp(-x^(2*i)), label=string(i), legend=:topleft, xlim=(-2,2))
end
plot(plt)

グラフをループで並べる
plt = []
for i in 1:9
    push!(plt, plot(x->sin(x)^i, title=string(i), label=""))
end
plot(plt...)

ここで利用した"splat"について補足する. 以下を実行した結果からprintln(A[1],A[2],A[3],A[4])println(A...)が等価な記法だとわかる.

A = [3,4,5,6]
println(A[1],A[2],A[3],A[4])
println(A...)
println(A)
3456
3456
[3, 4, 5, 6]
折れ線グラフとヒストグラムを重ねる例
f(x) = exp(-x^2/2) / sqrt(2*pi)
X = randn(1000)
Y = [-0.01-0.0001*i for i in 1:1000]

plt = histogram(X, bins=range(-5,5,step=0.2), ylims=(-0.14,), normed=true, label="Histogram", fa=0.3, fc=1, lc="#FFFFFF")
plot!(plt, f, lw=2, lc="#393e46", label="Exact PDF")

折れ線グラフ, ヒストグラム, 散布図を重ねる例
f(x) = exp(-x^2/2) / sqrt(2*pi)
X = randn(1000)
Y = [-0.01-0.00005*i for i in 1:1000]

plt = histogram(X, bins=range(-5,5,step=0.2), ylims=(-0.7,), normed=true, label="Histogram", fa=0.3, fc=1, lc="#FFFFFF")
scatter!(plt, X, Y, xlims=(-5,5), ms=1, msw=0, mc="#3261AB", label="Sample")
plot!(plt, f, lw=2, lc="#393e46", label="Exact PDF")

2次元ヒストグラムとサンプルを重ねる例
X = randn(1000)
Y = randn(1000)

histogram2d(X, Y, nbins=20, c=:heat)
scatter!(X, Y, mc="#000000", msw=0, ms=1, label="")

LaTeXを使う
# using Pkg
# Pkg.add("LaTeXStrings")
using LaTeXStrings
plot(sin, xlabel=L"x", ylabel=L"\sin(x)")

水素様原子のエネルギー準位の描写
E(Z,n) = -Z^2/(2*n^2)

plot(xlims=(0,3), xshowaxis=false, xgrid=false, ylabel="\$\\mathrm{Energy~Level~/~}E_\\mathrm{h}\$")

annotate!(0.5, E(3,1)-0.4, "\$\\mathrm{H}\$", 12)
annotate!(1.5, E(3,1)-0.4, "\$\\mathrm{He}^+\$", 12)
annotate!(2.5, E(3,1)-0.4, "\$\\mathrm{Li}^{2+}\$", 12)

for i in 1:30
    plot!([0.1,0.9], fill(E(1,i),2), label="", lc=:darkblue)
    plot!([1.1,1.9], fill(E(2,i),2), label="", lc=:darkblue)
    plot!([2.1,2.9], fill(E(3,i),2), label="", lc=:darkblue)
end

plot!()

余白を変更する例
plot(sin, left_margin=Plots.Measures.Length(:mm, 50.0))

画質:
dpiを変更することもできるらしい.
https://github.com/genkuroki/public/blob/main/0026/dpi of plot.ipynb

動作環境

versioninfo()
Julia Version 1.6.2
Commit 1b93d53fc4 (2021-07-14 15:36 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-4650U CPU @ 1.70GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-11.0.1 (ORCJIT, haswell)

参考文献

この記事の元になったJupyter Notebookのデータは下記のリンクにある.
https://gist.github.com/ohno/78d55921c3f88b50a7fb1835be132bcd

SVGからGIF画像への変換には次のサービスを利用した.

https://imageconvert.org/svg-to-gif

Discussion