Convert a matrix to a list of column-vectors

Viewed 101437

Say you want to convert a matrix to a list, where each element of the list contains one column. list() or as.list() obviously won't work, and until now I use a hack using the behaviour of tapply :

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

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

I'm not completely happy with this. Anybody knows a cleaner method I'm overlooking?

(for making a list filled with the rows, the code can obviously be changed to :

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

)

15 Answers

Use asplit to convert a matrix into a list of vectors. Use the MARGIN argument to give the margins to split by. For a matrix 1 indicates rows, 2 indicates columns.

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

The new function asplit() will be coming to base R in v3.6. Up until then and in similar spirit to the answer of @mdsumner we can also do

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

as per the docs of asplit(). As previously shown however, all split() based solutions are much slower than @Tommy's lapply/`[`. This also holds for the new asplit(), at least in its current form.

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

There's a function array_tree() in the tidyverse's purrr package that does this with minimum fuss:

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

Use margin=1 to list by row instead. Works for n-dimensional arrays. It preserves names by default:

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

(this is a near word-for-word copy of my answer to a similar question here)

In the trivial case where the number of columns is small and constant, then I've found that the fastest option is to simply hard-code the conversion:

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

The simplest way to create a list that has the columns of a matrix mat as its elements is to use the fact that a data.frame object in R is internally represented as a list of the columns. Thus all that is needed is the following line

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

A dplyr readable renewed approach for the same thing:

x <- matrix(1:10,ncol=2)
library(dplyr)
x %>% as_tibble() %>%
  as.list()

$V1
[1] 1 2 3 4 5

$V2
[1]  6  7  8  9 10
Related