Разделение нескольких таблиц внутри таблицы для разделения вопросов опроса Mutli-Select

Aug 21 2020

Я пытаюсь создать программное решение для развертывания вопросов «Множественный ответ» из опросов в отдельные столбцы. Настройка включает данные опроса (df1) и вспомогательный файл, который связывает переменные с информацией о переменной. В приведенном ниже примере данных цель состоит в том, чтобы развернуть ответы в DVar и EVar в отдельные столбцы, например, DVar.A, DVar.b и т. Д., С двоичным значением 1,0, отмечен ли этот идентификатор в соответствующем поле.

df1 <- tibble(ID = rep(1:8), AVar = sample(1:10, 8), BVar = rnorm(8), 
              CVar = c("Got", "Some", "Stuff", "In", "Here", "Got", "Others", "Too"),
              DVar = c("A,B", NA , "C", "A,C", "B,D", "C", "D", "B,D"), 
              EVar = c("Banana,Apple", "Orange,Raspberry", "Apple", NA, "Orange", "Banana", "Banana", "Raspberry"))

Helper <- tibble(VariableName = c("ID", "AVar", "BVar", "CVar", "DVar", "EVar"), 
                 QuestionType = c("ID", "Numeric", "Numeric", "Single Response", "Multiple Response", "Multiple Response"))

Текущая рабочая функция принимает идентификатор и столбец для распространения. Для моих текущих целей эта функция отлично работает. За исключением случаев, когда в столбце отсутствуют NAs (что необычно), что вызывает ошибку «None» не в наборе данных в последнем операторе select.


MultiToCol <- function(ID, toSpread) {
  X <- tibble(ID, toSpread)
  
  X %>% mutate(varLong = strsplit(as.character(replace_na(toSpread, "None")),split=",")) %>% 
    unnest(varLong) %>% mutate(tmpValue = 1) %>% spread(varLong, tmpValue, fill = 0) %>% select(-None, -ID, -toSpread, None)
  
}

Используя mutate (через), я могу вернуть необходимые данные, которые затем присоединяются к полному набору данных (или может быть в примере).

getCols <- Helper %>% filter(QuestionType == "Multiple Response") %>% select(VariableName)

spreadCols <- df1 %>% select_if(names(.) %in% c('ID', getCols$VariableName)) %>% 
  mutate(across(.cols = !ID, .fns = ~MultiToCol1(ID,.))) 

Когда я просматриваю данные, rstudio возвращает мне то, что я хочу!

ID  DVar.A  DVar.B  DVar.C  DVar.D  DVar.None   EVar.Apple  EVar.Banana EVar.Orange EVar.Raspberry  Evar.None
1   1   1   0   0   0   1   1   0   0   0
2   0   0   0   0   1   0   0   1   1   0
3   0   0   1   0   0   1   0   0   0   0
⋮

Однако при записи данных я получаю ошибку о несовпадении размеров. Это связано с тем, что результирующая структура данных представляет собой тиббл 8x3 со столбцами (Int, Tibble, Tibble). И внутренние тибблы вроде бы переставлены.

tibble [8 x 3] (S3: tbl_df/tbl/data.frame)
 $ ID  : int [1:8] 1 2 3 4 5 6 7 8
 $ DVar: tibble [8 x 5] (S3: tbl_df/tbl/data.frame) ..$ A   : num [1:8] 1 0 0 1 0 0 0 0
  ..$ B : num [1:8] 1 0 0 0 1 0 0 1 ..$ C   : num [1:8] 0 0 1 1 0 1 0 0
  ..$ D : num [1:8] 0 0 0 0 1 0 1 1 ..$ None: num [1:8] 0 1 0 0 0 0 0 0
 $ EVar: tibble [8 x 5] (S3: tbl_df/tbl/data.frame) ..$ Apple    : num [1:8] 1 0 1 0 0 0 0 0
  ..$ Banana : num [1:8] 1 0 0 0 0 1 1 0 ..$ Orange   : num [1:8] 0 1 0 0 1 0 0 0
  ..$ Raspberry: num [1:8] 0 1 0 0 0 0 0 1 ..$ None     : num [1:8] 0 0 0 1 0 0 0 0

Использование функции unnest приводит к той же ошибке, что и функции write_ о несовпадении размеров.

Я также попытался использовать unnest_wider , но у меня возникли проблемы с несколькими столбцами tibble, поскольку функция unnest_wider принимает только один столбец в качестве аргумента.

Я пытался использовать pivot_wider, но не могу понять, как правильно передать ему имена столбцов из getCols $ VariableName.

У меня есть несколько неудачных попыток, которые я могу добавить, но я чувствую, что это простое решение с картой, и я просто не нахожу его.

Есть ли какие-нибудь простые решения, чтобы развязать несколько тиблей изнутри? Мы рады услышать любые другие отзывы, чтобы создать более аккуратное и элегантное решение для более крупной проблемы.

Ответы

1 akrun Aug 21 2020 at 02:27

Мы могли бы использовать cSplit_e

library(splitstackshape)
library(dplyr)
df1 %>% 
    select_if(names(.) %in% c('ID', getCols$VariableName)) %>%
    cSplit_e("DVar", type = "character", fill = 0, sep=",") %>% 
    cSplit_e("EVar", type = "character", fill = 0, sep=",")

Или, если мы хотим использовать для нескольких столбцов, вариант map

library(purrr)
tmp <- df1 %>%  
           select_if(names(.) %in% c('ID', getCols$VariableName))
map_dfc(setdiff(names(tmp), "ID"), ~
     tmp %>%
      select(.x) %>% 
      cSplit_e( .x, type = "character", fill = 0, sep=",") %>% 
      select(-.x)) %>% 
 bind_cols(tmp, .)

Используя функцию OP, его можно легко сгладить с помощью as.data.frame

out <- df1 %>%
    select_if(names(.) %in% c('ID', getCols$VariableName)) %>% mutate(across(.cols = !ID, .fns = ~MultiToCol(ID,.))) %>% do.call(data.frame, .) out ID DVar.A DVar.B DVar.C DVar.D DVar.None EVar.Apple EVar.Banana EVar.Orange EVar.Raspberry EVar.None 1 1 1 1 0 0 0 1 1 0 0 0 2 2 0 0 0 0 1 0 0 1 1 0 3 3 0 0 1 0 0 1 0 0 0 0 4 4 1 0 1 0 0 0 0 0 0 1 5 5 0 1 0 1 0 0 0 1 0 0 6 6 0 0 1 0 0 0 1 0 0 0 7 7 0 0 0 1 0 0 1 0 0 0 8 8 0 1 0 1 0 0 0 0 1 0 str(out) #'data.frame': 8 obs. of 11 variables: # $ ID            : int  1 2 3 4 5 6 7 8
# $ DVar.A : num 1 0 0 1 0 0 0 0 # $ DVar.B        : num  1 0 0 0 1 0 0 1
# $ DVar.C : num 0 0 1 1 0 1 0 0 # $ DVar.D        : num  0 0 0 0 1 0 1 1
# $ DVar.None : num 0 1 0 0 0 0 0 0 # $ EVar.Apple    : num  1 0 1 0 0 0 0 0
# $ EVar.Banana : num 1 0 0 0 0 1 1 0 # $ EVar.Orange   : num  0 1 0 0 1 0 0 0
# $ EVar.Raspberry: num 0 1 0 0 0 0 0 1 # $ EVar.None     : num  0 0 0 1 0 0 0 0

Или можно использовать invoke

 ....
   %>% invoke(data.frame, .)