Изменить формат фрейма данных из длинного в широкий с помощью функции слияния, сгруппированной по двум факторам

#r #merge

#r #слияние

Вопрос:

Я хочу преобразовать длинный фрейм данных в широкий фрейм данных с помощью двух группирующих переменных (соответственно amp; company) и трех числовых переменных ответа (качество, количество, смысл). Я пытался сделать это с помощью функции dcast, но она не позволяет мне группировать по двум переменным. Кто-нибудь может мне помочь?

 #Current long dataframe: two grouping variables (resp amp; company), three numerical respons variables (Quality, Amount, Sense)
resp <- c(1325851107,1325851108,1325851109,1325851107,1325851108,1325851109,1325851107,1325851108,1325851109,1325851107,1325851108,1325851109)
company <- c("Dark.nl","Dark.nl","Dark.nl","Dark.nl","Dark.nl","Dark.nl","Manual.nl","Manual.nl","Manual.nl","Dark.nl","Dark.nl","Dark.nl")
question <- c("Quality","Quality","Quality","Amount","Amount","Amount","Quality","Quality","Quality","Sense","Sense","Sense")
score <- c(4,1,2,6,8,10,5,5,7,4,6,7)
current <- data.frame(resp,company,question,score,answer); current

#Desired wide dataframe
resp2 <- c(1325851107,1325851107,1325851108,1325851108,1325851109,1325851109)
company2 <- c("Dark.nl","Manual.nl","Dark.nl","Manual.nl","Dark.nl","Manual.nl")
Quality <- c(4,5,1,5,2,7)
Amount <- c(6,NA,8,NA,10,NA)
Sense <- c(4,NA,6,NA,7,NA)
desired <- data.frame(resp2,company2,Quality,Amount,Sense); desired

#Using dcast function to reshape
library("reshape2")
dcast(current, resp   company ~ question, value.var="score")
  

Функция слияния, предоставленная Parfait, работает. Я предоставляю здесь скрипт, который сделал свое дело (спасибо, Парфе ;)).

 cols2keep <- c("resp", "company", "score")
df <- merge(current[current$question=='Quality', cols2keep], #merge two dataframes
        current[current$question=='Amount', cols2keep],
        by=c("resp", "company"), all=TRUE)

df <- merge(df,current[current$question=='Sense',       c("resp","company","score")], #merge third respons variable into new dataframe
         by=c("resp", "company"), all=TRUE)
colnames(df) <- c("resp","company","quality","amount","sense")
  

Это решение работает, но мой реальный набор данных состоит из 53 переменных respons. Поэтому этот метод довольно трудоемкий. Я попробовал итеративный подход Parfait, но получаю следующую ошибку.

 dfList <- lapply(unique(current$question), function(i){
temp <- setNames(current[current$question==i, c("resp", "company", "score")],
               c("resp", "company", paste0(i)))
})

finaldf <- Reduce(function(...) merge(..., y=c("resp", "company"), all=T), dfList)
Error in fix.by(by.x, x) : 
'by' must specify one or more columns as numbers, names or logical
  

Я относительно новичок в кодировании на R и не могу понять, что я написал неправильно. Я доволен решением, которое у меня есть сейчас, но если есть более эффективные решения, я открыт для этого.

Ответ №1:

Рассмотрим слияние отфильтрованных подмножеств:

 cols2keep <- c("resp", "company", "score", "answer")

df <- merge(current[current$question=='Quality', cols2keep],
            current[current$question=='Amount', cols2keep],
            by=c("resp", "company"), all=TRUE)

colnames(df) <- c("resp", "company", "quality", "quality_a", "amount", "amount_a")    
df

#         resp   company quality     quality_a amount amount_a
# 1 1325851107   Dark.nl       4   Didn't like      6    Maybe
# 2 1325851107 Manual.nl       5          Fine     NA     <NA>
# 3 1325851108   Dark.nl       1        Was ok      8     Fine
# 4 1325851108 Manual.nl       5 No, thank you     NA     <NA>
# 5 1325851109   Dark.nl       2          Sure     10  Not bad
# 6 1325851109 Manual.nl       7       Why not     NA     <NA>
  

Для нескольких групп, таких как Sense, продолжайте объединять с отфильтрованным подмножеством:

 df <- merge(df, 
            current[current$question=='Sense',c("resp", "company", "score", "answer")],
            by=c("resp", "company"), all=TRUE)

colnames(df) <- c("resp", "company", "quality", "quality_a", "amount", "amount_a", 
                  "sense", "sense_a")
df
#         resp   company quality     quality_a amount amount_a sense sense_a
# 1 1325851107   Dark.nl       4   Didn't like      6    Maybe     4    Nice
# 2 1325851107 Manual.nl       5          Fine     NA     <NA>    NA    <NA>
# 3 1325851108   Dark.nl       1        Was ok      8     Fine     6      Ok
# 4 1325851108 Manual.nl       5 No, thank you     NA     <NA>    NA    <NA>
# 5 1325851109   Dark.nl       2          Sure     10  Not bad     7     Yuk
# 6 1325851109 Manual.nl       7       Why not     NA     <NA>    NA    <NA>
  

Кроме того, для итеративного слияния на всех уровнях вопроса рассмотрите следующее:

 dfList <- lapply(unique(current$question), function(i){
  temp <- setNames(current[current$question==i, c("resp", "company", "score", "answer")],
                 c("resp", "company", paste0(i), paste0(i, "_a")))
})

finaldf <- Reduce(function(...) merge(..., y=c("resp", "company"), all=T), dfList)
finaldf
#         resp   company Quality     Quality_a Amount Amount_a Sense Sense_a
# 1 1325851107   Dark.nl       4   Didn't like      6    Maybe     4    Nice
# 2 1325851107 Manual.nl       5          Fine     NA     <NA>    NA    <NA>
# 3 1325851108   Dark.nl       1        Was ok      8     Fine     6      Ok
# 4 1325851108 Manual.nl       5 No, thank you     NA     <NA>    NA    <NA>
# 5 1325851109   Dark.nl       2          Sure     10  Not bad     7     Yuk
# 6 1325851109 Manual.nl       7       Why not     NA     <NA>    NA    <NA>
  

Комментарии:

1. Большое вам спасибо, Парфе. Этот скрипт прост в использовании и приводит к тому, что я имею в виду фрейм данных.

2. Приятно слышать! Рад помочь. Пожалуйста, примите для подтверждения разрешения. Счастливого кодирования!

3. теперь я испытываю некоторые трудности, когда одна из моих группирующих переменных (company) состоит из более чем двух уровней (см. Дополнительный код, который я добавил к исходному сообщению: #Группирующая переменная более двух уровней, включает «Чувства»). Я получаю эту ошибку: ошибка в fix.by (by.x, x) : ‘by’ должен указывать один или несколько столбцов в виде чисел, имен или логических. Есть идеи, что здесь пошло не так?

4. Смотрите Обновление с добавлением слияния и даже итеративного подхода на всех уровнях.

5. Итеративный подход кажется очень полезным и коротким, но он выдает мне ту же ошибку, что и у меня ранее: ошибка в fix.by (by.x, x) : ‘by’ должен указывать один или несколько столбцов в виде чисел, имен или логических.

Ответ №2:

Вариант, использующий tidyr , преемник reshape2 :

 library(tidyverse)

current %>% group_by(resp, company) %>% 
    # join answer and score into a single column to be spread to wide form
    unite(answer_score, answer, score) %>% 
    spread(question, answer_score) %>% 
    # separate joined columns
    separate(Amount, c('amount', 'amount_a'), sep = '_', convert = TRUE) %>% 
    separate(Quality, into = c('quality', 'quality_a'), sep = '_', convert = TRUE)

## Source: local data frame [6 x 6]
## Groups: resp, company [6]
## 
##         resp   company  amount amount_a       quality quality_a
## *      <dbl>    <fctr>   <chr>    <int>         <chr>     <int>
## 1 1325851107   Dark.nl   Maybe        6   Didn't like         4
## 2 1325851107 Manual.nl    <NA>       NA          Fine         5
## 3 1325851108   Dark.nl    Fine        8        Was ok         1
## 4 1325851108 Manual.nl    <NA>       NA No, thank you         5
## 5 1325851109   Dark.nl Not bad       10          Sure         2
## 6 1325851109 Manual.nl    <NA>       NA       Why not         7
  

Вместо использования unite вы могли бы использовать nest , но spread в настоящее время в столбцах списка NULL вместо s NA используется s, что требует небольшой дополнительной обработки:

 current %>% group_by(resp, company, question) %>% 
    nest() %>% 
    spread(question, data) %>% 
    # insert NAs with purrr::`%||%` so Amount will spread nicely
    mutate(Amount = map(Amount, ~.x %||% data_frame(score = NA, answer = NA))) %>% 
    unnest(.sep = '_')

## # A tibble: 6 × 6
##         resp   company Amount_score Amount_answer Quality_score Quality_answer
##        <dbl>    <fctr>        <dbl>        <fctr>         <dbl>         <fctr>
## 1 1325851107   Dark.nl            6         Maybe             4    Didn't like
## 2 1325851107 Manual.nl           NA            NA             5           Fine
## 3 1325851108   Dark.nl            8          Fine             1         Was ok
## 4 1325851108 Manual.nl           NA            NA             5  No, thank you
## 5 1325851109   Dark.nl           10       Not bad             2           Sure
## 6 1325851109 Manual.nl           NA            NA             7        Why not
  

Комментарии:

1. Спасибо за ваш ответ, Алистер, первый вариант уже сделал свое дело!

2. Просто интересуюсь, мне интересно, почему вы используете эти знаки %>%. Скрипт работает, но я не совсем уверен, почему 🙂

3. %>% это канал из пакета magrittr, который сейчас используется многими пакетами (включая tidyr и dplyr здесь), особенно теми, которые связаны с tidyverse , основным принципом которого является канал. Основная идея заключается в том, что это упрощает чтение кода, избегая вложенных вызовов функций, множества промежуточных переменных или записи через одну и ту же переменную и чтения в порядке, в котором она выполняется. Вот гораздо лучшее объяснение.

4. Спасибо за разъяснение