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 | 31 | 88 | -72 | 394 |
1 | b | 43 | -62 | 358 | 398 |
1 | c | -157 | 180 | 403 | 160 |
2 | a | 185 | 179 | 79 | 225 |
2 | b | 96 | 230 | 291 | 214 |
2 | c | 10 | 126 | 508 | 336 |
これを、以下のようにしたいです:
id | pre_post | a_x | a_y | b_x | b_y | c_x | c_y |
---|---|---|---|---|---|---|---|
1 | post | 88 | 394 | -62 | 398 | 180 | 160 |
1 | pre | 31 | -72 | 43 | 358 | -157 | 403 |
2 | post | 179 | 225 | 230 | 214 | 126 | 336 |
2 | pre | 185 | 79 | 96 | 291 | 10 | 508 |
3 | post | 105 | 265 | 52 | 369 | 101 | 459 |
3 | pre | 6 | -435 | 40 | 272 | 124 | -457 |
どうしたらいいのでしょうか?
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 | 88 | 394 | -62 | 398 | 180 | 160 |
1 | pre | 31 | -72 | 43 | 358 | -157 | 403 |
2 | post | 179 | 225 | 230 | 214 | 126 | 336 |
2 | pre | 185 | 79 | 96 | 291 | 10 | 508 |
3 | post | 105 | 265 | 52 | 369 | 101 | 459 |
3 | pre | 6 | -435 | 40 | 272 | 124 | -457 |
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 | 31 |
1 | b | x_pre | 43 |
1 | c | x_pre | -157 |
2 | a | x_pre | 185 |
2 | b | x_pre | 96 |
2 | c | x_pre | 10 |
ここからがポイントで、当初の変数名を2つに切り離します:
res <- res %>%
separate(var_name, c("var", "pre_post"))
knitr::kable(head(res))
id | group | var | pre_post | value |
---|---|---|---|---|
1 | a | x | pre | 31 |
1 | b | x | pre | 43 |
1 | c | x | pre | -157 |
2 | a | x | pre | 185 |
2 | b | x | pre | 96 |
2 | c | x | pre | 10 |
これで要素がちゃんと分かれたデータになりました。そして目的の変数名になるようひっつけます:
res <- res %>%
unite(new_var, group, var)
knitr::kable(head(res))
id | new_var | pre_post | value |
---|---|---|---|
1 | a_x | pre | 31 |
1 | b_x | pre | 43 |
1 | c_x | pre | -157 |
2 | a_x | pre | 185 |
2 | b_x | pre | 96 |
2 | c_x | pre | 10 |
あとはこの変数名の列を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 | 88 | 394 | -62 | 398 | 180 | 160 |
1 | pre | 31 | -72 | 43 | 358 | -157 | 403 |
2 | post | 179 | 225 | 230 | 214 | 126 | 336 |
2 | pre | 185 | 79 | 96 | 291 | 10 | 508 |
3 | post | 105 | 265 | 52 | 369 | 101 | 459 |
3 | pre | 6 | -435 | 40 | 272 | 124 | -457 |
これで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 | 102 | -109 | kosaki | kosaki |
1 | b | 187 | 35 | kosaki | kosaki |
1 | c | -1 | 173 | chitoge | kosaki |
2 | a | 0 | 109 | kosaki | kosaki |
2 | b | 38 | 137 | kosaki | kosaki |
2 | c | -78 | 125 | 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 | -109 | kosaki | 35 | kosaki | 173 | kosaki |
1 | pre | 102 | kosaki | 187 | kosaki | -1 | chitoge |
2 | post | 109 | kosaki | 137 | kosaki | 125 | kosaki |
2 | pre | 0 | kosaki | 38 | kosaki | -78 | chitoge |
3 | post | 271 | kosaki | 164 | kosaki | 357 | kosaki |
3 | pre | -151 | kosaki | 129 | chitoge | -152 | kosaki |
以上です。なお個人的には全てtidyにしたいです。