Turning vectors of strings in a dataframe into categorical variables in R

Viewed 97

I'm fairly new to R and am sure there's a way to do the following without using loops, which I'm more familiar with.

Take the following example where you have a bunch of names and fruits each person likes:

name <- c("Alice", "Bob")
preference <- list(c("apple", "pear"), c("banana", "apple"))
df <- as.data.frame(cbind(name, preference))

How to I convert it to the following?

apple <- c(1, 1)
pear <- c(1, 0)
banana <- c(0, 1)
df2 <- data.frame(name, apple, pear, banana)

My basic instinct is to first extract all the fruits then do a loop to check if each fruit is in each row's preference:

fruits <- unique(unlist(df$preference))
for (fruit in fruits) {
    df <- df %>% rowwise %>% mutate("{fruit}" := fruit %in% preference)
}

This seems to work, but I'm pretty sure there's a better way to do this.

3 Answers
df %>%
  unnest(everything()) %>%
  xtabs(~., .) %>%
  as.data.frame.matrix() %>%
  rownames_to_column('name')

   name apple banana pear
1 Alice     1      0    1
2   Bob     1      1    0

In tidyverse (assuming the 'preference' is a list column), unnest the 'preference' and then use pivot_wider to reshape back to 'wide' format with values_fn as length

library(dplyr)
library(tidyr)
df %>% 
   unnest_longer(preference) %>%
    pivot_wider(names_from = preference, values_from = preference, 
      values_fn = length, values_fill = 0)

-output

# A tibble: 2 × 4
  name  apple  pear banana
  <chr> <int> <int>  <int>
1 Alice     1     1      0
2 Bob       1     0      1

data

df <- data.frame(name, preference = I(preference))

Another possible solution, based on tidyr::separate_rows and janitor::tabyl:

library(tidyverse)

df %>% 
  separate_rows(everything(), sep="(?<=\\w), (?=\\w)") %>% 
  janitor::tabyl(name, preference)

#>   name apple banana pear
#>  Alice     1      0    1
#>    Bob     1      1    0
Related