linhas data.frame para uma lista


123

Eu tenho um data.frame que gostaria de converter em uma lista por linhas, o que significa que cada linha corresponderia aos seus próprios elementos de lista. Em outras palavras, eu gostaria de uma lista que contenha o data.frame com linhas.

Até agora, lidei com esse problema da seguinte maneira, mas fiquei pensando se há uma maneira melhor de abordar isso.

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

Respostas:


163

Como isso:

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

E se você quiser xy.dfque os nomes de nomes sejam os nomes da lista de saída, você pode:

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

4
Observe que, depois de usar splitcada elemento, ele tem o tipo em data.frame with 1 rows and N columnsvez de #list of length N
Karol Daniluk 19/02/19

Gostaria apenas de acrescentar que, se você usar split, provavelmente deverá fazer o drop=Tcontrário, seus níveis originais de fatores não cairão
Denis

51

Eureka!

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

1
Cuidados para demonstrar como usar aplicar?
Roman Luštrik 17/08/10

3
unlist(apply(xy.df, 1, list), recursive = FALSE). No entanto, a solução do flodel é mais eficiente do que usar applyor t.
Arun 14/05

11
O problema aqui é que tconverte a data.fameem matrixpara que os elementos em sua lista sejam vetores atômicos, não listados como o OP solicitado. Normalmente não é um problema até que o seu xy.dfcontém tipos mistos ...
Calimo

2
Se você deseja fazer um loop sobre os valores, eu não recomendo apply. Na verdade, é apenas um loop for implementado em R. lapplyexecuta o loop em C, que é significativamente mais rápido. Esse formato de lista de linhas é realmente preferível se você estiver fazendo muitas repetições.
Liz Sander

1
Adicionando outro comentário do futuro, uma applyversão é.mapply(data.frame, xy.df, NULL)
alexis_laz

15

Se você deseja abusar completamente do data.frame (como eu) e gostaria de manter a funcionalidade $, uma maneira é dividir seu data.frame em uma linha data.frames reunidos em uma 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

Não é apenas masturbação intelectual, mas permite 'transformar' o data.frame em uma lista de suas linhas, mantendo a indexação $ que pode ser útil para uso posterior com lapply (assumindo que a função que você passa para lapply use essa indexação $)


Como os reunimos novamente? Transformar uma lista de data.frames em um único data.frame?
Aaron McDaid

4
@AaronMcDaid Você pode usar do.call e rbind: df == do.call ("rbind", ldf)
random_forest_fanatic

@AaronMcDaid Ou data.table :: rbindlist (). Se o seu quadro de dados original for grande, os ganhos de velocidade serão significativos.
Empiromancer

8

Uma solução mais moderna usa apenas 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

8

Eu estava trabalhando nisso hoje para um data.frame (realmente um data.table) com milhões de observações e 35 colunas. Meu objetivo era retornar uma lista de data.frames (data.tables), cada uma com uma única linha. Ou seja, eu queria dividir cada linha em um data.frame separado e armazená-los em uma lista.

Aqui estão dois métodos que eu criei que eram aproximadamente três vezes mais rápidos do que split(dat, seq_len(nrow(dat)))para esse conjunto de dados. Abaixo, comparo os três métodos em um conjunto de dados de 5 colunas e 7500 linhas, com 7500 linhas ( íris repetida 50 vezes).

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
) 

Isso retorna

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

Embora as diferenças não sejam tão grandes quanto no meu teste anterior, o setDFmétodo direto é significativamente mais rápido em todos os níveis da distribuição de execuções com max (setDF) <min (split) e o attrmétodo geralmente é mais do que o dobro da velocidade.

Um quarto método é o campeão extremo, que é um simples aninhado lapply, retornando uma lista aninhada. Este método exemplifica o custo de construção de um data.frame a partir de uma lista. Além disso, todos os métodos que tentei com a data.framefunção eram aproximadamente uma ordem de magnitude mais lenta que as data.tabletécnicas.

dados

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

6

Parece que uma versão atual do purrrpacote (0.2.2) é a solução mais rápida:

by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out

Vamos comparar as soluções mais interessantes:

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

Também podemos obter o mesmo resultado com 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;
}

Agora compare com 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

avaliação comparativa em um pequeno conjunto de dados de 150 linhas não faz muito sentido, já que ninguém vai notar nenhuma diferença em microssegundos e não escalar
David Arenburg

4
by_row()foi agora transferido paralibrary(purrrlyr)
MrHopko

Além de estar no purrrlyr, está prestes a ser preterido. Agora há outros métodos que combinam tidyr :: ninho, dplyr :: mutação purrr :: mapear para conseguir o mesmo resultado
Mike Stanley

3

Mais algumas opções:

Com 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 
#....

Com spliterow

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
#....

dados

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

2

A melhor maneira para mim foi:

Dados de exemplo:

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

Chamamos a BBmiscbiblioteca

library(BBmisc)

data$lists<-convertRowsToList(data[,2:4])

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

1

Uma maneira alternativa é converter o df em uma matriz e aplicar a lappyfunção de aplicação de lista sobre ela:ldf <- lapply(as.matrix(myDF), function(x)x)


1

Outra alternativa de uso library(purrr)(que parece ser um pouco mais rápida em grandes data.frames)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))

3
`by_row ()` agora foi transferido para `library (purrrlyr)`
MrHopko 17/08/19

1

Como o @flodel escreveu: Isso converte seu quadro de dados em uma lista que possui o mesmo número de elementos que o número de linhas no quadro de dados:

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

Além disso, você pode adicionar uma função para selecionar apenas as colunas que não são NA em cada elemento da lista:

NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])

0

A by_rowfunção do purrrlyrpacote fará isso por você.

Este exemplo demonstra

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 padrão, o valor retornado de myfné colocado em uma nova coluna da lista no df chamado .out. O $.outno final da instrução acima seleciona imediatamente essa coluna, retornando uma lista de listas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.