web-dev-qa-db-fra.com

Quelle est la manière la plus efficace de caster une liste en tant que bloc de données?

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?

46
DrewConway

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
48
Joshua Ulrich

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

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 ()

18
JD Long

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
16
Kevin Ushey

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é?

13
Martin Morgan

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 
3
Yi Li

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
0
sbha