functions - Convierte la lista de longitud mixta denominada data.frame
r data frame format (6)
Tengo una lista del siguiente formato:
[[1]]
[[1]]$a
[1] 1
[[1]]$b
[1] 3
[[1]]$c
[1] 5
[[2]]
[[2]]$c
[1] 2
[[2]]$a
[1] 3
Hay una lista predefinida de posibles "claves" ( a
, b
, c
, en este caso) y cada elemento de la lista ("fila") tendrá valores definidos para una o más de estas claves. Estoy buscando una manera rápida de pasar de la estructura de lista anterior a un data.frame que se vería como el siguiente, en este caso:
a b c
1 1 3 5
2 3 NA 2
¡Cualquier ayuda sería apreciada!
Apéndice
Estoy tratando con una tabla que tendrá hasta 50,000 filas y 3-6 columnas, con la mayoría de los valores especificados. Tomaré la tabla desde JSON e intentaré introducirla rápidamente en la estructura data.frame.
Aquí hay un código para crear una lista de muestra de la escala con la que estaré trabajando:
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
}
Simplemente cree una lista con un approxSize
de 50,000 para probar el rendimiento en una lista de este tamaño.
Aquí está mi pensamiento inicial. No acelera su enfoque, pero simplifica el código considerablemente:
# 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
Aquí hay una respuesta corta, aunque dudo que sea muy rápido.
> library(plyr)
> rbind.fill(lapply(x, as.data.frame))
a b c
1 1 3 5
2 3 NA 2
Bueno, lo intenté por primera vez y el rendimiento no fue tan malo como lo temía, pero estoy seguro de que todavía hay margen de mejora (especialmente en la matriz de residuos -> conversión de datos en cuadros).
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))
Se tarda unos 0.29 segundos para convertir las 50,000 filas en mi computadora portátil (Windows 7, Intel i7 M620 a 2.67 GHz, 4GB de RAM).
Todavía muy interesado en otras respuestas!
En 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
Sé que esta es una pregunta antigua, pero acabo de encontrarla y es insoportable no ver la solución más simple que conozco. Así que aquí está (simplemente especifique ''fill = TRUE'' en 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
No sé si esta es la forma más rápida, pero estaría dispuesto a apostar a que compite, dado el diseño cuidadoso de la tabla de datos y el rendimiento extremadamente bueno en muchas otras tareas.
Si conoce los posibles valores de antemano y está tratando con datos grandes, quizás el uso de data.table
y set
sea rápido
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
Antiguo (solución lenta) para la posteridad.
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
Esta segunda solución se ha dejado aquí para mostrar cómo data.table
se puede utilizar de manera deficiente.