如何将矩阵转换为R中的列向量列表?


79

假设您要将矩阵转换为列表,列表的每个元素都包含一列。list()as.list()显然将无法正常工作,直到现在我还是使用hack的行为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)


1
我想知道优化的Rccp解决方案是否会更快。
马雷克(Marek)

Answers:


66

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

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

9
这是tapply做什么的核心。但这很简单:)。可能会split(x, col(x))(和split(x, row(x))分别)较慢但美观的解决方案。
马雷克(Marek)

我检查了 同样快split(x, c(col(x)))。但是看起来更糟。
马雷克(Marek)

2
split(x,col(x))看起来更好-对向量的隐式强制性很好。。。
mdsumner 2011年

2
经过大量测试,这似乎是最快的,尤其是对于许多行或列。
Joris Meys 2012年

2
请注意,如果x具有列名称,split(x, col(x, as.factor = TRUE))则将保留名称。
banbh

73

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

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 

2
+1关于各种解决方案的相对效率的要点。迄今为止最好的答案。
加文·辛普森

但是我认为为了获得相同的结果,您需要执行lapply(seq_len(nrow(x)),function(i)x [i,]),然后再慢一点。
skan

26

我相信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())vs 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秒。该死,“你是人吗?”屏幕?:-)
Ari B. Friedman

1
我猜是很幸运的,我只是在@Joris悄悄地回答我Perter Flom的Q之后查看此内容。此外,松开as.data.frame()了数据框的名称,这样data.frame()会更好一些。
加文·辛普森,

2
等效的manual.coerce(x)可能unclass(as.data.frame(x))
马雷克(Marek)

谢谢Marek。大约快了6%,大概是因为我可以避免使用函数定义/调用。
阿里·弗里德曼

16

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

> 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


6

我知道这是R中的恶心,我对此并没有太多的声誉,但是我发现for循环会更有效。我正在使用以下函数将矩阵矩阵转换为其列的列表:

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 

当然,这会删除列名,但是在原始问题中它们似乎并不重要。
2013年

2
Tommy的解决方案更快,更紧凑:system.time( lapply(seq_len(ncol(x)), function(i) x[,i]) ) user: 1.668 system: 0.016 elapsed: 1.693
alfymbohm

试图在不同的上下文中解决这个问题,将无法正常工作: stackoverflow.com/questions/63801018 ....寻找这一点:vec2 = castMatrixToSequenceOfLists(vecs);
mshaffer

5

新功能asplit()将在v3.6中以R为基础。直到那时,以类似@mdsumner的答案,我们也可以做

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

根据的文档asplit()。但是,如前所示,所有split()基于解决方案的解决方案都比@Tommy的解决方案慢得多lapply/`[`asplit()至少以当前形式,这也适用于new 。

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

4

asplit一个矩阵转换为载体列表

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

3

array_tree()tidyverse的purrr程序包中有一个函数,可以最大限度地减少麻烦:

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

(这是我回答的近字对字复制到类似的问题在这里


2

通过nabble.com可访问的Some R Help网站下,我找到:

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

不能说任何关于性能比较或它的干净程度;-)


2
有趣。我认为这也是强制性的。 c(as.data.frame(x))产生的行为与as.list(as.data.frame(x)
Ari B. Friedman

我认为是这样,因为样本列表/矩阵的成员属于同一类型,但我不是专家。
Dilettant

2

你可以使用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

5
unlist(apply(x, 2, list), recursive = FALSE)
baptiste 2014年

是的 您应该将其添加为答案@baptiste。
Rich Scriven 2014年

1
但这需要向下滚动到页面底部!我为此太懒了
baptiste 2014年

我的机器上有一个“结束”按钮... :-)
Rich Scriven

我认为也可以通过创建一个空列表并填充它来完成此操作。 y <- vector("list", ncol(x))然后沿着的方向进行操作y[1:2] <- x[,1:2],尽管它不能完全按照这种方式工作。
Rich Scriven 2014年


1

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

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.