在R中查找矩阵的子集

时间:2016-05-19 17:05:32

标签: r

所以我在R(可能是数据框),mat1mat2中有两个矩阵,如下所示:

> mat1
    A  B  C
1  A1 B1 C1
2  A2 B1 C1
3  A1 B2 C1
4  A2 B2 C1
5  A1 B1 C2
6  A2 B1 C2
7  A1 B2 C2
8  A2 B2 C2
9  A1 B1 C3
10 A2 B1 C3
11 A1 B2 C3
12 A2 B2 C3

> mat2
   B  C
1 B1 C1
2 B2 C1
3 B1 C2
4 B2 C2
5 B1 C3
6 B2 C3

我想要做的是弄清楚mat2的哪些行是mat1行的子集。例如,mat2的第1行是B1 C1,它出现在mat1的第1行和第2行中,因此输出为1,1。现在,我需要遍历所有12行的mat1等等,使用上面的矩阵作为我的例子,我想要的输出是一个读取1,1,2,2,3,3,4,4,5,5,6的矢量6。

我能够以循环的方式实现这一目标,但这对我来说并不太有效。我想知道是否有1)内置命令(可能是子集?)和2)如果有更多"高效"或替代方式来做到这一点。

这是我的代码:

mat1 = expand.grid(list(A=c("A1","A2"),B=c("B1","B2"),C=c("C1","C2","C3")),stringsAsFactors=FALSE)
mat2 = expand.grid(list(B=c("B1","B2"),C=c("C1","C2","C3")),stringsAsFactors=FALSE)

N1 = nrow(mat1)
N2 = nrow(mat2)

idx = rep(NA,N1)
for(j in 1:N1){
    for(k in 1:N2){
        if(sum(mat2[k,]%in%mat1[j,])==length(mat2[k,])){
            idx[j] = k
        }
    }   
}


> idx
 [1] 1 1 2 2 3 3 4 4 5 5 6 6

3 个答案:

答案 0 :(得分:3)

解决方案1 ​​

我认为这是一个非常酷的解决方案,虽然它肯定会以速度换掉内存,因此对于非常大的输入可能会出现问题。它调用rep()重复每个data.frame的行,一个使用(默认)times参数,一个使用each参数,因此我们可以获得一个巨大的矢量化比较两个data.frames:

r <- rep(seq_len(nrow(mat2)),nrow(mat1));
r[rowSums(mat2[r,]==mat1[rep(seq_len(nrow(mat1)),each=nrow(mat2)),names(mat2)])==ncol(mat2)];
## [1] 1 1 2 2 3 3 4 4 5 5 6 6

解决方案2

我的第一直觉是使用lapply()rep() sum(),但可能会有更有效的解决方案:

unlist(lapply(seq_len(nrow(mat2)),function(ri) rep(ri,sum(mat2[ri,1L]==mat1[,2L] & mat2[ri,2L]==mat1[,3L]))))
## [1] 1 1 2 2 3 3 4 4 5 5 6 6

以上是对上述内容的概括。现在它应该能够处理任意数量的列,并将mat2中的列名称与mat1匹配:

unlist(lapply(seq_len(nrow(mat2)),function(ri) rep(ri,sum(Reduce(`&`,lapply(names(mat2),function(n) mat2[ri,n]==mat1[,n]))))))
## [1] 1 1 2 2 3 3 4 4 5 5 6 6

基准

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

bgoldst1 <- function(mat1,mat2) { r <- rep(seq_len(nrow(mat2)),nrow(mat1)); r[rowSums(mat2[r,]==mat1[rep(seq_len(nrow(mat1)),each=nrow(mat2)),names(mat2)])==ncol(mat2)]; };
bgoldst2 <- function(mat1,mat2) unlist(lapply(seq_len(nrow(mat2)),function(ri) rep(ri,sum(Reduce(`&`,lapply(names(mat2),function(n) mat2[ri,n]==mat1[,n]))))));
mtoto <- function(mat1,mat2) mat1[mat2[,id := 1 : .N], on = setdiff(names(mat2),'id')]$id; ## setdiff() only necessary to allow reusing the same data.tables
bonhomme <- function(mat1,mat2) match(apply(mat1[, names(mat2)], 1, paste0, collapse=""),apply(mat2, 1, paste0, collapse=""));
## OP's example data
mat1 = expand.grid(list(A=c("A1","A2"),B=c("B1","B2"),C=c("C1","C2","C3")),stringsAsFactors=FALSE);
mat2 = expand.grid(list(B=c("B1","B2"),C=c("C1","C2","C3")),stringsAsFactors=FALSE);
mat1.dt <- as.data.table(mat1);
mat2.dt <- as.data.table(mat2);

ex <- bgoldst1(mat1,mat2);
identical(ex,bgoldst2(mat1,mat2));
## [1] TRUE
identical(ex,mtoto(mat1.dt,mat2.dt));
## [1] TRUE
identical(ex,bonhomme(mat1,mat2));
## [1] TRUE

microbenchmark(bgoldst1(mat1,mat2),bgoldst2(mat1,mat2),mtoto(mat1.dt,mat2.dt),bonhomme(mat1,mat2));
## Unit: microseconds
##                     expr     min       lq      mean    median        uq      max neval
##     bgoldst1(mat1, mat2) 262.579 295.9365  320.3254  308.5525  335.7080  452.884   100
##     bgoldst2(mat1, mat2) 292.515 334.6390  358.5704  353.4555  368.2095  542.692   100
##  mtoto(mat1.dt, mat2.dt) 936.133 968.4200 1053.2665 1004.1290 1067.2080 2204.978   100
##     bonhomme(mat1, mat2) 169.779 204.8465  229.4792  218.7450  245.4735  550.390   100
set.seed(2L);
NC <- 10L; N1 <- 1e4L; N2 <- 1e2L;
ns <- make.unique(rep(LETTERS,len=NC));
mat1 <- setNames(nm=ns,as.data.frame(stringsAsFactors=F,lapply(ns,function(n) paste0(n,sample(1:2,N1,T)))));
repeat { mat2 <- setNames(nm=ns,as.data.frame(stringsAsFactors=F,lapply(ns,function(n) paste0(n,sample(1:2,N2,T))))); if (!anyDuplicated(mat2)) break; }; ## ensure we get unique mat2 because bonhomme's solution depends on it
mat1.dt <- as.data.table(mat1);
mat2.dt <- as.data.table(mat2);

ex <- sort(bgoldst1(mat1,mat2));
identical(ex,sort(bgoldst2(mat1,mat2)));
## [1] TRUE
identical(ex,sort(mtoto(mat1.dt,mat2.dt)));
## [1] TRUE
identical(ex,sort(na.omit(bonhomme(mat1,mat2))));
## [1] TRUE

microbenchmark(bgoldst1(mat1,mat2),bgoldst2(mat1,mat2),mtoto(mat1.dt,mat2.dt),bonhomme(mat1,mat2));
## Unit: milliseconds
##                     expr        min          lq        mean      median          uq        max neval
##     bgoldst1(mat1, mat2) 2911.58409 3124.520068 3327.642060 3343.112398 3482.843856 4032.33696   100
##     bgoldst2(mat1, mat2)  512.21419  526.099003  580.715562  567.748314  586.783137  805.94574   100
##  mtoto(mat1.dt, mat2.dt)    2.75109    3.124645    3.411087    3.271116    3.407965   12.83215   100
##     bonhomme(mat1, mat2)   37.60436   39.928438   54.498556   41.010399   42.958784  213.24218   100

结论:我的解决方案太糟糕了。

答案 1 :(得分:3)

我会用它,但可能有更优雅的方式:

match(apply(mat1[, c("B", "C")], 1, paste0, collapse=""),
      apply(mat2[, c("B", "C")], 1, paste0, collapse=""))

[1] 1 1 2 2 3 3 4 4 5 5 6 6

答案 2 :(得分:0)

以下解决方案不使用列名称。它应该适用于所有情况。它需要矩阵,而不是数据帧。

mat1 = as.matrix(expand.grid(list(A=c("A1","A2"),B=c("B1","B2"),C=c("C1","C2","C3")),stringsAsFactors=FALSE))
mat2 = as.matrix(expand.grid(list(B=c("B1","B2"),C=c("C1","C2","C3")),stringsAsFactors=FALSE))
match.rows = outer(seq(nrow(mat2)), seq(nrow(mat1)), Vectorize(function(x,y)length(setdiff(mat2[x,],mat1[y,]))==0))
apply(match.rows,2,function(x)ifelse(any(x),which(x)[1],0))