简体   繁体   中英

Split character column into several binary (0/1) columns

I have a character vector like this:

a <- c("a,b,c", "a,b", "a,b,c,d")

What I would like to do is create a data frame where the individual letters in each string are represented by dummy columns:

   a    b    c    d
1] 1    1    1    0
2] 1    1    0    0
3] 1    1    1    1

I have a feeling that I need to be using some combination of read.table and reshape but am really struggling. Any and help appreciated.

You can try cSplit_e from my "splitstackshape" package:

library(splitstackshape)
a <- c("a,b,c", "a,b", "a,b,c,d")
cSplit_e(as.data.table(a), "a", ",", type = "character", fill = 0)
#          a a_a a_b a_c a_d
# 1:   a,b,c   1   1   1   0
# 2:     a,b   1   1   0   0
# 3: a,b,c,d   1   1   1   1
cSplit_e(as.data.table(a), "a", ",", type = "character", fill = 0, drop = TRUE)
#    a_a a_b a_c a_d
# 1:   1   1   1   0
# 2:   1   1   0   0
# 3:   1   1   1   1

There's also mtabulate from "qdapTools":

library(qdapTools)
mtabulate(strsplit(a, ","))
#   a b c d
# 1 1 1 1 0
# 2 1 1 0 0
# 3 1 1 1 1

A very direct base R approach is to use table along with stack and strsplit :

table(rev(stack(setNames(strsplit(a, ",", TRUE), seq_along(a)))))
#    values
# ind a b c d
#   1 1 1 1 0
#   2 1 1 0 0
#   3 1 1 1 1

Another convoluted base-R solution:

x  <- strsplit(a,",")
xl <- unique(unlist(x))

t(sapply(x,function(z)table(factor(z,levels=xl))))

which gives

     a b c d
[1,] 1 1 1 0
[2,] 1 1 0 0
[3,] 1 1 1 1

A base R - but longer solution:

el = unique(unlist(strsplit(a, ',')))
do.call(rbind, lapply(a, function(u) setNames(el %in% strsplit(u,',')[[1]]+0L, el))
#     a b c d
#[1,] 1 1 1 0
#[2,] 1 1 0 0
#[3,] 1 1 1 1

Another option is tstrsplit() from :

library(data.table)
vapply(tstrsplit(a, ",", fixed = TRUE, fill = 0), ">", integer(length(a)), 0L)
#      [,1] [,2] [,3] [,4]
# [1,]    1    1    1    0
# [2,]    1    1    0    0
# [3,]    1    1    1    1

After I wrote this I noticed that Colonel Beauvel's solution is quite similar but perhaps this is sufficiently distinct to be a separate solution. No packages are used.

First we split the character strings into a list of vectors, L and then we compute the union of them all, u . Finally we determine a binary vector for each list element and rbind them together, convert the result from logical to numeric using + 0 and set the column names.

L <- strsplit(a, ",")
u <- Reduce(union, L)
m <- do.call(rbind, lapply(L, `%in%`, x = u)) + 0
colnames(m) <- u

giving:

> m
     a b c d
[1,] 1 1 1 0
[2,] 1 1 0 0
[3,] 1 1 1 1

Added The last two lines of code could be replaced by either of these:

do.call(rbind, lapply(lapply(L, factor, levels = u), table))

do.call(rbind, Map(function(x) sapply(u, `%in%`, x), L)) + 0

Unfortunately, base R does not offer a vectorized string matching function but the stringi package does.

library(stringi)
a=c("a,b,c", "a,b", "a,b,c,d")
1*outer(a,unique(unlist(strsplit(a,","))),stri_detect_regex)

#     [,1] [,2] [,3] [,4]
#[1,]    1    1    1    0
#[2,]    1    1    0    0
#[3,]    1    1    1    1

I've had a lot of success with dummy_cols within fastDummies which can take care of this fairly simply and can be specified by variable.

library(fastDummies)

a <- c("a,b,c", "a,b", "a,b,c,d")
a <- dummy_cols(a, split = ",")

outputs

#    .data .data_a .data_b .data_c .data_d
# 1   a,b,c       1       1       1       0
# 2     a,b       1       1       0       0
# 3 a,b,c,d       1       1       1       1

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM