【问题标题】:Faster way to unlist a list of large matrices?取消列出大型矩阵列表的更快方法?
【发布时间】:2016-03-14 16:46:28
【问题描述】:

我有一个大型矩阵列表。所有这些矩阵都有相同的行数,我想“取消列出”它们并将它们的所有列绑定在一起。下面是我写的一段代码,但我不确定这是否是我在计算效率方面能达到的最好的。

# simulate
n <- 10
nr <- 24
nc <- 8000
test <- list()
set.seed(1234)
for (i in 1:n){
  test[[i]] <- matrix(rnorm(nr*nc),nr,nc)
}

> system.time( res <- matrix( as.numeric( unlist(test) ) ,nr,nc*n) )
 user  system elapsed 
0.114   0.006   0.120 

【问题讨论】:

  • 也许可以试试do.call(cbind,test)
  • @Tensibai 谢谢,我试过了,确实快多了。将相应地更新我的帖子。任何直觉为什么它要快得多?
  • 我认为复制和选角最少
  • 使用microbenchmark 包进行基准测试。如果时间太短,system.time 不适合。
  • @Tensibai 好的,再次感谢!

标签: r


【解决方案1】:

要处理一个列表并在所有对象上调用一个函数,do.call 是我通常的第一个想法,这里还有cbind 按列绑定所有对象。

对于n=100(为了完整起见,其他答案):

n <- 10
nr <- 24
nc <- 8000
test <- list()
set.seed(1234)
for (i in 1:n){
  test[[i]] <- matrix(rnorm(nr*nc),nr,nc)
}

require(data.table)
ori <- function() { matrix( as.numeric( unlist(test) ) ,nr,nc*n) }
Tensibai <- function() { do.call(cbind,test) }
BrodieG <- function() { `attr<-`(do.call(c, test), "dim", c(nr, nc * n)) }
nicola <- function() { setattr(unlist(test),"dim",c(nr,nc*n)) }

library(microbenchmark)
microbenchmark(r1 <- ori(),
               r2 <- Tensibai(),
               r3 <- BrodieG(),
               r4 <- nicola(), times=10)

结果:

Unit: milliseconds
             expr       min        lq     mean    median        uq      max neval cld
      r1 <- ori() 23.834673 24.287391 39.49451 27.066844 29.737964 93.74249    10   a
 r2 <- Tensibai() 17.416232 17.706165 18.18665 17.873083 18.192238 21.29512    10   a
  r3 <- BrodieG()  6.009344  6.145045 21.63073  8.690869 10.323845 77.95325    10   a
   r4 <- nicola()  5.912984  6.106273 13.52697  6.273904  6.678156 75.40914    10   a

至于为什么(在 cmets 中),@nicola 确实给出了答案,副本比原始方法少。

所有方法都给出相同的结果:

> identical(r1,r2,r3,r4)
[1] TRUE

【讨论】:

    【解决方案2】:

    由于在matrix 调用期间制作的副本,do.call 似乎优于其他方法。有趣的是,您可以使用data.table::setattr 函数来避免该副本,该函数允许通过引用 设置属性,从而避免任何副本。我也省略了as.numeric 部分,因为它不是必需的(unlist(test) 已经是numeric)。所以:

    require(microbenchmark)
    require(data.table)
    f1<-function() setattr(unlist(test),"dim",c(nr,nc*n))
    f2<-function() do.call(cbind,test)
    microbenchmark(res <-f1(),res2 <- f2(),times=10)
    #Unit: milliseconds
    #        expr       min        lq      mean   median        uq      max neval
    # res <- f1()  4.088455  4.183504  7.540913  4.44109  4.988605 35.05378    10
    #res2 <- f2() 18.325302 18.379328 18.776834 18.66857 19.100681 19.47415    10
    identical(res,res2)
    #[1] TRUE
    

    【讨论】:

      【解决方案3】:

      我想我有一个更好的。我们可以避免来自cbind 的一些开销,因为我们知道这些都具有相同的行数和列数。相反,我们使用c 知道矩阵的基本向量性质将允许我们将它们重新包装成正确的维度:

      microbenchmark(
        x <- `attr<-`(do.call(c, test), "dim", c(nr, nc * n)), 
        y <- do.call(cbind, test)
      )
      # Unit: milliseconds
      #                                                   expr       min        lq
      #  x <- `attr<-`(do.call(c, test), "dim", c(nr, nc * n))  4.435943  4.699006
      #                              y <- do.call(cbind, test) 19.339477 19.567063
      #      mean    median        uq       max neval cld
      #  12.76214  5.209938  9.095001 379.77856   100  a
      #  21.64878 20.000279 24.210848  26.02499   100   b
      
      identical(x, y)
      # [1] TRUE
      

      如果您有不同数量的列,您可能仍然可以在计算总列数时小心谨慎。

      【讨论】:

      • 这真是快得惊人!感谢你的回答。两个问题:1.我实际应用中的这些矩阵有不同的列数,这种方法仍然可行吗? 2.我注意到你的方法除了max的时间量(max是379.77856)外,其他方面都优于do.call(cbind, test),这是为什么呢?
      • @EricWang,您应该检查一下,但只要它们的行数相同,并且您指定正确的列数(即 nc * n 的子列数),您应该没事。重新时间尖峰,我不会读到最大值,可能会遇到系统清理过程。我再次运行它并获得了一致的性能。
      猜你喜欢
      • 2021-08-23
      • 2013-11-12
      • 2019-02-10
      • 1970-01-01
      • 2018-10-21
      • 1970-01-01
      • 1970-01-01
      • 2017-04-21
      • 2012-07-12
      相关资源
      最近更新 更多