Recoding a semicolon separated list in R

With a data.table solution, you can use tstrsplit:

library(data.table)

df <- data.table::data.table(
  color = c("red;blue", "red;green")
)

df[, c("col1","col2") := tstrsplit(color, ";", fixed = TRUE)] 
df[, "green" := (col2 == "green")]

df

#       color col1  col2 green
# 1:  red;blue  red  blue FALSE
# 2: red;green  red green  TRUE

If you are not familiar with data.table update-by-reference operator :=, data.table vignettes are a good place to start. The option fixed = TRUE in tstrsplit assumes that you always have the same number of elements in your comma separated list.

There is a solution that, I think, is more adapted to a situation where you have more than a few values. Using repetitively lapply, you can add a series of columns to your data.table

Starting back with df:

df <- data.table::data.table(
  color = c("red;blue", "red;green")
)

Calling lapply with grepl to scan for the relevent color, we update by reference our object (note that you could use more than three colors):

lapply(c("red","green","blue"), function(x){
  df[grepl(x, color), c(as.character(x)) := TRUE]
})
#[[1]]

#[[2]]
#       color  red green blue
#1:  red;blue TRUE    NA TRUE
#2: red;green TRUE  TRUE   NA

#[[3]]
#       color  red green blue
#1:  red;blue TRUE    NA TRUE
#2: red;green TRUE  TRUE   NA

There is no need to re-assign the dataframe. It has been updated by reference. Only the last slot of df interests us. Finally, by selecting this one and setting NAs to FALSE:

df <- df[[length(df)]]
df[is.na(df)] <- FALSE

df
#       color  red green  blue
# 1:  red;blue TRUE FALSE  TRUE
# 2: red;green TRUE  TRUE FALSE

Hope it helps


We can use str_detect

library(dplyr)
library(stringr)
df %>% 
      mutate(green = +(str_detect(colors, 'green')))

Update

If we wanted new columns

library(qdapTools)
cbind(df, mtabulate(strsplit(df$colors, ";")))
#     colors blue green red
#1  red;blue    1     0   1
#2 red;green    0     1   1

Or using base R

cbind(df, as.data.frame.matrix(table(stack(setNames(strsplit(df$colors, ";"), 
                 seq_along(df$colors)))[2:1])))

In the OP's, code, the strsplit list first element ([[1]]) is selected instead of looping over the list, resulting in recycling of the element and getting FALSE as there is no 'green' in the first list element

library(purrr)
df %>%
   mutate(green = map_int(strsplit(colors, ";"), 
               ~ case_when('green' %in% .x ~ 1L, TRUE ~ 0L)))
#     colors green
#1  red;blue     0
#2 red;green     1

Data

colors <- c("red;blue", "red;green")
df <- data.frame(colors, stringsAsFactors = FALSE)

Code

cbind.data.frame(colors,
                 sapply( unique(unlist(strsplit( unlist(df), ";", fixed = TRUE))), 
                         function(x) as.integer(grepl(x, colors))))

Output

#      colors red blue green
# 1  red;blue   1    1     0
# 2 red;green   1    0     1

Using %in% and no regular expression on a different dataset with similar items: green and greenish

colors <- c("red;blue;greenish", "red;green")
df <- data.frame(colors, stringsAsFactors = FALSE)

myfun <- function(x) { unique(unlist(strsplit( unlist(x), ";", fixed = TRUE))) }
df2 <- t(sapply( df$colors, function(x) { as.integer(myfun(df) %in% myfun(x))}))
colnames(df2) <- myfun(df)
df2
#                   red blue greenish green
# red;blue;greenish   1    1        1     0
# red;green           1    0        0     1

Tags:

R