แปลงรายการเฟรมข้อมูลเป็นกรอบข้อมูลเดียว


336

ฉันมีรหัสที่ท้ายสถานที่ด้วยรายการของ data frames ซึ่งฉันต้องการแปลงเป็น data data ขนาดใหญ่เพียงอันเดียว

ฉันได้รับคำแนะนำจากคำถามก่อนหน้านี้ซึ่งพยายามทำสิ่งที่คล้ายกัน แต่ซับซ้อนกว่า

นี่คือตัวอย่างของสิ่งที่ฉันเริ่มต้นด้วย (นี่ง่ายสำหรับการทำภาพประกอบ):

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?
Dirk Eddelbuettel

5
ใครบางคนสามารถอธิบายความแตกต่างระหว่าง do.call ("rbind", list) และ rbind (list) ให้ฉันได้ - ทำไมเอาต์พุตไม่เหมือนกัน?
user6571411

1
@ user6571411 เนื่องจาก do.call () ไม่ส่งคืนอาร์กิวเมนต์ทีละตัว แต่ใช้รายการเพื่อเก็บอาร์กิวเมนต์ของฟังก์ชัน ดูhttps://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

คำตอบ:


130

ใช้ bind_rows () จากแพ็คเกจ dplyr:

bind_rows(list_of_dataframes, .id = "column_label")

5
ทางออกที่ดี .id = "column_label"เพิ่มชื่อแถวที่ไม่ซ้ำตามชื่อองค์ประกอบของรายการ
Sibo Jiang

10
เนื่องจากเป็นปี 2018 และdplyrทั้งรวดเร็วและเป็นเครื่องมือที่แข็งแกร่งในการใช้งานฉันได้เปลี่ยนสิ่งนี้เป็นคำตอบที่ยอมรับได้ ปีที่พวกเขาบินโดย!
JD Long

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", ...)จะเป็นวิธีที่เร็วที่สุดที่คุณจะพบเว้นแต่คุณสามารถทำสิ่งต่าง ๆ เช่น (a) ใช้เมทริกซ์แทน data.frames และ (b) preallocate เมทริกซ์สุดท้ายและกำหนดให้มันแทนที่จะเติบโต .

แก้ไข 1 :

จากความคิดเห็นของ Hadley นี่เป็นเวอร์ชั่นล่าสุดของrbind.fillจาก CRAN:

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

สิ่งนี้ง่ายกว่าการโยงและเร็วกว่าเล็กน้อย และเท่าที่ฉันเข้าใจเวอร์ชันของplyrgitHubนั้นเร็วกว่านี้มาก


28
rbind.fill ใน plyr เวอร์ชันล่าสุดนั้นเร็วกว่า do.call และ rbind มาก
hadley

1
น่าสนใจ สำหรับฉัน rbind.fill นั้นเร็วที่สุด แปลกมาก do.call / rbind ไม่ได้กลับ TRUE เหมือนกันแม้ว่าฉันจะพบความแตกต่าง อีกสองคนเท่ากัน แต่ plyr ช้ากว่า
Matt Bannert

I()สามารถแทนที่data.frameในการldplyโทรของคุณ
baptiste

4
นอกจากนี้ยังมีการmelt.listก่อร่างใหม่ (2)
baptiste

do.call(function(...) rbind(..., make.row.names=F), df)มีประโยชน์หากคุณไม่ต้องการชื่อแถวที่ไม่ซ้ำที่สร้างขึ้นโดยอัตโนมัติ
smci

111

เพื่อความสมบูรณ์ฉันคิดว่าคำตอบสำหรับคำถามนี้ต้องได้รับการอัปเดต "ฉันเดาว่าการใช้do.call("rbind", ...)จะเป็นวิธีที่เร็วที่สุดที่คุณจะพบ ... " มันอาจจะเป็นจริงในเดือนพฤษภาคม 2010 และอีกไม่นานหลังจากนั้น แต่ในเดือนกันยายน 2554 มีการ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

3
ขอบคุณมากสำหรับสิ่งนี้ - ฉันดึงผมออกมาเพราะชุดข้อมูลของฉันใหญ่เกินไปสำหรับldplyการรวบรวมกรอบข้อมูลขนาดยาวและหลอมเหลว อย่างไรก็ตามฉันได้รับความเร็วที่เหลือเชื่อโดยใช้rbindlistคำแนะนำของคุณ
KarateSnowMachine

11
และอีกหนึ่งสำหรับความสมบูรณ์: dplyr::rbind_all(listOfDataFrames)จะทำเคล็ดลับเช่นกัน
andyteucher

2
มีอะไรเทียบเท่าrbindlistแต่ผนวกข้อมูลเฟรมโดยคอลัมน์? อะไรเช่น cbindlist?
rafa.pereira

2
@ rafa.pereira มีคำขอคุณลักษณะล่าสุดคือ: เพิ่มฟังก์ชั่น cbindlist
Henrik

ฉันดึงผมออกมาเพราะdo.call()ใช้เฟรมข้อมูลเป็นเวลา 18 ชั่วโมงและยังไม่เสร็จขอบคุณ !!!
แกรมฟรอสต์

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

อัปเดต : เรียกใช้ซ้ำ 31-Jan-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

อัปเดต : เรียกใช้อีกครั้ง 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

ควรทำอย่างไรใน tidyverse:

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

3
ทำไมคุณถึงต้องใช้mapถ้าbind_rowsสามารถจดรายการข้อมูลได้?
See24

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  
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.