Misalkan Anda tidak mengetahui ukuran data.frame sebelumnya. Bisa jadi beberapa baris, atau beberapa juta. Anda perlu memiliki semacam wadah, yang tumbuh secara dinamis. Mempertimbangkan pengalaman saya dan semua jawaban terkait di SO saya datang dengan 4 solusi berbeda:
rbindlist
ke data.frame
Gunakan operasi data.table
cepat set
dan pasangkan dengan menggandakan tabel secara manual bila diperlukan.
Gunakan RSQLite
dan tambahkan ke tabel yang disimpan dalam memori.
data.frame
kemampuan sendiri untuk mengembangkan dan menggunakan lingkungan kustom (yang memiliki semantik referensi) untuk menyimpan data.frame sehingga tidak akan disalin saat kembali.
Berikut adalah pengujian semua metode untuk jumlah baris yang ditambahkan kecil dan besar. Setiap metode memiliki 3 fungsi yang terkait dengannya:
create(first_element)
yang mengembalikan objek pendukung yang sesuai dengan first_element
dimasukkan.
append(object, element)
yang menambahkan element
akhir tabel (diwakili oleh object
).
access(object)
mendapatkan data.frame
dengan semua elemen yang disisipkan.
rbindlist
ke data.frame
Itu cukup mudah dan tidak berbelit-belit:
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
+ menggandakan tabel secara manual saat diperlukan.
Saya akan menyimpan panjang sebenarnya dari tabel dalam rowcount
atribut.
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 harus dioptimalkan untuk penyisipan rekaman cepat, jadi saya awalnya memiliki harapan tinggi untuk RSQLite
solusi
Ini pada dasarnya adalah salin & tempel jawaban Karsten W. di utas serupa.
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
lingkungan kustom + penambahan baris sendiri.
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)
}
Rangkaian pengujian:
Untuk kenyamanan, saya akan menggunakan satu fungsi uji untuk mencakup semuanya dengan panggilan tidak langsung. (Saya memeriksa: menggunakan do.call
alih-alih memanggil fungsi secara langsung tidak membuat kode berjalan terukur lebih lama).
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)))
}
Mari kita lihat kinerja untuk n = 10 penyisipan.
Saya juga menambahkan fungsi 'plasebo' (dengan akhiran 0
) yang tidak melakukan apa-apa - hanya untuk mengukur overhead pengaturan pengujian.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
Untuk 1E5 baris (pengukuran dilakukan pada 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
Sepertinya sulusi berbasis SQLite, meskipun mendapatkan kembali beberapa kecepatan pada data besar, tidak jauh dari pertumbuhan eksponensial data.table + manual. Perbedaannya hampir dua kali lipat!
Ringkasan
Jika Anda tahu bahwa Anda akan menambahkan jumlah baris yang agak kecil (n <= 100), lanjutkan dan gunakan solusi yang paling sederhana: cukup tetapkan baris ke data.frame menggunakan notasi braket dan abaikan fakta bahwa data.frame adalah tidak diisi sebelumnya.
Untuk semua yang lain gunakan data.table::set
dan kembangkan data.table secara eksponensial (misalnya menggunakan kode saya).