Каков наиболее эффективный способ создания списка в виде фрейма данных?

Очень часто я хочу преобразовать список, в котором каждый индекс имеет одинаковые типы элементов для фрейма данных. Например, у меня может быть список:

> 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

Я хочу преобразовать этот список в кадр данных, где каждый индексный элемент является столбцом. Естественной (для меня) вещью является использование 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

Достаточно просто, но когда я пытаюсь использовать эту матрицу в качестве фрейма данных, столбцы остаются элементами списка, а не векторами:

> my.df<-as.data.frame(my.matrix, stringsAsFactors=FALSE)
> my.df[,1]
[[1]]
[1] 24267673

[[2]]
[1] 11561448

В настоящее время, чтобы правильно настроить кадр данных, я выполняю итерацию по каждому столбцу с помощью unlist и as.vector, а затем восстанавливая кадр данных как таковой:

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)

Это, однако, кажется очень неэффективным. Есть ли лучший способ сделать это?

Ответ 1

Я думаю, вы хотите:

> 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

Ответ 2

Другой вариант:

data.frame(t(sapply(mylist, `[`)))

но эта простая манипуляция приводит к кадру данных списков:

> 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"

Альтернативой этому, по тем же линиям, но теперь результатом, аналогичным другим решениям, является:

data.frame(lapply(data.frame(t(sapply(mylist, `[`))), unlist))

[ Изменить: включены тайминги @Martin Morgan двух решений, которые имеют преимущество над другим решением, которое возвращает кадр данных векторов.] Некоторые репрезентативные тайминги по очень простой проблеме:

mylist <- list(list(a = 1, b = 2, c = "a"), list(a = 2, b = 3, c = "b"))

> ## @Joshua Ulrich 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 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 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 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

Ответ 3

Я не могу сказать, что это "самый эффективный" с точки зрения памяти или скорости, но он довольно эффективен с точки зрения кодирования:

my.df <- do.call("rbind", lapply(my.list, data.frame))

шаг lapply() с data.frame() превращает каждый элемент списка в кадр данных с одной строкой, который затем действует с rbind()

Ответ 4

Хотя на этот вопрос уже давно дан ответ, стоит отметить, что пакет data.table имеет rbindlist, который быстро выполняет эту задачу:

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

дает мне

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

Ответ 5

Это

f = function(x) function(i) sapply(x, `[[`, i)

- это функция, которая возвращает функцию, которая извлекает i-й элемент из x. Так

Map(f(mylist), names(mylist[[1]]))

получает имя (спасибо Map!) список векторов, которые могут быть внесены в фрейм данных

as.data.frame(Map(f(mylist), names(mylist[[1]])))

Для скорости обычно быстрее использовать unlist(lapply(...), use.names=FALSE) как

f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE)

Более общий вариант

f = function(X, FUN) function(...) sapply(X, FUN, ...)

Когда появляются структуры списка списков? Может быть, есть более ранний шаг, когда итерация может быть заменена чем-то более векторным?

Ответ 6

Эффективный пакет dplyr bind_rows.

one <- mtcars[1:4, ]
two <- mtcars[11:14, ]
system.time(dplyr::bind_rows(one, two))
   user  system elapsed 
  0.001   0.000   0.001