データフレームにfor文でデータを追加する際の処理速度

データフレームに対してfor文を使って一行ごとに新規のデータをrbindで追加するという書き方は、処理に時間がかかるのであまり良い書き方ではありません(そもそもRではfor文を使うべきでない、みたいな話は今は気にしないことにします)。例えばこういうの。

N = 10000
df = data.frame()
for( i in 1:N ){
  df = rbind( df, c( i, i * 2 ) )
}

1万行・2列のデータフレームを作成していますが、処理の完了までおよそ2.3秒かかる。

こういう場合は、初めに1万行・2列の空のデータフレームを作成し、そこにデータを上書きして行く方が速いです。

createEmptyDf = function( nrow, ncol, colnames = c() ){
  data.frame( matrix( vector(), nrow, ncol, dimnames = list( c(), colnames ) ) )
}
df = createEmptyDf( N, 2, colnames = c( "id", "value" ) )
for( i in 1:N ){
  df[ i, ] = c( i, i * 2 )
}

この書き方だと処理の完了までおよそ1.5秒で終わります。

ちなみに、以下のような書き方にするともっと速くなりました(約0.9秒で処理完了しました)。

createEmptyDf = function( nrow, ncol, colnames = c() ){
  data.frame( matrix( vector(), nrow, ncol, dimnames = list( c(), colnames ) ) )
}
df = createEmptyDf( N, 2, colnames = c( "id", "value" ) )
for( i in 1:N ){
  df[ i, 1 ] = i
  df[ i, 2 ] = i * 2
}

rbindがなぜ遅いかと言えば、rbindするたびに行数の増えたデータフレームが新たに作成されているから。for文の繰り返し数がこの例のように1万回であれば、計1万回もデータフレームがループのたびに新規作成されています。
初めに必要な行数・列数でデータフレームを初期化しておけば、for文の中ではそのデータフレームに値を追加して行くだけなので、毎回新しいデータフレームを作成するという無駄をせずに済みます。
3番目の書き方がさらに速かったのは、c()を使った配列の作成をしていないからでしょう。

この事例では2.3sが0.9sになっただけなのでさほどのスピードアップではないですが、列数が多かったりデータの行数が100万とかだったりする普段のデータ解析だと、僕の場合は初期化する方の書き方に変えただけでプログラムの所要時間が5倍以上短くなったことがありました。一週間ほどかかる作業が1日で終わることになるので、これは大きな違いです。

使ったスクリプト全体を以下に記載します。

N = 10000

createEmptyDf = function( nrow, ncol, colnames = c() ){
  data.frame( matrix( vector(), nrow, ncol, dimnames = list( c(), colnames ) ) )
}

tic = proc.time()

df = createEmptyDf( N, 2, colnames = c( "id", "value" ) )
for( i in 1:N ){
  # df[ i, ] = c( i, i * 2 )
  df[ i, 1 ] = i
  df[ i, 2 ] = i * 2
}

print( proc.time() - tic )


tic = proc.time()

df = data.frame()
for( i in 1:N ){
  df = rbind( df, c( i, i * 2 ) )
}

print( proc.time() - tic )

コメント