94 votes

Convertir une matrice en une liste de vecteurs-colonnes

Supposons que vous souhaitiez convertir une matrice en une liste, où chaque élément de la liste contient une colonne. list() o as.list() ne fonctionne é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 n'en suis pas totalement satisfait. Quelqu'un connaît-il une méthode plus propre que j'ai oubliée ?

(pour créer une liste remplie de lignes, le code peut évidemment être modifié en :

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

)

1 votes

Je me demande si une solution Rccp optimisée ne serait pas plus rapide.

3 votes

Avec R 3.6 publié il y a plusieurs années, cette réponse (en utilisant asplit ) devrait être acceptée.

87voto

Tommy Points 16323

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

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

La différence de vitesse est de 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

2 votes

+1 Bon point sur l'efficacité relative des différentes solutions. La meilleure réponse jusqu'à présent.

0 votes

Mais je pense que pour obtenir les mêmes résultats, il faut faire lapply(seq_len(nrow(x)), function(i) x[i,]), ce qui est plus lent.

77voto

mdsumner Points 13001

Pour ne pas écorcher 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)))

9 votes

C'est l'essentiel de ce que tapply faire. Mais c'est plus simple :). La solution la plus lente mais la plus belle sera probablement split(x, col(x)) (et split(x, row(x)) respectivement).

1 votes

J'ai vérifié. Tout aussi rapide sera split(x, c(col(x))) . Mais cela semble pire.

2 votes

Split(x, col(x)) semble mieux - la coercion implicite vers un vecteur est correcte . . .

30voto

Ari B. Friedman Points 24940

Les data.frames sont stockés sous forme de listes, je crois. La coercition semble donc la meilleure solution :

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 colonnes est coûteux (voir la comparaison c(unname()) vs c()) ? La solution lapply proposée par @Tommy est plus rapide d'un ordre de grandeur. Les résultats de as.data.frame() peuvent être quelque peu améliorés en les contraignant 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

0 votes

Battu par Gavin de 5 secondes. Maudite soit l'écran "Êtes-vous un humain" :-)

1 votes

La chance du tirage au sort, je suppose, j'ai regardé ceci juste après que @Joris se soit faufilé avant moi pour répondre à la question de Perter Flom, as.data.frame() perd les noms du cadre de données, donc data.frame() est un peu plus agréable.

2 votes

Équivalent de manual.coerce(x) pourrait être unclass(as.data.frame(x)) .

17voto

Gavin Simpson Points 72349

La conversion en data frame 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

13voto

Sacha Epskamp Points 14956

Utilisation plyr peut s'avérer très utile pour ce genre de choses :

library("plyr")

alply(x,2)

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

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

attr(,"class")
[1] "split" "list"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X