Functional way to stack a list of 2d matrices into a 3d matrix

After clever lapply , I still have a list of two-dimensional matrices.

For example:

 set.seed(1) test <- replicate( 5, matrix(runif(25),ncol=5), simplify=FALSE ) > test [[1]] [,1] [,2] [,3] [,4] [,5] [1,] 0.8357088 0.29589546 0.9994045 0.2862853 0.6973738 [2,] 0.2377494 0.14704832 0.0348748 0.7377974 0.6414624 [3,] 0.3539861 0.70399206 0.3383913 0.8340543 0.6439229 [4,] 0.8568854 0.10380669 0.9150638 0.3142708 0.9778534 [5,] 0.8537634 0.03372777 0.6172353 0.4925665 0.4147353 [[2]] [,1] [,2] [,3] [,4] [,5] [1,] 0.1194048 0.9833502 0.9674695 0.6687715 0.1928159 [2,] 0.5260297 0.3883191 0.5150718 0.4189159 0.8967387 [3,] 0.2250734 0.2292448 0.1630703 0.3233450 0.3081196 [4,] 0.4864118 0.6232975 0.6219023 0.8352553 0.3633005 [5,] 0.3702148 0.1365402 0.9859542 0.1438170 0.7839465 [[3]] ... 

I would like to turn this into a three-dimensional array:

 set.seed(1) replicate( 5, matrix(runif(25),ncol=5) ) 

Obviously, if I use replication, I can simply enable simplify , but sapply does not simplify the result correctly, and stack fails. do.call(rbind,mylist) turns it into a 2d matrix, not a 3d array.

I can do this with a loop, but I'm looking for a neat and functional way to handle this.

The closest way I came up with is:

 array( do.call( c, test ), dim=c(dim(test[[1]]),length(test)) ) 

But I feel that it is inelegant (because it parses and then reassembles the attributes of an array of vectors and requires a lot of testing to ensure security (for example, the sizes of each element are the same).

+8
r functional-programming
source share
4 answers

You can use the abind package and then use do.call(abind, c(test, along = 3))

 library(abind) testArray <- do.call(abind, c(test, along = 3)) 

Or you can use simplify = 'array' in a sapply call, instead of lapply ). simplify = 'array' does not match simplify = TRUE , as it will change the higher argument to simplify2array

eg,

 foo <- function(x) matrix(1:10, ncol = 5) # the default is simplify = TRUE sapply(1:5, foo) [,1] [,2] [,3] [,4] [,5] [1,] 1 1 1 1 1 [2,] 2 2 2 2 2 [3,] 3 3 3 3 3 [4,] 4 4 4 4 4 [5,] 5 5 5 5 5 [6,] 6 6 6 6 6 [7,] 7 7 7 7 7 [8,] 8 8 8 8 8 [9,] 9 9 9 9 9 [10,] 10 10 10 10 10 # which is *not* what you want # so set `simplify = 'array' sapply(1:5, foo, simplify = 'array') , , 1 [,1] [,2] [,3] [,4] [,5] [1,] 1 3 5 7 9 [2,] 2 4 6 8 10 , , 2 [,1] [,2] [,3] [,4] [,5] [1,] 1 3 5 7 9 [2,] 2 4 6 8 10 , , 3 [,1] [,2] [,3] [,4] [,5] [1,] 1 3 5 7 9 [2,] 2 4 6 8 10 , , 4 [,1] [,2] [,3] [,4] [,5] [1,] 1 3 5 7 9 [2,] 2 4 6 8 10 , , 5 [,1] [,2] [,3] [,4] [,5] [1,] 1 3 5 7 9 [2,] 2 4 6 8 10 
+6
source share

Try the following:

 simplify2array(test) 
+10
source share
 test2 <- unlist(test) dim(test2) <- c(dim(test[[1]]),5) 

or if you do not know the expected size in advance:

 dim3 <- c(dim(test[[1]]), length(test2)/prod(dim(test[[1]]))) dim(test2) <- dim3 
+2
source share

An array is just an atomic vector with dimensions. Each of the matrix components of test is actually just a vector with dimensions. Therefore, the simplest solution I can imagine is to expand the test list into a vector and convert it to an array using array and suitable sizes.

 set.seed(1) foo <- replicate( 5, matrix(runif(25),ncol=5) ) tmp <- array(unlist(test), dim = c(5,5,5)) > all.equal(foo, tmp) [1] TRUE > is.array(tmp) [1] TRUE > dim(tmp) [1] 5 5 5 

If you do not want to rigidly set the sizes, we must make some assumptions, but we can easily fill the size from test , for example.

 tmp2 <- array(unlist(test), dim = c(dim(test[[1]]), length(test))) > all.equal(foo, tmp2) [1] TRUE 

This assumes that the dimensions of each component are the same, but then I don’t see how you could put submatrices in the 3rd array if this condition is not met.

It may seem hacked to expand the list, but it's just using how R treats matrices and arrays as dimension vectors.

+2
source share

All Articles