Преобразовать список фреймов данных в один фрейм данных

195

У меня есть код, который в одном месте заканчивается списком кадров данных, которые я действительно хочу преобразовать в один большой фрейм данных.

Я получил несколько указателей из более раннего вопроса , который пытался сделать что-то подобное, но более сложное.

Вот пример того, с чего я начинаю (это очень упрощено для иллюстрации):

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

В настоящее время я использую это:

  df <- do.call("rbind", listOfDataFrames)
  • 0
    Также смотрите этот вопрос: stackoverflow.com/questions/2209258/…
  • 22
    do.call("rbind", list) - это то, что я использовал и раньше. Зачем вам нужен первоначальный unlist ?
Показать ещё 3 комментария
Теги:
dataframe
list

8 ответов

144
Лучший ответ

Еще одна опция - использовать функцию plyr:

df <- ldply(listOfDataFrames, data.frame)

Это немного медленнее оригинала:

> 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

Мое предположение заключается в том, что использование do.call("rbind", ...) будет самым быстрым подходом, который вы найдете, если не сможете сделать что-то вроде (a) использовать матрицы вместо data.frames и (b) предустановить окончательную матрицу и назначить к ней, а не к ее росту.

Изменить 1:

Основываясь на комментарии Хэдли, здесь последняя версия rbind.fill из CRAN:

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

Это проще, чем rbind и немного быстрее (эти тайминги сохраняются в течение нескольких прогонов). И насколько я понимаю, версия plyr в github еще быстрее, чем это.

  • 26
    rbind.fill в последней версии plyr значительно быстрее, чем do.call и rbind
  • 1
    интересно. для меня rbind.fill был самым быстрым. Как ни странно, do.call / rbind не вернул идентичное ИСТИНА, даже если я не смог найти разницу. Два других были равны, но Плир был медленнее.
Показать ещё 4 комментария
72

В целях полноты, я думал, что ответы на этот вопрос требуют обновления. "Я предполагаю, что использование do.call("rbind", ...) будет самым быстрым подходом, который вы найдете..." Вероятно, это было в мае 2010 года и некоторое время спустя, но примерно в сентябре 2011 года появилась новая функция rbindlist пакет версии data.table версии 1.8.2 с замечанием "Это делает то же самое, что и do.call("rbind",l), но намного быстрее". Насколько быстрее?

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
  • 2
    Огромное спасибо за это - я выдернул свои волосы, потому что мои наборы данных становились слишком большими, чтобы ldply кучу длинных расплавленных кадров данных. В любом случае, я получил невероятное ускорение благодаря вашему предложению rbindlist .
  • 10
    И еще один для полноты: dplyr::rbind_all(listOfDataFrames) добьется dplyr::rbind_all(listOfDataFrames) .
Показать ещё 2 комментария
31

Существует также bind_rows(x, ...) в 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
  • 0
    технически говоря, вам не нужен as.data.frame - все, что делает его, делает его исключительно data.frame, в отличие от table_df (от deplyr)
25

Изображение 4327

код:

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)

Сеанс:

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’
  • 2
    Это отличный ответ. Я запустил одно и то же (та же ОС, те же пакеты, другая рандомизация, потому что вы не set.seed ), но увидел некоторые различия в производительности в худшем случае rbindlist самом деле имел лучший худший случай, а также лучший типичный случай в моих результатах
5

Здесь можно сделать другой способ (просто добавив его в ответы, потому что reduce - очень эффективный функциональный инструмент, который часто игнорируется как замена циклов. В этом конкретном случае ни один из них не намного быстрее, чем .call)

с использованием базы R:

df <- Reduce(rbind, listOfDataFrames)

или, используя tidyverse:

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

Как это сделать в tidyverse:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)
  • 5
    df_dplyr_purrr если вы хотите быть чистым сторонником
  • 0
    @yeedle Спасибо - почти что проскользнуло;)
Показать ещё 2 комментария
3

Единственное, что отсутствуют в решениях с data.table, - это столбец идентификатора, чтобы узнать, из какого файла данных в списке поступают данные.

Что-то вроде этого:

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

Параметр idcol добавляет столбец (.id), идентифицирующий источник данных, содержащийся в списке. Результат будет выглядеть примерно так:

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

Обновленный визуальный для тех, кто хочет сравнить некоторые из последних ответов (я хотел сравнить purrr с решением dplyr). В основном я комбинировал ответы от @TheVTM и @rmf.

Изображение 4328

код:

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)

Информация о сеансе:

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

Версии пакетов:

> packageVersion("tidyverse")
[1] ‘1.1.1’
> packageVersion("data.table")
[1] ‘1.10.0’

Ещё вопросы

Сообщество Overcoder
Наверх
Меню