filas de data.frame a una lista

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,] } 

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

Eureka!

 xy.list <- as.list(as.data.frame(t(xy.df))) 

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 xy 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]] xy 1 a 3 [[2]] xy 2 b 2 [[3]] xy 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)

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  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(x[j]); RObject t2 = wrap(t[i]); t2.attr("class") = "factor"; t2.attr("levels") = t.attr("levels"); tmp[j] = t2; } else { tmp[j] = as(x[j])[i]; } break; } case LGLSXP: { tmp[j] = as(x[j])[i]; break; } case CPLXSXP: { tmp[j] = as(x[j])[i]; break; } case REALSXP: { tmp[j] = as(x[j])[i]; break; } case STRSXP: { tmp[j] = as(as(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 

Estuve trabajando en esto hoy para un data.frame (realmente una data.table) con millones de observaciones y 35 columnas. Mi objective 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) 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)) 

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

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) 

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)

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 statement anterior selecciona inmediatamente esta columna y devuelve una lista de listas.

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 

Al igual que @flodel escribió: Esto convierte su dataframe en una lista que tiene la misma cantidad de elementos que el número de filas en el dataframe:

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