在R中将因子矩阵转换为二进制(指示)矩阵的最有效方法是什么?

3

我可以想到几种方法来转换这种类型的矩阵(数据框):

    dat = data.frame(
    x1 = rep(c('a', 'b'), 100),
    x2 = rep(c('x', 'y'), 100)
)

head(dat)
  x1 x2
1  a  x
2  b  y
3  a  x
4  b  y
5  a  x
6  b  y

把数据转换成二元矩阵(或数据框),格式如下:
a  b  x  y
1  0  1  0
0  1  0  1
...

(这个结构当然是微不足道的,只是为了说明目的!)
非常感谢!
4个回答

4
我们可以使用 table
tbl <- table(rep(1:nrow(dat),2),unlist(dat))
head(tbl, 2)
#    a b x y
#  1 1 0 1 0
#  2 0 1 0 1

或者可能更有效的选项是

library(Matrix)
sM <- sparse.model.matrix(~ -1 + x1 +x2, dat, 
      contrasts.arg = lapply(dat, contrasts, contrasts = FALSE))
colnames(sM) <- sub(".*\\d", "", colnames(sM))
head(sM, 2)
# 2 x 4 sparse Matrix of class "dgCMatrix"
# a b x y
#1 1 . 1 .
#2 . 1 . 1

将其转换为矩阵,即可将其转换为二进制。

head(as.matrix(sM),2)  
#  a b x y
#1 1 0 1 0
#2 0 1 0 1

3

已经有一些好的解决方案发布了,但是对于性能来说都不是最优的。我们可以通过循环每个输入列,并且循环每个输入列中的每个因子级别索引,然后进行直接的整数比较来优化性能。这不是最简洁或最优雅的代码,但是它相当直接和快速。

do.call(cbind,lapply(dat,function(col)
    `colnames<-`(do.call(cbind,lapply(seq_along(levels(col)),function(i)
        as.integer(as.integer(col)==i)
    )),levels(col))
));

性能:

library(Matrix);
library(data.table);
library(microbenchmark);

bgoldst <- function(dat) do.call(cbind,lapply(dat,function(col) `colnames<-`(do.call(cbind,lapply(seq_along(levels(col)),function(i) as.integer(as.integer(col)==i))),levels(col))));
akrun1 <- function(dat) table(rep(1:nrow(dat),2),unlist(dat));
akrun2 <- function(dat) sparse.model.matrix(~-1+x1+x2,dat,contrasts.arg=lapply(dat,contrasts,contrasts=FALSE));
davidar <- function(dat) { dat[,rowid:=.I]; dcast(melt(dat,id='rowid'),rowid~value,length); }; ## requires a data.table
dataminer <- function(dat) t(apply(dat,1,function(x) as.numeric(unique(unlist(dat))%in%x)));

N <- 100L; dat <- data.frame(x1=rep(c('a','b'),N),x2=rep(c('x','y'),N)); datDT <- setDT(copy(dat));
identical(unname(bgoldst(dat)),matrix(as.vector(akrun1(dat)),ncol=4L));
## [1] TRUE
identical(unname(bgoldst(dat)),unname(matrix(as.integer(as.matrix(akrun2(dat))),ncol=4L)));
## [1] TRUE
identical(bgoldst(dat),as.matrix(davidar(datDT)[,rowid:=NULL]));
## [1] TRUE
identical(unname(bgoldst(dat)),matrix(as.integer(dataminer(dat)),ncol=4L));
## [1] TRUE

N <- 100L;
dat <- data.frame(x1=rep(c('a','b'),N),x2=rep(c('x','y'),N)); datDT <- setDT(copy(dat));
microbenchmark(bgoldst(dat),akrun1(dat),akrun2(dat),davidar(datDT),dataminer(dat));
## Unit: microseconds
##            expr       min        lq       mean     median         uq       max neval
##    bgoldst(dat)    67.570    92.374   106.2853    99.6440   121.2405   188.596   100
##     akrun1(dat)   581.182   652.386   773.6300   690.6605   916.4625  1192.299   100
##     akrun2(dat)  4429.208  4836.119  5554.5902  5145.3135  5977.0990 11263.537   100
##  davidar(datDT)  5064.273  5498.555  6104.7621  5664.9115  6203.9695 11713.856   100
##  dataminer(dat) 47577.729 49529.753 55217.3726 53190.8940 60041.9020 74346.268   100

N <- 1e4L;
dat <- data.frame(x1=rep(c('a','b'),N),x2=rep(c('x','y'),N)); datDT <- setDT(copy(dat));
microbenchmark(bgoldst(dat),akrun1(dat),akrun2(dat),davidar(datDT));
## Unit: milliseconds
##            expr       min        lq      mean   median        uq        max neval
##    bgoldst(dat)  1.775617  1.820949  2.299493  1.84725  1.972124   8.362336   100
##     akrun1(dat) 38.954524 41.109257 48.409613 45.60304 52.147633 162.365472   100
##     akrun2(dat) 16.915832 17.762799 21.288200 19.20164 23.775180  46.494055   100
##  davidar(datDT) 36.151684 38.366715 42.875940 42.38794 45.916937  58.695008   100

N <- 1e5L;
dat <- data.frame(x1=rep(c('a','b'),N),x2=rep(c('x','y'),N)); datDT <- setDT(copy(dat));
microbenchmark(bgoldst(dat),akrun1(dat),akrun2(dat),davidar(datDT));
## Unit: milliseconds
##            expr       min        lq      mean    median        uq      max neval
##    bgoldst(dat)  17.16473  22.97654  35.01815  26.76662  31.75562 152.6188   100
##     akrun1(dat) 501.72644 626.14494 671.98315 680.91152 727.88262 828.8313   100
##     akrun2(dat) 212.12381 242.65505 298.90254 272.28203 357.65106 429.6023   100
##  davidar(datDT) 368.04924 461.60078 500.99431 511.54921 540.39358 638.3840   100

2
如果您有一个像您展示的data.frame(而不是矩阵)一样的数据,您也可以重新整理数据。
library(data.table)
setDT(dat)[, rowid := .I] # Creates a row index
res <- dcast(melt(dat, id = "rowid"), rowid ~ value, length) # long/wide format
head(res)
#   rowid a b x y
# 1     1 1 0 1 0
# 2     2 0 1 0 1
# 3     3 1 0 1 0
# 4     4 0 1 0 1
# 5     5 1 0 1 0
# 6     6 0 1 0 1

一些基准测试


dat = data.frame(
  x1 = rep(c('a', 'b'), 1e3),
  x2 = rep(c('x', 'y'), 1e3)
)

library(data.table)
library(Matrix)
library(microbenchmark)

dat2 <- copy(dat)


microbenchmark("akrun1 : " = table(rep(1:nrow(dat),2),unlist(dat)),
               "akrun2 : " = sparse.model.matrix(~ -1 + x1 +x2, dat, contrasts.arg = lapply(dat, contrasts, contrasts = FALSE)),
               "DatamineR : " = t(apply(dat,1, function(x) as.numeric(unique(unlist(dat)) %in% x))),
               "David Ar : " = {setDT(dat2)[, rowid := .I] ; dcast(melt(dat2, id = "rowid"), rowid ~ value, length)},
               times = 10L)
# Unit: milliseconds
#          expr         min          lq        mean      median         uq        max neval cld
#     akrun1 :     3.826075    4.061904    6.654399    5.165376   11.26959   11.82029    10  a 
#     akrun2 :     5.269531    5.713672    8.794434    5.943422   13.34118   20.01961    10  a 
#  DatamineR :  3199.336286 3343.774160 3410.618547 3385.756972 3517.22133 3625.70909    10   b
#   David Ar :     8.092769    8.254682   11.030785    8.465232   15.44893   19.83914    10  a 
< p > apply解决方案效率极低,在更大的数据集上会花费很长时间。在排除apply解决方案的情况下比较更大的数据集。

dat = data.frame(
  x1 = rep(c('a', 'b'), 1e4),
  x2 = rep(c('x', 'y'), 1e4)
)

dat2 <- copy(dat)

microbenchmark("akrun1 : " = table(rep(1:nrow(dat),2),unlist(dat)),
               "akrun2 : " = sparse.model.matrix(~ -1 + x1 +x2, dat, contrasts.arg = lapply(dat, contrasts, contrasts = FALSE)),
               #"DatamineR : " = t(apply(dat,1, function(x) as.numeric(unique(unlist(dat)) %in% x))),
               "David Ar : " = {setDT(dat2)[, rowid := .I] ; dcast(melt(dat2, id = "rowid"), rowid ~ value, length)},
               times = 100L)
# Unit: milliseconds
#        expr      min       lq     mean   median       uq      max neval cld
#   akrun1 :  38.66744 41.27116 52.97982 42.72534 47.17203 161.0420   100   b
#   akrun2 :  17.02006 18.93534 27.27582 19.35580 20.72022 153.2397   100  a 
# David Ar :  34.15915 37.91659 46.11050 38.58536 41.40412 149.0038   100   b

看起来 Matrix 包在处理更大的数据集时表现出色。

当存在更多列/唯一值的情况下,比较不同的场景可能是值得的。


1
一种使用apply的替代方法。
head(t(apply(dat,1, function(x) as.numeric(unique(unlist(dat)) %in% x))))
     [,1] [,2] [,3] [,4]
[1,]    1    0    1    0
[2,]    0    1    0    1
[3,]    1    0    1    0
[4,]    0    1    0    1
[5,]    1    0    1    0
[6,]    0    1    0    1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接