superponer - filas de data.frame a una lista
superponer graficas en r (11)
Al igual que @flodel escribió: Esto convierte su marco de datos en una lista que tiene la misma cantidad de elementos que el número de filas en el marco de datos:
NewList <- split(df, f = seq(nrow(df)))
También puede agregar una función para seleccionar solo aquellas columnas que no son NA en cada elemento de la lista:
NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])
Tengo un data.frame que me gustaría convertir a una lista por filas, lo que significa que cada fila correspondería a sus propios elementos de lista. En otras palabras, me gustaría una lista que sea tan larga como data.frame tenga filas.
Hasta ahora, he abordado este problema de la siguiente manera, pero me preguntaba si hay una mejor manera de abordar esto.
xy.df <- data.frame(x = runif(10), y = runif(10))
# pre-allocate a list and fill it with a loop
xy.list <- vector("list", nrow(xy.df))
for (i in 1:nrow(xy.df)) {
xy.list[[i]] <- xy.df[i,]
}
Estuve trabajando en esto hoy para un data.frame (realmente una data.table) con millones de observaciones y 35 columnas. Mi objetivo era devolver una lista de data.frames (data.tables) cada uno con una sola fila. Es decir, quería dividir cada fila en un data.frame separado y almacenarlos en una lista.
Aquí hay dos métodos que se me ocurrieron que fueron aproximadamente 3 veces más rápidos que split(dat, seq_len(nrow(dat)))
para ese conjunto de datos. A continuación, comparé los tres métodos en un conjunto de datos de 7500 filas y 5 columnas ( iris repetido 50 veces).
library(data.table)
library(microbenchmark)
microbenchmark(
split={dat1 <- split(dat, seq_len(nrow(dat)))},
setDF={dat2 <- lapply(seq_len(nrow(dat)),
function(i) setDF(lapply(dat, "[", i)))},
attrDT={dat3 <- lapply(seq_len(nrow(dat)),
function(i) {
tmp <- lapply(dat, "[", i)
attr(tmp, "class") <- c("data.table", "data.frame")
setDF(tmp)
})},
datList = {datL <- lapply(seq_len(nrow(dat)),
function(i) lapply(dat, "[", i))},
times=20
)
Esto regresa
Unit: milliseconds
expr min lq mean median uq max neval
split 861.8126 889.1849 973.5294 943.2288 1041.7206 1250.6150 20
setDF 459.0577 466.3432 511.2656 482.1943 500.6958 750.6635 20
attrDT 399.1999 409.6316 461.6454 422.5436 490.5620 717.6355 20
datList 192.1175 201.9896 241.4726 208.4535 246.4299 411.2097 20
Si bien las diferencias no son tan grandes como en mi prueba anterior, el método setDF
directo es significativamente más rápido en todos los niveles de la distribución de ejecuciones con max (setDF) <min (división) y el método attr
suele ser más del doble de rápido.
Un cuarto método es el campeón extremo, que es una simple aplicación anidada, que devuelve una lista anidada. Este método ejemplifica el costo de construir un data.frame a partir de una lista. Además, todos los métodos que probé con la función data.frame
eran aproximadamente un orden de magnitud más lentos que las técnicas data.table
.
datos
dat <- vector("list", 50)
for(i in 1:50) dat[[i]] <- iris
dat <- setDF(rbindlist(dat))
Eureka!
xy.list <- as.list(as.data.frame(t(xy.df)))
La función purrrlyr
paquete purrrlyr
hará esto por usted.
Este ejemplo demuestra
myfn <- function(row) {
#row is a tibble with one row, and the same number of columns as the original df
l <- as.list(row)
return(l)
}
list_of_lists <- purrrlyr::by_row(df, myfn, .labels=FALSE)$.out
De forma predeterminada, el valor devuelto de myfn
se coloca en una nueva columna de lista en el df llamado .out
. El $.out
al final de la declaración anterior selecciona inmediatamente esta columna y devuelve una lista de listas.
La mejor manera para mí fue:
Ejemplo de datos:
Var1<-c("X1",X2","X3")
Var2<-c("X1",X2","X3")
Var3<-c("X1",X2","X3")
Data<-cbind(Var1,Var2,Var3)
ID Var1 Var2 Var3
1 X1 X2 X3
2 X4 X5 X6
3 X7 X8 X9
Llamamos a la biblioteca BBmisc
library(BBmisc)
data$lists<-convertRowsToList(data[,2:4])
Y el resultado será:
ID Var1 Var2 Var3 lists
1 X1 X2 X3 list("X1", "X2", X3")
2 X4 X5 X6 list("X4","X5", "X6")
3 X7 X8 X9 list("X7,"X8,"X9)
Me gusta esto:
xy.list <- split(xy.df, seq(nrow(xy.df)))
Y si quiere que los nombres de fila de xy.df
sean los nombres de la lista de salida, puede hacer:
xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
Otra alternativa que usa la library(purrr)
(que parece ser un poco más rápida en los grandes data.frames)
flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
Parece que una versión actual del paquete purrr
(0.2.2) es la solución más rápida:
by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out
Comparemos las soluciones más interesantes:
data("Batting", package = "Lahman")
x <- Batting[1:10000, 1:10]
library(benchr)
library(purrr)
benchmark(
split = split(x, seq_len(.row_names_info(x, 2L))),
mapply = .mapply(function(...) structure(list(...), class = "data.frame", row.names = 1L), x, NULL),
purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out
)
Rsults:
Benchmark summary:
Time units : milliseconds
expr n.eval min lw.qu median mean up.qu max total relative
split 100 983.0 1060.0 1130.0 1130.0 1180.0 1450 113000 34.3
mapply 100 826.0 894.0 963.0 972.0 1030.0 1320 97200 29.3
purrr 100 24.1 28.6 32.9 44.9 40.5 183 4490 1.0
También podemos obtener el mismo resultado con Rcpp
:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
List df2list(const DataFrame& x) {
std::size_t nrows = x.rows();
std::size_t ncols = x.cols();
CharacterVector nms = x.names();
List res(no_init(nrows));
for (std::size_t i = 0; i < nrows; ++i) {
List tmp(no_init(ncols));
for (std::size_t j = 0; j < ncols; ++j) {
switch(TYPEOF(x[j])) {
case INTSXP: {
if (Rf_isFactor(x[j])) {
IntegerVector t = as<IntegerVector>(x[j]);
RObject t2 = wrap(t[i]);
t2.attr("class") = "factor";
t2.attr("levels") = t.attr("levels");
tmp[j] = t2;
} else {
tmp[j] = as<IntegerVector>(x[j])[i];
}
break;
}
case LGLSXP: {
tmp[j] = as<LogicalVector>(x[j])[i];
break;
}
case CPLXSXP: {
tmp[j] = as<ComplexVector>(x[j])[i];
break;
}
case REALSXP: {
tmp[j] = as<NumericVector>(x[j])[i];
break;
}
case STRSXP: {
tmp[j] = as<std::string>(as<CharacterVector>(x[j])[i]);
break;
}
default: stop("Unsupported type ''%s''.", type2name(x));
}
}
tmp.attr("class") = "data.frame";
tmp.attr("row.names") = 1;
tmp.attr("names") = nms;
res[i] = tmp;
}
res.attr("names") = x.attr("row.names");
return res;
}
Ahora venga con purrr
:
benchmark(
purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out,
rcpp = df2list(x)
)
Resultados:
Benchmark summary:
Time units : milliseconds
expr n.eval min lw.qu median mean up.qu max total relative
purrr 100 25.2 29.8 37.5 43.4 44.2 159.0 4340 1.1
rcpp 100 19.0 27.9 34.3 35.8 37.2 93.8 3580 1.0
Si desea abusar completamente de data.frame (como yo lo hago) y desea mantener la funcionalidad $, una forma es dividir su data.frame en una línea de datos. Marcos reunidos en una lista:
> df = data.frame(x=c(''a'',''b'',''c''), y=3:1)
> df
x y
1 a 3
2 b 2
3 c 1
# ''convert'' into a list of data.frames
ldf = lapply(as.list(1:dim(df)[1]), function(x) df[x[1],])
> ldf
[[1]]
x y
1 a 3
[[2]]
x y
2 b 2
[[3]]
x y
3 c 1
# and the ''coolest''
> ldf[[2]]$y
[1] 2
No es solo masturbación intelectual, sino que permite "transformar" el data.frame en una lista de sus líneas, manteniendo la indexación $ que puede ser útil para un uso posterior con lapply (suponiendo que la función que pasa a aplicar utiliza esta $ indexación)
Una forma alternativa es convertir el df en una matriz y luego aplicar la lista aplicar la función lappy
sobre él: ldf <- lapply(as.matrix(myDF), function(x)x)
Una solución más moderna usa solo purrr::transpose
:
library(purrr)
iris[1:2,] %>% purrr::transpose()
#> [[1]]
#> [[1]]$Sepal.Length
#> [1] 5.1
#>
#> [[1]]$Sepal.Width
#> [1] 3.5
#>
#> [[1]]$Petal.Length
#> [1] 1.4
#>
#> [[1]]$Petal.Width
#> [1] 0.2
#>
#> [[1]]$Species
#> [1] 1
#>
#>
#> [[2]]
#> [[2]]$Sepal.Length
#> [1] 4.9
#>
#> [[2]]$Sepal.Width
#> [1] 3
#>
#> [[2]]$Petal.Length
#> [1] 1.4
#>
#> [[2]]$Petal.Width
#> [1] 0.2
#>
#> [[2]]$Species
#> [1] 1