Преобразовать список имен смешанной длины в 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 для проверки производительности в списке такого размера.
Ответы
Ответ 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