将列表转换为数据框的最有效方法是什么?

48
很多时候,我想把一个列表转换成一个数据框,其中每个索引都有相同的元素类型。例如,我可能有这样一个列表:
> 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

目前,为了正确地转换数据框,我正在使用unlistas.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)

然而,这样做似乎非常低效。有更好的方法吗?


2
请参见?data.table::rbindlist - marbel
从2017年开始,您应该使用purrr中的your_list %>% reduce(bind_rows) - Zafar
7个回答

50

我认为你想要的是:

> 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

10
plyr::rbind.fill通常比rbind.fill快一点,整个操作等同于plyr::ldply(my.list, data.frame) - hadley

31
另一种选择是:
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'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

嗯...这个答案中的replicate()用法有点奇怪。你正在测试将一个小列表转换为数据框多次的效率。这似乎只在很少的情况下有用。难道测试转换一个的列表不是更有意义吗? - naught101
@naught101 可能吧;你有代码,试一下吧;-)(报告结果——如果你愿意,你可以将它们编辑到我的答案中) - Gavin Simpson
@naught101,如果有人需要处理大量数据框中的数字,我有一种方法可以创建这样的列表。参考链接 - jxramos

18

我不能告诉你这是在内存或速度方面最有效的,但从编码角度来看它相当有效:

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

使用data.frame()的lapply()步骤可以将每个列表项转换为单行数据框,然后与rbind()很好地配合使用。


16

虽然这个问题早已有了答案,但值得指出的是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

13

这个

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

是一个函数,它返回一个可以提取x的第i个元素的函数。

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

列表嵌套结构何时出现?也许有一个更早的步骤可以用更向量化的方法替代迭代?


2
我需要将 MapReduce 等工具融入日常编程工作中,+1 为 Map 的示例。 - Joshua Ulrich
如何使用这些东西?as.data.frame(Map(f(mylist), names(mylist))) 版本对于我所使用的像@DrewConway和我使用的数据那样没有名称的列表不起作用。我得到了返回值data frame with 0 columns and 0 rows。即使有名称,我也无法将其应用到我的答案中的mylist。我真的很好奇,因为我根本没有使用Map等函数,所以我对它们的工作原理、功能、最佳部署时间等都很感兴趣。 - Gavin Simpson
糟糕,应该是names(mylist[[1]]),即从第一个元素获取子元素的名称。 - Martin Morgan
到目前为止最快的解决方案(为了比较,我在答案中添加了一些时间)。 - Gavin Simpson

3
包中的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 

0

不确定它们在效率方面排名如何,但根据您的列表结构,有一些tidyverse选项。一个额外的好处是它们可以很好地处理长度不相等的列表:

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

你也可以混合使用向量和数据框:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接