1 Case 1: multi-gather/spread問題
1.1 Question 1
以下のようなデータがあります:
# 資料を読んでる人が再現できるように、データ生成用コードも残しときます
n = 30
df_1 <- data.frame(
id = rep(1:10, each = 3),
group = rep(letters[1:3], 10),
x_pre = round(rnorm(n) * 100),
x_post = round(rnorm(n, 1, 1) * 100),
y_pre = round(rnorm(n, 2, 3) * 100),
y_post = round(rnorm(n, 3, 1) * 100)
)
knitr::kable(head(df_1))
| id | group | x_pre | x_post | y_pre | y_post |
|---|---|---|---|---|---|
| 1 | a | -12 | 139 | 405 | 400 |
| 1 | b | -42 | -147 | -101 | 301 |
| 1 | c | 118 | -34 | 409 | 376 |
| 2 | a | -42 | 150 | 807 | 362 |
| 2 | b | -10 | 168 | 251 | 282 |
| 2 | c | -139 | 66 | 291 | 400 |
これを、以下のようにしたいです:
| id | pre_post | a_x | a_y | b_x | b_y | c_x | c_y |
|---|---|---|---|---|---|---|---|
| 1 | post | 139 | 400 | -147 | 301 | -34 | 376 |
| 1 | pre | -12 | 405 | -42 | -101 | 118 | 409 |
| 2 | post | 150 | 362 | 168 | 282 | 66 | 400 |
| 2 | pre | -42 | 807 | -10 | 251 | -139 | 291 |
| 3 | post | 75 | 290 | 105 | 431 | 76 | 438 |
| 3 | pre | 140 | -204 | 18 | 164 | 24 | -95 |
どうしたらいいのでしょうか?
1.2 Answer
以下のようにやります:
library(tidyverse)
df_1_result <-df_1 %>%
gather(key = var_name, value = value, -c(id, group)) %>%
separate(var_name, c("var", "pre_post")) %>%
unite(new_var, group, var) %>%
spread(key = new_var, value = value)
knitr::kable(head(df_1_result))
| id | pre_post | a_x | a_y | b_x | b_y | c_x | c_y |
|---|---|---|---|---|---|---|---|
| 1 | post | 139 | 400 | -147 | 301 | -34 | 376 |
| 1 | pre | -12 | 405 | -42 | -101 | 118 | 409 |
| 2 | post | 150 | 362 | 168 | 282 | 66 | 400 |
| 2 | pre | -42 | 807 | -10 | 251 | -139 | 291 |
| 3 | post | 75 | 290 | 105 | 431 | 76 | 438 |
| 3 | pre | 140 | -204 | 18 | 164 | 24 | -95 |
1.3 解説
1.3.1 考え方
通称「multi-gather, multi-spread問題」の一種です。
wide-long変換をするにはtidyr::gatherやtidyr::spreadを使えばいいのですが、今回は単純にそれらを使うだけではうまく行きません。そこで以下のようなアプローチをします:
- 一旦tidyなデータ(long data)に整形
- 変数名を切り離す
- 目的の変数名を作成
- 新たに作った変数名をkeyにしてwideに展開
1.3.2 手順
まずはgather:
res <- df_1 %>%
gather(key = var_name, value = value, -c(id, group))
knitr::kable(head(res))
| id | group | var_name | value |
|---|---|---|---|
| 1 | a | x_pre | -12 |
| 1 | b | x_pre | -42 |
| 1 | c | x_pre | 118 |
| 2 | a | x_pre | -42 |
| 2 | b | x_pre | -10 |
| 2 | c | x_pre | -139 |
ここからがポイントで、当初の変数名を2つに切り離します:
res <- res %>%
separate(var_name, c("var", "pre_post"))
knitr::kable(head(res))
| id | group | var | pre_post | value |
|---|---|---|---|---|
| 1 | a | x | pre | -12 |
| 1 | b | x | pre | -42 |
| 1 | c | x | pre | 118 |
| 2 | a | x | pre | -42 |
| 2 | b | x | pre | -10 |
| 2 | c | x | pre | -139 |
これで要素がちゃんと分かれたデータになりました。そして目的の変数名になるようひっつけます:
res <- res %>%
unite(new_var, group, var)
knitr::kable(head(res))
| id | new_var | pre_post | value |
|---|---|---|---|
| 1 | a_x | pre | -12 |
| 1 | b_x | pre | -42 |
| 1 | c_x | pre | 118 |
| 2 | a_x | pre | -42 |
| 2 | b_x | pre | -10 |
| 2 | c_x | pre | -139 |
あとはこの変数名の列をkeyとしてwideにします:
res <- res %>%
spread(key = new_var, value = value)
knitr::kable(head(res))
| id | pre_post | a_x | a_y | b_x | b_y | c_x | c_y |
|---|---|---|---|---|---|---|---|
| 1 | post | 139 | 400 | -147 | 301 | -34 | 376 |
| 1 | pre | -12 | 405 | -42 | -101 | 118 | 409 |
| 2 | post | 150 | 362 | 168 | 282 | 66 | 400 |
| 2 | pre | -42 | 807 | -10 | 251 | -139 | 291 |
| 3 | post | 75 | 290 | 105 | 431 | 76 | 438 |
| 3 | pre | 140 | -204 | 18 | 164 | 24 | -95 |
これでOKです。
1.3.3 応用
今回はvalueにあたるデータが全て数値だったのでスムーズでしたが、型が違う場合もあります:
n = 30
df_1a <- data.frame(
id = rep(1:10, each = 3),
group = rep(letters[1:3], 10),
x_pre = round(rnorm(n) * 100),
x_post = round(rnorm(n, 1, 1) * 100),
y_pre = sample(c("kosaki", "chitoge"), n, replace = TRUE, prob = c(5, 5)),
y_post = sample(c("kosaki", "chitoge"), n, replace = TRUE, prob = c(9, 1))
)
knitr::kable(head(df_1a))
| id | group | x_pre | x_post | y_pre | y_post |
|---|---|---|---|---|---|
| 1 | a | 9 | -42 | kosaki | kosaki |
| 1 | b | -23 | 198 | kosaki | kosaki |
| 1 | c | -5 | 52 | kosaki | kosaki |
| 2 | a | -39 | 4 | chitoge | chitoge |
| 2 | b | 96 | 198 | kosaki | kosaki |
| 2 | c | -8 | 20 | chitoge | kosaki |
この場合、まずは気にせずに同じように整形し、あとから列の型を変更すればOKです
res_a <- df_1a %>%
# このときvalueがcharacter型になる
gather(key = var_name, value = value, -c(id, group)) %>%
# 気にせず処理
separate(var_name, c("var", "pre_post")) %>%
unite(new_var, group, var) %>%
spread(new_var, value) %>%
# 数値にしたい列を変換
mutate_at(vars(ends_with("_x")), as.numeric)
knitr::kable(head(res_a))
| id | pre_post | a_x | a_y | b_x | b_y | c_x | c_y |
|---|---|---|---|---|---|---|---|
| 1 | post | -42 | kosaki | 198 | kosaki | 52 | kosaki |
| 1 | pre | 9 | kosaki | -23 | kosaki | -5 | kosaki |
| 2 | post | 4 | chitoge | 198 | kosaki | 20 | kosaki |
| 2 | pre | -39 | chitoge | 96 | kosaki | -8 | chitoge |
| 3 | post | 71 | kosaki | -133 | kosaki | 60 | kosaki |
| 3 | pre | -49 | kosaki | -49 | kosaki | -49 | chitoge |
以上です。なお個人的には全てtidyにしたいです。