Подтвердить что ты не робот

Преобразовать список имен смешанной длины в data.frame

У меня есть список следующего формата:

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

[[1]]$b
[1] 3

[[1]]$c
[1] 5

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

[[2]]$a
[1] 3

В этом случае существует предопределенный список возможных "ключей" (a, b и c), и каждый элемент в списке ( "строка" ) будет иметь значения, определенные для одного или нескольких этих ключей. Я ищу быстрый способ получить из структуры списка выше в 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
}

Просто создайте список с approxSize из 50 000 для проверки производительности в списке такого размера.

4b9b3361

Ответ 1

Вот моя первоначальная мысль. Это не ускоряет ваш подход, но значительно упрощает код:

# 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 

Ответ 2

Вот короткий ответ, я сомневаюсь, что это будет очень быстро, хотя.

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

Ответ 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 можно использовать плохо.

Ответ 4

Хорошо, я впервые попытался, и производительность была не такой плохой, как я боялся, но я уверен, что еще есть возможности для улучшения (особенно в матрице waster matrix → data.frame conversion).

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

Это займет около 0,29 секунды, чтобы конвертировать 50 000 строк на моем ноутбуке (Windows 7, Intel i7 M620 @2,67 ГГц, 4 ГБ ОЗУ).

Все еще очень заинтересованы в других ответах!

Ответ 5

Я знаю, что это старый вопрос, но я просто наткнулся на него, и это мучительно, чтобы не увидеть самое простое решение, о котором я знаю. Итак, вот это (просто укажите "fill = TRUE" в rbindlist):

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 и чрезвычайно хорошую производительность по многим другим задачам.

Ответ 6

В 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