data.frame filas a una lista

123

Tengo un data.frame que me gustaría convertir en 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 abordarlo.

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,]
}
Roman Luštrik
fuente

Respuestas:

163

Me gusta esto:

xy.list <- split(xy.df, seq(nrow(xy.df)))

Y si desea que los nombres de fila de xy.dfsean los nombres de la lista de salida, puede hacer:

xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
flodel
fuente
44
Tenga en cuenta que, después de usar splitcada elemento tiene tipo en data.frame with 1 rows and N columnslugar delist of length N
Karol Daniluk
Solo agregaría que si lo usa split, probablemente debería hacer lo drop=Tcontrario, sus niveles originales de factores no caerán
Denis
51

Eureka!

xy.list <- as.list(as.data.frame(t(xy.df)))
Roman Luštrik
fuente
1
Cuidado para demostrar cómo utilizar aplicar?
Roman Luštrik
3
unlist(apply(xy.df, 1, list), recursive = FALSE). Sin embargo, la solución de flodel es más eficiente que usar applyo t.
Arun
11
El problema aquí es que tconvierte data.famea a matrixpara que los elementos en su lista sean vectores atómicos, no una lista como la OP solicitada. Por lo general, no es un problema hasta que xy.dfcontiene tipos mixtos ...
Calimo
2
Si desea recorrer los valores, no lo recomiendo apply. En realidad, es solo un bucle for implementado en R. lapplyrealiza el bucle en C, que es significativamente más rápido. Este formato de lista de filas es realmente preferible si está haciendo muchos bucles.
Liz Sander
1
Agregando otro comentario del futuro, una applyversión es.mapply(data.frame, xy.df, NULL)
alexis_laz
15

Si desea abusar por completo de data.frame (como lo hago yo) y desea mantener la funcionalidad $, una forma es dividir data.frame en data.frames de una línea 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 una 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 su uso posterior con lapply (suponiendo que la función que pasa a lapply usa esta indexación $)

Qiou Bi
fuente
¿Cómo los volvemos a armar? ¿Convertir una lista de data.frames en una sola data.frame?
Aaron McDaid
44
@AaronMcDaid Puede usar do.call y rbind: df == do.call ("rbind", ldf)
random_forest_fanatic
@AaronMcDaid O data.table :: rbindlist (). Si su marco de datos original era grande, las ganancias de velocidad serán significativas.
Empiromancer
8

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
Mike Stanley
fuente
8

Trabajé en esto hoy para un data.frame (realmente un 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 marco de datos independiente 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, comparo 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 vuelve

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 setDFmétodo directo es significativamente más rápido en todos los niveles de distribución de ejecuciones con max (setDF) <min (split) y el attrmétodo suele ser más del doble de rápido.

Un cuarto método es el campeón extremo, que es un simple anidado lapply, 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 data.framefunción fueron aproximadamente un orden de magnitud más lentos que las data.tabletécnicas.

datos

dat <- vector("list", 50)
for(i in 1:50) dat[[i]] <- iris
dat <- setDF(rbindlist(dat))
lmo
fuente
6

Parece que una versión actual del purrrpaquete (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
)

Resultados:

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 caompare 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
Artem Klevtsov
fuente
evaluación comparativa en un pequeño conjunto de datos de 150 filas no tiene mucho sentido, ya que nadie notará ninguna diferencia en microsegundos y no escala
David Arenburg
44
by_row()ahora se mudó alibrary(purrrlyr)
MrHopko
Y además de estar en ronroneo, está a punto de quedar en desuso. Ahora hay otros métodos que combinan tidyr :: nest, dplyr :: mutate purrr :: map para lograr el mismo resultado
Mike Stanley
3

Un par de opciones más:

Con asplit

asplit(xy.df, 1)
#[[1]]
#     x      y 
#0.1137 0.6936 

#[[2]]
#     x      y 
#0.6223 0.5450 

#[[3]]
#     x      y 
#0.6093 0.2827 
#....

Con splityrow

split(xy.df, row(xy.df)[, 1])

#$`1`
#       x      y
#1 0.1137 0.6936

#$`2`
#       x     y
#2 0.6223 0.545

#$`3`
#       x      y
#3 0.6093 0.2827
#....

datos

set.seed(1234)
xy.df <- data.frame(x = runif(10),  y = runif(10))
Ronak Shah
fuente
2

La mejor manera para mí fue:

Datos de ejemplo:

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 BBmiscbiblioteca

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) 
Cro-Magnon
fuente
1

Una forma alternativa es convertir el df en una matriz y luego aplicar la lappyfunción de aplicación de lista sobre él:ldf <- lapply(as.matrix(myDF), function(x)x)

usuario3553260
fuente
1

Otra alternativa de uso library(purrr)(que parece ser un poco más rápido en grandes data.frames)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
MrHopko
fuente
3
`by_row ()` ahora se ha movido a `library (purrrlyr)`
MrHopko
1

Como escribió @flodel: Esto convierte su marco de datos en una lista que tiene el mismo número de elementos que el número de filas en el marco de datos:

NewList <- split(df, f = seq(nrow(df)))

Además, 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)])
michal
fuente
0

La by_rowfunción del purrrlyrpaquete lo hará 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

Por defecto, el valor devuelto desde myfnse coloca en una nueva columna de lista en el df llamado .out. El $.outfinal de la declaración anterior selecciona inmediatamente esta columna, devolviendo una lista de listas.

RobinL
fuente