Reúna varios conjuntos de columnas
Tengo datos de una encuesta en línea en la que los encuestados realizan un ciclo de preguntas de 1 a 3 veces. El software de encuestas (Qualtrics) registra estos datos en varias columnas; es decir, la pregunta 3.2 de la encuesta tendrá las columnas Q3.2.1.
, Q3.2.2.
y 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
...
Quiero combinar todas las columnas QN.N* en columnas QN.N individuales ordenadas y, en última instancia, terminar con algo como esto:
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
...
La tidyr
biblioteca tiene la gather()
función que funciona muy bien para combinar un conjunto de columnas:
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
El marco de datos resultante tiene 30 filas, como se esperaba (10 individuos, 3 bucles cada uno). Sin embargo, reunir un segundo conjunto de columnas no funciona correctamente: combina con éxito las dos columnas Q3.2
y Q3.3
, pero termina con 90 filas en lugar de 30 (todas las combinaciones de 10 individuos, 3 bucles de Q3.2 y 3 bucles de Q3 .3; las combinaciones aumentarán sustancialmente para cada grupo de columnas en los datos reales):
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
¿Hay alguna forma de utilizar varias llamadas para gather()
darle me gusta a esto, combinando pequeños subconjuntos de columnas como este y manteniendo el número correcto de filas?
Este enfoque me parece bastante natural:
df %>%
gather(key, value, -id, -time) %>%
extract(key, c("question", "loop_number"), "(Q.\\..)\\.(.)") %>%
spread(question, value)
Primero reúna todas las columnas de preguntas, utilícelas extract()
para separarlas en question
y loop_number
y luego spread()
vuelva a colocar las preguntas en las columnas.
#> 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
Esto se podría hacer usando reshape
. Sin embargo, es posible 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
O usandodplyr
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
Actualizar
Con la nueva versión de tidyr
, podemos utilizarla pivot_longer
para remodelar varias columnas. (Usando los nombres de columna modificados de gsub
arriba)
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
NOTA: Los valores son diferentes porque no hubo una semilla establecida al crear el conjunto de datos de entrada.
Con la reciente actualización de melt.data.table
, ahora podemos fusionar varias columnas. Con eso podemos hacer:
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
Puede obtener la versión de desarrollo desde aquí .
No tiene ninguna relación con "tidyr" y "dplyr", pero aquí hay otra opción a considerar: merged.stack
de mi paquete "splitstackshape" , V1.4.0 y superior.
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.
En caso de que sea como yo y no pueda descubrir cómo usar "expresión regular con grupos de captura" extract
, el siguiente código replica la extract(...)
línea en la respuesta de 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)
El problema aquí es que la recopilación inicial forma una columna de claves que en realidad es una combinación de dos claves. Elegí usar mutate
en mi solución original en los comentarios dividir esta columna en dos columnas con información equivalente, una loop_number
columna y una question_number
columna. spread
Luego se puede utilizar para transformar los datos de formato largo, que son pares de valores clave (question_number, value)
en datos de formato amplio.