web-dev-qa-db-fra.com

Comment convertir une matrice en une liste de vecteurs colonnes dans R?

Supposons que vous souhaitiez convertir une matrice en liste, où chaque élément de la liste contient une colonne. list() ou as.list() ne fonctionnera évidemment pas, et jusqu'à présent j'utilise un hack utilisant le comportement de tapply:

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

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

Je ne suis pas complètement satisfait de cela. Quelqu'un connaît une méthode plus propre que je néglige?

(pour faire une liste remplie de lignes, le code peut évidemment être changé en:

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

)

67
Joris Meys

Dans l'intérêt de dépouiller le chat, traitez le tableau comme un vecteur comme s'il n'avait pas d'attribut dim:

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

La réponse de Gavin est simple et élégante. Mais s'il y a beaucoup de colonnes, une solution beaucoup plus rapide serait:

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

La différence de vitesse est 6x dans l'exemple ci-dessous:

> 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 
69
Tommy

les cadres de données sont stockés sous forme de listes, je crois. Par conséquent, la coercition semble la meilleure:

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

Les résultats de l'analyse comparative sont intéressants. as.data.frame est plus rapide que data.frame, soit parce que data.frame doit créer un tout nouvel objet, soit parce que le suivi des noms de colonne est en quelque sorte coûteux (voyez le c(unname()) vs c() comparaison)? La solution lapply fournie par @Tommy est plus rapide d'un ordre de grandeur. Les résultats as.data.frame () peuvent être quelque peu améliorés en forçant manuellement.

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
25
Ari B. Friedman

La conversion en bloc de données, puis en liste, semble fonctionner:

> 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
15
Gavin Simpson

L'utilisation de plyr peut être très utile pour des choses comme ceci:

library("plyr")

alply(x,2)

$`1`
[1] 1 2 3 4 5

$`2`
[1]  6  7  8  9 10

attr(,"class")
[1] "split" "list" 
11
Sacha Epskamp

Je sais que c'est un anathème dans R, et je n'ai pas vraiment beaucoup de réputation pour le sauvegarder, mais je trouve une boucle for pour être plus efficace. J'utilise la fonction suivante pour convertir le mat de matrice en une liste de ses colonnes:

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
}

Comparaison rapide avec mdsummer et la solution originale:

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 
5
alfymbohm

La nouvelle fonction asplit() arrivera à la base R dans la v3.6. Jusque-là et dans un esprit similaire à la réponse de @mdsumner, nous pouvons également faire

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

selon les documents de asplit(). Cependant, comme indiqué précédemment, toutes les solutions basées sur split() sont beaucoup plus lentes que lapply/`[` De @ Tommy. Cela vaut également pour la nouvelle asplit(), au moins dans sa forme actuelle.

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
2
nbenn

Sous Certains sites d'aide R accessibles via nabble.com Je trouve:

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

comme solution valide et dans mon installation de R v2.13.0, cela semble correct:

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

[[2]]
[1]  6  7  8  9 10

Je ne peux rien dire sur les comparaisons de performances ou sur la propreté ;-)

2
Dilettant

Il y a une fonction array_tree() dans le package purrr de tidyverse qui fait cela avec un minimum de bruit:

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

Utilisation margin=1 pour afficher la liste par ligne. Fonctionne pour les tableaux à n dimensions. Il conserve les noms par défaut:

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

(ceci est une copie presque mot pour mot de ma réponse à une question similaire ici )

2
wjchulme

convertRowsToList {BBmisc}

Convertissez des lignes (colonnes) de data.frame ou matrice en listes.

BBmisc::convertColsToList(x)

réf: http://berndbischl.github.io/BBmisc/man/convertRowsToList.html

2
Zhilong Jia

Vous pouvez utiliser apply puis c avec do.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

Et il semble qu'il conservera les noms des colonnes, lorsqu'il sera ajouté à la matrice.

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
1
Rich Scriven

Dans le cas trivial où le nombre de colonnes est petit et constant, j'ai trouvé que l'option la plus rapide est de simplement coder en dur la 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
0
ms609