tidyr v1.0.0?を使ってみた。

tidyr v1.0.0がそろそろリリースされそうな雰囲気を感じるのでそろそろ重い腰を上げて使ってみます。

ちなみにこのv1.0.0というバージョンは、stableになったという意味ではなくて、新機能てんこ盛りなのでメジャーバージョン上げとくか、というノリなんだと思います。 既存の機能には基本的には変更はないはずです(ただし内部実装は変わってたりするので念のため動作確認しましょう)。

pivot_longer()/pivot_wider()

gather()spread()の上位互換です。縦長に変形したいときはpivot_longer()、横長に変形したいときはpivot_wider()を使います。 かなーりいろんな機能が詰め込まれているので、詳しくはvignetteを見てください。 ここで紹介する例もvignetteから取ってきたものです。

pivot_longer()

基本的な使い方は、

pivot_longer(<縦長に変形したい列>)

という感じです。<縦長に変形したい列>は、dplyr::select()と同じ記法が使えます。

例えば、こういう横長なデータを縦長にしたいとします。

relig_income
#> # A tibble: 18 x 11
#>    religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k`
#>    <chr>      <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>
#>  1 Agnostic      27        34        60        81        76       137
#>  2 Atheist       12        27        37        52        35        70
#>  3 Buddhist      27        21        30        34        33        58
#>  4 Catholic     418       617       732       670       638      1116
#>  5 Don’t k~      15        14        15        11        10        35
#>  6 Evangel~     575       869      1064       982       881      1486
#>  7 Hindu          1         9         7         9        11        34
#>  8 Histori~     228       244       236       238       197       223
#>  9 Jehovah~      20        27        24        24        21        30
#> 10 Jewish        19        19        25        25        30        95
#> 11 Mainlin~     289       495       619       655       651      1107
#> 12 Mormon        29        40        48        51        56       112
#> 13 Muslim         6         7         9        10         9        23
#> 14 Orthodox      13        17        23        32        32        47
#> 15 Other C~       9         7        11        13        13        14
#> 16 Other F~      20        33        40        46        49        63
#> 17 Other W~       5         2         3         4         2         7
#> 18 Unaffil~     217       299       374       365       341       528
#> # ... with 4 more variables: `$75-100k` <dbl>, `$100-150k` <dbl>,
#> #   `>150k` <dbl>, `Don't know/refused` <dbl>

ここで、religion以外の列を縦長に変形するには以下のようにします。

relig_income %>%
  pivot_longer(-religion)
#> # A tibble: 180 x 3
#>    religion name               value
#>    <chr>    <chr>              <dbl>
#>  1 Agnostic <$10k                 27
#>  2 Agnostic $10-20k               34
#>  3 Agnostic $20-30k               60
#>  4 Agnostic $30-40k               81
#>  5 Agnostic $40-50k               76
#>  6 Agnostic $50-75k              137
#>  7 Agnostic $75-100k             122
#>  8 Agnostic $100-150k            109
#>  9 Agnostic >150k                 84
#> 10 Agnostic Don't know/refused    96
#> # ... with 170 more rows

上のように、特に何も指定しなければ、列名がname列、値がvalue列に格納されます。 別の名前を使いたければ、それぞれnames_tovalues_toという引数に列名を指定します。 例えば、列名をincome列、値をcount列に格納したい場合は次のようになります。

relig_income %>%
  pivot_longer(-religion, names_to = "income", values_to = "count")

pivot_longer()の機能をすべて解説しているとGWが終わってしまうので省略しますが、gather()より便利な例を一つだけ。 pivot_longer()を使うと、gather() + separate()が一気にできます。names_sep/names_patternで列名の分割の仕方を指定し、values_toに複数の列名を指定します。 以下の例だと、.の前後で列名を分割し、前半をS/P列、後半をL/W列に格納しています。

iris %>%
  pivot_longer(
    -Species,
    names_to = c("S/P", "L/W"),
    names_sep = "\\."
  )
#> # A tibble: 600 x 4
#>    Species `S/P` `L/W`  value
#>    <fct>   <chr> <chr>  <dbl>
#>  1 setosa  Sepal Length   5.1
#>  2 setosa  Sepal Width    3.5
#>  3 setosa  Petal Length   1.4
#>  4 setosa  Petal Width    0.2
#>  5 setosa  Sepal Length   4.9
#>  6 setosa  Sepal Width    3  
#>  7 setosa  Petal Length   1.4
#>  8 setosa  Petal Width    0.2
#>  9 setosa  Sepal Length   4.7
#> 10 setosa  Sepal Width    3.2
#> # ... with 590 more rows
注意点

ちなみに、gather()と違って列の指定はひとつの引数にまとめないといけない点に注意してください。 例えば、以下のようにするとエラーになってしまいます。

iris %>%
  pivot_longer(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)
#> Error in pivot_longer_spec(data, !!cols, names_to = names_to, values_to = values_to, : object 'Petal.Length' not found

ひとつにまとめるというのは、具体的にはc()で囲めばOKです。

iris %>%
  pivot_longer(c(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width))

pivot_wider()

基本的な使い方は、

pivot_longer(names_from = <列名が格納された列>, values_from = <値が格納された列>)

という感じです。 例えば、元データのstation列を列名に、seen列を値に使って横長に変形するには以下のようにします。

fish_encounters
#> # A tibble: 114 x 3
#>    fish  station  seen
#>    <fct> <fct>   <int>
#>  1 4842  Release     1
#>  2 4842  I80_1       1
#>  3 4842  Lisbon      1
#>  4 4842  Rstr        1
#>  5 4842  Base_TD     1
#>  6 4842  BCE         1
#>  7 4842  BCW         1
#>  8 4842  BCE2        1
#>  9 4842  BCW2        1
#> 10 4842  MAE         1
#> # ... with 104 more rows

fish_encounters %>%
  pivot_wider(names_from = station, values_from = seen)
#> # A tibble: 19 x 12
#>    fish  Release I80_1 Lisbon  Rstr Base_TD   BCE   BCW  BCE2  BCW2   MAE
#>    <fct>   <int> <int>  <int> <int>   <int> <int> <int> <int> <int> <int>
#>  1 4842        1     1      1     1       1     1     1     1     1     1
#>  2 4843        1     1      1     1       1     1     1     1     1     1
#>  3 4844        1     1      1     1       1     1     1     1     1     1
#>  4 4845        1     1      1     1       1    NA    NA    NA    NA    NA
#>  5 4847        1     1      1    NA      NA    NA    NA    NA    NA    NA
#>  6 4848        1     1      1     1      NA    NA    NA    NA    NA    NA
#>  7 4849        1     1     NA    NA      NA    NA    NA    NA    NA    NA
#>  8 4850        1     1     NA     1       1     1     1    NA    NA    NA
#>  9 4851        1     1     NA    NA      NA    NA    NA    NA    NA    NA
#> 10 4854        1     1     NA    NA      NA    NA    NA    NA    NA    NA
#> 11 4855        1     1      1     1       1    NA    NA    NA    NA    NA
#> 12 4857        1     1      1     1       1     1     1     1     1    NA
#> 13 4858        1     1      1     1       1     1     1     1     1     1
#> 14 4859        1     1      1     1       1    NA    NA    NA    NA    NA
#> 15 4861        1     1      1     1       1     1     1     1     1     1
#> 16 4862        1     1      1     1       1     1     1     1     1    NA
#> 17 4863        1     1     NA    NA      NA    NA    NA    NA    NA    NA
#> 18 4864        1     1     NA    NA      NA    NA    NA    NA    NA    NA
#> 19 4865        1     1      1    NA      NA    NA    NA    NA    NA    NA
#> # ... with 1 more variable: MAW <int>

欠損値を埋めるにはvalues_fill引数を指定します。これは列名 = <デフォルト値>という形式のリストを取ります。pivot_wider(..., values_fill = 0)とかやるとエラーになるので注意しましょう。

fish_encounters %>% pivot_wider(
  names_from = station, 
  values_from = seen,
  values_fill = list(seen = 0)
)
#> # A tibble: 19 x 12
#>    fish  Release I80_1 Lisbon  Rstr Base_TD   BCE   BCW  BCE2  BCW2   MAE
#>    <fct>   <int> <int>  <int> <int>   <int> <int> <int> <int> <int> <int>
#>  1 4842        1     1      1     1       1     1     1     1     1     1
#>  2 4843        1     1      1     1       1     1     1     1     1     1
#>  3 4844        1     1      1     1       1     1     1     1     1     1
#>  4 4845        1     1      1     1       1     0     0     0     0     0
#>  5 4847        1     1      1     0       0     0     0     0     0     0
#>  6 4848        1     1      1     1       0     0     0     0     0     0
#>  7 4849        1     1      0     0       0     0     0     0     0     0
#>  8 4850        1     1      0     1       1     1     1     0     0     0
#>  9 4851        1     1      0     0       0     0     0     0     0     0
#> 10 4854        1     1      0     0       0     0     0     0     0     0
#> 11 4855        1     1      1     1       1     0     0     0     0     0
#> 12 4857        1     1      1     1       1     1     1     1     1     0
#> 13 4858        1     1      1     1       1     1     1     1     1     1
#> 14 4859        1     1      1     1       1     0     0     0     0     0
#> 15 4861        1     1      1     1       1     1     1     1     1     1
#> 16 4862        1     1      1     1       1     1     1     1     1     0
#> 17 4863        1     1      0     0       0     0     0     0     0     0
#> 18 4864        1     1      0     0       0     0     0     0     0     0
#> 19 4865        1     1      1     0       0     0     0     0     0     0
#> # ... with 1 more variable: MAW <int>

pivot_wider()も、pivot_longer()と同じく、複数の列を処理することができます。 例えば、英語ブログの方に書いたgather()/spread()ではうまく処理できない例は以下のように扱えます(Date型がちゃんと保持されているのに注目)。

# a bit different version of https://github.com/tidyverse/tidyr/issues/149#issue-124411755
d <- tribble(
  ~place, ~censor,                  ~date, ~status,
    "g1",    "c1",  as.Date("2019-02-01"),    "ok",
    "g1",    "c2",  as.Date("2019-02-01"),   "bad",
    "g1",    "c3",  as.Date("2019-02-01"),    "ok",
    "g2",    "c1",  as.Date("2019-02-01"),   "bad",
    "g2",    "c2",  as.Date("2019-02-02"),    "ok"
)

d %>%
  pivot_wider(names_from = place, values_from = c(date, status))
#> # A tibble: 3 x 5
#>   censor date_g1    date_g2    status_g1 status_g2
#>   <chr>  <date>     <date>     <chr>     <chr>    
#> 1 c1     2019-02-01 2019-02-01 ok        bad      
#> 2 c2     2019-02-01 2019-02-02 bad       ok       
#> 3 c3     2019-02-01 NA         ok        <NA>

あと、pivot_wider()は、キーがユニークでない場合にどう処理するかを指定するvalues_fnという引数がけっこう奥深いですが、ここでは省略します。興味ある人はvignetteのWider - Aggregationという項目を読んでください。

hoist()

これはlist型の列から一部の要素を抜き出してくるときに便利な関数です。NEWS.mdに載ってる以下の例がわかりやすいです。

df %>% hoist(metadata, name = "name")
# shortcut for
df %>% mutate(name = map_chr(metadata, "name"))

unnest_longer()/unnest_wider()

unnest()の、列方向にしか展開しないバージョン、行方向にしか展開しないバージョン、です。 これは?unnestに載ってる例を出すと、こういうデータを、

df <- tibble(
  x = 1:3,
  y = list(
    NULL,
    tibble(a = 1, b = 2),
    tibble(a = 1:3, b = 3:1)
  )
)

df
#> # A tibble: 3 x 2
#>       x y               
#>   <int> <list>          
#> 1     1 <NULL>          
#> 2     2 <tibble [1 x 2]>
#> 3     3 <tibble [3 x 2]>

unnest()だと列方向・行方向ともに展開しますが、

df %>% unnest(y)
#> # A tibble: 4 x 3
#>       x     a     b
#>   <int> <dbl> <dbl>
#> 1     2     1     2
#> 2     3     1     3
#> 3     3     2     2
#> 4     3     3     1

unnest_wider()だと行方向、unnest_longer()だと列方向にしか展開しません。

df %>% unnest_wider(y)
#> # A tibble: 3 x 3
#>       x a         b        
#>   <int> <list>    <list>   
#> 1     1 <NULL>    <NULL>   
#> 2     2 <dbl [1]> <dbl [1]>
#> 3     3 <int [3]> <int [3]>

df %>% unnest_longer(y)
#> # A tibble: 4 x 2
#>       x   y$a    $b
#>   <int> <dbl> <dbl>
#> 1     2     1     2
#> 2     3     1     3
#> 3     3     2     2
#> 4     3     3     1

また、これに関連して?unnest()の内部実装もけっこう変わってるっぽいので、もしunnest()を使っているコードがあれば、念のため動作を確認しておきましょう。

I have done my best to ensure that common uses of unnest() will continue to work, generating an informative warning tell you how to update your code. Please file an issue if I've missed an important use case.

pack()/unpack()/chop()/unchop()

このあたりの関数を直接使う機会はあまりないかもしれませんが、以下でツイートされていた概念です(dice()chop()になったらしい)。

unnest_()/nest_()の廃止

これらの関数は完全に動かなくなります。まだ使ってるという人ははやいところ脱却しましょう。

tidyr::nest_()
#> Error: 'nest_' is defunct.
#> Use 'nest' instead.
#> See help("Defunct")

その他

とりあえずこんなものかな...。何かあれば随時追記します。