【问题标题】:How to convert a list of lists to a dataframe - non-identical lists如何将列表列表转换为数据框 - 不相同的列表
【发布时间】:2014-08-27 20:21:19
【问题描述】:

我有一个列表,其中每个元素都是一个命名列表,但元素在任何地方都不相同。我已经阅读了有关如何将列表列表转换为数据框 herehere 的解决方案,但是当列表不相同时,这些都不起作用。

示例 - 请注意,我也有混合类型,如果解决方案将所有内容强制转换为字符,那很好。

lisnotOK <- list(list(a=1, b=2, c="hi"), list(b=2, c="hello", d="nope"))

结果应该只是 NA,其中列不能被列表填充,就像来自 plyr 的 rbind.fill 或来自 dplyr 的 rbind_all

例子

lisOK <- list(list(a=1, b=2, c="hi"), list(a=3, b=5, c="bye"))

# One of many solutions
do.call(rbind.data.frame, lisOK)

# gives
   a b   c
2  1 2  hi
21 3 5 bye

任何使用rbind 或尝试将lisnotOK 转换为矩阵的解决方案都将失败,而上面链接的帖子中的任何示例都不起作用,即使我尝试使用rbind_allrbind.fill .

一个解决方案是一个丑陋的 for 循环,其中每个连续列表都更改为一个数据帧,并使用rbind_all 绑定到一个数据帧。

有人知道有效的解决方案吗?

【问题讨论】:

    标签: r


    【解决方案1】:

    使用lapply 将您的列表元素转换为data.frames 和rbind_all

    rbind_all(lapply(lisnotOK,data.frame))
       a b     c    d
    1  1 2    hi <NA>
    2 NA 2 hello nope
    Warning message:
    In rbind_all(lapply(lisnotOK, data.frame)) :
      Unequal factor levels: coercing to character
    

    或来自plyrldplydata.frame

    ldply(lisnotOK,data.frame)
       a b     c    d
    1  1 2    hi <NA>
    2 NA 2 hello nope
    

    【讨论】:

    • 这非常好,不幸的是我发现它在我的实际列表中不起作用,因为某些元素包含 NULL。但这很容易在另一个 lapply 中解决。
    • @akrun,为什么? ldply 有什么特别之处?
    • @akrun,那是因为您没有查看 ldply 的代码。按照这个逻辑,我的代码就是list2mat(lisnotOK) :-)
    【解决方案2】:

    考虑到结果矩阵都是同一类型(例如,character),您可以尝试编写自己的函数,如下所示:

    list2mat <- function(inList) {
      UL <- unlist(inList)
      Nam <- unique(names(UL))
      M <- matrix(NA_character_, 
                  nrow = length(inList), ncol = length(Nam), 
                  dimnames = list(NULL, Nam))
      Row <- rep(seq_along(inList), sapply(inList, length))
      Col <- match(names(UL), Nam)
      M[cbind(Row, Col)] <- UL
      M
    }
    

    用法如下:

    list2mat(lisnotOK)
    #      a   b   c       d     
    # [1,] "1" "2" "hi"    NA    
    # [2,] NA  "2" "hello" "nope"
    

    应该相当快,因为​​一切都是预先分配的,并且您正在使用矩阵索引。


    更新:基准(因为您说效率是一个问题)

    fun1 <- function(inList) ldply(inList, data.frame)
    fun2 <- function(inList) list2mat(inList)
    
    library(microbenchmark)
    microbenchmark(fun1(lisnotOK), fun2(lisnotOK))
    # Unit: microseconds
    #            expr      min        lq    median       uq      max neval
    #  fun1(lisnotOK) 4193.808 4340.0585 4523.3000 4912.233 7600.341   100
    #  fun2(lisnotOK)  163.784  182.3865  211.2515  236.910  363.489   100
    
    L2 <- unlist(replicate(1000, lisnotOK, simplify=FALSE), recursive=FALSE)
    microbenchmark(fun1(L2), fun2(L2), times = 10)
    # Unit: milliseconds
    #      expr        min         lq     median         uq        max neval
    #  fun1(L2) 3032.71572 3106.79006 3196.17178 3306.11756 3609.67445    10
    #  fun2(L2)   24.16817   24.86991   25.65569   27.44128   29.41908    10
    

    【讨论】:

    • +1。这是一个巨大的差异。不确定这是否适用于 `rbindlist(,fill=TRUE)。我没有新版本的 data.table 来测试。
    【解决方案3】:

    在绑定之前在列表的每个元素上使用data.frame(.) 的任何函数都会非常低效(更不用说不必要了)。这是使用data.tablerbindlist(来自v1.9.3)的另一种方式,您可以得到here

    require(data.table) ## 1.9.3
    rbindlist(lisnotOK, fill=TRUE)
    #     a b     c    d
    # 1:  1 2    hi   NA
    # 2: NA 2 hello nope
    

    它适用于列表列表(如在这个问题中)、data.frames 和 data.tables。

    如果不是这个,那么我会使用 Ananda 的 list2mat 函数(如果你的类型都是相同的)。


    Ananda 的L2 数据的基准测试:

    fun1 <- function(inList) ldply(inList, as.data.frame)
    fun2 <- function(inList) list2mat(inList)
    fun3 <- function(inList) rbindlist(inList, fill=TRUE)
    fun4 <- function(inList) rbind_all(lapply(inList, as.data.frame))
    
    microbenchmark(fun1(L2), fun2(L2), fun3(L2), fun4(L2), times = 10)
    # Unit: milliseconds
    #      expr         min          lq      median          uq         max neval
    #  fun1(L2) 1927.857847 2161.432665 2221.999940 2276.241366 2366.649614    10
    #  fun2(L2)   12.039652   12.167613   12.361629   12.483751   16.040885    10
    #  fun3(L2)    1.225929    1.374395    1.473621    1.510876    1.858597    10
    #  fun4(L2) 1435.153576 1457.053482 1492.334965 1548.547706 1630.443430    10
    

    注意:我使用了as.data.frame(.) 而不是data.frame(.)(前者稍快一些)。

    【讨论】:

    • 感谢更新的基准。我没有意识到rbindlist 是这样工作的——或者这是 1.9.3 中的更新(因此您指定了版本号)?
    • @AnandaMahto, rbind 从 1.9.2 开始就有 fill=TRUE,但它是在 R 中实现的。在 1.9.3 中,所有内容都移到了 C 中,并且对 rbindlsit 进行了修饰。你可以阅读更多here
    • 为 rbindlist 中的 fill=TRUE 参数欢呼!
    猜你喜欢
    • 2019-11-16
    • 2020-04-17
    • 2022-11-14
    • 2020-07-29
    相关资源
    最近更新 更多