Rでfor
ループで大量に{ggplot2}
でグラフを書き、Rmarkdown
で書いたレポートに入れたいと思った。for
ループじゃなくpurrr::map()
の例は最後に書く。
{ggplot2}
で、例としてiris
データのSpeciesごとに、Sepal.Length
とSepal.width
の散布図を書きたいとする。3つくらいならコードをその都度書いてもよいがfor
ループで
データを取り出すために、{dplyr}
のfilter()
をつかってやりたいとする。
# 今回使うパッケージをロードする library(ggplot2) library(dplyr)
##
## 次のパッケージを付け加えます: 'dplyr'
## 以下のオブジェクトは 'package:stats' からマスクされています:
##
## filter, lag
## 以下のオブジェクトは 'package:base' からマスクされています:
##
## intersect, setdiff, setequal, union
iris
データのSpecies
はunique()
で取り出す。
(描出されないけど)普通の書き方をすると次のようになると思う。
for(i in unique(iris$Species)){ iris %>% filter(Species == i) %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + labs(title = i) }
これは思ったようにグラフは描出されない。
すぐに思いつくのは、print()
に最後にパイプで渡せば描出してくれそう(これも思ったように挙動しない)。
for(i in unique(iris$Species)){ iris %>% filter(Species == i) %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + labs(title = i) %>% print() }
## $title
## [1] "setosa"
##
## attr(,"class")
## [1] "labels"
## $title
## [1] "versicolor"
##
## attr(,"class")
## [1] "labels"
## $title
## [1] "virginica"
##
## attr(,"class")
## [1] "labels"
グラフがほしいんですよ、グラフが。
次にちょっと手間だけれど適当な空オブジェクト(今回はfor.gg
というオブジェクト)をつくってそれの中にループさせて作ったものをリストとして格納していく風にして、最後にループ外でオブジェクトに格納されたものを取り出してみる。
for.gg <- NULL for(i in unique(iris$Species)){ for.gg[[i]] <- iris %>% filter(Species == i) %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + labs(title = i) }
これはgg[[1]]
、gg[[2]]
、gg[[3]]
に各散布図のデータが格納されている。図を取り出すにはgg[[1]]
やgg$setosa
として1個ずつ必要なグラフを取り出すか、gg
でまとめて取り出せる。
for.gg
## $setosa
##
## $versicolor
##
## $virginica
こうすると、for.gg
内のグラフオブジェクトは更にgeome_*()
とかで書き込める。
for.gg[[1]] + geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
これは一つのメリットでもあるが、もう図に書き込みはしないし再利用(再掲)する予定もなければオブジェクト内に保持する必要もないのでfor
ループ内にでグラフの描画まで完結させたい。
そうしたとき、グラフ描画部分のコードを()
でくくって、パイプでprint()
にわたすと実現する。
for(i in unique(iris$Species)){ (iris %>% filter(Species == i) %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + labs(title = i)) %>% print() }
for
ループのコードを走らせただけで書きたい図がすべて描出される。(゚д゚)ウマー
場合により使い分けするとよいかもしれない。
ところで{ggplot2}
で作るオブジェクトはRStudioのGroval
Environmentをみると分かる通りリスト形式になっているので{purrr}
のmap()
をつかっても同じことが実現できる。
これなら空のオブジェクトを作る必要はない。オブジェクトに格納する方法であれば次のように書ける。
map.gg <- purrr::map(unique(iris$Species), function(i){ iris %>% filter(Species == i) %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + labs(title = i) })
一つだけ取り出してみる。
map.gg[[1]]
こちらのほうがmap()
つかって処理しているのでなんとなくRっぽい。
これも先のfor
ループでオブジェクトに格納したときと同様にいじることができる。
map.gg[[1]] + geom_smooth(method = "lm")
## `geom_smooth()` using formula 'y ~ x'
オブジェクトに格納しないなら次のように書くこともできる。
purrr::map(unique(iris$Species), function(i){ iris %>% filter(Species == i) %>% ggplot(aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + labs(title = i) })
## [[1]]
##
## [[2]]
##
## [[3]]
2重以上での条件(条件1 * 条件2)の組み合わせでグラフを書きたいと思ったときはこの延長で入れ子構造にすると書ける。
# サンプルデータを作る d <- tibble::tibble(v1 = rnorm(1000), v2 = rnorm(1000), type = sample(c("A", "B"), 1000, replace = TRUE), group = sample(c("X", "Y"), 1000, replace = TRUE))
例えば、for
ループだと次のようにして書ける。
for(i in unique(d$type)){ for(j in unique(d$group)){ (d %>% filter(type == i) %>% filter(group == j) %>% ggplot(aes(x = v1, y = v2)) + geom_point() + labs(title = paste0(i, "*", j))) %>% print() } }
purrr::map()
ならこんな感じ。
purrr::map(unique(d$type), function(i){ purrr::map(unique(d$group), function(j){ d %>% filter(type == i) %>% filter(group == j) %>% ggplot(aes(x = v1, y = v2)) + geom_point() + labs(title = paste0(i, "*", j)) }) })
## [[1]]
## [[1]][[1]]
##
## [[1]][[2]]
##
##
## [[2]]
## [[2]][[1]]
##
## [[2]][[2]]
少ない量なら一つずつ書くのもいいしcolor
で簡単に色分けするくらいで大丈夫だけれど、探索的にデータ見てるときに膨大な数になるとループ処理が活きてくる。