Mutate multiple logical columns with dplyr in a way that unites them in a character column without summarizing

Viewed 123

I am a beginner R user. I've noted the many similar questions on this subject but they don't quite address what I'm trying to do (leave output unsummarized) - but if I've missed it, please send me on to them.

I have a dataset of that can be represented by this short mockup (columns of respondents and whether or not certain facts about them are true or false):

df <- tribble(
  ~"Respondent", ~Fact1, ~Fact2, ~Fact3,
  #------------------------------------
  "A", TRUE, FALSE, TRUE,
  "B", FALSE, TRUE, FALSE,
  "C", TRUE, TRUE, TRUE,
  "D", FALSE, FALSE, TRUE,
)

# A tibble: 4 x 4
#  Respondent Fact1 Fact2 Fact3
#  <chr>      <lgl> <lgl> <lgl>
#1 A          TRUE  FALSE TRUE 
#2 B          FALSE TRUE  FALSE
#3 C          TRUE  TRUE  TRUE 
#4 D          FALSE FALSE TRUE 

I want a new column which will contain all and only what is "true" about each respondent in character form for me to then generate a table summary.

Through mutate and if_else I can put together all the "true" facts for each respondent in its own character column and then using unite I put them all into a single column before de-selecting the true/false columns, like this:

mdf <- df %>% 
  mutate("Profile1" = if_else(Fact1 == TRUE, 
                           true = "Factoid1", 
                           false = NULL)) %>%
  mutate("Profile2" = if_else(Fact2 == TRUE, 
                              true = "Factoid2", 
                              false = NULL)) %>%
  mutate("Profile3" = if_else(Fact3 == TRUE, 
                              true = "Factoid3", 
                              false = NULL)) %>%
  unite("Profile", "Profile1":"Profile3", 
        sep = ", ", na.rm = TRUE) %>%
  select(-c(Fact1:Fact3))

# A tibble: 4 x 2
#  Respondent Profile                     
#  <chr>      <chr>                       
#1 A          Factoid1, Factoid3          
#2 B          Factoid2                    
#3 C          Factoid1, Factoid2, Factoid3
#4 D          Factoid3 

My question: My actual dataset is larger and I was wondering if there was a quicker, smarter way to achieve this than how I am doing it?

Thank you for the help or direction towards the answers!

2 Answers

You can do this with rowwise :

library(dplyr)
cols <- grep('Fact', names(df), value = TRUE)

df %>%
  rowwise() %>%
  transmute(Respondent, 
            Profile = toString(cols[c_across(starts_with('Fact'))]))

# Respondent Profile            
#  <chr>      <chr>              
#1 A          Fact1, Fact3       
#2 B          Fact2              
#3 C          Fact1, Fact2, Fact3
#4 D          Fact3              

Or in base R :

df$Profile <- apply(df[cols], 1, function(x) toString(cols[x]))

We can reshape to 'long' format with pivot_longer and then do a group by summarise

library(dplyr)
library(tidyr)
df %>% 
    pivot_longer(cols = where(is.logical)) %>% 
    group_by(Respondent) %>%
    summarise(Profile = toString(name[value]), .groups = 'drop')

-output

# A tibble: 4 x 2
#  Respondent Profile            
#* <chr>      <chr>              
#1 A          Fact1, Fact3       
#2 B          Fact2              
#3 C          Fact1, Fact2, Fact3
#4 D          Fact3          

Or another option with pmap

library(purrr)
df %>% 
    transmute(Respondent, Profile = pmap_chr(select(., where(is.logical)), 
           ~ toString(names(which(c(...))))))
# A tibble: 4 x 2
#  Respondent Profile            
#  <chr>      <chr>              
#1 A          Fact1, Fact3       
#2 B          Fact2              
#3 C          Fact1, Fact2, Fact3
#4 D          Fact3     
Related