#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. Спасибо за разъяснение