【问题标题】:What is the most efficient way to cast a list as a data frame?将列表转换为数据框的最有效方法是什么?
【发布时间】:2011-05-29 14:06:19
【问题描述】:

我经常想将其中每个索引具有相同元素类型的列表转换为数据框。例如,我可能有一个列表:

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

然而,这似乎非常低效。有没有更好的方法来做到这一点?

【问题讨论】:

  • ?data.table::rbindlist
  • 从 2017 年开始,您应该使用来自 purrryour_list %&gt;% reduce(bind_rows)

标签: list r dataframe


【解决方案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

【讨论】:

  • plyr::rbind.fill往往比rbind.fill快一点,整个操作相当于plyr::ldply(my.list, data.frame)
【解决方案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'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

【讨论】:

  • Hrm.. 这个答案中的replicate() 用法有点奇怪。您正在测试多次将小列表转换为数据框的效率。这似乎很少有用。测试 large 列表的转换效率不是更有意义吗?
  • @naught101 可能;你有代码,试试看;-)(报告结果——如果你愿意,你可以将它们编辑到我的答案中)
  • @naught101 我有一个means 创建这样一个列表,因为有人有一个大数据框来处理数字。
【解决方案3】:

我不能告诉你这在内存或速度方面是“最有效的”,但在编码方面非常有效:

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

带有 data.frame() 的 lapply() 步骤将每个列表项转换为单行数据框,然后与 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)
      

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

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

      获取一个命名的(感谢 Map!)向量列表,可以将其制成数据框

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

      为了速度,通常使用unlist(lapply(...), use.names=FALSE) as 会更快

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

      一个更通用的变体是

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

      list-of-lists 结构什么时候出现?也许有一个更早的步骤可以用更矢量化的东西代替迭代?

      【讨论】:

      • +1 表示Map。我需要合并MapReduce 等。一直到我的日常生活中……
      • 如何使用这些东西? @DrewConway 的数据类型不适用于我的as.data.frame(Map(f(mylist), names(mylist))) 版本,因为列表没有名称;我得到了这个返回而不是data frame with 0 columns and 0 rows。即使有名字,我也无法在我的回答中为mylist 工作。我真的很好奇,因为我根本没有使用过 Map 等,所以我对它们的工作方式、作用、最佳部署时间等感兴趣。
      • 哎呀,应该是names(mylist[[1]]),即从第一个元素中获取子元素的名字。
      • 迄今为止最快的解决方案(在我的答案中添加了一些时间以进行比较)。
      【解决方案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 
      

      【讨论】:

        【解决方案7】:

        不确定它们在效率方面的排名,但根据您的列表结构,有一些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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-27
          • 2020-01-15
          相关资源
          最近更新 更多