6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

purrrとggplot2で各水準毎の図形を一度に保存する

Posted at

分析を行っている中で、各水準毎に可視化を行い傾向の違いがあるかなどを確認することがあるかと思います。その場合、ggplot2ユーザーであれば以下のようにggplot2::facet_wrap()を使って一つのグラフで確認することが多いかと思います。

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  facet_wrap(~cyl)

facet_wrap.png

分析者が手元で傾向を確かめるだけなら十二分かなと思うのですが、レポートを行う時などに、どうしても各水準毎のグラフを保存したい時があったりします。(PowerPointに画像を添付するなんて恐ろしいことも時にはありますよね?)

そんな時に各水準毎に繰り返しコードを書かずに、purrrで対応する方法を記載しようと思います。

準備

一応Rとパッケージのversionを掲載しつつ、必要なパッケージを読み込みます。

> R.version.string
[1] "R version 3.6.1 (2019-07-05)"
> packageVersion("purrr")
[1] 0.3.2
> packageVersion("ggplot2")
[1] 3.2.1

library(purrr)
library(ggplot2)

また、今回可視化にはRに標準で組み込まれているmtcarsのデータセットを利用します。詳細は?mtcarsで確認することができます。

結論

いきなり結論ですが、split()purrr::iwalk()を組み合わせることでサクッと実現できます。

mtcars %>% 
  split(.$cyl) %>% 
  iwalk(~ ggplot(.x, aes(disp, mpg)) +
          geom_point() +
          ggtitle(paste0("cyl-", .y)) +
          ggsave(paste0("fig/cyl-", .y, ".png")))

解説

結論に記載したサンプルコードですが、purrr::map()くらいしか利用していなかった当時の自分には理解に時間がかかったので解説を記載しておこうと思います。

なぜimap()ではなくiwalk()なのか

imap()とは

map2(x, names(x), ...)もしくはmap2(x, seq_along(x), ...)の短縮版です。
例えば以下のように利用することができます。

> imap_chr(mtcars, ~ paste0(.y, ": ", median(.x)))
          mpg           cyl          disp            hp          drat 
  "mpg: 19.2"      "cyl: 6" "disp: 196.3"     "hp: 123" "drat: 3.695" 
           wt          qsec            vs            am          gear 
  "wt: 3.325" "qsec: 17.71"       "vs: 0"       "am: 0"     "gear: 4" 
         carb 
    "carb: 2" 

# imap_chr()と同様の結果が得られる
> map2_chr(mtcars, names(mtcars), ~ paste0(.y, ": ", median(.x)))
          mpg           cyl          disp            hp          drat 
  "mpg: 19.2"      "cyl: 6" "disp: 196.3"     "hp: 123" "drat: 3.695" 
           wt          qsec            vs            am          gear 
  "wt: 3.325" "qsec: 17.71"       "vs: 0"       "am: 0"     "gear: 4" 
         carb 
    "carb: 2" 

walk()とは

多くの関数の場合、返り値を取得することは理に適っていますが、cat()write.csv()などの関数に関して言えば副次的な効果が目的であり返り値を持たない方が適切な場合があります。
例えばAdvanced R 2nd edition9 Functionalsに具体例が載っているので引用します。

welcome <- function(x) {
  cat("Welcome ", x, "!\n", sep = "")
}
names <- c("Hadley", "Jenny")

# As well as generate the welcomes, it also shows 
# the return value of cat()
map(names, welcome)
# > Welcome Hadley!
# > Welcome Jenny!
# > [[1]]
# > NULL
# > 
# > [[2]]
# > NULL

上記のような場合だとNULLは不要なので、返り値を隠してくれるwalk()の出番になります。

walk(names, welcome)
# > Welcome Hadley!
# > Welcome Jenny!

まとめ

画像の保存だけを行うのであれば、特に返り値としてグラフは必要でないので、iwalk()を利用します。
(ただし、RStudio上で出力を確認しておきたい場合などはimap()を利用したほうが手っ取り早いです)

~は何者なのか

こちらに全て書いてあるので蛇足感しかありませんが、一応解説を記載しておきます。

apply族やpurrrを利用していると、複数の関数を組み合わせたりするために以下のように無名関数を書くことが多くなります。

> map_dbl(mtcars, function(x) length(unique(x)))
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
  25    3   27   22   22   29   30    2    2    3    6 

無名関数は十分有用なのですが、やや冗長になってしまいがちです。そこでpurrrは~を利用したショートカットを提供しています。

> map_dbl(mtcars, ~ length(unique(.x)))
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
  25    3   27   22   22   29   30    2    2    3    6 

なので、結論に記載したショートカットを利用したコードを無名関数を利用して書き直すと以下のようになります。

mtcars %>% 
  split(.$cyl) %>% 
  iwalk(function(x, y) ggplot(x, aes(disp, mpg)) +
          geom_point() +
          ggtitle(paste0("cyl-", y)) +
          ggsave(paste0("fig/cyl-", y, ".png")))

ちなみに、.xとなっているのは一般的な引数と被らないための工夫からです。(こちらを読むと記載があります)

誤っている部分やよりスマートに書く方法がある場合は是非ご指摘いただけますと幸いです。
Enjoy!

参考

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?