ทำไม rbindlist จึง“ ดีกว่า” กว่า rbind


136

ฉันกำลังจะผ่านเอกสารdata.tableและสังเกตเห็นจากบางส่วนของการสนทนามากกว่าที่นี่ในดังนั้นที่ควรจะเป็นดีกว่าrbindlistrbind

ผมอยากจะรู้ว่าทำไมเป็นrbindlistดีกว่าrbindและในสถานการณ์rbindlistจริงๆเก่งกว่าrbind?

มีข้อได้เปรียบในแง่ของการใช้หน่วยความจำหรือไม่?

คำตอบ:


157

rbindlistเป็นเวอร์ชันที่ปรับให้เหมาะสมdo.call(rbind, list(...))ซึ่งเป็นที่ทราบกันดีว่าทำงานช้าเมื่อใช้งานrbind.data.frame


มันเก่งจริงตรงไหน

คำถามบางอย่างที่แสดงว่าrbindlistส่องแสงอยู่ที่ไหน

การรวมรายการ data.frame แบบเวกเตอร์อย่างรวดเร็วตามแถว

ปัญหาในการแปลงรายการ data.frame แบบยาว (~ 1 ล้าน) เป็น data.frame เดียวโดยใช้ do.call และ ldply

สิ่งเหล่านี้มีเกณฑ์มาตรฐานที่แสดงให้เห็นว่าสามารถทำได้เร็วเพียงใด


rbind.data.frame ช้าด้วยเหตุผล

rbind.data.frameตรวจสอบจำนวนมากและจะจับคู่ตามชื่อ (เช่น rbind.data.frame จะอธิบายถึงข้อเท็จจริงที่ว่าคอลัมน์อาจอยู่ในลำดับที่ต่างกันและจับคู่ตามชื่อ) rbindlistไม่ทำการตรวจสอบประเภทนี้และจะเข้าร่วมตามตำแหน่ง

เช่น

do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
##    a b
## 1  1 2
## 2  2 3
## 3  2 1
## 4  3 2

rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
##     a b
##  1: 1 2
##  2: 2 3
##  3: 1 2
##  4: 2 3

ข้อ จำกัด อื่น ๆ ของ rbindlist

มันใช้ในการต่อสู้เพื่อจัดการกับfactorsเนื่องจากข้อผิดพลาดที่มีตั้งแต่การแก้ไข:

rbindlist สอง data.tables ที่หนึ่งมีปัจจัยและอื่น ๆ มีประเภทอักขระสำหรับคอลัมน์ ( Bug # 2650 )

มีปัญหากับชื่อคอลัมน์ที่ซ้ำกัน

ดู ข้อความเตือน: ใน rbindlist (พาดพิง): NAs นำมาใช้โดยการบีบบังคับ: ข้อผิดพลาดที่เป็นไปได้ใน data.table? ( ข้อบกพร่อง # 2384 )


rbind.data.frame rownames อาจทำให้หงุดหงิด

rbindlistสามารถจัดการlists data.framesและdata.tablesและจะส่งคืน data.table โดยไม่มีชื่อแถว

คุณสามารถสับสนกับชื่อแถวโดยใช้do.call(rbind, list(...)) ดู

วิธีหลีกเลี่ยงการเปลี่ยนชื่อแถวเมื่อใช้ rbind ภายใน do.call


ประสิทธิภาพของหน่วยความจำ

ในแง่ของหน่วยความจำrbindlistถูกนำมาใช้Cดังนั้นหน่วยความจำก็มีประสิทธิภาพเช่นกันมันใช้setattrเพื่อตั้งค่าแอตทริบิวต์โดยการอ้างอิง

rbind.data.frameถูกนำไปใช้Rมันทำการมอบหมายจำนวนมากและใช้attr<-( class<-และrownames<-ทั้งหมด (ภายใน) จะสร้างสำเนาของ data.frame ที่สร้างขึ้น


1
FYI attr<-, class<-และ (ฉันคิด) rownames<-ทั้งหมดปรับเปลี่ยนในสถานที่
hadley

5
@hadley แน่ใจเหรอ? ลองDF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle

4
rbind.data.frameมีตรรกะ "หักหลัง" แบบพิเศษ - เมื่ออาร์กิวเมนต์แรกเป็น a data.tableจะเรียก.rbind.data.tableแทนซึ่งจะทำการตรวจสอบเล็กน้อยแล้วเรียกrbindlistภายใน ดังนั้นถ้าคุณมีอยู่แล้วdata.tableวัตถุที่จะผูกอาจมีความแตกต่างเล็ก ๆ น้อย ๆ ระหว่างประสิทธิภาพและrbind rbindlist
Ken Williams

6
mnel โพสต์นี้อาจต้องการการแก้ไขซึ่งตอนนี้rbindlistสามารถจับคู่ด้วยชื่อ ( use.names=TRUE) และเติมคอลัมน์ที่ขาดหายไป ( fill=TRUE) ผมได้ปรับปรุงนี้ , นี้และนี้โพสต์ คุณสามารถแก้ไขสิ่งนี้ได้หรือไม่หรือถ้าฉันทำมันได้ ไม่ว่าจะด้วยวิธีใดก็ตามฉันก็สบายดี
อรุณ

1
dplyr::rbind_listก็ค่อนข้างคล้ายกัน
hadley

48

ตามv1.9.2, rbindlistมีวิวัฒนาการไม่น้อยใช้คุณลักษณะหลายอย่างรวมถึง:

  • เลือกสูงสุดSEXPTYPEของคอลัมน์ในขณะที่มีผลผูกพัน - การดำเนินการในv1.9.2การปิดFR # 2456และbug #
  • การจัดการfactorคอลัมน์ถูกต้อง - ใช้เป็นครั้งแรกในv1.8.10การปิดbug # 2650และขยายไปยังผูกพันสั่งซื้อปัจจัยอย่างรอบคอบv1.9.2เป็นอย่างดีปิดFR # 4856และbug #

นอกจากนี้ในv1.9.2, rbind.data.tableนอกจากนี้ยังได้รับfillการโต้เถียงที่ช่วยให้การผูกโดยการกรอกข้อมูลคอลัมน์หายไปดำเนินการในอาร์

ในตอนนี้v1.9.3มีการปรับปรุงคุณสมบัติที่มีอยู่เหล่านี้มากยิ่งขึ้น:

  • rbindlistได้รับอาร์กิวเมนต์use.namesซึ่งโดยค่าเริ่มต้นFALSEสำหรับความเข้ากันได้แบบย้อนกลับ
  • rbindlistนอกจากนี้ยังได้รับอาร์กิวเมนต์fillซึ่งโดยค่าเริ่มต้นจะใช้FALSEสำหรับความเข้ากันได้ย้อนหลัง
  • คุณลักษณะเหล่านี้ใช้งานได้ทั้งหมดในภาษา C และเขียนอย่างระมัดระวังเพื่อไม่ให้ความเร็วลดลงในขณะที่เพิ่มฟังก์ชันการทำงาน
  • เนื่องจากrbindlistตอนนี้สามารถจับคู่ตามชื่อและเติมคอลัมน์ที่ขาดหายไปrbind.data.tableเพียงโทรrbindlistเลย ข้อแตกต่างเพียงอย่างเดียวคือuse.names=TRUEโดยค่าเริ่มต้นrbind.data.tableสำหรับความเข้ากันได้แบบย้อนกลับ

rbind.data.frameช้าลงเล็กน้อยส่วนใหญ่เนื่องจากสำเนา (ซึ่ง @mnel ชี้ให้เห็นเช่นกัน) ที่สามารถหลีกเลี่ยงได้ (โดยย้ายไปที่ C) ฉันคิดว่านั่นไม่ใช่เหตุผลเดียว การใช้งานสำหรับการตรวจสอบ / การจับคู่ชื่อคอลัมน์ในrbind.data.frameอาจช้าลงเมื่อมีหลายคอลัมน์ต่อ data.frame และมี data.frame จำนวนมากที่จะผูก (ดังแสดงในเกณฑ์มาตรฐานด้านล่าง)

แต่ที่rbindlistขาด (เอ็ด) คุณสมบัติบางอย่าง (เช่นการตรวจสอบระดับปัจจัยหรือตรงกับชื่อ) หมีขนาดเล็กมาก (หรือไม่) rbind.data.frameน้ำหนักต่อมันเป็นเร็วกว่า เป็นเพราะพวกเขาถูกนำมาใช้อย่างระมัดระวังใน C ซึ่งปรับให้เหมาะสมกับความเร็วและหน่วยความจำ

นี่คือมาตรฐานที่ไฮไลท์ที่มีประสิทธิภาพมีผลผูกพันในขณะที่การจับคู่โดยชื่อคอลัมน์เช่นกันโดยใช้rbindlist's คุณลักษณะจากuse.names v1.9.3ชุดข้อมูลประกอบด้วย 10,000 ข้อมูลแต่ละเฟรมขนาด 10 * 500

หมายเหตุ: เกณฑ์มาตรฐานนี้ได้รับการอัปเดตเพื่อรวมการเปรียบเทียบกับdplyr'sbind_rows

library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
set.seed(1L)
names = paste0("V", 1:500)
cols = 500L
foo <- function() {
    data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
    setnames(data, sample(names))
}
n = 10e3L
ll = vector("list", n)
for (i in 1:n) {
    .Call("Csetlistelt", ll, i, foo())
}

system.time(ans1 <- rbindlist(ll))
#  user  system elapsed 
# 1.226   0.070   1.296 

system.time(ans2 <- rbindlist(ll, use.names=TRUE))
#  user  system elapsed 
# 2.635   0.129   2.772 

system.time(ans3 <- do.call("rbind", ll))
#   user  system elapsed 
# 36.932   1.628  38.594 

system.time(ans4 <- bind_rows(ll))
#   user  system elapsed 
# 48.754   0.384  49.224 

identical(ans2, setDT(ans3)) 
# [1] TRUE
identical(ans2, setDT(ans4))
# [1] TRUE

การผูกคอลัมน์โดยไม่ตรวจสอบชื่อใช้เวลาเพียง 1.3 ในขณะที่การตรวจสอบชื่อคอลัมน์และการเชื่อมโยงอย่างเหมาะสมใช้เวลาเพิ่มขึ้นเพียง 1.5 วินาที เมื่อเทียบกับโซลูชันพื้นฐานแล้วจะเร็วกว่า 14 เท่าและเร็วกว่าdplyrเวอร์ชันของ18 เท่า

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