data.frameのサイズが事前にわからない場合を考えてみましょう。数行または数百万になることもあります。動的に成長する何らかのコンテナが必要です。SOでの私の経験と関連するすべての回答を考慮して、4つの異なるソリューションが用意されています。
rbindlist
data.frameに
data.table
の高速set
操作を使用し、必要に応じてテーブルを手動で2倍にすることと組み合わせます。
使用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
+必要に応じて手動でテーブルを2倍にします。
テーブルの実際の長さを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)
}
テストスイート:
便宜上、1つのテスト関数を使用して、間接呼び出しですべてをカバーします。(私はチェックしました: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 +手動での指数関数的な増加にはほど遠いものです。違いはほぼ2桁です!
概要
かなり少ない行数(n <= 100)を追加することがわかっている場合は、先に進んで最も簡単な解決策を使用します。ブラケット表記を使用して行をdata.frameに割り当てるだけで、data.frameが事前入力されていません。
それ以外の場合はすべてdata.table::set
、data.tableを指数関数的に使用して拡張します(たとえば、私のコードを使用します)。