Heavy Watal

ggplot2 — きれいなグラフを簡単に合理的に

“The Grammer of Graphics” という体系に基づいて設計されたパッケージ。 単にいろんなグラフを「描ける」だけじゃなく「一貫性のある文法で合理的に描ける」。

Rのグラフ描画システムにはgraphicsgridの2つが存在しており、 R標準のboxplot()hist()などは前者の上に、 本項で扱うggplot2は後者の上に成り立っている。 使い方が全く異なるので、前者を知らずにいきなりggplot2から始めても大丈夫。

tidyverse に含まれているので、 install.packages("tidyverse") で一括インストール、 library(tidyverse) で一括ロード。

基本的な使い方

まずは手元のデータを整形して、1行が1観測、1列が1変数という形の 整然データ を用意することが重要。 言い換えると「この列がx軸、この列がy軸、この列によって色を変える」というように指示できる形。 例えばggplot2に付属のmpgデータ:

library(tidyverse)
mpg
#     manufacturer  model displ  year   cyl      trans   drv   cty   hwy    fl   class
#            <chr>  <chr> <dbl> <int> <int>      <chr> <chr> <int> <int> <chr>   <chr>
#   1         audi     a4   1.8  1999     4   auto(l5)     f    18    29     p compact
#   2         audi     a4   1.8  1999     4 manual(m5)     f    21    29     p compact
#  --
# 233   volkswagen passat   2.8  1999     6 manual(m5)     f    18    26     p midsize
# 234   volkswagen passat   3.6  2008     6   auto(s6)     f    17    26     p midsize

このようなデータを渡して、指示をどんどん + していく:

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = cty)) +
  theme_classic(base_size = 20, base_family = "Helvetica")

途中経過をオブジェクトとして取っておける:

p0 = ggplot(data = mpg, aes(x = displ, y = cty))
p1 = p0 + geom_point()
p2 = p1 + theme_classic(base_size = 20, base_family = "Helvetica")
p3 = p2 + stat_smooth(method = lm, formula = y ~ log(x))
p4 = p3 + facet_grid(drv ~ fl)
print(p4)

画像ファイルに保存するところまできっちり書く:

ggsave("mpg-displ-cty.png", p3, width = 4, height = 4, dpi = 300)

Aesthetic mapping

データと見せ方を紐付ける。 aes(colour = Species) のように aes() 内で列名を指定すると、 その変数に応じて色やサイズなどを変えることができる。 言い換えると、データの値を色やサイズに変換する(スケールする)ことに相当する。

# データによって点のサイズ・色・形を変える
p0 + geom_point(mapping = aes(x = displ, y = cty, size = cyl,
                              colour = class, shape = drv))

# aes() の外で指定するとデータによらず全体に反映。
# サイズは常に6、色はオレンジ、不透明度は0.4
p0 + geom_point(mapping = aes(x = displ, y = cty),
                size = 6, colour = "darkorange", alpha = 0.4)

scale_*() 関数で調整

変数をどうやって色やサイズに反映させるか、 各項目に対応する scale_*() 関数で調整する。

ggplot(mpg) +
  geom_point(aes(x = displ, y = cty, colour = class)) +
  scale_colour_brewer(palette = "Spectral")
scale_*_viridis_c
for color, fill
色覚多様性が考慮されたパレットなので、連続値ならまずこれを使う。 元は別パッケージが必要だったけど ggplot2 v3.0 から標準装備になった。
option =: viridis, magma, inferno, plasma
連続値には _c、離散値には _d
scale_*_brewer
for colour, fill
いい感じに考えられたパレット Colorbrewer から選んで指定するだけなので楽ちん。 特に離散値でお世話になる。 利用可能なパレットは RColorBrewer::display.brewer.all() でも一覧できる。
scale_*_brewer(..., palette = "Blues", direction = 1): 離散値
scale_*_distiller(..., palette = "Blues", direction = -1): 連続値
scale_*_gradient
for colour, fill
グラデーションの基準となる色を指定する。
scale_*_gradient(..., low, high, ...): 普通の連続値に
scale_*_gradient2(..., low, mid, high, midpoint = 0, ...): ある中央値を挟んで上下に分けたいとき
scale_*_gradientn(..., colours, values = NULL, ...): 多色のヒートマップなどに e.g., colours = c("#000000", "#0000FF", "#00FFFF", "#00FF00", "#FFFF00", "#FF0000")
scale_*_identity
for colour, fill, size, shape, linetype, alpha
色の名前やサイズなどを示す列が予めデータに含まれている場合にそのまま使う。 点や線の種類
scale_*_manual
for colour, fill, size, shape, linetype, alpha
対応関係を values 引数で直に指定する。
scale_size
デフォルトではpointの面積を値にほぼ比例させるが、面積0にはならない。 値0に面積0を対応させるには scale_size_area() を使う。 半径を比例させるには scale_radius() があるけど要注意。

scale_alpha(..., range = c(0.1, 1))
scale_linetype
scale_shape

スケール共通オプション

値と見え方の対応関係が凡例(legend/colourbar)として表示される。 scale_*() 関数に以下のオプションを指定することで設定を変えられる。 連続値の場合離散値の場合 で微妙に意味が変わるけどだいたいこんな感じ:

変数によってパネルを分割する

多変量データを俯瞰するには、データに応じたパネル分割も便利。 色・サイズなどと合わせれば、x軸y軸プラス3次元程度はパッと可視化できることになる。

facet_wrap()

1変数で分割して並べる

facet_wrap(facets, nrow = NULL, ncol = NULL, scales = "fixed",
  shrink = TRUE, labeller = "label_value", as.table = TRUE,
  switch = NULL, drop = TRUE, dir = "h", strip.position = "top")

p1 + facet_wrap(vars(class), ncol = 4L) # new style
p1 + facet_wrap(~ class, ncol = 4L)     # old style
facet_grid()

2変数以上で分割して縦横に並べる

facet_grid(rows = NULL, cols = NULL, scales = "fixed",
  space = "fixed", shrink = TRUE, labeller = "label_value",
  as.table = TRUE, switch = NULL, drop = TRUE, margins = FALSE,
  facets = NULL)

p1 + facet_grid(vars(cyl), vars(class, drv)) # new style
p1 + facet_grid(cyl ~ class + drv)           # old style

1変数でいい場合は . ~ class のように片方をドットにする。

ファセットラベルの調整 labeller =

デフォルトでは値だけがfacetラベルに表示されるが、 変数名を同時に表示したり、数式を表示したりもできる。 それを変数ごとに指定したい場合は labeller() 関数を使う。e.g.,
labeller = labeller(cyl = label_both, class = label_value)

見た目の調整はテーマの strip.* で。

パネル同士が近すぎて axis.text が重なってしまう場合は theme(panel.spacing = grid::unit(1, "lines")) などとすれば間隔を空けられる。

内部変数を使う

ヒストグラムや箱ヒゲなどの表示に必要な計算は stat_*() を通して内部的に行われる。 そうした値 (calculated aesthetics) の一部は aes(y = stat(count)) のようにして stat() 関数を通じて参照できる。 (昔は ..count.. のようにピリオドつきの変な名前で参照していた。)

これらはggplotオブジェクトを作るときではなく、描画するときに計算されるらしい。 強制的にそこまで計算させて値を参照するには ggplot_build() を使う。 たとえば h = hist(mpg$cty) のようなものを取得したいときは:

p = ggplot(mpg, aes(cty)) + geom_histogram(bins = 6L)
ggplot_build(p)$data[[1L]]
# layer_data(p)

https://github.com/hadley/ggplot2-book/blob/master/layers.rmd#generated-variables

座標軸やタイトルを変更

軸の区切りを変更したり対数にしたり
scale_x_continuous(breaks = seq(10, 100, by = 10))
scale_y_log10("Beer consumption")
scale_y_reverse()
上記のスケール共通オプションに加えて:
  • expand: デフォルトでは値域よりも少し余裕を持たせてあるが c(0, 0) を与えるとピッタリになる。geom_tile()を使うときなどに。
  • trans: 数値の変換。exp, identity, log, log10, reciprocal, reverse など。 文字列変数の順序を変えたい場合は limits のほうを使う。
  • position: top, bottom, left, right
  • sec.axis: 第二軸
描画する範囲を指定
ylim(0, 42) + xlim("b", "c", "d")
coord_cartesian(xlim = NULL, ylim = NULL)
前者はデータそのものを切るが、後者はデータを変えずに描画領域だけ切る
X軸とY軸の比率を固定
coord_fixed(ratio = 1)
XY軸の反転
coord_flip()
極座標
パイチャートも作れるらしい
座標変換
coord_trans(x = "log10", y = "sqrt")
表示する座標を変換する。 stat前に適用される scale_x_* とは微妙に違う。
軸ラベルとタイトル
labs(x = "time", y = "weight", title = "growth", tag = "A")
xlab("time") + ylab("weight") + ggtitle("growth")

theme: 背景やラベルの調整

https://ggplot2.tidyverse.org/reference/ggtheme.html

既成テーマ

theme_grey(base_size = 11, base_family = ""), theme_gray(...)
灰色背景に白い格子。 ggplotらしいデフォルトだが、論文には使いにくい。
theme_bw(base_size = 11, base_family = "")
黒枠白背景にうっすら灰色格子
theme_linedraw(base_size = 11, base_family = "")
細いけど濃い色の panel.grid
theme_light(base_size = 11, base_family = "")
それを薄くした感じ
theme_minimal(base_size = 11, base_family = "")
外枠なしの theme_bw
theme_classic(base_size = 11, base_family = "")
xy軸がL字に描かれているだけで枠もグリッドも無し
theme_void(base_size = 11, base_family = "")
完全に枠なし

これらをカッコ無しでコンソールに打ち込むと、 下記の各エレメントの設定方法やデフォルト値を知ることができる。

引数として base_family に”Helvetica Neue”などのフォントを指定できる。 Macなら”HiraKakuProN-W3”を指定すれば日本語でも文字化けしなくなるはず。 テーマを構成する axis.text などはこの設定を継承するが、 geom_text() などプロット内部の要素には引き継がれないことに注意。

ほかにもいろんなテーマが ggthemes というパッケージで提供されている。

設定項目

https://ggplot2.tidyverse.org/reference/theme.html

theme() 関数に項目と値を指定したものを、 ほかのレイヤーと同じようにどんどん足しながら変更していく。 テーマは直線、長方形、文字の3種類のエレメントからなり、 それらの性質を変更する場合は element_***() を介して行う

## ベースとなるテーマを先に適用してから
gp = gp + theme_bw(base_family = "HiraKakuProN-W3", base_size = 14)
gp = gp + theme(legend.position = "bottom")
gp = gp + theme(plot.background = element_rect(fill = "transparent"))
全体
line: (element_line)
rect: (element_rect)
text: (element_text)
title: (element_text; inherits from text)
aspect.ratio:
軸タイトル、軸ラベル、目盛
axis.title: (element_text; inherits from text)
__.x, __.x.top, __.y, __.y.right
axis.text: (element_text; inherits from text)
__.x, __.x.top, __.y, __.y.right
axis.ticks: (element_line; inherits from line)
__.x, __.y
axis.ticks.length: (unit)
axis.line: (element_line; inherits from line)
__.x, __.y
凡例
legend.background: (element_rect; inherits from rect)
legend.margin: (margin)
legend.spacing:(unit)
__.x, __.y
legend.key: (element_rect; inherits from rect)
legend.key.size: (unit)
legend.key.height: (unit; inherits from legend.key.size)
legend.key.width: (unit; inherits from legend.key.size)
legend.text: (element_text; inherits from text)
legend.text.align: (number from 0 (left) to 1 (right))
legend.title: (element_text; inherits from title)
legend.title.align: (number from 0 (left) to 1 (right))
legend.position: ("left", "right", "bottom", "top", "none" c(0, 1))
legend.direction: ("horizontal" or "vertical")
legend.justification: ("center" or c(0, 1) のような数値でアンカー位置を指定)
legend.box: ("horizontal" or "vertical")
legend.box.just: ("top", "bottom", "left", or "right")
legend.box.margin: (margin)
legend.box.background: (element_rect; inherits fromrect`)
legend.box.spacing:(unit)
プロット領域の背景、余白、格子
panel.background: (element_rect; inherits from rect)
panel.border: (element_rect; inherits from rect; should be used with fill = NA)
panel.spacing: (unit; facet_* の間隔)
__.x, __.y
panel.grid: (element_line; inherits from line)
panel.grid.major: (element_line; inherits from panel.grid)
__.x, __.y
panel.grid.minor: (element_line; inherits from panel.grid)
__.x, __.y
全体の背景、タイトル、余白
plot.background: (element_rect; inherits from rect)
plot.title: (element_text; inherits from title)
plot.subtitle: (element_text; inherits from title)
plot.caption: (element_text; inherits from title)
plot.margin: (unit with the sizes of the top, right, bottom, and left margins)
facet したときのラベル
strip.background: (element_rect; inherits from rect)
strip.placement: (“inside”, “outside”)
strip.text: (element_text; inherits from text)
__.x, __.y
その他
complete: 部分的な変更か、完全なテーマか (FALSE)
validate: 毎回チェックするか (TRUE)

エレメント

element_rect(fill, colour, size, linetype, inherit.blank) — 長方形
fill: 塗りつぶしの色
colour: 枠の色

element_line(colour, size, linetype, lineend, arrow, inherit.blank) — 直線

element_text(family, face, colour, size, hjust, vjust, angle, lineheight, margin) — 文字
family: フォントファミリー。 空なら theme_bw(base_family = ...) などの指定を継承。
face: ("plain", "italic", "bold", "bold.italic")
hjust, vjust: 水平位置と垂直位置の寄せ方をそれぞれ [0, 1] の実数で。
angle: 角度 [0, 360]
margin: スペース調整を関数 margin(top, right, bottom, left) 越しに。
element_blank() — 空

消したい要素にはこれを指定する

gp = gp + theme(
  axis.ticks = element_blank(),
  panel.grid = element_blank()
)
rel(x)

デフォルトからの相対値で size 引数を指定したいときに。

grid::unit(x, units, data = NULL)

こちらは絶対指定。grid パッケージに入ってる。 units で使いそうなのは cm, mm, inches, points, lines, native

grid::arrow(angle, length, ends, type)

axis.lineelement_line() にこれを与えて軸を矢印にするとか。

margin(t = 0, r = 0, b = 0, l = 0, unit = "pt")

marginクラス

calc_element(element, theme, verbose = FALSE)

継承などを考慮した上でelementがどんな値にセットされるか確かめる。

ファイルに書き出す

RStudioやQuartzの保存ダイアログを利用して書き出すこともできるけど、 それだとサイズ調整やファイル名入力を手作業でやることになってしまう。 ggsave() をスクリプトに書いておけば何回でも同じ設定で出力できる。

ggsave(filename, plot = last_plot(), device = NULL, path = NULL,
       scale = 1,  width = NA, height = NA,
       units = c("in", "cm", "mm"), dpi = 300, limitsize = TRUE, ...)
# 7inch x 300dpi = 2100px四方 (デフォルト)
ggsave("mpg1.png", p1) # width = 7, height = 7, dpi = 300

# 4     x 300    = 1200  全体7/4倍ズーム
ggsave("mpg2.png", p1, width = 4, height = 4) # dpi = 300

# 2     x 600    = 1200  全体をさらに2倍ズーム
ggsave("mpg3.png", p1, width = 2, height = 2, dpi = 600)

# 4     x 300    = 1200  テーマを使って文字だけ拡大
ggsave("mpg4.png", p1 + theme_bw(base_size = 22), width = 4, height = 4)

プロットの種類

散布図
geom_point(size = 2, alpha = 0.3)
重なった点をランダムにばらかしたいときは geom_jitter()
点の形(shape)一覧
折れ線グラフ
geom_path(size = 2, linetype = "dashed") データ順に結ぶ
geom_line() x軸上の順で結ぶ
geom_step() 階段状に結ぶ
線の種類(linetype)一覧
面グラフ
geom_ribbon() — yminからymaxの面
geom_area() — 0からyの面
ヒストグラム、密度曲線
geom_histogram() — 棒グラフ(連続値をstat_bin() で区切って)
geom_bar() — 棒グラフ(離散値をstat_count()で数えて)
geom_freqpoly() — 折れ線
geom_density() — 密度推定されたスムーズな線
geom_bin2d() — 二次元ヒストグラム
geom_hex() — 六角形版二次元ヒストグラム
棒グラフ
geom_col()
グループ分けする場合のオプション:
箱ひげ図とその代替案
geom_boxplot(): 生データの分布に関する情報量が落ちすぎるので要注意。
geom_violin(scale = "count"): 箱ひげ図よりは密度が分かりやすいけど、推定であることとスケールに注意。
ggridges::geom_density_ridges(): violinの片側を横にしたようなもの。
geom_dotplot() or ggbeeswarm::geom_beeswarm(): 生データの分布が読み取りやすい。
ヒートマップ
geom_tile(aes(fill = z))
geom_raster(aes(fill = z)) — 各タイルの大きさを揃える制約のため高速
エラーバー
geom_errorbar(aes(ymax = y + se, ymin = y - se), width = 0.1)
geom_linerange(...)
geom_pointrange(...)
関数
ggplot(data.frame(x = c(-4, 4)), aes(x)) + stat_function(fun = dnorm, args = c(0, 1), n = 200)
回帰曲線
geom_smooth(method = glm, method.args = list(family = poisson), se = FALSE)
始点と終点で曲線や矢印を描く
geom_curve(aes(x, y, xend, yend), curvature = -0.2)
geom_segment(aes(x, y, xend, yend), arrow = arrow(type = "closed"), linejoin = "mitre")
矢印の調整はgrid::arrow()。 普通の線より矢尻の分だけ長くなることに注意。
切片と傾きで直線を描く
geom_abline(intercept = 3, slope = 5)
geom_hline(yintercept = 7) + geom_vline(xintercept = 11)
これらは annotate() 的な位置づけであり、ほかの geom_ 関数とはかなり挙動が違う。 そのためか annotate("hline", yintercept = 7) なども思い通りの結果にはならない。
文字列や図形を書き加える
annotate("text", x = 1:4, y = 4:1, label = sprintf("x = %d", 1:4))
テーマの base_family は引き継がれないので family = で指定すべし。
数式を表示するには label = "italic(N[t])" のような文字列で渡して parse = TRUE
データ点に対応する文字列を添えるには geom_text(aes(label = foo)) のほうが適している。 オプションで nudge_x = 2, nudge_y = 2 などとすれば点と重ならないようにずらせる。 position_nudge()

Extensions

ggplotを拡張するための仕組みがversion 2.0から正式に導入され、 ユーザーが独自の stats や geom を作って登録することが容易になった。

gridExtra

https://github.com/baptiste/gridextra/wiki

データによって自動的にパネルを分割するには facet_grid()facet_wrap() を使えばよいが、 関係ない複数の図を1枚に描きたい場合は gridgtable の機能を使う必要がある。 gridExtra はそのへんの操作を手軽にできるようにしてくれるパッケージ。

“grob” は “grid graphical object” の略。 ggplotオブジェクトと同じように ggsave() に渡して保存可能。

grob = gridExtra::arrangeGrob(p1, p2, nrow = 2, ncol = 1, bottom = "Time")
grid.newpage()
grid.draw(grob)

複数ページのPDFに書き出したい場合は purrr などを使って list of ggplots を作っておき、 marrangeGrob() に渡す。

.grobs = purrr::map(.dataframes, my_ggplot_func)
.gtable = gridExtra::marrangeGrob(.grobs, nrow = 4, ncol = 3)
ggsave("multi_page.pdf", .gtable, width = 7, height = 9.9)

cowplot

ggplotを学術論文向けにカスタマイズしやすくする。 主な利用目的はgridExtraと同じでggplotを並べる機能。 論文figureのようなA, B, Cラベルをオプションで簡単に付けられるのが良い。

cowplot::plot_grid()

facet_wrap()のように、ざっと並べるのに便利。 もちろん入れ子も可能。

cowplot::plot_grid(..., plotlist = NULL,
    align = c("none", "h", "v", "hv"),
    nrow = NULL, ncol = NULL,
    scale = 1, rel_widths = 1, rel_heights = 1,
    labels = NULL, label_size = 14,
    hjust = -0.5, vjust = 1.5)

さらに細やかな制御をしたいときは以下の関数を個別に重ねていく。

cowplot::ggdraw(plot = NULL, xlim = c(0, 1), ylim = c(0, 1))

これの後ろに + 演算子で draw_***() を足していく。

cowplot::get_legend(plot)

凡例が共通する図を並べるとき、代表のやつをこれで取っておいてあとで並べる。

draw_figure_label() draw_grob() draw_label() draw_line() draw_plot() draw_plot_label() draw_text()

theme_cowplot() ggsave2()

パッケージ読み込みと同時に勝手にテーマを変更したり、 ggsave() 関数を上書きしたりという問題が過去にはあったが、今は大丈夫。

できあがった図を並べるための新しいパッケージとして patchwork がすごくエレガントで期待大。 ただし、演算子を多用するスタイルは忘れやすく検索しにくい諸刃の剣。

関連書籍