17

我有以下格式的列表:

[[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
}

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

4

6 回答 6

9

这是一个简短的答案,但我怀疑它会非常快。

> library(plyr)
> rbind.fill(lapply(x, as.data.frame))
  a  b c
 1 1  3 5
 2 3 NA 2
于 2013-04-01T23:00:54.290 回答
9

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

# 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 
于 2013-04-01T23:14:44.130 回答
3

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

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使用不好。

于 2013-04-01T23:13:47.350 回答
3

我知道这是一个老问题,但我刚刚遇到它,没有看到我所知道的最简单的解决方案真是令人痛苦。所以在这里(只需在 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 的周到设计和在许多其他任务上的出色表现。

于 2016-01-07T05:30:50.173 回答
2

好吧,我尝试了第一个想法,性能并没有我担心的那么糟糕,但我确信仍有改进的空间(尤其是在浪费矩阵 -> 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 秒。

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

于 2013-04-01T22:58:11.563 回答
0

在 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
于 2017-10-13T01:57:06.157 回答