Перетворити список кадрів даних в один кадр даних


336

У мене є код, який в одному місці закінчується списком кадрів даних, які я дуже хочу перетворити на один великий кадр даних.

Я отримав кілька покажчиків з попереднього питання, яке намагалося зробити щось подібне, але більш складне.

Ось приклад того, з чого я починаю (це наочно спрощено для ілюстрації):

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)

Також дивіться це запитання: stackoverflow.com/questions/2209258/…
— Shane

27
do.call("rbind", list)Ідіома , що я використовував до того, як добре. Для чого потрібен початковий unlist?
— Дірк Еддельбуеттель

5
може хтось пояснить мені різницю між do.call ("rbind", список) і rbind (список) - чому результати не однакові?
— користувач6571411

1
@ user6571411 Оскільки do.call () не повертає аргументи один за одним, але використовує список для зберігання аргументів функції. Дивіться https://www.stat.berkeley.edu/~s133/Docall.html
— Майолейн Фоккема

Відповіді:


130

Використовуйте bind_rows () з пакету dplyr:

bind_rows(list_of_dataframes, .id = "column_label")

5
Приємне рішення. .id = "column_label"додає унікальні імена рядків на основі імен елементів списку.
— Сібо Цзян

10
оскільки це 2018 рік і dplyrє швидким і надійним інструментом для використання, я змінив це на прийняту відповідь. Роки вони пролітають!
— JD Довгий

186

Ще один варіант - використовувати функцію 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", ...)буде найшвидшим підходом, який ви знайдете, якщо ви не зможете зробити щось на зразок (а) використання матриць замість data.frames та (b) попередньо виділити остаточну матрицю та призначити їй, а не збільшувати її .

Редагувати 1 :

На основі коментаря Хедлі, ось остання версія rbind.fillCRAN:

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

Це простіше, ніж rbind, і незначно швидше (ці терміни тримаються протягом декількох циклів). Наскільки я розумію, версія plyrна github навіть швидша за цю.


28
rbind.fill в останній версії plyr значно швидше, ніж do.call та rbind
— hadley

1
цікаво. для мене rbind.fill був найшвидшим. Досить дивно, що do.call / rbind не повернув ідентичну ІСТИНУ, навіть якщо я не міг знайти різницю. Інші два були рівними, але plyr був повільнішим.
— Метт Баннерт

I()може замінити data.frameваш ldplyдзвінок
— baptiste

4
є також melt.listу перегляді (2)
— баптист

do.call(function(...) rbind(..., make.row.names=F), df)корисно, якщо ви не хочете автоматично генерувати унікальні назви рядків.
— smci

111

З метою повноти, я вважав, що відповіді на це питання потребують оновлення. "Я здогадуюсь, що використання do.call("rbind", ...)буде найшвидшим підходом, який ви знайдете ..." Це, мабуть, було вірно в травні 2010 року і через деякий час після, але приблизно в вересні 2011 року в пакетній версії 1.8.2 rbindlistбула введена нова функція. data.table, із зауваженням, що "Це робиться так само 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

3
Дякую вам за це - я витягнув волосся, тому що мої набори даних стали надто великими для ldplyтого, щоб отримати купу довгих розплавлених кадрів даних. У будь-якому разі, я отримав неймовірну швидкість, використовуючи вашу rbindlistпропозицію.
— KarateSnowMachine

11
І ще один для повноти: dplyr::rbind_all(listOfDataFrames)також зробить трюк.
— andyteucher

2
Чи є еквівалент, rbindlistале додають кадри даних за стовпцями? щось на кшталт cbindlist?
— rafa.pereira

2
@ rafa.pereira Нещодавній запит на функцію: додати функцію cbindlist
— Генрік,

Я також витягував волосся, бо do.call()працював у списку кадрів даних протягом 18 годин, і досі не закінчив, дякую !!!
— Graeme Frost

74

прив’язувати-сюжет

Код:

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’

ОНОВЛЕННЯ : Rerun 31 січня-2018. Побіг на одному комп’ютері. Нові версії пакетів. Додано насіння для любителів насіння.

введіть тут опис зображення

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’

ОНОВЛЕННЯ : Rerun 06-серпня 2019 року.

введіть тут опис зображення

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’

2
Це чудова відповідь. Я запускав те саме (та сама ОС, ті ж пакунки, різні рандомізації, тому що ви цього не робите set.seed), але побачив деякі відмінності в гірших показниках. rbindlistнасправді у мене були найкращі гірші випадки, а також найкращий типовий випадок у моїх результатах
— C8H10N4O2

48

Є також 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

технічно кажучи, вам не потрібно as.data.frame - все, що це робить, робить його виключно data.frame, на відміну від також table_df (від deplyr)
— user1617979

14

Ось ще один спосіб це можна зробити (просто додавши його до відповідей, оскільки reduce це дуже ефективний функціональний інструмент, який часто не помічають як заміну циклів. У цьому конкретному випадку жодне з них не є значно швидшим, ніж do.call)

з використанням бази R:

df <- Reduce(rbind, listOfDataFrames)

або, використовуючи tidyverse:

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

11

Як це робити у вікні:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)

3
Навіщо використовуватись, mapякщо bind_rowsможна взяти список фреймів даних?
— див24

9

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

введіть тут опис зображення

Код:

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’

7

Єдине, чого у рішеннях data.tableнемає, це стовпчик ідентифікатора, щоб знати, з якого фрейму даних у списку беруться дані.

Щось на зразок цього:

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

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

.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  
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.