Giả sử bạn chỉ đơn giản là không biết trước kích thước của data.frame. Nó cũng có thể là một vài hàng, hoặc một vài triệu. Bạn cần phải có một số loại thùng chứa, phát triển linh hoạt. Xem xét kinh nghiệm của tôi và tất cả các câu trả lời liên quan trong SO, tôi đưa ra 4 giải pháp riêng biệt:
rbindlist vào data.frame
Sử dụng thao tác data.tablenhanh setvà ghép nối nó bằng cách nhân đôi bảng theo cách thủ công khi cần thiết.
Sử dụng RSQLitevà thêm vào bảng được lưu trong bộ nhớ.
data.framekhả năng của riêng mình để phát triển và sử dụng môi trường tùy chỉnh (có ngữ nghĩa tham chiếu) để lưu trữ data.frame, do đó nó sẽ không bị sao chép ngược lại.
Đây là một thử nghiệm của tất cả các phương pháp cho cả số lượng nhỏ và lớn các hàng được nối thêm. Mỗi phương thức có 3 chức năng được liên kết với nó:
create(first_element) trả về đối tượng sao lưu thích hợp với first_element đưa vào.
append(object, element)gắn elementvào cuối bảng (được đại diện bởiobject ).
access(object)nhận được data.framevới tất cả các phần tử được chèn.
rbindlist vào data.frame
Điều đó khá dễ dàng và dễ hiểu:
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 + nhân đôi bàn thủ công khi cần thiết.
Tôi sẽ lưu trữ độ dài thực của bảng trong một rowcountthuộc tính.
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 nên được tối ưu hóa để chèn bản ghi nhanh, vì vậy ban đầu tôi rất hy vọng RSQLite giải pháp
Về cơ bản, đây là bản sao và dán câu trả lời của Karsten W. trên chủ đề tương tự.
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.framemôi trường tùy chỉnh + bổ sung hàng của riêng.
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)
}
Bộ thử nghiệm:
Để thuận tiện, tôi sẽ sử dụng một chức năng kiểm tra để bao gồm tất cả chúng bằng cách gọi gián tiếp. (Tôi đã kiểm tra: sử dụng do.callthay vì gọi trực tiếp các hàm không làm cho mã chạy lâu hơn có thể đo được).
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)))
}
Hãy xem hiệu suất của n = 10 lần chèn.
Tôi cũng đã thêm các chức năng 'giả dược' (có hậu tố 0) không thực hiện bất kỳ điều gì - chỉ để đo lường chi phí của thiết lập thử nghiệm.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)


Đối với hàng 1E5 (các phép đo được thực hiện trên CPU Intel (R) Core (TM) i7-4710HQ @ 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
Có vẻ như sulution dựa trên SQLite, mặc dù lấy lại một số tốc độ trên dữ liệu lớn, nhưng không bằng data.table + tăng trưởng theo cấp số nhân thủ công. Sự khác biệt gần như là hai bậc của độ lớn!
Tóm lược
Nếu bạn biết rằng bạn sẽ thêm một số lượng hàng khá nhỏ (n <= 100), hãy tiếp tục và sử dụng giải pháp đơn giản nhất có thể: chỉ cần gán các hàng cho data.frame bằng cách sử dụng ký hiệu ngoặc và bỏ qua thực tế rằng data.frame là không được điền trước.
Đối với mọi thứ khác, hãy sử dụng data.table::setvà phát triển data.table theo cấp số nhân (ví dụ: sử dụng mã của tôi).