首页 文章

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

提问于
浏览
45

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

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

然而,这看起来非常低效 . 有更好的方法吗?

7 回答

  • 16

    我想你想要:

    > 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
    
  • 47

    另一种选择是:

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

    [ Edit: 包括了@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
    
  • 0

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

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

    data.frame()的lapply()步骤将每个列表项转换为单行数据框,然后使用rbind()运行良好

  • 18

    虽然这个问题早已得到回答,但值得指出 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
    
  • 31

    这个

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

    什么时候列表列表结构出现了?也许有一个更早的步骤,迭代可以被更多矢量化的东西取代?

  • 13

    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
    
  • 3

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

相关问题