Très souvent, je veux convertir une liste dans laquelle chaque index a des types d'éléments identiques en une trame de données. Par exemple, je peux avoir une liste:
> my.list
[[1]]
[[1]]$global_stdev_ppb
[1] 24267673
[[1]]$range
[1] 0.03114799
[[1]]$tok
[1] "hello"
[[1]]$global_freq_ppb
[1] 211592.6
[[2]]
[[2]]$global_stdev_ppb
[1] 11561448
[[2]]$range
[1] 0.08870838
[[2]]$tok
[1] "world"
[[2]]$global_freq_ppb
[1] 1002043
Je veux convertir cette liste en un bloc de données où chaque élément d'index est une colonne. La chose naturelle (pour moi) est d’utiliser do.call
:
> my.matrix<-do.call("rbind", my.list)
> my.matrix
global_stdev_ppb range tok global_freq_ppb
[1,] 24267673 0.03114799 "hello" 211592.6
[2,] 11561448 0.08870838 "world" 1002043
Assez simple, mais lorsque j'essaie de convertir cette matrice en un bloc de données, les colonnes restent des éléments de liste, plutôt que des vecteurs:
> my.df<-as.data.frame(my.matrix, stringsAsFactors=FALSE)
> my.df[,1]
[[1]]
[1] 24267673
[[2]]
[1] 11561448
Actuellement, pour que le bloc de données soit correctement converti, j'itère sur chaque colonne en utilisant unlist
et as.vector
, puis refonte du bloc de données en tant que tel:
new.list<-lapply(1:ncol(my.matrix), function(x) as.vector(unlist(my.matrix[,x])))
my.df<-as.data.frame(do.call(cbind, new.list), stringsAsFactors=FALSE)
Cela semble cependant très inefficace. Existe-t-il une meilleure façon de procéder?
Je pense que tu veux:
> do.call(rbind, lapply(my.list, data.frame, stringsAsFactors=FALSE))
global_stdev_ppb range tok global_freq_ppb
1 24267673 0.03114799 hello 211592.6
2 11561448 0.08870838 world 1002043.0
> str(do.call(rbind, lapply(my.list, data.frame, stringsAsFactors=FALSE)))
'data.frame': 2 obs. of 4 variables:
$ global_stdev_ppb: num 24267673 11561448
$ range : num 0.0311 0.0887
$ tok : chr "hello" "world"
$ global_freq_ppb : num 211593 1002043
Une autre option est:
data.frame(t(sapply(mylist, `[`)))
mais cette simple manipulation se traduit par un bloc de données de listes:
> str(data.frame(t(sapply(mylist, `[`))))
'data.frame': 2 obs. of 3 variables:
$ a:List of 2
..$ : num 1
..$ : num 2
$ b:List of 2
..$ : num 2
..$ : num 3
$ c:List of 2
..$ : chr "a"
..$ : chr "b"
Une alternative à cela, dans le même sens mais maintenant le même résultat que les autres solutions, est:
data.frame(lapply(data.frame(t(sapply(mylist, `[`))), unlist))
[Edit: inclus les timings des deux solutions de @Martin Morgan, qui ont l'avantage sur l'autre solution qui retournent un bloc de données de vecteurs.] Quelques timings représentatifs sur un problème très simple:
mylist <- list(list(a = 1, b = 2, c = "a"), list(a = 2, b = 3, c = "b"))
> ## @Joshua Ulrich's solution:
> system.time(replicate(1000, do.call(rbind, lapply(mylist, data.frame,
+ stringsAsFactors=FALSE))))
user system elapsed
1.740 0.001 1.750
> ## @JD Long's solution:
> system.time(replicate(1000, do.call(rbind, lapply(mylist, data.frame))))
user system elapsed
2.308 0.002 2.339
> ## my sapply solution No.1:
> system.time(replicate(1000, data.frame(t(sapply(mylist, `[`)))))
user system elapsed
0.296 0.000 0.301
> ## my sapply solution No.2:
> system.time(replicate(1000, data.frame(lapply(data.frame(t(sapply(mylist, `[`))),
+ unlist))))
user system elapsed
1.067 0.001 1.091
> ## @Martin Morgan's Map() sapply() solution:
> f = function(x) function(i) sapply(x, `[[`, i)
> system.time(replicate(1000, as.data.frame(Map(f(mylist), names(mylist[[1]])))))
user system elapsed
0.775 0.000 0.778
> ## @Martin Morgan's Map() lapply() unlist() solution:
> f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE)
> system.time(replicate(1000, as.data.frame(Map(f(mylist), names(mylist[[1]])))))
user system elapsed
0.653 0.000 0.658
Je ne peux pas vous dire que c'est le "plus efficace" en termes de mémoire ou de vitesse, mais c'est assez efficace en termes de codage:
my.df <- do.call("rbind", lapply(my.list, data.frame))
l'étape lapply () avec data.frame () transforme chaque élément de liste en un cadre de données à une seule ligne qui agit ensuite Nice avec rbind ()
Bien que cette question ait été résolue depuis longtemps, il convient de souligner le data.table
le paquet a rbindlist
qui accomplit cette tâche très rapidement:
library(microbenchmark)
library(data.table)
l <- replicate(1E4, list(a=runif(1), b=runif(1), c=runif(1)), simplify=FALSE)
microbenchmark( times=5,
R=as.data.frame(Map(f(l), names(l[[1]]))),
dt=data.frame(rbindlist(l))
)
donne moi
Unit: milliseconds
expr min lq median uq max neval
R 31.060119 31.403943 32.278537 32.370004 33.932700 5
dt 2.271059 2.273157 2.600976 2.635001 2.729421 5
Cette
f = function(x) function(i) sapply(x, `[[`, i)
est une fonction qui renvoie une fonction qui extrait le ième élément de x. Alors
Map(f(mylist), names(mylist[[1]]))
obtient une liste nommée (merci Map!) de vecteurs qui peuvent être transformés en une trame de données
as.data.frame(Map(f(mylist), names(mylist[[1]])))
Pour la vitesse, il est généralement plus rapide d'utiliser unlist(lapply(...), use.names=FALSE)
comme
f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE)
Une variante plus générale est
f = function(X, FUN) function(...) sapply(X, FUN, ...)
Quand les structures de liste de listes apparaissent-elles? Peut-être y a-t-il une étape antérieure où une itération pourrait être remplacée par quelque chose de plus vectorisé?
Le paquetage dplyr bind_rows
est efficace.
one <- mtcars[1:4, ]
two <- mtcars[11:14, ]
system.time(dplyr::bind_rows(one, two))
user system elapsed
0.001 0.000 0.001
Je ne sais pas où ils se classent en termes d'efficacité, mais selon la structure de vos listes, il existe des options tidyverse
. Un bonus est qu'ils fonctionnent bien avec des listes de longueurs inégales:
l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
, b = list(var.1 = 4, var.2 = 5)
, c = list(var.1 = 7, var.3 = 9)
, d = list(var.1 = 10, var.2 = 11, var.3 = NA))
df <- dplyr::bind_rows(l)
df <- purrr::map_df(l, dplyr::bind_rows)
df <- purrr::map_df(l, ~.x)
# all create the same data frame:
# A tibble: 4 x 3
var.1 var.2 var.3
<dbl> <dbl> <dbl>
1 1 2 3
2 4 5 NA
3 7 NA 9
4 10 11 NA
Et vous pouvez également mélanger des vecteurs et des trames de données:
library(dplyr)
bind_rows(
list(a = 1, b = 2),
data_frame(a = 3:4, b = 5:6),
c(a = 7)
)
# A tibble: 4 x 2
a b
<dbl> <dbl>
1 1 2
2 3 5
3 4 6
4 7 NA