tidyr

はじめに

環境設定

例のごとくtidyverseパッケージを読み込む

library(tidyverse)

tidyrパッケージとは

tidy dataの哲学を実行するための強力なツールである。
簡単にいえばmessyなデータをtidyに変換するパッケージである

messyなデータ

messyな仮想データを作ろう。
いま、株式X, Y, Zがあるとする。これらの価格データを次のように作る。

stocks <- tibble(time = as.Date("2009-01-01") + 0:9, X = rnorm(10, 0, 1), Y = rnorm(10, 
    0, 2), Z = rnorm(10, 0, 4))

kable(stocks)
time X Y Z
2009-01-01 0.6176920 -2.7158434 -1.3350309
2009-01-02 -0.5398496 -0.4788283 -3.4077334
2009-01-03 -0.3593153 2.9838842 -0.0771073
2009-01-04 0.5603960 0.7983227 3.4557741
2009-01-05 1.2146006 -0.4148579 0.7754762
2009-01-06 0.2159499 -0.8053077 2.3886740
2009-01-07 1.9637734 -1.4962876 -2.3562366
2009-01-08 -0.8140700 1.6496587 1.3012453
2009-01-09 -0.5647787 -0.3443496 1.8011643
2009-01-10 0.7637772 0.9174859 -3.2889390

tidyなデータの原則

  • 個々の変数 (variable) が1つの列 (column) をなす。
  • 個々の観測 (observation) が1つの行 (row) をなす。
  • 個々の観測の構成単位の類型 (type of observational unit) が1つの表 (table) をなす。
  • 個々の値 (value) が1つのセル (cell) をなす

参考:http://id.fnshr.info/2017/01/09/tidy-data-intro/

上記の仮想株価データは、原則のうち

  • 個々の観測が1つの行をなす。

を満たしていないことになる(X, Y, Zの株価観測は別の観測)
我々が欲しいtidyなデータは

time stock price
2009-01-01 X 0.6176920
2009-01-01 Y -2.7158434
2009-01-01 Z -1.3350309
2009-01-02 X -0.5398496
2009-01-02 Y -0.4788283
2009-01-02 Z -3.4077334

である(省略してある)

変換すべき点

上のmessyなデータでは

  • 本来、同質な変数であるはずの株式名X, Y, Zが列名に来てしまっている
  • 株式名、株価 で別々の列になるようにすべき

つまり、やることは2つ

  1. messy dataで列名に来ていた変数(株価)を格納する列を用意
  2. 複数の列にまたがっていた変数(株価)を一つの列にまとめる

tidyなデータへの変換

gather()

tidyなデータへの変換にはtidyr::gather()を用いる
上で見せたデータは以下のようにして作成している

stocks_tidy <- stocks %>% 
  gather(stock, price, -time)

stocks_tidy %>% 
  arrange(time) %>% # timeで並び替え
  head(6) %>% # 縦に長いので省略して上から6行
  kable() # Rmdでキレイに表示する関数
time stock price
2009-01-01 X 0.6176920
2009-01-01 Y -2.7158434
2009-01-01 Z -1.3350309
2009-01-02 X -0.5398496
2009-01-02 Y -0.4788283
2009-01-02 Z -3.4077334

基本的な使い方

gather()の使い方は簡単で

gather(key = 列名に来ていた変数を格納する列名,
       value = 複数の列にまたがっていた変数をまとめる列名,
       -変換に考慮しないす(もともとtidyの原則を満たしている)列名)

とするだけである。その他のオプションはヘルプを参照されたい

messyなデータへの変換

とはいえmessyなデータも捨てたものではない

  • 人間にとって解釈しやすい表にするとき
  • 他のパッケージや関数との連携をとるとき
  • などなど

spread()

tidyなデータをmessyなデータに変換するにはtidyr::spread()を使う。
手順は

  1. 列名に持ってきたい変数(株式名)を指定
  2. 実際にセルに格納(横長形式に変換)する変数(株価)を指定

基本的な使い方

stocks_tidy %>% spread(stock, price)
# A tibble: 10 x 4
   time            X      Y       Z
 * <date>      <dbl>  <dbl>   <dbl>
 1 2009-01-01  0.618 -2.72  -1.34  
 2 2009-01-02 -0.540 -0.479 -3.41  
 3 2009-01-03 -0.359  2.98  -0.0771
 4 2009-01-04  0.560  0.798  3.46  
 5 2009-01-05  1.21  -0.415  0.775 
 6 2009-01-06  0.216 -0.805  2.39  
 7 2009-01-07  1.96  -1.50  -2.36  
 8 2009-01-08 -0.814  1.65   1.30  
 9 2009-01-09 -0.565 -0.344  1.80  
10 2009-01-10  0.764  0.917 -3.29  
spread(key = 横長に変換する際列名に持ってきたい列を指定,
       value = 横長に変換する値が実際に入っている列を指定)

その他のオプションはヘルプを参照されたい

その他便利なtidyrの関数たち

ここからはお馴染みstarwarsデータを使って解説する

starwars
# A tibble: 87 x 13
   name     height  mass hair_color skin_color eye_color birth_year gender
   <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
 1 Luke Sk~    172  77.0 blond      fair       blue            19.0 male  
 2 C-3PO       167  75.0 <NA>       gold       yellow         112   <NA>  
 3 R2-D2        96  32.0 <NA>       white, bl~ red             33.0 <NA>  
 4 Darth V~    202 136   none       white      yellow          41.9 male  
 5 Leia Or~    150  49.0 brown      light      brown           19.0 female
 6 Owen La~    178 120   brown, gr~ light      blue            52.0 male  
 7 Beru Wh~    165  75.0 brown      light      blue            47.0 female
 8 R5-D4        97  32.0 <NA>       white, red red             NA   <NA>  
 9 Biggs D~    183  84.0 black      light      brown           24.0 male  
10 Obi-Wan~    182  77.0 auburn, w~ fair       blue-gray       57.0 male  
# ... with 77 more rows, and 5 more variables: homeworld <chr>,
#   species <chr>, films <list>, vehicles <list>, starships <list>

NA処理系

drop_na()

欠損値のある行を除きたいとき、あるよね?

  • na.omit()でいいじゃん

でも、特定の列においてだけ欠損値のある行を除きたいとき、あるよね?

  • ここではhair_color列かgender列にnaがある行を取り除きたい
starwars %>% drop_na(hair_color, gender)
# A tibble: 82 x 13
   name     height  mass hair_color skin_color eye_color birth_year gender
   <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
 1 Luke Sk~    172  77.0 blond      fair       blue            19.0 male  
 2 Darth V~    202 136   none       white      yellow          41.9 male  
 3 Leia Or~    150  49.0 brown      light      brown           19.0 female
 4 Owen La~    178 120   brown, gr~ light      blue            52.0 male  
 5 Beru Wh~    165  75.0 brown      light      blue            47.0 female
 6 Biggs D~    183  84.0 black      light      brown           24.0 male  
 7 Obi-Wan~    182  77.0 auburn, w~ fair       blue-gray       57.0 male  
 8 Anakin ~    188  84.0 blond      fair       blue            41.9 male  
 9 Wilhuff~    180  NA   auburn, g~ fair       blue            64.0 male  
10 Chewbac~    228 112   brown      unknown    blue           200   male  
# ... with 72 more rows, and 5 more variables: homeworld <chr>,
#   species <chr>, films <list>, vehicles <list>, starships <list>

replace_na()

欠損しているところをを何らかの値で埋めたいとき、あるよね?

  • hair_colorをno_hairで埋めたい
  • genderをrobotで埋めたい
  • 同時に埋めたい

次のようにすればよい。リストで指定する

starwars %>% replace_na(list(hair_color = "no_hair", gender = "robot"))
# A tibble: 87 x 13
   name     height  mass hair_color skin_color eye_color birth_year gender
   <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
 1 Luke Sk~    172  77.0 blond      fair       blue            19.0 male  
 2 C-3PO       167  75.0 no_hair    gold       yellow         112   robot 
 3 R2-D2        96  32.0 no_hair    white, bl~ red             33.0 robot 
 4 Darth V~    202 136   none       white      yellow          41.9 male  
 5 Leia Or~    150  49.0 brown      light      brown           19.0 female
 6 Owen La~    178 120   brown, gr~ light      blue            52.0 male  
 7 Beru Wh~    165  75.0 brown      light      blue            47.0 female
 8 R5-D4        97  32.0 no_hair    white, red red             NA   robot 
 9 Biggs D~    183  84.0 black      light      brown           24.0 male  
10 Obi-Wan~    182  77.0 auburn, w~ fair       blue-gray       57.0 male  
# ... with 77 more rows, and 5 more variables: homeworld <chr>,
#   species <chr>, films <list>, vehicles <list>, starships <list>

fill()

欠損してるところを直前もしくは直後の値で埋めたいとき、あるよね?
例えば土日の株価を金曜の終値で埋めたいときとか

  • 今回は不適切だが、starwarsのhair_colorを直上の値で埋める
  • 埋める方向は.directionで指定。直上の値で埋めるときは“down”
starwars %>% tidyr::fill(hair_color, .direction = "down")
# A tibble: 87 x 13
   name     height  mass hair_color skin_color eye_color birth_year gender
   <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
 1 Luke Sk~    172  77.0 blond      fair       blue            19.0 male  
 2 C-3PO       167  75.0 blond      gold       yellow         112   <NA>  
 3 R2-D2        96  32.0 blond      white, bl~ red             33.0 <NA>  
 4 Darth V~    202 136   none       white      yellow          41.9 male  
 5 Leia Or~    150  49.0 brown      light      brown           19.0 female
 6 Owen La~    178 120   brown, gr~ light      blue            52.0 male  
 7 Beru Wh~    165  75.0 brown      light      blue            47.0 female
 8 R5-D4        97  32.0 brown      white, red red             NA   <NA>  
 9 Biggs D~    183  84.0 black      light      brown           24.0 male  
10 Obi-Wan~    182  77.0 auburn, w~ fair       blue-gray       57.0 male  
# ... with 77 more rows, and 5 more variables: homeworld <chr>,
#   species <chr>, films <list>, vehicles <list>, starships <list>
  • 直下の値で埋める
starwars %>% tidyr::fill(hair_color, .direction = "up")
# A tibble: 87 x 13
   name     height  mass hair_color skin_color eye_color birth_year gender
   <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
 1 Luke Sk~    172  77.0 blond      fair       blue            19.0 male  
 2 C-3PO       167  75.0 none       gold       yellow         112   <NA>  
 3 R2-D2        96  32.0 none       white, bl~ red             33.0 <NA>  
 4 Darth V~    202 136   none       white      yellow          41.9 male  
 5 Leia Or~    150  49.0 brown      light      brown           19.0 female
 6 Owen La~    178 120   brown, gr~ light      blue            52.0 male  
 7 Beru Wh~    165  75.0 brown      light      blue            47.0 female
 8 R5-D4        97  32.0 black      white, red red             NA   <NA>  
 9 Biggs D~    183  84.0 black      light      brown           24.0 male  
10 Obi-Wan~    182  77.0 auburn, w~ fair       blue-gray       57.0 male  
# ... with 77 more rows, and 5 more variables: homeworld <chr>,
#   species <chr>, films <list>, vehicles <list>, starships <list>

複数の列をまとめたり切り離したり

stocksデータに戻る

separate()

一つの列を切り離したいとき、あるよね?

  • time 列を年、月、日に切り離す
  • separate()は自動で境目になっているような文字を判断してくれる
  • 切り離した後の列名はintoで指定
  • 切り離し後はcharacter型になることに注意
stocks_sep <- stocks %>% separate(time, into = c("year", "month", "day"))
stocks_sep
# A tibble: 10 x 6
   year  month day        X      Y       Z
 * <chr> <chr> <chr>  <dbl>  <dbl>   <dbl>
 1 2009  01    01     0.618 -2.72  -1.34  
 2 2009  01    02    -0.540 -0.479 -3.41  
 3 2009  01    03    -0.359  2.98  -0.0771
 4 2009  01    04     0.560  0.798  3.46  
 5 2009  01    05     1.21  -0.415  0.775 
 6 2009  01    06     0.216 -0.805  2.39  
 7 2009  01    07     1.96  -1.50  -2.36  
 8 2009  01    08    -0.814  1.65   1.30  
 9 2009  01    09    -0.565 -0.344  1.80  
10 2009  01    10     0.764  0.917 -3.29  

unite()

くっつけたいときはunite()を使う

  • colでくっつけた後の列名
  • sepでくっつける間に入れる文字
stocks_sep %>% unite(year, month, day, col = "time", sep = "-")
# A tibble: 10 x 4
   time            X      Y       Z
 * <chr>       <dbl>  <dbl>   <dbl>
 1 2009-01-01  0.618 -2.72  -1.34  
 2 2009-01-02 -0.540 -0.479 -3.41  
 3 2009-01-03 -0.359  2.98  -0.0771
 4 2009-01-04  0.560  0.798  3.46  
 5 2009-01-05  1.21  -0.415  0.775 
 6 2009-01-06  0.216 -0.805  2.39  
 7 2009-01-07  1.96  -1.50  -2.36  
 8 2009-01-08 -0.814  1.65   1.30  
 9 2009-01-09 -0.565 -0.344  1.80  
10 2009-01-10  0.764  0.917 -3.29  

組み合わせ列挙系

complete()

expand()

tibbleの中にtibble

nest()

例えば、starwarsのspeciesごとに分析したい、モデルを立てたいとする

nest() - group_byしたのちnestしてしまって、各tibbleごとに分析すればよい

starwars_nest <- starwars %>% group_by(species) %>% nest()
starwars_nest
# A tibble: 38 x 2
   species        data              
   <chr>          <list>            
 1 Human          <tibble [35 x 12]>
 2 Droid          <tibble [5 x 12]> 
 3 Wookiee        <tibble [2 x 12]> 
 4 Rodian         <tibble [1 x 12]> 
 5 Hutt           <tibble [1 x 12]> 
 6 Yoda's species <tibble [1 x 12]> 
 7 Trandoshan     <tibble [1 x 12]> 
 8 Mon Calamari   <tibble [1 x 12]> 
 9 Ewok           <tibble [1 x 12]> 
10 Sullustan      <tibble [1 x 12]> 
# ... with 28 more rows

unnest()

もどすときはunnest()

starwars_nest %>% unnest()
# A tibble: 87 x 13
   species name    height  mass hair_color skin_color eye_color birth_year
   <chr>   <chr>    <int> <dbl> <chr>      <chr>      <chr>          <dbl>
 1 Human   Luke S~    172  77.0 blond      fair       blue            19.0
 2 Human   Darth ~    202 136   none       white      yellow          41.9
 3 Human   Leia O~    150  49.0 brown      light      brown           19.0
 4 Human   Owen L~    178 120   brown, gr~ light      blue            52.0
 5 Human   Beru W~    165  75.0 brown      light      blue            47.0
 6 Human   Biggs ~    183  84.0 black      light      brown           24.0
 7 Human   Obi-Wa~    182  77.0 auburn, w~ fair       blue-gray       57.0
 8 Human   Anakin~    188  84.0 blond      fair       blue            41.9
 9 Human   Wilhuf~    180  NA   auburn, g~ fair       blue            64.0
10 Human   Han So~    180  80.0 brown      fair       brown           29.0
# ... with 77 more rows, and 5 more variables: gender <chr>,
#   homeworld <chr>, films <list>, vehicles <list>, starships <list>

Yutaka Kuroki

2018年2月28日