2014-09-19 4 views
61

У меня есть данные из онлайн-опроса, где респонденты проходят цикл вопросов 1-3 раза. Обзор программного обеспечения (Qualtrics) записывает эти данные в нескольких столбцах, то есть, В3.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 цикла из В3.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() как это, объединяя небольшие подмножества столбцов, как это при сохранении правильное количество строк?

+0

Что случилось с 'df%>% gather (loop_number, Q3.2, starts_with (" Q3. "))' – Alex

+0

Это дает мне один консолидированный столбец с 60 строками. Я думаю, что это могло бы работать, если бы я включил какой-то вызов 'seperate()', чтобы разделить значения Q3.3 (и выше) в свои собственные столбцы. Но это все еще похоже на действительно обходное хакерское решение ... – Andrew

+0

использовать 'распространение' Я сейчас работаю над решением: p – Alex

ответ

84

Такой подход кажется вполне естественным для меня:

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 
+0

Здравствуйте. У меня много столбцов с именами, оканчивающимися на 1 и 2, например age1, age2, weight1, weight2, blood1, blood2 .... Как бы я применил ваш метод здесь? – skan

+1

Что означает эта часть: "(Q. \\ ..) \\. (.)" Что я буду искать для декодирования того, что там происходит? – mob

+0

@mob Регулярные выражения – hadley

19

Это можно сделать, используя 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 
+0

Эй, это прекрасно работает. tidyr - это якобы замена/обновление для изменения - интересно, знает ли @hadley способ сделать то же самое с dplyr или tidyr ... – Andrew

+0

Это чистая магия. Единственное, что я добавил: 'mutate (loop_number = as.numeric (L2))' перед тем, как сбросить 'L2', и это прекрасно. – Andrew

+0

@Andrew Я лично предпочитаю метод 'reshape' для своего компактного кода, хотя' dplyr' может быть быстрее для больших наборов данных. – akrun

9

Это вовсе не связанной с "tidyr" и "dplyr", но вот еще один вариант, чтобы рассмотреть: merged.stack от my "splitstackshape" package, 1.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. 
+0

Привет. У меня много столбцов с именами, оканчивающимися на 1 и 2, например age1, age2, weight1, weight2, blood1, blood2 .... Как бы я применил ваш метод здесь? – skan

14

С недавним обновлением 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 

Вы можете получить версию развития от here.

+0

Ответ на запрос 'tidyr' – NiuBiBang

+0

Здравствуйте. У меня много столбцов с именами, оканчивающимися на 1 и 2, например age1, age2, weight1, weight2, blood1, blood2 .... Как бы я применил ваш метод здесь? – skan

+0

skan, проверьте [виньетка изменения] (https://github.com/Rdatatable/data.table/wiki/Getting-started). Удачи! – Arun

3

В случае, если вы похожи на меня, и не может решить, как использовать «регулярное выражение с помощью захвата групп» для 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) для широкоформатных данных.