Annullamento di più tabelle all'interno di una tabella per dividere le domande del sondaggio Mutli-Select
Sto cercando di generare una soluzione programmatica per espandere le domande a "risposta multipla" dai sondaggi in colonne separate. L'impostazione prevede dati di rilevamento (df1) e un file di supporto che mette in relazione le variabili con le informazioni sulla variabile. Con i dati di esempio sotto l'obiettivo è espandere le risposte in DVar ed EVar in colonne separate, ad esempio DVar.A, DVar.b, ecc ..., con binario 1,0 se quell'ID ha selezionato la casella appropriata.
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"))
La funzione di lavoro corrente prende un ID e la colonna da diffondere. Per i miei scopi attuali, questa funzione funziona alla grande. Tranne quando una colonna ha un'assenza di NA (cosa insolita), che genera un errore su "Nessuno" non nel set di dati nell'istruzione select finale.
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)
}
Usando mutate (across) sono in grado di recuperare i dati necessari, che vengono quindi uniti al set di dati completo (o potrebbero essere nell'esempio).
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,.)))
Quando visualizzo i dati, rstudio mi dà quello che voglio!
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
⋮
Tuttavia, durante la scrittura dei dati, ricevo un errore relativo alle dimensioni non corrispondenti. Questo perché la struttura dati risultante è una tabella 8x3 con colonne che sono (Int, Tibble, Tibble). E i Tibble interni sembrano essere trasposti.
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
L'utilizzo della funzione unest produce lo stesso errore delle funzioni write_ sulle dimensioni non corrispondenti.
Ho anche tentato di utilizzare unnest_wider , ma sto riscontrando problemi con più colonne tibble poiché la funzione unest_wider accetta solo una singola colonna come argomento.
Ho provato a utilizzare pivot_wider ma non riesco a capire come passare correttamente i nomi delle colonne da getCols $ VariableName.
Ho alcuni dei miei tentativi falliti che posso aggiungere, ma sento che questa è una soluzione semplice con map, e semplicemente non ci sto provando.
Esistono soluzioni semplici per evitare più tavoli dall'interno di un piatto. Felice di ricevere qualsiasi altro feedback per creare una soluzione più ordinata ed elegante anche per il problema più grande.
Risposte
Potremmo usare 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=",")
Oppure, se volessimo utilizzare per più colonne, un'opzione è 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, .)
Utilizzando la funzione dell'OP, può essere facilmente appiattito con 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
Oppure puoi usare invoke
....
%>% invoke(data.frame, .)