matrices - Fusión vectorizada rápida de la lista de data.frames por fila
filtrar datos en r (3)
La mayoría de las preguntas sobre la fusión de data.frame en listas en SO no se relacionan exactamente con lo que intento transmitir aquí, pero pueden probar que estoy equivocado.
Tengo una lista de data.frames. Me gustaría "juntar" filas en otro data.frame por fila. En esencia, todas las primeras filas forman un data.frame, second rows second data.frame y así sucesivamente. El resultado sería una lista de la misma longitud que el número de filas en mi data.frame (s) original. Hasta ahora, los data.frames son idénticos en dimensiones.
Aquí hay algunos datos para jugar.
sample.list <- list(data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)),
data.frame(x = sample(1:100, 10), y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)))
Esto es lo que se me ocurrió con el buen ol ''for loop.
#solution 1
my.list <- vector("list", nrow(sample.list[[1]]))
for (i in 1:nrow(sample.list[[1]])) {
for (j in 1:length(sample.list)) {
my.list[[i]] <- rbind(my.list[[i]], sample.list[[j]][i, ])
}
}
#solution 2 (so far my favorite)
sample.list2 <- do.call("rbind", sample.list)
my.list2 <- vector("list", nrow(sample.list[[1]]))
for (i in 1:nrow(sample.list[[1]])) {
my.list2[[i]] <- sample.list2[seq(from = i, to = nrow(sample.list2), by = nrow(sample.list[[1]])), ]
}
¿Se puede mejorar esto utilizando la vectorización sin mucho daño cerebral? La respuesta correcta contendrá un fragmento de código, por supuesto. "Sí" como una respuesta no cuenta.
EDITAR
#solution 3 (a variant of solution 2 above)
ind <- rep(1:nrow(sample.list[[1]]), times = length(sample.list))
my.list3 <- split(x = sample.list2, f = ind)
BENCHMARKING
He hecho mi lista más grande con más filas por data.frame. He comparado los resultados que son los siguientes:
#solution 1
system.time(for (i in 1:nrow(sample.list[[1]])) {
for (j in 1:length(sample.list)) {
my.list[[i]] <- rbind(my.list[[i]], sample.list[[j]][i, ])
}
})
user system elapsed
80.989 0.004 81.210
# solution 2
system.time(for (i in 1:nrow(sample.list[[1]])) {
my.list2[[i]] <- sample.list2[seq(from = i, to = nrow(sample.list2), by = nrow(sample.list[[1]])), ]
})
user system elapsed
0.957 0.160 1.126
# solution 3
system.time(split(x = sample.list2, f = ind))
user system elapsed
1.104 0.204 1.332
# solution Gabor
system.time(lapply(1:nr, bind.ith.rows))
user system elapsed
0.484 0.000 0.485
# solution ncray
system.time(alply(do.call("cbind",sample.list), 1,
.fun=matrix, ncol=ncol(sample.list[[1]]), byrow=TRUE,
dimnames=list(1:length(sample.list),names(sample.list[[1]]))))
user system elapsed
11.296 0.016 11.365
Aquí está mi intento con plyr, pero me gusta el enfoque de G. Grothendieck:
library(plyr)
alply(do.call("cbind",sample.list), 1, .fun=matrix,
ncol=ncol(sample.list[[1]]), byrow=TRUE,
dimnames=list(1:length(sample.list),
names(sample.list[[1]])
))
Prueba esto:
bind.ith.rows <- function(i) do.call(rbind, lapply(sample.list, "[", i, TRUE))
nr <- nrow(sample.list[[1]])
lapply(1:nr, bind.ith.rows)
Un par de soluciones que lo harán más rápido usando data.table
EDITAR - con un conjunto de datos más grande que muestra la data.table
datos. data.table
más.
# here are some sample data
sample.list <- replicate(10000, data.frame(x = sample(1:100, 10),
y = sample(1:100, 10), capt = sample(0:1, 10, replace = TRUE)), simplify = F)
La solución rápida de Gabor:
# Solution Gabor
bind.ith.rows <- function(i) do.call(rbind, lapply(sample.list, "[", i, TRUE))
nr <- nrow(sample.list[[1]])
system.time(rowbound <- lapply(1:nr, bind.ith.rows))
## user system elapsed
## 25.87 0.01 25.92
La función rbindlist
lo hará aún más rápido incluso cuando se trabaja con data.frames)
library(data.table)
fastbind.ith.rows <- function(i) rbindlist(lapply(sample.list, "[", i, TRUE))
system.time(fastbound <- lapply(1:nr, fastbind.ith.rows))
## user system elapsed
## 13.89 0.00 13.89
Una solución data.table
Aquí hay una solución que usa data.tables: es una solución split
en esteroides.
# data.table solution
system.time({
# change each element of sample.list to a data.table (and data.frame) this
# is done instaneously by reference
invisible(lapply(sample.list, setattr, name = "class",
value = c("data.table", "data.frame")))
# combine into a big data set
bigdata <- rbindlist(sample.list)
# add a row index column (by refere3nce)
index <- as.character(seq_len(nr))
bigdata[, `:=`(rowid, index)]
# set the key for binary searches
setkey(bigdata, rowid)
# split on this -
dt_list <- lapply(index, function(i, j, x) x[i = J(i)], x = bigdata)
# if you want to drop the `row id` column
invisible(lapply(dt_list, function(x) set(x, j = "rowid", value = NULL)))
# if you really don''t want them to be data.tables run this line
# invisible(lapply(dt_list, setattr,name = ''class'', value =
# c(''data.frame'')))
})
################################
## user system elapsed ##
## 0.08 0.00 0.08 ##
################################
¡Qué impresionante es data.table
!
Usuario de rbindlist
con rbindlist
rbindlist
es rápido porque no realiza la comprobación de que do.call(rbind,....)
hará. Por ejemplo, asume que las columnas de factores tienen los mismos niveles que en el primer elemento de la lista.