tidyrの関数たちと向き合ってみる

tidyrの関数たちと向き合ってみる
================

いつもお世話になっているtidyrパッケージですが，いつも`gather()`と`spread()`あたりしか使っていませんでした。もっといろんな関数があるのは知っていたので調査してみました。

なお自分用のメモなので自分が既知のものや面倒なもの，`_`の分は省略します。

tidyrの関数一覧
---------------

exportされてる分だけを対象にします。あと面倒なので`tidyverse`で関連するのをまとめて読み込みます:

``` r
library(tidyverse)
#>  Loading tidyverse: ggplot2
#>  Loading tidyverse: tibble
#>  Loading tidyverse: tidyr
#>  Loading tidyverse: readr
#>  Loading tidyverse: purrr
#>  Loading tidyverse: dplyr
#>  Conflicts with tidy packages ----------------------------------------------
#>  filter(): dplyr, stats
#>  lag():    dplyr, stats
ls("package:tidyr")
#>   [1] "%>%"             "complete"        "complete_"      
#>   [4] "crossing"        "crossing_"       "drop_na"        
#>   [7] "drop_na_"        "expand"          "expand_"        
#>  [10] "extract"         "extract_"        "extract_numeric"
#>  [13] "fill"            "fill_"           "full_seq"       
#>  [16] "gather"          "gather_"         "nest"           
#>  [19] "nest_"           "nesting"         "nesting_"       
#>  [22] "population"      "replace_na"      "separate"       
#>  [25] "separate_"       "separate_rows"   "separate_rows_" 
#>  [28] "smiths"          "spread"          "spread_"        
#>  [31] "table1"          "table2"          "table3"         
#>  [34] "table4a"         "table4b"         "table5"         
#>  [37] "unite"           "unite_"          "unnest"         
#>  [40] "unnest_"         "who"
```

このうち，以下のオブジェクトはデータセットです:

-   `population`
-   `smiths`
-   `table1`
-   `table2`
-   `table3`
-   `table4a`
-   `table4b`
-   `table5`
-   `who`

また，`%>%`はパイプ演算子です。さらに名前の最後に`_`が付いているものを除外すると，以下のような感じになります:

-   `complete`
-   `crossing`
-   `drop_na`
-   `expand`
-   `extract`
-   `extract_numeric`
-   `fill`
-   `full_seq`
-   `gather`
-   `nest`
-   `nesting`
-   `replace_na`
-   `separate`
-   `separate_rows`
-   `spread`
-   `unite`
-   `unnest`

このうち，`gather`, `spread`, `unite`, `separate`については，すでに以下でまとめたことがあるので省略します:

-   [データハンドリング - Kazutan.R](https://kazutan.github.io/kazutanR/data_handling.html)

あと，`extract_numeric`はDEPRECATEDで，`readr::parse_number()`を使えとのことで除きます。

各関数のメモ
------------

### `complete`

ヘルプのDescriptionによると「implicitな欠損値をexplicitな欠損値として埋めるよ!」という関数です。ヘルプドキュメントの例を使いながら試します。以下のようなデータフレームがあったとします:

``` r
library(dplyr)
df <- data_frame(
  group = c(1:2, 1),
  item_id = c(1:2, 2),
  item_name = c("a", "b", "b"),
  value1 = 1:3,
  value2 = 4:6
)
df
#>  # A tibble: 3 × 5
#>    group item_id item_name value1 value2
#>    <dbl>   <dbl>     <chr>  <int>  <int>
#>  1     1       1         a      1      4
#>  2     2       2         b      2      5
#>  3     1       2         b      3      6
```

もし，本来ならgroupとitem\_idの全ての組み合わせが存在したんだよって場合，この`df`では`group = 2, item_id = 1`の組み合わせがあったはずです。でも欠損のためここにはありません。これを埋め合わせるような仕事をこの関数はします。

組み合わせを出す列を，以下のように引数に指定すると以下のようになります:

``` r
df %>% complete(group, item_id)
#>  # A tibble: 4 × 5
#>    group item_id item_name value1 value2
#>    <dbl>   <dbl>     <chr>  <int>  <int>
#>  1     1       1         a      1      4
#>  2     1       2         b      3      6
#>  3     2       1      <NA>     NA     NA
#>  4     2       2         b      2      5
```

このように，元のデータに存在しなかった`group = 2, item_id = 1`の組み合わせを挿入してくれました。なるほどいい感じです。

しかしこの変数名でitem\_idとitem\_nameとあるように，実はこの2つはセットになっているのでitem\_idに合わせてitem\_nameも連動してほしいという場面もあるかと思います。その時は以下のようにします:

``` r
df %>% complete(group, nesting(item_id, item_name))
#>  # A tibble: 4 × 5
#>    group item_id item_name value1 value2
#>    <dbl>   <dbl>     <chr>  <int>  <int>
#>  1     1       1         a      1      4
#>  2     1       2         b      3      6
#>  3     2       1         a     NA     NA
#>  4     2       2         b      2      5
```

この`nesting`については後で説明します。また，欠損値を特定の値(`0`とか`missing`とか)で埋めたい場合は，以下のように`fill = list()`と名前付きリストで指定します:

``` r
df %>% complete(group, nesting(item_id, item_name), fill = list(value1 = 0))
#>  # A tibble: 4 × 5
#>    group item_id item_name value1 value2
#>    <dbl>   <dbl>     <chr>  <dbl>  <int>
#>  1     1       1         a      1      4
#>  2     1       2         b      3      6
#>  3     2       1         a      0     NA
#>  4     2       2         b      2      5
```

大抵はこれでOKかと。なお，この`complete()`は`expand()`, `left_join()`, そして`replace_na`を組み合わせたwrapperとのことです。なるほど。

より詳細な説明は`?tidyr::complete`で確認してください。

### `nest`, `unnest`

データフレームを入れ子にする/入れ子を解除する関数です。これらに関してはyutannihilationさんの以下の記事がわかりやすいので省略します:

-   [tidyr 0.4を使ってみる - Technically, technophobic.](http://notchained.hatenablog.com/entry/2016/01/19/215332)

### `expand`, `crossing`, `nesting`

直積集合を返す関数です。これらに関してもyutannihilationさんの以下の記事がわかりやすいので省略します:

-   [tidyr 0.4を使ってみる - Technically, technophobic.](http://notchained.hatenablog.com/entry/2016/01/19/215332)

一応ちょいちょい試します。

`mtcars`のvs(0,1)とcyl(4,6,8)で，ありうる全ての組み合わせを`expand()`は出力してきます:

``` r
expand(mtcars, vs, cyl)
#>  # A tibble: 6 × 2
#>       vs   cyl
#>    <dbl> <dbl>
#>  1     0     4
#>  2     0     6
#>  3     0     8
#>  4     1     4
#>  5     1     6
#>  6     1     8
```

ここで，vsとcylとで実際のデータに存在する組み合わせだけを取り出すには`nesting`を組み込みます:

``` r
expand(mtcars, nesting(vs, cyl))
#>  # A tibble: 5 × 2
#>       vs   cyl
#>    <dbl> <dbl>
#>  1     0     4
#>  2     0     6
#>  3     0     8
#>  4     1     4
#>  5     1     6
```

要するに`nesting()`は引数に指定した変数で，実際のデータにある組み合わせを出力してきてくれます。また，`crossing()`は`expand.grid()`とほぼ同様ですが，(1)文字列をfactorに変換しない，(2)付加的な属性を足さずに`tbl_df`を返すなどが違うようです:

``` r
crossing(x = 1:2, y = letters[1:3]) %>% glimpse()
#>  Observations: 6
#>  Variables: 2
#>  $ x <int> 1, 1, 1, 2, 2, 2
#>  $ y <chr> "a", "b", "c", "a", "b", "c"
expand.grid(x = 1:2, y = letters[1:3]) %>% glimpse()
#>  Observations: 6
#>  Variables: 2
#>  $ x <int> 1, 2, 1, 2, 1, 2
#>  $ y <fctr> a, a, b, b, c, c
```

その他詳しいことは`?expand`を参照してください。

### `drop_na`, `replace_na`, `fill`

これらはNA処理をします。

#### `drop_na()`

`drop_na()`は`NA`がある行を削除します:

``` r
df <- data_frame(x = c(1, 2, NA), y = c("a", NA, "b"))
df %>% drop_na()
#>  # A tibble: 1 × 2
#>        x     y
#>    <dbl> <chr>
#>  1     1     a
```

また，引数に列を指定すると，指定した列で`NA`がある行だけを削除します:

``` r
df %>% drop_na(x)
#>  # A tibble: 2 × 2
#>        x     y
#>    <dbl> <chr>
#>  1     1     a
#>  2     2  <NA>
```

この変数の指定方法は`dplyr::select`での方法がまるっと使えます。

#### `replace_na()`

`replace_na()`は`NA`を別の値で置換します。ただし，名前付きリストで対象変数と置換値を指定する必要があります:

``` r
df %>% replace_na(list(x = 0))
#>  # A tibble: 3 × 2
#>        x     y
#>    <dbl> <chr>
#>  1     1     a
#>  2     2  <NA>
#>  3     0     b
```

もちろんlist内に複数詰め込んでもOKです:

``` r
df %>% replace_na(list(x = 0, y = "unknown"))
#>  # A tibble: 3 × 2
#>        x       y
#>    <dbl>   <chr>
#>  1     1       a
#>  2     2 unknown
#>  3     0       b
```

#### `fill()`

欠損値を埋めるのですが，**その前のエントリー値を利用して埋めます**。以下のようなデータがあったとします:

``` r
df <- data.frame(Month = c(rep(1:3, 2), 1),
                 Year = c(2000, rep(NA, 2), 2001, rep(NA, 2), 2002)
                 )
df
#>    Month Year
#>  1     1 2000
#>  2     2   NA
#>  3     3   NA
#>  4     1 2001
#>  5     2   NA
#>  6     3   NA
#>  7     1 2002
```

これにfillを適用すると以下のような感じになります:

``` r
df %>% fill(Year)
#>    Month Year
#>  1     1 2000
#>  2     2 2000
#>  3     3 2000
#>  4     1 2001
#>  5     2 2001
#>  6     3 2001
#>  7     1 2002
```

また，この場合下へ降りていく感じですが，逆方向も可能です:

``` r
df %>% fill(Year, .direction = "up")
#>    Month Year
#>  1     1 2000
#>  2     2 2001
#>  3     3 2001
#>  4     1 2001
#>  5     2 2002
#>  6     3 2002
#>  7     1 2002
```

### `full_seq()`

欠損値補完の一種で，与えらたベクトルに対し，`period`で指定した間隔について自動で補完するというものです。これは具体例をひたすら試したほうがわかりやいかと。

``` r
full_seq(c(1, 2, 4, 5, 10), 1)
#>   [1]  1  2  3  4  5  6  7  8  9 10
full_seq(c(2, 4, 10), 2)
#>  [1]  2  4  6  8 10
```

ここで与えるベクトルは，Date型やPOSIXct型でも対応しています。ただ，2017/04/08時点で少数やPOSIXctだとちょっと挙動があやしくなりそうですので注意してください。例えば以下は構文的にはいいのですがエラーが出ます:

``` r
# 評価させてません
full_seq(c(0, 0.3, 0.8), 0.1)
# エラーメッセージは以下のとおりです:
# エラー: `x` is not a regular sequence.
```

理由は省略しますが，[ソースコード](https://github.com/tidyverse/tidyr/blob/master/R/seq.R#L21)を確認して検証するとわかるかと思います。

### `extract`

データフレームのある列と，複数の列へと抜き出してくる関数。抜き出すルールに正規表現が使えるってのが特徴かと。あと`separate()`だと区切りを指定するけど，これはあくまで正規表現にマッチしてるのをうまいこと持ってくる感じ，かな?

ヘルプドキュメントにある例を以下に貼り付けてます:

``` r
df <- data.frame(x = c(NA, "a-b", "a-d", "b-c", "d-e"))
df %>% extract(x, "A")
#>       A
#>  1 <NA>
#>  2    a
#>  3    a
#>  4    b
#>  5    d
df %>% extract(x, c("A", "B"), "([[:alnum:]]+)-([[:alnum:]]+)")
#>       A    B
#>  1 <NA> <NA>
#>  2    a    b
#>  3    a    d
#>  4    b    c
#>  5    d    e

# If no match, NA:
df %>% extract(x, c("A", "B"), "([a-d]+)-([a-d]+)")
#>       A    B
#>  1 <NA> <NA>
#>  2    a    b
#>  3    a    d
#>  4    b    c
#>  5 <NA> <NA>
```

詳しくは`?extract`を参照してください。

雑感
----

`gather`や`spread`は頻繁に利用するのですが，それ以外にも色々と使えそうな関数を発掘できました。ただ結構高頻度で仕様や関数が変わるので備えましょう。

**Enjoy!**

sessionInfo
-----------

    #>  R version 3.3.3 (2017-03-06)
    #>  Platform: x86_64-pc-linux-gnu (64-bit)
    #>  Running under: Ubuntu 16.04.2 LTS
    #>  
    #>  locale:
    #>   [1] LC_CTYPE=ja_JP.UTF-8       LC_NUMERIC=C              
    #>   [3] LC_TIME=ja_JP.UTF-8        LC_COLLATE=ja_JP.UTF-8    
    #>   [5] LC_MONETARY=ja_JP.UTF-8    LC_MESSAGES=ja_JP.UTF-8   
    #>   [7] LC_PAPER=ja_JP.UTF-8       LC_NAME=C                 
    #>   [9] LC_ADDRESS=C               LC_TELEPHONE=C            
    #>  [11] LC_MEASUREMENT=ja_JP.UTF-8 LC_IDENTIFICATION=C       
    #>  
    #>  attached base packages:
    #>  [1] stats     graphics  grDevices utils     datasets  methods   base     
    #>  
    #>  other attached packages:
    #>  [1] dplyr_0.5.0.9001 purrr_0.2.2      readr_1.1.0      tidyr_0.6.1     
    #>  [5] tibble_1.3.0     ggplot2_2.2.1    tidyverse_1.1.1 
    #>  
    #>  loaded via a namespace (and not attached):
    #>   [1] Rcpp_0.12.10        plyr_1.8.4          forcats_0.2.0      
    #>   [4] tools_3.3.3         digest_0.6.12       lubridate_1.6.0    
    #>   [7] jsonlite_1.3        evaluate_0.10       nlme_3.1-131       
    #>  [10] gtable_0.2.0        lattice_0.20-35     pkgconfig_2.0.1    
    #>  [13] rlang_0.0.0.9006    psych_1.7.3.21      yaml_2.1.14        
    #>  [16] parallel_3.3.3      haven_1.0.0         xml2_1.1.1         
    #>  [19] stringr_1.2.0       httr_1.2.1          knitr_1.15.17      
    #>  [22] hms_0.3             rprojroot_1.2       grid_3.3.3         
    #>  [25] glue_0.0.0.9000     R6_2.2.0            readxl_0.1.1       
    #>  [28] foreign_0.8-67      rmarkdown_1.4       modelr_0.1.0       
    #>  [31] reshape2_1.4.2      magrittr_1.5        backports_1.0.5    
    #>  [34] scales_0.4.1        htmltools_0.3.6     rvest_0.3.2        
    #>  [37] assertthat_0.1      mnormt_1.5-5        colorspace_1.3-2   
    #>  [40] stringi_1.1.3       lazyeval_0.2.0.9000 munsell_0.4.3      
    #>  [43] broom_0.4.2