【问题标题】:How to convert a matrix to a list of column-vectors in R?如何将矩阵转换为 R 中的列向量列表?
【发布时间】:2011-10-12 18:50:08
【问题描述】:

假设您要将矩阵转换为列表,其中列表的每个元素都包含一列。 list()as.list() 显然不起作用,直到现在我使用 tapply 的行为来进行破解:

x <- matrix(1:10,ncol=2)

tapply(x,rep(1:ncol(x),each=nrow(x)),function(i)i)

我对此并不完全满意。有人知道我忽略的更清洁的方法吗?

(为了制作一个充满行的列表,代码显然可以更改为:

tapply(x,rep(1:nrow(x),ncol(x)),function(i)i)

)

【问题讨论】:

  • 我想知道优化的 Rccp 解决方案是否会更快。
  • 随着多年前发布的 R 3.6,this answer(使用 asplit)应该是公认的。

标签: list r matrix


【解决方案1】:

Gavin 的回答简单而优雅。但如果有很多列,更快的解决方案是:

lapply(seq_len(ncol(x)), function(i) x[,i])

下例中速度差为 6 倍:

> x <- matrix(1:1e6, 10)
> system.time( as.list(data.frame(x)) )
   user  system elapsed 
   1.24    0.00    1.22 
> system.time( lapply(seq_len(ncol(x)), function(i) x[,i]) )
   user  system elapsed 
    0.2     0.0     0.2 

【讨论】:

  • +1 关于各种解决方案的相对效率的好点。迄今为止最好的答案。
  • 但我认为为了获得相同的结果,您需要执行 lapply(seq_len(nrow(x)), function(i) x[i,]) 然后速度较慢。
【解决方案2】:

为了给猫剥皮,将数组视为一个向量,就好像它没有 dim 属性一样:

 split(x, rep(1:ncol(x), each = nrow(x)))

【讨论】:

  • 这是tapply 所做工作的核心。但它更简单:)。 split(x, col(x))(和 split(x, row(x)) 分别)可能更慢但看起来不错的解决方案。
  • 我查过了。同样快的是split(x, c(col(x)))。但看起来更糟。
  • split(x, col(x)) 看起来更好 - 对向量的隐式强制很好。 . .
  • 经过多次测试,这似乎是最快的,尤其是在有很多行或列的情况下。
  • 请注意,如果x 有列名,那么split(x, col(x, as.factor = TRUE)) 将保留这些名称。
【解决方案3】:

data.frames 存储为列表,我相信。因此强制似乎是最好的:

as.list(as.data.frame(x))
> as.list(as.data.frame(x))
$V1
[1] 1 2 3 4 5

$V2
[1]  6  7  8  9 10

基准测试结果很有趣。 as.data.frame 比 data.frame 快,要么是因为 data.frame 必须创建一个全新的对象,要么是因为跟踪列名的成本很高(看看 c(unname()) 与 c() 的比较)? @Tommy 提供的 lapply 解决方案快了一个数量级。 as.data.frame() 结果可以通过手动强制有所改善。

manual.coerce <- function(x) {
  x <- as.data.frame(x)
  class(x) <- "list"
  x
}

library(microbenchmark)
x <- matrix(1:10,ncol=2)

microbenchmark(
  tapply(x,rep(1:ncol(x),each=nrow(x)),function(i)i) ,
  as.list(data.frame(x)),
  as.list(as.data.frame(x)),
  lapply(seq_len(ncol(x)), function(i) x[,i]),
  c(unname(as.data.frame(x))),
  c(data.frame(x)),
  manual.coerce(x),
  times=1000
  )

                                                      expr     min      lq
1                                as.list(as.data.frame(x))  176221  183064
2                                   as.list(data.frame(x))  444827  454237
3                                         c(data.frame(x))  434562  443117
4                              c(unname(as.data.frame(x)))  257487  266897
5             lapply(seq_len(ncol(x)), function(i) x[, i])   28231   35929
6                                         manual.coerce(x)  160823  167667
7 tapply(x, rep(1:ncol(x), each = nrow(x)), function(i) i) 1020536 1036790
   median      uq     max
1  186486  190763 2768193
2  460225  471346 2854592
3  449960  460226 2895653
4  271174  277162 2827218
5   36784   37640 1165105
6  171088  176221  457659
7 1052188 1080417 3939286

is.list(manual.coerce(x))
[1] TRUE

【讨论】:

  • 被加文打败了 5 秒。该死的,“你是人类”的屏幕吗? :-)
  • 我猜是幸运的,我只是在@Joris 在我前面回答 Perter Flom 的 Q 之后才看到这个。另外,as.data.frame() 丢失了数据框的名称,所以data.frame()更好一点。
  • manual.coerce(x) 的等价物可能是 unclass(as.data.frame(x))
  • 谢谢马雷克。这大约快 6%,大概是因为我可以避免使用函数定义/调用。
【解决方案4】:

将数据框转换为列表似乎可行:

> as.list(data.frame(x))
$X1
[1] 1 2 3 4 5

$X2
[1]  6  7  8  9 10
> str(as.list(data.frame(x)))
List of 2
 $ X1: int [1:5] 1 2 3 4 5
 $ X2: int [1:5] 6 7 8 9 10

【讨论】:

    【解决方案5】:

    使用plyr对于这样的事情非常有用:

    library("plyr")
    
    alply(x,2)
    
    $`1`
    [1] 1 2 3 4 5
    
    $`2`
    [1]  6  7  8  9 10
    
    attr(,"class")
    [1] "split" "list" 
    

    【讨论】:

      【解决方案6】:

      使用asplit 将矩阵转换为向量列表

      asplit(x, 1) # split into list of row vectors
      asplit(x, 2) # split into list of column vectors
      

      【讨论】:

        【解决方案7】:

        我知道这是 R 中的诅咒,我并没有太多的声誉来支持这一点,但我发现 for 循环更有效。我正在使用以下函数将矩阵 mat 转换为其列的列表:

        mat2list <- function(mat)
        {
            list_length <- ncol(mat)
            out_list <- vector("list", list_length)
            for(i in 1:list_length) out_list[[i]] <- mat[,i]
            out_list
        }
        

        与 mdsummer 和原始解决方案进行比较的快速基准测试:

        x <- matrix(1:1e7, ncol=1e6)
        
        system.time(mat2list(x))
           user  system elapsed 
          2.728   0.023   2.720 
        
        system.time(split(x, rep(1:ncol(x), each = nrow(x))))
           user  system elapsed 
          4.812   0.194   4.978 
        
        system.time(tapply(x,rep(1:ncol(x),each=nrow(x)),function(i)i))
           user  system elapsed 
         11.471   0.413  11.817 
        

        【讨论】:

        • 当然这会删除列名,但它们在原始问题中似乎并不重要。
        • Tommy 的解决方案更快更紧凑:system.time( lapply(seq_len(ncol(x)), function(i) x[,i]) ) user: 1.668 system: 0.016 elapsed: 1.693
        • 试图在不同的上下文中解决这个问题,但不起作用:stackoverflow.com/questions/63801018 .... 寻找这个:vec2 = castMatrixToSequenceOfLists(vecs);
        【解决方案8】:

        新函数asplit() 将在 v3.6 中加入基础 R。在那之前,本着与@mdsumner 的回答类似的精神,我们也可以这样做

        split(x, slice.index(x, MARGIN))
        

        根据asplit() 的文档。然而,如前所述,所有基于split() 的解决方案都比@Tommy 的lapply/`[` 慢得多。这也适用于新的asplit(),至少目前的形式如此。

        split_1 <- function(x) asplit(x, 2L)
        split_2 <- function(x) split(x, rep(seq_len(ncol(x)), each = nrow(x)))
        split_3 <- function(x) split(x, col(x))
        split_4 <- function(x) split(x, slice.index(x, 2L))
        split_5 <- function(x) lapply(seq_len(ncol(x)), function(i) x[, i])
        
        dat <- matrix(rnorm(n = 1e6), ncol = 100)
        
        #> Unit: milliseconds
        #>          expr       min        lq     mean   median        uq        max neval
        #>  split_1(dat) 16.250842 17.271092 20.26428 18.18286 20.185513  55.851237   100
        #>  split_2(dat) 52.975819 54.600901 60.94911 56.05520 60.249629 105.791117   100
        #>  split_3(dat) 32.793112 33.665121 40.98491 34.97580 39.409883  74.406772   100
        #>  split_4(dat) 37.998140 39.669480 46.85295 40.82559 45.342010  80.830705   100
        #>  split_5(dat)  2.622944  2.841834  3.47998  2.88914  4.422262   8.286883   100
        
        dat <- matrix(rnorm(n = 1e6), ncol = 1e5)
        
        #> Unit: milliseconds
        #>          expr       min       lq     mean   median       uq      max neval
        #>  split_1(dat) 204.69803 231.3023 261.6907 246.4927 289.5218 413.5386   100
        #>  split_2(dat) 229.38132 235.3153 253.3027 242.0433 259.2280 339.0016   100
        #>  split_3(dat) 208.29162 216.5506 234.2354 221.7152 235.3539 342.5918   100
        #>  split_4(dat) 214.43064 221.9247 240.7921 231.0895 246.2457 323.3709   100
        #>  split_5(dat)  89.83764 105.8272 127.1187 114.3563 143.8771 209.0670   100
        

        【讨论】:

          【解决方案9】:

          在 tidyverse 的 purrr 包中有一个函数 array_tree() 可以轻松做到这一点:

          x <- matrix(1:10,ncol=2)
          xlist <- purrr::array_tree(x, margin=2)
          xlist
          
          #> [[1]]
          #> [1] 1 2 3 4 5
          #>  
          #> [[2]]
          #> [1]  6  7  8  9 10
          

          改为使用margin=1 按行列出。适用于 n 维数组。它默认保留名称:

          x <- matrix(1:10,ncol=2)
          colnames(x) <- letters[1:2]
          xlist <- purrr::array_tree(x, margin=2)
          xlist
          
          #> $a
          #> [1] 1 2 3 4 5
          #>
          #> $b
          #> [1]  6  7  8  9 10
          

          (这是我对类似问题here的回答的逐字逐句复制)

          【讨论】:

            【解决方案10】:

            Some R Help site accessible via nabble.com 下我找到:

            c(unname(as.data.frame(x))) 
            

            作为一个有效的解决方案,在我的 R v2.13.0 中安装这看起来不错:

            > y <- c(unname(as.data.frame(x)))
            > y
            [[1]]
            [1] 1 2 3 4 5
            
            [[2]]
            [1]  6  7  8  9 10
            

            不能说性能比较或它有多干净;-)

            【讨论】:

            • 有趣。我认为这也可以通过胁迫来实现。 c(as.data.frame(x)) 产生与 as.list(as.data.frame(x) 相同的行为
            • 我认为是这样,因为样本列表/矩阵的成员是同一类型的,但我不是专家。
            【解决方案11】:

            您可以使用apply,然后使用cdo.call

            x <- matrix(1:10,ncol=2)
            do.call(c, apply(x, 2, list))
            #[[1]]
            #[1] 1 2 3 4 5
            #
            #[[2]]
            #[1]  6  7  8  9 10
            

            看起来它会在添加到矩阵时保留列名。

            colnames(x) <- c("a", "b")
            do.call(c, apply(x, 2, list))
            #$a
            #[1] 1 2 3 4 5
            #
            #$b
            #[1]  6  7  8  9 10
            

            【讨论】:

            • unlist(apply(x, 2, list), recursive = FALSE)
            • 是的。您应该将其添加为@baptiste 的答案。
            • 但这需要向下滚动到页面底部!我太懒了
            • 我的机器上有一个“END”按钮... :-)
            • 我认为这也可以通过创建一个空列表并填充它来完成。 y &lt;- vector("list", ncol(x))y[1:2] &lt;- x[,1:2] 类似的东西,虽然它的工作方式并不完全一样。
            【解决方案12】:

            convertRowsToList {BBmisc}

            将 data.frame 或矩阵的行(列)转换为列表。

            BBmisc::convertColsToList(x)
            

            参考:http://berndbischl.github.io/BBmisc/man/convertRowsToList.html

            【讨论】:

              【解决方案13】:

              在列数很小且恒定的微不足道的情况下,我发现最快的选择是简单地对转换进行硬编码:

              mat2list  <- function (mat) lapply(1:2, function (i) mat[, i])
              mat2list2 <- function (mat) list(mat[, 1], mat[, 2])
              
              
              ## Microbenchmark results; unit: microseconds
              #          expr   min    lq    mean median    uq    max neval
              ##  mat2list(x) 7.464 7.932 8.77091  8.398 8.864 29.390   100
              ## mat2list2(x) 1.400 1.867 2.48702  2.333 2.333 27.525   100
              

              【讨论】:

                【解决方案14】:

                创建以矩阵 mat 的列作为其元素的列表的最简单方法是使用 R 中的 data.frame 对象在内部表示为列的列表这一事实。因此,所需要的一切 是下面一行

                mat.list <- as.data.frame(mat)
                

                【讨论】:

                  猜你喜欢
                  • 2018-07-17
                  • 1970-01-01
                  • 2015-01-30
                  • 2014-03-09
                  • 2013-06-08
                  • 1970-01-01
                  • 2015-05-27
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多