君の知らない物語(あまり知られていないtidyverseの便利な機能)


「なんでもは知らないわよ。知ってることだけ。[^1]」

[^1]: 最近、物語シリーズを見返しました。

これは[R Advent Calendar2018](https://qiita.com/advent-calendar/2018/rlang)の第一日目の記事です。"tidyverse"という言葉がUser!2016で爆誕してから早くも2年が経ちました。皆さんは普段どれだけtidyverseのパッケージを使って作業をしているでしょうか。

今日は、日々進化を続けるtidyverseに含まれるコアパッケージの、（私的に）あまり知られていない関数を紹介したいと思います。なお各パッケージのバージョンは全て現在CRANに登録されている最新版となります。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">「あなたの好きなtidyverseの関数を教えてください！」のコーナーだ（謎）！！ slice()わいも好き。 <a href="https://twitter.com/hashtag/tokyor?src=hash&amp;ref_src=twsrc%5Etfw">#tokyor</a></p>&mdash; Uryu Shinya (@u_ribo) <a href="https://twitter.com/u_ribo/status/1053533938994446336?ref_src=twsrc%5Etfw">October 20, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

皆さんの好きなtidyverseの関数は何ですか??

それでは行きます。

``` r
library(tidyverse)
```

    ## ─ Attaching packages ───────────────────────────────── tidyverse 1.2.1 ─

    ## ✔ ggplot2 3.1.0     ✔ purrr   0.2.5
    ## ✔ tibble  1.4.2     ✔ dplyr   0.7.8
    ## ✔ tidyr   0.8.2     ✔ stringr 1.3.1
    ## ✔ readr   1.2.1     ✔ forcats 0.3.0

    ## ─ Conflicts ─────────────────────────────────── tidyverse_conflicts() ─
    ## ✖ dplyr::filter() masks stats::filter()
    ## ✖ dplyr::lag()    masks stats::lag()

ggplot2
-------

NEWSというよりはtips的な紹介です。

### gghighlightを使わないハイライト

[パイプ演算子を無名関数として利用](https://qiita.com/uri/items/17c213bf2bca7c95b154#ミソ)することで、ハイライトのための用意を整えます。実際のデータの選択・抽出は`geom_*(data = )`で行います。

例えば、gghighlightを使ってirisのSpecies == “setosa”をハイライトするには次のようにします。

``` r
library(gghighlight)

iris %>%
  ggplot() +
  geom_point(aes(Sepal.Length, Petal.Width)) +
  gghighlight(Species == "setosa")
```

これをパイプ処理で再現します。

``` r
iris %>% {
    ggplot(data = .,
           aes(Sepal.Length, Petal.Width)) +
      geom_point(data = . %>%
                   dplyr::filter(Species == "setosa"),
                 color = "black") +
    geom_point(data = . %>%
                   dplyr::filter(Species != "setosa"),
                 color = "gray",
                 alpha = 0.5) +
    guides(color = FALSE)
}
```

![unnamed-chunk-4-1.png](https://qiita-image-store.s3.amazonaws.com/0/19462/97084021-4f2e-1ee0-ae7f-4301245b6e4c.png)

冗長ですが、細かな部分の調整やgghighlightで対応できない処理（があるかもしれない）があった時に覚えておくと便利です。

### 複数の図をまとめて表示する際に便利なタグ

ggplot2には、図の説明用にtitle, subtitle, captionが用意されていることは知っていましたが、tagの存在に気が付いたのは比較的最近です。`ggtitle()`のような専用関数は用意されておらず、`labs(tag = )`で指定します。

``` r
library(patchwork)

p1 <-
  iris %>%
  ggplot(aes(Sepal.Length, Petal.Width)) +
  geom_point() +
  labs(tag = "A")

p2 <-
  iris %>%
  ggplot(aes(Species, Sepal.Width)) +
  geom_boxplot() +
  labs(tags = "B")


theme_set(theme_gray(base_size = 12, base_family = "IPAexGothic"))
p1 + p2 +
  plot_layout(ncol = 2) +
  plot_annotation(title = "アヤメデータのあれこれ",
                  caption = "A: 萼片長と花弁幅の散布図\nB: 品種ごとの萼片幅のばらつき")
```

![unnamed-chunk-5-1.png](https://qiita-image-store.s3.amazonaws.com/0/19462/963870d8-1b47-82ab-df28-ebfb14039281.png)

**patchwork** パッケージの解説は　[@Atsushi776](http://twitter.com/Atsushi776)
さんによる[資料](https://atusy.github.io/presentation/tokyor073/tokyor073-multi-ggplot2.html?raw#/)に詳しい説明があります。そちらも要チェックです。

### 最後に描画したggplot2の図を復元する

**大きな声では言えませんが**
これも[@Atsushi776](http://twitter.com/Atsushi776)さんに教えてもらいました。

直前に描画したggplot2での図をもう一度描画するために`.Last.value`を使っていましたが、これあと別の処理を挟んだ後に復元することはできません。`last_plot()`を使いましょう。

``` r
# 作図デバイスを初期化する
dev.off()
# 先ほどの図をもう一度描画
last_plot()
```

これは確認のためにプロットして、`ggsave()`で保存する時などに便利です。

### scaleの凡例を極める

連続値の凡例を横長に配置したり、長さをグラフの横の幅に合わせたりといった処理です。`guides()`および`theme()`に用意されている引数を適切に指定して次のような図が作れます。

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Today&#39;s my <a href="https://twitter.com/hashtag/ggplot2?src=hash&amp;ref_src=twsrc%5Etfw">#ggplot2</a> tips📈Adjust the position and size of the legend. Data are based on the 2015 census of the Ministry of Internal Affairs and Communications statistics Bureau small area population. <a href="https://t.co/zGu00zOxyf">pic.twitter.com/zGu00zOxyf</a></p>&mdash; Uryu Shinya (@u_ribo) <a href="https://twitter.com/u_ribo/status/1065243405398110208?ref_src=twsrc%5Etfw">November 21, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


``` r
p <-
  faithfuld %>%
  ggplot(aes(waiting, eruptions)) +
  geom_raster(aes(fill = density))

p
```

![unnamed-chunk-7-1.png](https://qiita-image-store.s3.amazonaws.com/0/19462/b98ca660-d396-ae18-010a-3c788a4b58d0.png)

``` r
p +
  guides(fill = guide_colorbar(title.position = "top",
                               title.hjust = 0.5)) +
  theme(legend.position = "bottom",
        legend.key.height = unit(0.5, "line"),
        legend.key.width = unit(4.5 ,"line"))
```

![unnamed-chunk-8-1.png](https://qiita-image-store.s3.amazonaws.com/0/19462/44000c43-4c4d-83c1-7caf-15d731bf9434.png)

tibble
------

データフレーム (data.frame)の使い勝手を改善したtbl\_df(tibble data.frame)というオブジェクトのクラスを提供するパッケージです。そのため、tbl\_dfとして実装するパッケージの内部で使われることが目立ちますが、データフレームに関するいくつかの処理も備えています[^2]。

[^2]: tidyverseのhexロゴの中でtibbleが一番格好良くて好きです。

また、次期バージョンに関して [@yutannihilation](https://twitter.com/yutannihilation)サンから注意勧告があります。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">tibbleパッケージの次期リリースの変更点、as_tibble()がヤバ目なので確認したほうがよさげ。<br><br>・列名がついていないとエラーに（列名のない行列とか）<br>・列名が重複しているとエラーに<a href="https://t.co/QWRDOUwIxI">https://t.co/QWRDOUwIxI</a></p>&mdash; Hiroaki Yutani (@yutannihilation) <a href="https://twitter.com/yutannihilation/status/1067951803629678594?ref_src=twsrc%5Etfw">November 29, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


### データフレームオブジェクトの作成

説明の時など、単純なデータフレームを作る際に重宝する関数です。

tibbleには`data.frame()`同様にベクトルでデータフレームの列を構築する`data_frame()`がありますが、もう一つ、データフレーム作成用の関数、`tribble()`があります。`tribble()`(`frame_data()`という名前から変更されました)は、データフレームを記述的に作成します。一つ一つの値を入力するのでサイズの大きなデータフレームを作るのには向きませんが、一行ずつデータフレームを作っていく形式になっているのでうっかり桁数の揃わないデータを作ってしまうことを防ぐのに役立ちます。

`tribble()`では列名を`~`で表現し、引数の最初に与えます。`~`をつけた値が列名として使われます。列名、値はカンマで区切ります。次のように、データフレームの構造を意識しながらデータを作れます。

``` r
tribble(
  ~id, ~value,
  "a", 1,
  "b", 2)
```

    ## # A tibble: 2 x 2
    ##   id    value
    ##   <chr> <dbl>
    ## 1 a         1
    ## 2 b         2

``` r
# こうした記述も可能ですが、tribble()のありがたみが薄れる気がします
tribble(
  ~id,
  ~value,
  "a",
  1,
  "b",
  2)
```

また、より単純なデータフレームを作る場合は、任意のベクトルからデータフレームを作成する`enframe()`が役立ちます。この関数は引数にベクトルを与えて実行します。すると、nameとvalueという2列のデータフレームが生成され、ベクトルの要素はvalue列に格納されます。nameには1から始まる正の自然数が与えられます。

``` r
seq_len(3) %>%
  enframe()
```

    ## # A tibble: 3 x 2
    ##    name value
    ##   <int> <int>
    ## 1     1     1
    ## 2     2     2
    ## 3     3     3

生成されるデータフレームの列名は引数で指定できます。また、ベクトルの要素に名前が与えられている際にはそれがname列の値に使われます。

``` r
seq_len(3) %>%
  enframe(name = "id",
          value = "my_var")
```

    ## # A tibble: 3 x 2
    ##      id my_var
    ##   <int>  <int>
    ## 1     1      1
    ## 2     2      2
    ## 3     3      3

``` r
x <-
  seq_len(3) %>%
  set_names(c("a", NA_character_, "c"))

enframe(x)
```

    ## # A tibble: 3 x 2
    ##   name  value
    ##   <chr> <int>
    ## 1 a         1
    ## 2 <NA>      2
    ## 3 c         3

### データフレームへの列・行の追加

一行ないし一列だけデータを追加したい…。そんなことが多々あります。これらの処理は標準関数を使っても可能ですが、パイプ処理の連結処理を中断させてしまいます。また任意の位置への挿入は困難です。`add_row()`、`add_column()`は、パイプ処理で記述可能な、位置指定によるデータの挿入を行う関数となります。

``` r
df <-
  data_frame(
    id = seq_len(3),
    my_var = letters[seq_len(3)]
  )

df
```

    ## # A tibble: 3 x 2
    ##      id my_var
    ##   <int> <chr> 
    ## 1     1 a     
    ## 2     2 b     
    ## 3     3 c

このデータフレームに一行追加します。引数に列と値の組み合わせを与えて実行します。

``` r
df %>%
  add_row(id = 4)
```

    ## # A tibble: 4 x 2
    ##      id my_var
    ##   <dbl> <chr> 
    ## 1     1 a     
    ## 2     2 b     
    ## 3     3 c     
    ## 4     4 <NA>

ベクトルの長さに応じて、追加する行は増やせます。

``` r
df %>%
  add_row(id = c(4, 5), my_var = c("d", "e"))
```

    ## # A tibble: 5 x 2
    ##      id my_var
    ##   <dbl> <chr> 
    ## 1     1 a     
    ## 2     2 b     
    ## 3     3 c     
    ## 4     4 d     
    ## 5     5 e

`add_row()`、`add_column()`には引数`.before`、`.after`があり、ここに挿入する位置を数値で指定することで、データフレーム内の自由な位置へデータを追加できます。

``` r
df %>%
  add_row(id = 0, .before = 1)
```

    ## # A tibble: 4 x 2
    ##      id my_var
    ##   <dbl> <chr> 
    ## 1     0 <NA>  
    ## 2     1 a     
    ## 3     2 b     
    ## 4     3 c

``` r
df %>%
  add_row(id = 2.5, .after = 2)
```

    ## # A tibble: 4 x 2
    ##      id my_var
    ##   <dbl> <chr> 
    ## 1   1   a     
    ## 2   2   b     
    ## 3   2.5 <NA>  
    ## 4   3   c

``` r
df %>%
  add_column(new_var = c("あ", "い", "う"), .before = "my_var")
```

    ## # A tibble: 3 x 3
    ##      id new_var my_var
    ##   <int> <chr>   <chr> 
    ## 1     1 あ      a     
    ## 2     2 い      b     
    ## 3     3 う      c

### tbl\_dfを継承するオブジェクトクラスの構築

上述の通り、tbl\_dfはデータフレームの拡張です。`as_tibble()`を使って変換が可能です。一方で、同じくデータフレームを拡張したsfやtibbletimeクラスのオブジェクトに対してこの処理を適用するとクラスが上書きされてしまいます。

``` r
nc <-
  system.file("shape/nc.shp", package = "sf") %>%
  sf::st_read(quiet = TRUE)

class(nc)
```

    ## [1] "sf"         "data.frame"

``` r
nc %>%
  as_tibble() %>%
  class()
```

    ## [1] "tbl_df"     "tbl"        "data.frame"

あら不思議、sfクラスではなくなってしまいます。しかし、オブジェクトを出力した際に全件出力されるのは煩わしい…。そんな時に`new_tibble()`が役立ちます。

``` r
nc %>%
  new_tibble(subclass = "sf") %>%
  class()
```

    ## [1] "sf"         "tbl_df"     "tbl"        "data.frame"

なおsfパッケージのデータ読み込み関数`st_read()`にはtibble形式への変換を行うas\_tibbleオプションが備わっており、`st_read(as_tibble = TRUE`としておくことで上記の処理が不要になります。

``` r
nc <-
  system.file("shape/nc.shp", package = "sf") %>%
  sf::st_read(quiet = TRUE,
              as_tibble = TRUE)

class(nc)
```

    ## [1] "sf"         "tbl_df"     "tbl"        "data.frame"

tidyr
-----

データフレームをRで処理しやすいtidy形式へと変換するのがtidyrの主な機能です。dplyrがデータフレームの列や行の値を操作するのに対して、データフレーム全体に影響する関数が多いのが特徴です。

ここにない他の関数の挙動については[@kazutan](https://twitter.com/kazutan)の[記事](https://qiita.com/kazutan/items/ab81958994426e67e017)にまとめられています。

### カウント値を行に反映させる

特定の列の値をカウントした値をもつ列に対して、`uncount()`を実行するとカウント値の分だけ行が複製されたデータを作れます。例えば次のように“a”、“b”、“c”からなる値をx列に、それぞれの出現頻度をn列に記録したデータを用意し、これに`uncount()`を実行します。するとnの数に応じてx列の要素が複製されます。

``` r
df <-
  data_frame(
    x = c("a", "b", "c", "a", "b", "b", "a")
  ) %>%
  count(x)

df
```

    ## # A tibble: 3 x 2
    ##   x         n
    ##   <chr> <int>
    ## 1 a         3
    ## 2 b         3
    ## 3 c         1

``` r
df %>%
  uncount(n)
```

    ## # A tibble: 7 x 1
    ##   x    
    ##   <chr>
    ## 1 a    
    ## 2 a    
    ## 3 a    
    ## 4 b    
    ## 5 b    
    ## 6 b    
    ## 7 c

例で示したように`dplyr::count()`した後の処理として使うことがありそうです。

### 欠損値を含む行を除外する

`drop_na()`は、欠損値を含む行をデータから除外するのに役立ちます。

``` r
df <-
  tibble(x = c(1, 2, NA), y = c("a", NA, "b"))

df %>%
  drop_na()
```

    ## # A tibble: 1 x 2
    ##       x y    
    ##   <dbl> <chr>
    ## 1     1 a

欠損値が含まれた際に除外対象とする列を引数で指定可能です。

``` r
df %>%
  drop_na(x)
```

    ## # A tibble: 2 x 2
    ##       x y    
    ##   <dbl> <chr>
    ## 1     1 a    
    ## 2     2 <NA>

この他、あまり知られていないと感じる関数に`separate_rows()`がありますが、これについては[以前記事を書いています](https://qiita.com/uri/items/5b87a76eeb50a377fb93)ので、そちらを参照いただければと思います。

dplyr
-----

データ操作に欠かせないdplyrパッケージの関数は、[@matsuou1](https://qiita.com/matsuou1) さんの一連の記事にまとめられているもの使うことがほとんどですが、ちょくちょく追加がありますので紹介します。

### データフレームの列を参照

パイプ処理の中で列を参照するために`magrittr::use_series()`を使っていましたが、`dplyr::pull()`で同様の処理ができます。

``` r
iris %>%
  dplyr::pull(Species) %>%
  levels()
```

    ## [1] "setosa"     "versicolor" "virginica"

``` r
# magrittr::use_series()を使う場合... 同じ結果です
# iris %>%
#   magrittr::use_series(Species) %>%
#   levels()
```

### データの一部を位置指定で取り出す

`head()`(先頭)や`tail()`(末尾)とは異なり、任意の位置でデータフレームの一部を参照するために`slice()`を使います。引数に行番号を指定します。

``` r
df <-
  data_frame(
    x = seq_len(3))

df %>%
  slice(2)
```

    ## # A tibble: 1 x 1
    ##       x
    ##   <int>
    ## 1     2

``` r
df %>%
  slice(c(1, 3))
```

    ## # A tibble: 2 x 1
    ##       x
    ##   <int>
    ## 1     1
    ## 2     3

### カウント値を列に追加する

出現頻度をかぞえあげる関数には`count()`、`tally()`が使えますが、`add_count()`および`add_tall()`は、対象のデータフレームの構造を維持したまま、列を追加する形でカウントを行います。

``` r
mtcars %>%
  count(cyl)
```

    ## # A tibble: 3 x 2
    ##     cyl     n
    ##   <dbl> <int>
    ## 1     4    11
    ## 2     6     7
    ## 3     8    14

``` r
mtcars %>%
  select(mpg, cyl) %>%
  add_count(cyl)
```

    ## # A tibble: 32 x 3
    ##      mpg   cyl     n
    ##    <dbl> <dbl> <int>
    ##  1  21       6     7
    ##  2  21       6     7
    ##  3  22.8     4    11
    ##  4  21.4     6     7
    ##  5  18.7     8    14
    ##  6  18.1     6     7
    ##  7  14.3     8    14
    ##  8  24.4     4    11
    ##  9  22.8     4    11
    ## 10  19.2     6     7
    ## # ... with 22 more rows

今日は国内Rイベントにおいて最大規模を誇る[Japan.R](https://japanr.connpass.com/event/105802/)の開催日です。私も会場手伝いとして参加します。ぜひ会場でお会いしましょう。

明日は[@utaka233](https://twitter.com/utaka233)の記事です。お楽しみに！それでは、Enjoy!