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つ
- messy dataで列名に来ていた変数(株価)を格納する列を用意
- 複数の列にまたがっていた変数(株価)を一つの列にまとめる
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()を使う。
手順は
- 列名に持ってきたい変数(株式名)を指定
- 実際にセルに格納(横長形式に変換)する変数(株価)を指定
基本的な使い方
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>