สมมติว่าคุณไม่ทราบขนาดของ data.frame ล่วงหน้า อาจเป็นไม่กี่แถวหรือไม่กี่ล้าน คุณต้องมีคอนเทนเนอร์บางประเภทที่เติบโตอย่างไม่หยุดนิ่ง โดยคำนึงถึงประสบการณ์ของฉันและคำตอบที่เกี่ยวข้องทั้งหมดในดังนั้นฉันจึงมาพร้อมกับ 4 โซลูชันที่แตกต่างกัน:
rbindlist
ไปยัง data.frame
ใช้การทำงานที่data.table
รวดเร็วset
และจับคู่กับการเพิ่มตารางด้วยตนเองเมื่อจำเป็น
ใช้RSQLite
และต่อท้ายตารางที่จัดไว้ในหน่วยความจำ
data.frame
ความสามารถของตัวเองในการเติบโตและใช้สภาพแวดล้อมที่กำหนดเอง (ซึ่งมีความหมายอ้างอิง) ในการจัดเก็บ data.frame ดังนั้นจึงจะไม่ถูกคัดลอกในทางกลับกัน
นี่คือการทดสอบวิธีการทั้งหมดสำหรับแถวต่อท้ายทั้งขนาดเล็กและจำนวนมาก แต่ละวิธีมี 3 ฟังก์ชันที่เกี่ยวข้อง:
create(first_element)
ที่ส่งคืนวัตถุสำรองที่เหมาะสมพร้อมfirst_element
ใส่
append(object, element)
ที่element
ต่อท้ายตาราง (แสดงโดยobject
)
access(object)
รับdata.frame
องค์ประกอบที่แทรกทั้งหมด
rbindlist
ไปยัง data.frame
มันค่อนข้างง่ายและตรงไปตรงมา:
create.1<-function(elems)
{
return(as.data.table(elems))
}
append.1<-function(dt, elems)
{
return(rbindlist(list(dt, elems),use.names = TRUE))
}
access.1<-function(dt)
{
return(dt)
}
data.table::set
+ เพิ่มตารางสองเท่าด้วยตนเองเมื่อจำเป็น
ฉันจะเก็บความยาวที่แท้จริงของตารางไว้ในrowcount
แอตทริบิวต์
create.2<-function(elems)
{
return(as.data.table(elems))
}
append.2<-function(dt, elems)
{
n<-attr(dt, 'rowcount')
if (is.null(n))
n<-nrow(dt)
if (n==nrow(dt))
{
tmp<-elems[1]
tmp[[1]]<-rep(NA,n)
dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
setattr(dt,'rowcount', n)
}
pos<-as.integer(match(names(elems), colnames(dt)))
for (j in seq_along(pos))
{
set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
}
setattr(dt,'rowcount',n+1)
return(dt)
}
access.2<-function(elems)
{
n<-attr(elems, 'rowcount')
return(as.data.table(elems[1:n,]))
}
SQL ควรได้รับการปรับให้เหมาะสมสำหรับการแทรกบันทึกอย่างรวดเร็วดังนั้นในตอนแรกฉันมีความหวังสูงสำหรับRSQLite
การแก้ปัญหา
นี่คือการคัดลอกและวางคำตอบของKarsten W.ในหัวข้อที่คล้ายกัน
create.3<-function(elems)
{
con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
return(con)
}
append.3<-function(con, elems)
{
RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
return(con)
}
access.3<-function(con)
{
return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}
data.frame
แถวต่อท้าย + สภาพแวดล้อมที่กำหนดเอง
create.4<-function(elems)
{
env<-new.env()
env$dt<-as.data.frame(elems)
return(env)
}
append.4<-function(env, elems)
{
env$dt[nrow(env$dt)+1,]<-elems
return(env)
}
access.4<-function(env)
{
return(env$dt)
}
ชุดทดสอบ:
เพื่อความสะดวกฉันจะใช้ฟังก์ชั่นการทดสอบเดียวเพื่อครอบคลุมทั้งหมดด้วยการโทรทางอ้อม (ฉันตรวจสอบแล้ว: การใช้do.call
แทนการเรียกใช้ฟังก์ชันโดยตรงไม่ได้ทำให้โค้ดทำงานวัดผลได้นานขึ้น)
test<-function(id, n=1000)
{
n<-n-1
el<-list(a=1,b=2,c=3,d=4)
o<-do.call(paste0('create.',id),list(el))
s<-paste0('append.',id)
for (i in 1:n)
{
o<-do.call(s,list(o,el))
}
return(do.call(paste0('access.', id), list(o)))
}
มาดูประสิทธิภาพของการแทรก n = 10 กัน
ฉันยังเพิ่มฟังก์ชัน 'ยาหลอก' (พร้อมคำต่อท้าย0
) ที่ไม่ได้ทำอะไรเลย - เพียงเพื่อวัดค่าโสหุ้ยของการตั้งค่าการทดสอบ
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
สำหรับแถว 1E5 (การวัดที่ทำบน Intel (R) Core (TM) i7-4710HQ CPU @ 2.50GHz):
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
ดูเหมือนว่าการเจือจางที่ใช้ SQLite แม้ว่าจะคืนความเร็วให้กับข้อมูลขนาดใหญ่ แต่ก็ไม่มีที่ไหนใกล้ data.table + การเติบโตแบบเอ็กซ์โพเนนเชียลด้วยตนเอง ความแตกต่างเกือบสองคำสั่งขนาด!
สรุป
หากคุณรู้ว่าคุณจะต่อท้ายแถวจำนวนค่อนข้างน้อย (n <= 100) ให้ดำเนินการต่อและใช้วิธีแก้ไขปัญหาที่ง่ายที่สุด: เพียงกำหนดแถวให้กับ data.frame โดยใช้เครื่องหมายวงเล็บและไม่สนใจข้อเท็จจริงที่ว่า data.frame คือ ไม่ได้เติมข้อมูลไว้ล่วงหน้า
สำหรับสิ่งอื่น ๆ ให้ใช้data.table::set
และขยายข้อมูลตารางแบบทวีคูณ (เช่นการใช้รหัสของฉัน)