Generate multiple columns of variables with `dplyr` and function in a vectorized manner

Viewed 498

Imagine for a collected questionnaire for dozens of subjects, I want to check whether the answers are correct for each question. To achieve this column-wise manipulation, I use the mapply function, where the vectorized pairs are columns(multiple variables) and vectors (multiple elements) respectively, see below the demo code for details. However, with such a method, I need to generate the variables manually to incorporate it into the original data frame, therefore, I wonder if there is an alternative method with which it can achieve a similar effect using the multivariate version of mutate function in dplyr packages? so it will not necessary to generate the names of the variables manually and realized it in a vectorized manner.

##simulated data generation with four questions
IDs<-paste0('subj',0,1:7)
Q1<-c('A','B','C','A','A','A','D')
Q2<-c('A','B','B','B','B','D','C')
Q3<-c('B','B','C','C','C','D','C')
Q4<-c('A','D','D','B','D','D','C')
vect<-data.frame(cbind(IDs,Q1,Q2,Q3,Q4))

##answer for the four questions
answer<-c('A','B','C','D')

##method with mapply function
myfunc<-function(vec,value) {
  ifelse(vec==value, TRUE, FALSE)
}

newvariable<-mapply(myfunc, vect[,2:5], answer)

colnames(newvariable)<-paste0('Q',1:4,"_ans")
newvariable
vect_new1<-cbind(vect,newvariable)
vect_new1
3 Answers

One option could be:

bind_cols(vect, vect %>%
           do(data.frame(sweep(select(., -1), 2, FUN = `==`, c("A", "B", "C", "D")))) %>%
           rename_all(~ paste0(., "_ans")))

     IDs Q1 Q2 Q3 Q4 Q1_ans Q2_ans Q3_ans Q4_ans
1 subj01  A  A  B  A   TRUE  FALSE  FALSE  FALSE
2 subj02  B  B  B  D  FALSE   TRUE  FALSE   TRUE
3 subj03  C  B  C  D  FALSE   TRUE   TRUE   TRUE
4 subj04  A  B  C  B   TRUE   TRUE   TRUE  FALSE
5 subj05  A  B  C  D   TRUE   TRUE   TRUE   TRUE
6 subj06  A  D  D  D   TRUE  FALSE  FALSE   TRUE
7 subj07  D  C  C  C  FALSE  FALSE   TRUE  FALSE

A base way using apply:

cbind(vect, `colnames<-`(t(apply(vect[-1], 1, `==`, answer)), paste0("Q", 1:4, "_Ans")))

Or using map2:

library(tidyverse)

vect %>%
  select(-IDs) %>%
  map2_dfc(answer, `==`) %>%
  set_names(~ str_c(., "_Ans")) %>%
  bind_cols(vect, .)

Or

vect %>%
  pivot_longer(Q1:Q4) %>%
  mutate(Ans = value == answer) %>%
  pivot_wider(values_from = c(value, Ans))

Output

#      IDs Q1 Q2 Q3 Q4 Q1_Ans Q2_Ans Q3_Ans Q4_Ans
# 1 subj01  A  A  B  A   TRUE  FALSE  FALSE  FALSE
# 2 subj02  B  B  B  D  FALSE   TRUE  FALSE   TRUE
# 3 subj03  C  B  C  D  FALSE   TRUE   TRUE   TRUE
# 4 subj04  A  B  C  B   TRUE   TRUE   TRUE  FALSE
# 5 subj05  A  B  C  D   TRUE   TRUE   TRUE   TRUE
# 6 subj06  A  D  D  D   TRUE  FALSE  FALSE   TRUE
# 7 subj07  D  C  C  C  FALSE  FALSE   TRUE  FALSE

Your question is related to the issue of standard evaluation. This can be rather tricky with dplyr (see a blog post I wrote yesterday).

With dplyr you will it will be rather hard because it is not very adapted to column names in string. Your problem would be more easily solved with data.table

First, convert your object to data.table

library(data.table)
data.table::setDT(vect)

Then, you just need to iterate over your answer vector (assuming they are on the good order !) and use data.table conditional replacement with :=

output <- lapply(seq_len(length(answer)), function(ans) {

  vect[,c(paste0("Q",ans,"_ans")) := FALSE]
  vect[get(paste0("Q",ans)) == answer[ans], c(paste0("Q",ans,"_ans")) := TRUE]
} )
output <- output[[length(output)]]

The final line is because your repetitively iterating through the same dataset but returning a last. Only the output of the final iteration interests you.

The result is :

output
# IDs Q1 Q2 Q3 Q4 Q1_ans Q2_ans Q3_ans Q4_ans
# 1: subj01  A  A  B  A   TRUE  FALSE  FALSE  FALSE
# 2: subj02  B  B  B  D  FALSE   TRUE  FALSE   TRUE
# 3: subj03  C  B  C  D  FALSE   TRUE   TRUE   TRUE
# 4: subj04  A  B  C  B   TRUE   TRUE   TRUE  FALSE
# 5: subj05  A  B  C  D   TRUE   TRUE   TRUE   TRUE
# 6: subj06  A  D  D  D   TRUE  FALSE  FALSE   TRUE
# 7: subj07  D  C  C  C  FALSE  FALSE   TRUE  FALSE
Related