【问题标题】:Convert Mixed-Length named List to data.frame将名为 List 的混合长度转换为 data.frame
【发布时间】:2013-03-23 02:19:32
【问题描述】:

我有以下格式的列表:

[[1]]
[[1]]$a
[1] 1

[[1]]$b
[1] 3

[[1]]$c
[1] 5

[[2]]       
[[2]]$c
[1] 2

[[2]]$a
[1] 3

有一个可能的“键”(abc,在这种情况下)的预定义列表,并且列表中的每个元素(“行”)将具有为一个或多个定义的值这些键。我正在寻找一种从上面的列表结构到 data.frame 的快速方法,在这种情况下如下所示:

  a  b c
1 1  3 5
2 3 NA 2

任何帮助将不胜感激!


附录

我正在处理一个最多包含 50,000 行和 3-6 列的表,其中大部分值都已指定。我将从 JSON 中获取表格并尝试快速将其放入 data.frame 结构中。

下面是一些代码,用于创建我将使用的秤的示例列表:

ids <- c("a", "b", "c")
createList <- function(approxSize=100){     
    set.seed(1234)

    fifth <- round(approxSize/5)

    list <- list()
    list[1:(fifth*5)] <- rep(
        list(list(a=1, b=2, c=3), 
                 list(a=3, b=4, c=5),
                 list(a=7, c=9),
                 list(c=6, a=8, b=3),
                 list(b=6)), 
        fifth)

    list
}

只需创建一个包含 50,000 个 approxSize 的列表来测试这种大小的列表的性能。

【问题讨论】:

标签: r dataframe


【解决方案1】:

在 dplyr 中:

bind_rows(lapply(x, as_data_frame))

# A tibble: 2 x 3
      a     b     c
  <dbl> <dbl> <dbl>
1     1     3     5
2     3    NA     2

【讨论】:

    【解决方案2】:

    我知道这是一个老问题,但我只是遇到了这个问题,没有看到我所知道的最简单的解决方案真是令人痛苦。所以在这里(只需在 rbindlist 中指定 'fill=TRUE'):

    library(data.table)
    list = list(list(a=1,b=3,c=5),list(c=2,a=3))
    rbindlist(list,fill=TRUE)
    
    #    a  b c
    # 1: 1  3 5
    # 2: 3 NA 2
    

    我不知道这是否是最快的方式,但鉴于 data.table 的周到设计和在许多其他任务上的出色表现,我愿意打赌它会竞争。

    【讨论】:

      【解决方案3】:

      如果您事先知道可能的值,并且您正在处理大数据,那么使用data.tableset 可能会很快

      cc <- createList(50000)
      
      
      
      system.time({
      nas <- rep.int(NA_real_, length(cc))
      DT <-  setnames(as.data.table(replicate(length(ids),nas, simplify = FALSE)), ids)
      
      for(xx in seq_along(cc)){
      
        .n <- names(cc[[xx]])
        for(j in .n){
          set(DT, i = xx, j = j, value = cc[[xx]][[j]])
        }
      
      
      }
      
      })
      
      
      # user  system elapsed 
      # 0.68    0.01    0.70 
      

      后代的旧(慢解决方案)

      full <- c('a','b', 'c')
      
      system.time({
      for(xx in seq_along(cc)) {
        mm <- setdiff(full, names(cc[[xx]]))
        if(length(mm) || all(names(cc[[xx]]) == full)){
        cc[[xx]] <- as.data.table(cc[[xx]])
        # any missing columns
      
        if(length(mm)){
        # if required add additional columns
          cc[[xx]][, (mm) := as.list(rep(NA_real_, length(mm)))]
        }
        # put columns in correct order
        setcolorder(cc[[xx]], full) 
        }
      }
      
       cdt <- rbindlist(cc)
      })
      
      #   user  system elapsed 
      # 21.83    0.06   22.00 
      

      这里留下第二个解决方案来说明data.table 的使用不当。

      【讨论】:

      • 酷,谢谢。我得留意那个包裹。为后代着想:代码目前在前面提到的“基准测试笔记本电脑”上需要大约 25 秒。
      • @JeffAllen -- 我已经更新了 data.table 解决方案,该解决方案的速度提高了约 31 倍(在我的机器上)(0.7 秒比 22 秒)
      【解决方案4】:

      这是我最初的想法。它不会加快你的方法,但它确实大大简化了代码:

      # makeDF <- function(List, Names) {
      #     m <- t(sapply(List, function(X) unlist(X)[Names], 
      #     as.data.frame(m)
      # }    
      
      ## vapply() is a bit faster than sapply()
      makeDF <- function(List, Names) {
          m <- t(vapply(List, 
                        FUN = function(X) unlist(X)[Names], 
                        FUN.VALUE = numeric(length(Names))))
          as.data.frame(m)
      }
      
      ## Test timing with a 50k-item list
      ll <- createList(50000)
      nms <- c("a", "b", "c")
      
      system.time(makeDF(ll, nms))
      # user  system elapsed 
      # 0.47    0.00    0.47 
      

      【讨论】:

      • 您可以通过将sapply(...) 替换为vapply(..., numeric(length(Names)) 来稍微插入一下。将很难被击败。
      • @flodel -- 你是对的!这将时间缩短了约 20%。感谢您的提示
      • 整洁!为了基准测试:它在我的机器上运行了 0.31 秒。我认为当需要对函数进行调整时,额外 5% 的 CPU 时间是值得的——我实际上能够记住这段代码在做什么......
      • 您可以通过手动将列表转换为数据框而不进行复制再减少约 30%:class(m) &lt;- "data.frame"; attr(m, "row.names") &lt;- c(NA_integer_, -length(m[[1]]))
      • @hadley -- 有趣的想法,但我的m 是一个矩阵,因此手动转换会产生一个 1×150000 的 data.frame。如果我使用lapply() 而不是vapply() 创建一个列表,您的代码会节省大约5% 的时间,但最终会得到一个3×50000(而不是50000×3)的data.frame。不过,很高兴看到如何在没有副本的情况下将列表转换为 data.frame。
      【解决方案5】:

      这是一个简短的答案,不过我怀疑它会很快。

      > library(plyr)
      > rbind.fill(lapply(x, as.data.frame))
        a  b c
       1 1  3 5
       2 3 NA 2
      

      【讨论】:

      • 是的。只是调用as.data.frame 50k 次的部分在我的机器上需要 27 秒,然后 rbind.fill() 在通过 50k data.frames 时完全窒息。这对于小问题来说非常简洁,但看起来不能很好地扩展。
      【解决方案6】:

      好吧,我尝试了第一个想法,性能并没有我担心的那么糟糕,但我确信仍有改进的空间(尤其是在浪费矩阵 -> data.frame 转换方面)。

      convertList <- function(myList, ids){
          #this computes a list of the numerical index for each value to handle the missing/
          # improperly ordered list elements. So it will have a list in which each element 
          # associated with A has a value of 1, B ->2, and C -> 3. So a row containing
          # A=_, C=_, B=_ would have a value of `1,3,2`
          idInd <- lapply(myList, function(x){match(names(x), ids)})
      
          # Calculate the row indices if I were to unlist myList. So if there were two elements
          # in the first row, 3 in the third, and 1 in the fourth, you'd see: 1, 1, 2, 2, 2, 3
          rowInd <- inverse.rle(list(values=1:length(myList), lengths=sapply(myList, length)))
      
          #Unlist the first list created to just be a numerical matrix
          idInd <- unlist(idInd)
      
          #create a grid of addresses. The first column is the row address, the second is the col
          address <- cbind(rowInd, idInd)
      
          #have to use a matrix because you can't assign a data.frame 
          # using an addressing table like we have above
          mat <- matrix(ncol=length(ids), nrow=length(myList))
      
          # assign the values to the addresses in the matrix
          mat[address] <- unlist(myList)
      
          # convert to data.frame
          df <- as.data.frame(mat)
          colnames(df) <- ids
      
          df
      }   
      myList <- createList(50000)
      ids <- letters[1:3]
      
      system.time(df <- convertList(myList, ids))
      

      在我的笔记本电脑(Windows 7、Intel i7 M620 @ 2.67 GHz、4GB RAM)上转换 50,000 行大约需要 0.29 秒。

      仍然对其他答案非常感兴趣!

      【讨论】:

      • 对于 Interweb 的未来用户:这个解决方案最终是最快的,大约 5%,但肯定是最臃肿且最难维护的。
      猜你喜欢
      • 2016-03-03
      • 1970-01-01
      • 1970-01-01
      • 2017-11-16
      • 2018-06-20
      • 2020-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多