dplyr の select() の引数に指定可能なバリエーションまとめ #rstatsj


[福島真太朗『データ分析プロセス』](http://www.amazon.co.jp/dp/4320123654)を読んでいると、`dplyr` の `select()` の使い方で知らないものが載っていた。
調べてみると、`select()` は引数に様々なバリエーションを受け付けることができることを知ったので、ここにまとめておく。

これらのカラム選択方法は `summarise_each()`, `mutate_each()` においても全く同様である。
さらに、`tidyr` パッケージの各種関数(`gather()`など)に対しても同じようにして使える。

## 1. 全コード

一覧性のために、最初に全コードを載せておく。以降のセクションでは一つずつ実行結果を含めて解説する。

```r:R
# データの準備 ------------------------------------------------------------------
library(dplyr)
library(nycflights13)
set.seed(123)
data <- sample_n(flights, 3)

glimpse(data)

# 基本 ----------------------------------------------------------------------
select(data, year)
select(data, year, month, day)
select(data, year:day)

select(data, -year, -month, -day)
select(data, -(year:day))

select(data, 1, 2, 3)
select(data, 1:3)

select(data, -1, -2, -3)
select(data, -(1:3))

select(data, year:day, -month)
select(data, -(year:day), month)
select(data, 1:3, -2)
select(data, -(1:3), 2)

# 選択ユーティリティー --------------------------------------------------------------
select(data, starts_with("arr"))
select(data, ends_with("time"))
select(data, contains("_"))
select(data, matches("^(dep|arr)_"))

data2 <- data
colnames(data2) <- sprintf("x%d", 1:16)
select(data2, num_range("x", 8:11))
select(data2, num_range("x", c(9, 11)))

data3 <- data
colnames(data3) <- sprintf("x%02d", 1:16)
select(data3, num_range("x", 8:11, width=2))

col_vector <- c("year", "month", "day")
select(data, col_vector)
select(data, one_of(col_vector))

select(data, everything())

select(data, -starts_with("arr"))

# 標準評価(SE) --------------------------------------------------------------------
select_(data, "year", "month", "day")

col_vector <- c("year", "month", "day")
select_(data, .dots = col_vector)

select_(data, 'year:day')
select_(data, 'year:day', '-month')
select_(data, '-(year:day)')
select_(data, 'starts_with("arr")')
select_(data, '-ends_with("time")')

select_(data, .dots = c('starts_with("arr")', '-ends_with("time")'))
```

## 2. データの準備

まずはデータを準備する。
[福島真太朗『データ分析プロセス』](http://www.amazon.co.jp/dp/4320123654)にならって、`nycflights13` パッケージの `flights` データを使用する。

```r:R
library(dplyr)
library(nycflights13)

set.seed(123)
data <- sample_n(flights, 3)

glimpse(data)
```

```:結果
Variables:
$ year      (int) 2013, 2013, 2013
$ month     (int) 12, 7, 3
$ day       (int) 15, 17, 2
$ dep_time  (int) 2124, 651, 1636
$ dep_delay (dbl) -4, -9, 1
$ arr_time  (int) 2322, 936, 1800
$ arr_delay (dbl) 1, -28, 0
$ carrier   (chr) "UA", "DL", "WN"
$ tailnum   (chr) "N801UA", "N194DN", "N475WN"
$ flight    (int) 289, 763, 1501
$ origin    (chr) "EWR", "JFK", "LGA"
$ dest      (chr) "DTW", "LAX", "MKE"
$ air_time  (dbl) 88, 306, 103
$ distance  (dbl) 488, 2475, 738
$ hour      (dbl) 21, 6, 16
$ minute    (dbl) 24, 51, 36
```

`year` から `minute` までの 16 個のカラムがある。

## 2. 基本

まず、`select()` の基本的な使い方は、次のようになる。

```r:R
select(data, year)
```

```:結果
  year
1 2013
2 2013
3 2013
```

データから `year` カラムだけを取り出した。
複数のカラムを取り出したいときは、次のようにする。

```r:R
select(data, year, month, day)
```

```:結果
  year month day
1 2013    12  15
2 2013     7  17
3 2013     3   2
```

カラムがデータ上で連続している場合は、次のような書き方ができる。

```r:R
select(data, year:day)
```

```:結果
  year month day
1 2013    12  15
2 2013     7  17
3 2013     3   2
```

データから特定のカラムを取り除きたいときは、次のようにマイナス記号を付ける。

```r:R
select(data, -year, -month, -day)
```

```:結果
  dep_time dep_delay arr_time arr_delay carrier tailnum flight origin dest air_time distance hour minute
1     2124        -4     2322         1      UA  N801UA    289    EWR  DTW       88      488   21     24
2      651        -9      936       -28      DL  N194DN    763    JFK  LAX      306     2475    6     51
3     1636         1     1800         0      WN  N475WN   1501    LGA  MKE      103      738   16     36
```

連続したカラムを取り除くには、括弧を付けた上でマイナスを付ける。

```r:R
select(data, -(year:day))
```

```:結果
  dep_time dep_delay arr_time arr_delay carrier tailnum flight origin dest air_time distance hour minute
1     2124        -4     2322         1      UA  N801UA    289    EWR  DTW       88      488   21     24
2      651        -9      936       -28      DL  N194DN    763    JFK  LAX      306     2475    6     51
3     1636         1     1800         0      WN  N475WN   1501    LGA  MKE      103      738   16     36
```

カラム番号を指定して取り出すこともできる。

```r:R
select(data, 1, 2, 3)
select(data, 1:3)
```

```:結果
  year month day
1 2013    12  15
2 2013     7  17
3 2013     3   2
```

ここからは少し高度になる。

連続したカラムを一旦取り出し、その中から特定のカラムを除去するということも、次のように書けばできる。

```r:R
select(data, year:day, -month)
```

```:結果
  year day
1 2013  15
2 2013  17
3 2013   2
```

連続したカラムを取り除くが、その中で必要なカラムだけ残すことも同様にしてできる。

```r:R
select(data, -(year:day), month)
```

```:結果
  dep_time dep_delay arr_time arr_delay carrier tailnum flight origin dest air_time distance hour minute month
1     2124        -4     2322         1      UA  N801UA    289    EWR  DTW       88      488   21     24    12
2      651        -9      936       -28      DL  N194DN    763    JFK  LAX      306     2475    6     51     7
3     1636         1     1800         0      WN  N475WN   1501    LGA  MKE      103      738   16     36     3
```

カラム番号を使った次の書き方も全く同様の結果になる。(※結果は省略)

```r:R
select(data, 1:3, -2)
select(data, -(1:3), 2)
```

## 3. 選択ユーティリティー関数

`dplyr` の `select()` の引数の中だけで使える、選択ユーティリティー関数というものがある。 
(※選択ユーティリティー関数は、`summarise_each()`, `mutate_each()`, `tidyr` の各種関数においても有効)

選択ユーティリティー関数には、以下の 7 つがある。

- `starts_with(match, ignore.case = TRUE)`
- `ends_with(match, ignore.case = TRUE)`
- `contains(match, ignore.case = TRUE)`
- `matches(match, ignore.case = TRUE)`
- `num_range(prefix, range, width = NULL)`
- `one_of(...)`
- `everything()`

使い方を一つずつ見ていこう。

`starts_with()` は、カラム名が指定された文字列から始まるものだけを取り出す。

```r:R
select(data, starts_with("arr"))
```

```:結果
  arr_time arr_delay
1     2322         1
2      936       -28
3     1800         0
```

`ignore.case` 引数は大文字と小文字を区別するかどうかを指定するもので、デフォルトでは区別しない(`TRUE`)。

`ends_with()` は、カラム名が指定された文字列で終わるものだけを取り出す。

```r:R
select(data, ends_with("time"))
```

```:結果
  dep_time arr_time air_time
1     2124     2322       88
2      651      936      306
3     1636     1800      103
```

`contains()` は、カラム名が指定された文字列を含むものだけを取り出す。

```r:R
select(data, contains("_"))
```

```:結果
  dep_time dep_delay arr_time arr_delay air_time
1     2124        -4     2322         1       88
2      651        -9      936       -28      306
3     1636         1     1800         0      103
```

`matches()` は、カラム名が指定された正規表現に一致するものだけを取り出す。

```r:R
select(data, matches("^(dep|arr)_"))
```

```:結果
  dep_time dep_delay arr_time arr_delay
1     2124        -4     2322         1
2      651        -9      936       -28
3     1636         1     1800         0
```

`num_range()` はカラム名に番号が振られている場合に便利である。
データのカラム名を `x1`～`x16` に変更したものを作り、それに対して実行してみる。

```r:R
data2 <- data
colnames(data2) <- sprintf("x%d", 1:16)
select(data2, num_range("x", 8:11))
```

```:結果
  x8     x9  x10 x11
1 UA N801UA  289 EWR
2 DL N194DN  763 JFK
3 WN N475WN 1501 LGA
```

`num_range("x", 8:11)` と指定することで、カラム名が `x8` から `x11` までのものを取り出すことができた。
カラム名の数字は、連続している必要はない。

```r:R
select(data2, num_range("x", c(9, 11)))
```

```:結果
      x9 x11
1 N801UA EWR
2 N194DN JFK
3 N475WN LGA
```

`num_range()` の `width` 引数は、カラム名が `x01` のように 0 でパディングされている場合に便利である。
これもカラム名を `x01` ～ `x16` に変更したデータに対して実行してみる。

```r:R
data3 <- data
colnames(data3) <- sprintf("x%02d", 1:16)
select(data3, num_range("x", 8:11, width=2))
```

```:結果
  x08    x09  x10 x11
1  UA N801UA  289 EWR
2  DL N194DN  763 JFK
3  WN N475WN 1501 LGA
```

`width=2` と指定することで、0 埋めされたカラム名を指定することができた。

`one_of()` は、選択したいカラム名を文字列のベクトルとして持っている場合に便利である。
`select()` は次のような場合にエラーとなる。

```r:R
col_vector <- c("year", "month", "day")
select(data, col_vector)
```

```:結果
Error: All select() inputs must resolve to integer column positions.
The following do not:
*  col_vector
```

これに対して、`one_of()` を使えば、意図通りの動きになる。

```r:R
select(data, one_of(col_vector))
```

```:結果
  year month day
1 2013    12  15
2 2013     7  17
3 2013     3   2
```

`everything()` は全てのカラムを選択する。(※結果省略)

```r:R
select(data, everything())
```

選択ユーティリティー関数を使った場合も、マイナス符号をつけることによって、「それ以外」を選択するという意味になる。

```r:R
select(data, -starts_with("arr"))
```

```r:R
  year month day dep_time dep_delay carrier tailnum flight origin dest air_time distance hour minute
1 2013    12  15     2124        -4      UA  N801UA    289    EWR  DTW       88      488   21     24
2 2013     7  17      651        -9      DL  N194DN    763    JFK  LAX      306     2475    6     51
3 2013     3   2     1636         1      WN  N475WN   1501    LGA  MKE      103      738   16     36
```

## 4. 標準評価(Standard Evaluation)

ここまでは、通常の `select()` 関数を説明したが、`select()` は引数として文字列を受け付けない。
普通に使用するぶんにはこれで問題ないのだが、カラム名が文字列ベクトルで与えられる場合などに問題となる。
そのため、文字列を受け付けるバージョンとして `select_()` 関数が用意されている。(※関数名の最後にアンダーバーが付いていることに注意)
使い方はカラム名を文字列で指定するという点を除いて同じだが、カラム名をベクトルで指定する場合には注意が必要である。

```r:R
select_(data, "year", "month", "day")
```

```:結果
  year month day
1 2013    12  15
2 2013     7  17
3 2013     3   2
```

カラム名をベクトルとして指定する場合、そのベクトルは `.dots` 引数に与えなければならない。

```r:R
col_vector <- c("year", "month", "day")
select_(data, .dots = col_vector)
```

```:結果
  year month day
1 2013    12  15
2 2013     7  17
3 2013     3   2
```

`select()` に通常与えることのできる引数は、文字列にすることで全て `select_()` に与えることができる。

```r:R
select_(data, 'year:day')
select_(data, 'year:day', '-month')
select_(data, '-(year:day)')
select_(data, 'starts_with("arr")')
select_(data, '-ends_with("time")')
```

ただし、この場合も、ベクトルで与えるためには、`.dots` 引数に与えなければならない。

```r:R
select_(data, .dots = c('starts_with("arr")', '-ends_with("time")'))
```

## 5. 参考

- [dplyrを使いこなす！基礎編](http://qiita.com/matsuou1/items/e995da273e3108e2338e)
- [dplyrのなんたら_eachを効率的に使う](http://d.hatena.ne.jp/dichika/20141027/p1)
- [Rの data.table と data.frame を dplyr で区別なく扱う](http://sinhrks.hatenablog.com/entry/2014/10/11/231321)
- [データ分析プロセス](http://www.amazon.co.jp/dp/4320123654)
- [データサイエンティスト養成読本 R活用編](http://www.amazon.co.jp/dp/4774170577)