使用大型数据集在 R 中创建二进制矩阵

Posted

技术标签:

【中文标题】使用大型数据集在 R 中创建二进制矩阵【英文标题】:Creating Binary Matrix in R with a large dataset 【发布时间】:2016-11-13 13:04:57 【问题描述】:

我正在 Linux 机器上使用 R 版本 3.2.3。

我有一个包含 145 个变量的 1,374,439 个观测值的数据集。我需要将此数据帧转换为二进制矩阵。

我查看了不同的论坛,并找到了使用 reshape2 包和函数 melt()dcast() 的解决方案。这适用于一个小数据集(我总是首先在一个小部分上尝试我的代码,以检查它是否在做我想要的)。当我想在整个数据集上使用此代码时,它不再起作用了。

我查看了其他论坛并尝试过(没有成功),以下功能:

table() sparseMatrix()as.Matrix() xtabs()

我还发现使用包 dplyr 和 tidyr 来处理更大的数据集。但我没有成功。老实说,我也为理解而苦苦挣扎。但似乎我的数据集的大小是主要问题......

数据是这样的(这是一个版本短版):

    Code_1  Code_2  Code_3  Code_4  Code_5  Code_6  Code_7
1   M201            M2187   M670
2   O682            O097    Z370             O48    O759
3   S7211   Z966            Z501

我想要这个(二进制矩阵):

    M201    M2187   M670   O682   O097  Z370    O48   0759    S7211  Z966    Z501
1   1          1       1      0      0     0      0      0        0     0       0
2   0          0       0      1      1     1      1      1        0     0       0
3   0          0       0      0      0     0      0      0        1     1       1

我还想准确地说,空格不是 NA。真的是空格。

【问题讨论】:

可能感兴趣***.com/questions/23035982/… 【参考方案1】:

这是一个基本的 R 方法:

## define input data.frame
data <- data.frame(Code_1=c('M201','O682','S7211'),Code_2=c('','','Z966'),Code_3=c('M2187',
'O097',''),Code_4=c('M670','Z370','Z501'),Code_5=c('','',''),Code_6=c('','O48',''),Code_7=c(
'','O759',''),stringsAsFactors=F);

## coerce to a matrix to speed up subsequent operations
data <- as.matrix(data);

## solution
im <- which(arr.ind=T,data!='');
u <- unique(data[im[order(im[,'row'],im[,'col']),]]);
res <- matrix(0L,nrow(data),length(u),dimnames=list(NULL,u));
res[cbind(im[,'row'],match(data[im],u))] <- 1L;
res;
##      M201 M2187 M670 O682 O097 Z370 O48 O759 S7211 Z966 Z501
## [1,]    1     1    1    0    0    0   0    0     0    0    0
## [2,]    0     0    0    1    1    1   1    1     0    0    0
## [3,]    0     0    0    0    0    0   0    0     1    1    1

基准测试

library(microbenchmark);
library(reshape2);
library(dplyr);
library(tidyr);
library(data.table);

akrun1 <- function(df1) table(droplevels(subset(melt(as.matrix(df1)),value!='',select=-2)));
akrun2 <- function(df1) data_frame(rn=rep(1:nrow(df1),ncol(df1)),v1=unlist(df1)) %>% filter(v1!="") %>% group_by(rn,v1) %>% summarise(n=n()) %>% spread(v1,n,fill=0) %>% ungroup() %>% select(-rn);
akrun3 <- function(df1) dcast(data.table(rn=rep(1:nrow(df1),ncol(df1)),v1=unlist(df1))[v1!=''],rn~v1,length,value.var='v1')[,!'rn',with=FALSE];
bgoldst <- function(data)  data <- as.matrix(data); im <- which(arr.ind=T,data!=''); u <- unique(data[im[order(im[,'row'],im[,'col']),]]); res <- matrix(0L,nrow(data),length(u),dimnames=list(NULL,u)); res[cbind(im[,'row'],match(data[im],u))] <- 1L; res; ;

harmonize <- function(res) 
    res <- as.matrix(if ('table'%in%class(res)) unclass(res) else res);
    res <- res[,order(colnames(res))];
    res <- res[do.call(order,as.data.frame(res)),];
    res;
; ## end harmonize()

## OP's example
data <- data.frame(Code_1=c('M201','O682','S7211'),Code_2=c('','','Z966'),Code_3=c('M2187','O097',''),Code_4=c('M670','Z370','Z501'),Code_5=c('','',''),Code_6=c('','O48',''),Code_7=c('','O759',''),stringsAsFactors=F);

ex <- harmonize(akrun1(data));
all.equal(ex,harmonize(akrun2(data)),check.attributes=F);
## [1] TRUE
all.equal(ex,harmonize(akrun3(data)),check.attributes=F);
## [1] TRUE
all.equal(ex,harmonize(bgoldst(data)),check.attributes=F);
## [1] TRUE

microbenchmark(akrun1(data),akrun2(data),akrun3(data),bgoldst(data));
## Unit: microseconds
##           expr      min        lq      mean   median       uq       max neval
##   akrun1(data) 1155.945 1287.2345 1356.0013 1356.301 1396.072  1745.678   100
##   akrun2(data) 4053.292 4313.7315 4639.1197 4544.664 4763.408  6839.875   100
##   akrun3(data) 5866.965 6115.4320 6542.8618 6353.848 6601.886 11951.178   100
##  bgoldst(data)  108.197  144.1195  162.6198  162.936  180.684   240.769   100

## scale test
set.seed(1L);
NR <- 1374439L; NC <- 145L; NU <- as.integer(11/7*NC); probBlank <- 10/21;
repeat  u <- paste0(sample(LETTERS,NU,T),sprintf('%03d',sample(0:999,NU,T))); if (length(u)==NU) break; ;
data <- setNames(nm=paste0('Code_',seq_len(NC)),as.data.frame(matrix(sample(c('',u),NR*NC,T,c(probBlank,rep((1-probBlank)/NU,NU))),NR)));

ex <- harmonize(akrun1(data));
all.equal(ex,harmonize(akrun2(data)),check.attributes=F);
## Error: cannot allocate vector of size 1.5 Gb
all.equal(ex,harmonize(akrun3(data)),check.attributes=F);
## Error: cannot allocate vector of size 1.5 Gb
all.equal(ex,harmonize(bgoldst(data)),check.attributes=F);
## [1] "Mean relative difference: 1.70387"

microbenchmark(akrun1(data),bgoldst(data),times=1L);
## Unit: seconds
##           expr       min        lq      mean    median        uq       max neval
##   akrun1(data) 101.81215 101.81215 101.81215 101.81215 101.81215 101.81215     1
##  bgoldst(data)  30.82899  30.82899  30.82899  30.82899  30.82899  30.82899     1

我不知道为什么我的结果与akrun1() 不一样,但他的结果似乎不正确,因为他的结果中包含非二进制值:

unique(c(ex));
## [1] 0 1 2 3 4 5 6 7 8

【讨论】:

拥有imu,如果可以节省一些内存,稀疏替代方案是sparseMatrix(im[, "row"], match(data[im], u), x = 1L, dimnames = list(NULL, u))【参考方案2】:

您真正需要的是Matrix::sparse.model.matrix() 函数。下面的答案会创建密集矩阵,这会很快吃掉你在这个数据集上的所有内存。

这是一个简单的例子:

M = sparse.model.matrix( ~ ., data=data.frame(x = letters , y = LETTERS))

如果你不需要拦截,使用下面的公式

M = sparse.model.matrix( ~ -1 + ., data=data.frame(x = letters , y = LETTERS))

【讨论】:

感谢您的回答。请问什么代表“x”和“y”值?如果我理解正确,它将允许我在大数据集上使用它?还是我完全误解了?再次感谢您的回答。 它们只是我示例中的一些抽象列。您应该将数据框传递给sparse.model.matrix:M = sparse.model.matrix( ~ ., data=your_data_frame) 我使用你的答案。我希望最后将结果转换为数据框格式(就像我在我的问题中显示的那样)。我查看了几个选项(例如摘要,as.data.frame,...)。但结果不是我想要的。所以我现在不确定我是否正确使用了你的函数(因为输出对我来说不是自我解释的)或者我以错误的方式转换结果。 你写了“我想要这个(二进制矩阵)”。你有稀疏矩阵。将其转换为密集的 data.frame 几乎没有意义 - 你可能会耗尽内存。如果由于某种原因您仍想获取 data.frame - 使用 as.data.frame(as.matrix(M))【参考方案3】:

我们可以在melt将数据转换成long formatand remove the blank (''`) 元素后应用table

library(reshape2)
table(droplevels(subset(melt(as.matrix(df1)), value!='', select = -2)))
#    value
# Var1 M201 M2187 M670 O097 O48 O682 O759 S7211 Z370 Z501 Z966
#    1    1     1    1    0   0    0    0     0    0    0    0
#    2    0     0    0    1   1    1    1     0    1    0    0
#    3    0     0    0    0   0    0    0     1    0    1    1

或使用dplyr/tidyr

library(dplyr)
library(tidyr)
data_frame(rn = rep(1:nrow(df1), ncol(df1)), v1 = unlist(df1)) %>% 
        filter(v1!="") %>%
        group_by(rn, v1) %>% 
        summarise(n = n()) %>%
        spread(v1, n, fill = 0)
#    rn  M201 M2187  M670  O097   O48  O682  O759 S7211  Z370  Z501  Z966
#   <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#1     1     1     1     1     0     0     0     0     0     0     0     0
#2     2     0     0     0     1     1     1     1     0     1     0     0
#3     3     0     0     0     0     0     0     0     1     0     1     1

或者使用来自data.tabledcast

library(data.table)
dcast(data.table(rn = rep(1:nrow(df1), ncol(df1)), 
                   v1 = unlist(df1))[v1!=''], rn~v1, length)
#    rn M201 M2187 M670 O097 O48 O682 O759 S7211 Z370 Z501 Z966
#1:  1    1     1    1    0   0    0    0     0    0    0    0
#2:  2    0     0    0    1   1    1    1     0    1    0    0
#3:  3    0     0    0    0   0    0    0     1    0    1    1

【讨论】:

以上是关于使用大型数据集在 R 中创建二进制矩阵的主要内容,如果未能解决你的问题,请参考以下文章

基于 Distinct 结果集在 Microsoft SQL Server 中创建表

大型数据集上的 R 中的矩阵数学

Sas程序优化使用较少的工作空间

逐行在python中创建大型数据集

如何在 R 中创建聚类图?

如何在矩阵内使用for循环和数据在R中创建点图?