Converter uma lista de quadros de dados em um quadro de dados

336

Eu tenho um código que em um local termina com uma lista de quadros de dados que eu realmente quero converter em um único quadro de big data.

Eu recebi alguns indicadores de uma pergunta anterior que estava tentando fazer algo semelhante, mas mais complexo.

Aqui está um exemplo do que estou começando (isso é bastante simplificado para ilustração):

listOfDataFrames <- vector(mode = "list", length = 100)

for (i in 1:100) {
    listOfDataFrames[[i]] <- data.frame(a=sample(letters, 500, rep=T),
                             b=rnorm(500), c=rnorm(500))
}

Atualmente, estou usando isso:

  df <- do.call("rbind", listOfDataFrames)
JD Long
fonte
Consulte também esta pergunta: stackoverflow.com/questions/2209258/…
Shane
27
O do.call("rbind", list)idioma é o que eu já usei antes. Por que você precisa da inicial unlist?
Dirk Eddelbuettel
5
alguém pode me explicar a diferença entre do.call ("rbind", list) e rbind (list) - por que as saídas não são as mesmas?
user6571411
11
@ user6571411 Porque do.call () não devolve os argumentos um por um, mas utiliza uma lista para conter os argumentos da função. Veja https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Respostas:

130

Use bind_rows () do pacote dplyr:

bind_rows(list_of_dataframes, .id = "column_label")
joeklieg
fonte
5
Ótima solução. .id = "column_label"adiciona os nomes de linha exclusivos com base nos nomes dos elementos da lista.
Sibo Jiang 29/04
10
como é 2018 e dplyré uma ferramenta rápida e sólida de usar, alterei isso para a resposta aceita. Os anos, eles voam!
JD Longo
186

Uma outra opção é usar uma função plyr:

df <- ldply(listOfDataFrames, data.frame)

Isso é um pouco mais lento que o original:

> system.time({ df <- do.call("rbind", listOfDataFrames) })
   user  system elapsed 
   0.25    0.00    0.25 
> system.time({ df2 <- ldply(listOfDataFrames, data.frame) })
   user  system elapsed 
   0.30    0.00    0.29
> identical(df, df2)
[1] TRUE

Meu palpite é que usar do.call("rbind", ...)será a abordagem mais rápida que você encontrará, a menos que você possa fazer algo como (a) usar matrizes em vez de data.frames e (b) pré-alocar a matriz final e atribuí-la a ela, em vez de aumentá-la. .

Editar 1 :

Com base no comentário de Hadley, aqui está a versão mais recente rbind.filldo CRAN:

> system.time({ df3 <- rbind.fill(listOfDataFrames) })
   user  system elapsed 
   0.24    0.00    0.23 
> identical(df, df3)
[1] TRUE

Isso é mais fácil do que o rbind e marginalmente mais rápido (esses tempos são mantidos em várias execuções). E até onde eu entendo, a versão do plyron github é ainda mais rápida que isso.

Shane
fonte
28
rbind.fill na última versão do plyr é consideravelmente mais rápido do que do.call e rbind
Hadley
11
interessante. para mim, o rbind.fill foi o mais rápido. Estranho o suficiente, do.call / rbind não retornou VERDADEIRO idêntico, mesmo que eu pudesse encontrar uma diferença. Os outros dois eram iguais, mas plyr era mais lento.
Matt Bannert
I()poderia substituir data.frameem sua ldplychamada
baptiste
4
há também melt.listem remodelar (2)
Baptiste
do.call(function(...) rbind(..., make.row.names=F), df)é útil se você não desejar os nomes de nomes de usuário exclusivos gerados automaticamente.
SMCI
111

Para fins de completude, pensei que as respostas a essa pergunta exigiam uma atualização. "Meu palpite é que o uso do.call("rbind", ...)será a abordagem mais rápida que você encontrará ..." Provavelmente era verdade para maio de 2010 e algum tempo depois, mas por volta de setembro de 2011 uma nova função rbindlistfoi introduzida na data.tableversão 1.8.2 do pacote , com uma observação de que "Isso faz o mesmo que do.call("rbind",l), mas muito mais rápido". Quão rápido?

library(rbenchmark)
benchmark(
  do.call = do.call("rbind", listOfDataFrames),
  plyr_rbind.fill = plyr::rbind.fill(listOfDataFrames), 
  plyr_ldply = plyr::ldply(listOfDataFrames, data.frame),
  data.table_rbindlist = as.data.frame(data.table::rbindlist(listOfDataFrames)),
  replications = 100, order = "relative", 
  columns=c('test','replications', 'elapsed','relative')
  ) 

                  test replications elapsed relative
4 data.table_rbindlist          100    0.11    1.000
1              do.call          100    9.39   85.364
2      plyr_rbind.fill          100   12.08  109.818
3           plyr_ldply          100   15.14  137.636
andrekos
fonte
3
Muito obrigado por isso - eu estava arrancando os cabelos porque meus conjuntos de dados estavam ficando grandes demais para ldplyum monte de quadros de dados longos e fundidos. De qualquer forma, recebi uma aceleração incrível usando sua rbindlistsugestão.
KarateSnowMachine
11
E mais uma por completude: dplyr::rbind_all(listOfDataFrames)também fará o truque.
18714 Andyteucher
2
existe um equivalente a rbindlistmas que acrescenta os quadros de dados por coluna? algo como uma lista cbind?
Rafa.pereira
2
@ rafa.pereira Existe uma solicitação recente de recurso: add function cbindlist #
Henrik
Eu também estava puxando meu cabelo porque do.call()estava em uma lista de quadros de dados há 18 horas e ainda não tinha terminado, obrigado !!!
Graeme Frost
74

bind-plot

Código:

library(microbenchmark)

dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
  dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
                            c=rep(LETTERS,10),d=rep(LETTERS,10))
}


mb <- microbenchmark(
plyr::rbind.fill(dflist),
dplyr::bind_rows(dflist),
data.table::rbindlist(dflist),
plyr::ldply(dflist,data.frame),
do.call("rbind",dflist),
times=1000)

ggplot2::autoplot(mb)

Sessão:

R version 3.3.0 (2016-05-03)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

> packageVersion("plyr")
[1]1.8.4
> packageVersion("dplyr")
[1]0.5.0
> packageVersion("data.table")
[1]1.9.6

ATUALIZAÇÃO : Execute novamente 31 de janeiro de 2018. Funcionou no mesmo computador. Novas versões de pacotes. Sementes adicionadas para os amantes de sementes.

insira a descrição da imagem aqui

set.seed(21)
library(microbenchmark)

dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
  dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
                            c=rep(LETTERS,10),d=rep(LETTERS,10))
}


mb <- microbenchmark(
  plyr::rbind.fill(dflist),
  dplyr::bind_rows(dflist),
  data.table::rbindlist(dflist),
  plyr::ldply(dflist,data.frame),
  do.call("rbind",dflist),
  times=1000)

ggplot2::autoplot(mb)+theme_bw()


R version 3.4.0 (2017-04-21)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

> packageVersion("plyr")
[1]1.8.4
> packageVersion("dplyr")
[1]0.7.2
> packageVersion("data.table")
[1]1.10.4

ATUALIZAÇÃO : Execute novamente 06-Aug-2019.

insira a descrição da imagem aqui

set.seed(21)
library(microbenchmark)

dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
  dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
                            c=rep(LETTERS,10),d=rep(LETTERS,10))
}


mb <- microbenchmark(
  plyr::rbind.fill(dflist),
  dplyr::bind_rows(dflist),
  data.table::rbindlist(dflist),
  plyr::ldply(dflist,data.frame),
  do.call("rbind",dflist),
  purrr::map_df(dflist,dplyr::bind_rows),
  times=1000)

ggplot2::autoplot(mb)+theme_bw()

R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.2.20.so

packageVersion("plyr")
packageVersion("dplyr")
packageVersion("data.table")
packageVersion("purrr")

>> packageVersion("plyr")
[1]1.8.4
>> packageVersion("dplyr")
[1]0.8.3
>> packageVersion("data.table")
[1]1.12.2
>> packageVersion("purrr")
[1]0.3.2
rmf
fonte
2
Esta é uma ótima resposta. Fiz a mesma coisa (mesmo sistema operacional, mesmos pacotes, randomização diferente porque você não o fez set.seed), mas vi algumas diferenças no pior desempenho. rbindlistNa verdade, tivemos o melhor do pior caso, bem como melhor caso típico nos meus resultados
C8H10N4O2
48

Há também bind_rows(x, ...)em dplyr.

> system.time({ df.Base <- do.call("rbind", listOfDataFrames) })
   user  system elapsed 
   0.08    0.00    0.07 
> 
> system.time({ df.dplyr <- as.data.frame(bind_rows(listOfDataFrames)) })
   user  system elapsed 
   0.01    0.00    0.02 
> 
> identical(df.Base, df.dplyr)
[1] TRUE
TheVTM
fonte
tecnicamente falando você não precisa o as.data.frame - tudo o que faz torna-se exclusivamente a data.frame, ao contrário também um table_df (de deplyr)
user1617979
14

Aqui está outra maneira de fazer isso (basta adicioná-lo às respostas, porque reduceé uma ferramenta funcional muito eficaz que muitas vezes é negligenciada como um substituto para os loops. Nesse caso em particular, nenhum deles é significativamente mais rápido que o call.c)

usando a base R:

df <- Reduce(rbind, listOfDataFrames)

ou, usando o tidyverse:

library(tidyverse) # or, library(dplyr); library(purrr)
df <- listOfDataFrames %>% reduce(bind_rows)
yeedle
fonte
11

Como isso deve ser feito no sentido inverso:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)
usuario
fonte
3
Por que você usaria mapse bind_rowspode obter uma lista de quadros de dados?
#
9

Um visual atualizado para aqueles que desejam comparar algumas das respostas recentes (eu queria comparar a solução purrr para dplyr). Basicamente, combinei respostas do @TheVTM e do @rmf.

insira a descrição da imagem aqui

Código:

library(microbenchmark)
library(data.table)
library(tidyverse)

dflist <- vector(length=10,mode="list")
for(i in 1:100)
{
  dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260),
                            c=rep(LETTERS,10),d=rep(LETTERS,10))
}


mb <- microbenchmark(
  dplyr::bind_rows(dflist),
  data.table::rbindlist(dflist),
  purrr::map_df(dflist, bind_rows),
  do.call("rbind",dflist),
  times=500)

ggplot2::autoplot(mb)

Informações da sessão:

sessionInfo()
R version 3.4.1 (2017-06-30)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

Versões do pacote:

> packageVersion("tidyverse")
[1]1.1.1
> packageVersion("data.table")
[1]1.10.0
Nova
fonte
7

A única coisa que data.tablefalta às soluções é a coluna identificadora para saber de qual quadro de dados na lista os dados são provenientes.

Algo assim:

df_id <- data.table::rbindlist(listOfDataFrames, idcol = TRUE)

O idcolparâmetro adiciona uma coluna ( .id) identificando a origem do quadro de dados contida na lista. O resultado seria algo parecido com isto:

.id a         b           c
1   u   -0.05315128 -1.31975849 
1   b   -1.00404849 1.15257952  
1   y   1.17478229  -0.91043925 
1   q   -1.65488899 0.05846295  
1   c   -1.43730524 0.95245909  
1   b   0.56434313  0.93813197  
f0nzie
fonte