Соберите несколько наборов столбцов
У меня есть данные из онлайн-опроса, где респонденты проходят цикл вопросов 1-3 раза. Программное обеспечение опроса (Qualtrics) записывает эти данные в несколько столбцов, то есть Q3.2 в опросе будет иметь столбцы Q3.2.1.
, Q3.2.2.
и Q3.2.3.
:
df <- data.frame(
id = 1:10,
time = as.Date('2009-01-01') + 0:9,
Q3.2.1. = rnorm(10, 0, 1),
Q3.2.2. = rnorm(10, 0, 1),
Q3.2.3. = rnorm(10, 0, 1),
Q3.3.1. = rnorm(10, 0, 1),
Q3.3.2. = rnorm(10, 0, 1),
Q3.3.3. = rnorm(10, 0, 1)
)
# Sample data
id time Q3.2.1. Q3.2.2. Q3.2.3. Q3.3.1. Q3.3.2. Q3.3.3.
1 1 2009-01-01 -0.2059165 -0.29177677 -0.7107192 1.52718069 -0.4484351 -1.21550600
2 2 2009-01-02 -0.1981136 -1.19813815 1.1750200 -0.40380049 -1.8376094 1.03588482
3 3 2009-01-03 0.3514795 -0.27425539 1.1171712 -1.02641801 -2.0646661 -0.35353058
...
Я хочу объединить все столбцы QN.N * в аккуратные отдельные столбцы QN.N, в итоге получив что-то вроде этого:
id time loop_number Q3.2 Q3.3
1 1 2009-01-01 1 -0.20591649 1.52718069
2 2 2009-01-02 1 -0.19811357 -0.40380049
3 3 2009-01-03 1 0.35147949 -1.02641801
...
11 1 2009-01-01 2 -0.29177677 -0.4484351
12 2 2009-01-02 2 -1.19813815 -1.8376094
13 3 2009-01-03 2 -0.27425539 -2.0646661
...
21 1 2009-01-01 3 -0.71071921 -1.21550600
22 2 2009-01-02 3 1.17501999 1.03588482
23 3 2009-01-03 3 1.11717121 -0.35353058
...
Библиотека tidyr
имеет функцию gather()
, которая отлично подходит для объединения одного набора столбцов:
library(dplyr)
library(tidyr)
library(stringr)
df %>% gather(loop_number, Q3.2, starts_with("Q3.2")) %>%
mutate(loop_number = str_sub(loop_number,-2,-2)) %>%
select(id, time, loop_number, Q3.2)
id time loop_number Q3.2
1 1 2009-01-01 1 -0.20591649
2 2 2009-01-02 1 -0.19811357
3 3 2009-01-03 1 0.35147949
...
29 9 2009-01-09 3 -0.58581232
30 10 2009-01-10 3 -2.33393981
Результирующий фрейм данных имеет 30 строк, как и ожидалось (10 особей, 3 петли каждый). Однако сбор второго набора столбцов работает некорректно - он успешно делает два комбинированных столбца Q3.2
и Q3.3
, но заканчивается 90 строками вместо 30 (все комбинации из 10 человек, 3 цикла Q3.2, и 3 цикла Q3.3, комбинации будут существенно увеличиваться для каждой группы столбцов в фактических данных):
df %>% gather(loop_number, Q3.2, starts_with("Q3.2")) %>%
gather(loop_number, Q3.3, starts_with("Q3.3")) %>%
mutate(loop_number = str_sub(loop_number,-2,-2))
id time loop_number Q3.2 Q3.3
1 1 2009-01-01 1 -0.20591649 1.52718069
2 2 2009-01-02 1 -0.19811357 -0.40380049
3 3 2009-01-03 1 0.35147949 -1.02641801
...
89 9 2009-01-09 3 -0.58581232 -0.13187024
90 10 2009-01-10 3 -2.33393981 -0.48502131
Есть ли способ использовать несколько вызовов для gather()
, как это, объединяя небольшие подмножества столбцов, подобные этому, сохраняя при этом правильное количество строк?
Ответы
Ответ 1
Этот подход кажется мне довольно естественным:
df %>%
gather(key, value, -id, -time) %>%
extract(key, c("question", "loop_number"), "(Q.\\..)\\.(.)") %>%
spread(question, value)
Сначала соберите все столбцы вопросов, используйте extract()
для разделения на question
и loop_number
, затем spread()
вопрос обратно в столбцы.
#> id time loop_number Q3.2 Q3.3
#> 1 1 2009-01-01 1 0.142259203 -0.35842736
#> 2 1 2009-01-01 2 0.061034802 0.79354061
#> 3 1 2009-01-01 3 -0.525686204 -0.67456611
#> 4 2 2009-01-02 1 -1.044461185 -1.19662936
#> 5 2 2009-01-02 2 0.393808163 0.42384717
Ответ 2
Это может быть сделано с помощью reshape
. Это возможно с dplyr
хотя.
colnames(df) <- gsub("\\.(.{2})$", "_\\1", colnames(df))
colnames(df)[2] <- "Date"
res <- reshape(df, idvar=c("id", "Date"), varying=3:8, direction="long", sep="_")
row.names(res) <- 1:nrow(res)
head(res)
# id Date time Q3.2 Q3.3
#1 1 2009-01-01 1 1.3709584 0.4554501
#2 2 2009-01-02 1 -0.5646982 0.7048373
#3 3 2009-01-03 1 0.3631284 1.0351035
#4 4 2009-01-04 1 0.6328626 -0.6089264
#5 5 2009-01-05 1 0.4042683 0.5049551
#6 6 2009-01-06 1 -0.1061245 -1.7170087
Или используя dplyr
library(tidyr)
library(dplyr)
colnames(df) <- gsub("\\.(.{2})$", "_\\1", colnames(df))
df %>%
gather(loop_number, "Q3", starts_with("Q3")) %>%
separate(loop_number,c("L1", "L2"), sep="_") %>%
spread(L1, Q3) %>%
select(-L2) %>%
head()
# id time Q3.2 Q3.3
#1 1 2009-01-01 1.3709584 0.4554501
#2 1 2009-01-01 1.3048697 0.2059986
#3 1 2009-01-01 -0.3066386 0.3219253
#4 2 2009-01-02 -0.5646982 0.7048373
#5 2 2009-01-02 2.2866454 -0.3610573
#6 2 2009-01-02 -1.7813084 -0.7838389
Обновить
С tidyr_0.8.3.9000
мы можем использовать pivot_longer
для изменения формы нескольких столбцов. (Используя измененные имена столбцов из gsub
выше)
library(dplyr)
library(tidyr)
df %>%
pivot_longer(cols = starts_with("Q3"),
names_to = c(".value", "Q3"), names_sep = "_") %>%
select(-Q3)
# A tibble: 30 x 4
# id time Q3.2 Q3.3
# <int> <date> <dbl> <dbl>
# 1 1 2009-01-01 0.974 1.47
# 2 1 2009-01-01 -0.849 -0.513
# 3 1 2009-01-01 0.894 0.0442
# 4 2 2009-01-02 2.04 -0.553
# 5 2 2009-01-02 0.694 0.0972
# 6 2 2009-01-02 -1.11 1.85
# 7 3 2009-01-03 0.413 0.733
# 8 3 2009-01-03 -0.896 -0.271
#9 3 2009-01-03 0.509 -0.0512
#10 4 2009-01-04 1.81 0.668
# … with 20 more rows
ПРИМЕЧАНИЕ. Значения отличаются, поскольку при создании входного набора данных не было заданного начального числа.
Ответ 3
С недавним обновлением до melt.data.table
мы можем теперь расплавить несколько столбцов. С этим мы можем:
require(data.table) ## 1.9.5
melt(setDT(df), id=1:2, measure=patterns("^Q3.2", "^Q3.3"),
value.name=c("Q3.2", "Q3.3"), variable.name="loop_number")
# id time loop_number Q3.2 Q3.3
# 1: 1 2009-01-01 1 -0.433978480 0.41227209
# 2: 2 2009-01-02 1 -0.567995351 0.30701144
# 3: 3 2009-01-03 1 -0.092041353 -0.96024077
# 4: 4 2009-01-04 1 1.137433487 0.60603396
# 5: 5 2009-01-05 1 -1.071498263 -0.01655584
# 6: 6 2009-01-06 1 -0.048376809 0.55889996
# 7: 7 2009-01-07 1 -0.007312176 0.69872938
Вы можете получить версию разработки здесь.
Ответ 4
Это совсем не связано с "tidyr" и "dplyr", но вот еще один вариант: merged.stack
из моего пакета splitstackshape, V1. 4.0 и выше.
library(splitstackshape)
merged.stack(df, id.vars = c("id", "time"),
var.stubs = c("Q3.2.", "Q3.3."),
sep = "var.stubs")
# id time .time_1 Q3.2. Q3.3.
# 1: 1 2009-01-01 1. -0.62645381 1.35867955
# 2: 1 2009-01-01 2. 1.51178117 -0.16452360
# 3: 1 2009-01-01 3. 0.91897737 0.39810588
# 4: 2 2009-01-02 1. 0.18364332 -0.10278773
# 5: 2 2009-01-02 2. 0.38984324 -0.25336168
# 6: 2 2009-01-02 3. 0.78213630 -0.61202639
# 7: 3 2009-01-03 1. -0.83562861 0.38767161
# <<:::SNIP:::>>
# 24: 8 2009-01-08 3. -1.47075238 -1.04413463
# 25: 9 2009-01-09 1. 0.57578135 1.10002537
# 26: 9 2009-01-09 2. 0.82122120 -0.11234621
# 27: 9 2009-01-09 3. -0.47815006 0.56971963
# 28: 10 2009-01-10 1. -0.30538839 0.76317575
# 29: 10 2009-01-10 2. 0.59390132 0.88110773
# 30: 10 2009-01-10 3. 0.41794156 -0.13505460
# id time .time_1 Q3.2. Q3.3.
Ответ 5
Если вы похожи на меня и не можете решить, как использовать "регулярное выражение с группами захвата" для extract
, следующий код реплицирует строку extract(...)
в ответе Hadleys:
df %>%
gather(question_number, value, starts_with("Q3.")) %>%
mutate(loop_number = str_sub(question_number,-2,-2), question_number = str_sub(question_number,1,4)) %>%
select(id, time, loop_number, question_number, value) %>%
spread(key = question_number, value = value)
Проблема заключается в том, что начальная сборка формирует ключевой столбец, который на самом деле представляет собой комбинацию из двух ключей. Я решил использовать mutate
в своем исходном решении в комментариях, чтобы разбить этот столбец на два столбца с эквивалентной информацией, столбец loop_number
и столбец question_number
. spread
может затем использоваться для преобразования данных длинной формы, которые представляют собой пары ключевых значений (question_number, value)
для широкоформатных данных.