Creation an R dataframe row-by-rowによると、毎回 data.frame 全体のコピーを作成するため、 data.frame
usingに追加するのは理想的ではありません。このペナルティを受けずにデータを蓄積rbind
するにはどうすればよいですか? 中間形式は . である必要はありません。R
data.frame
data.frame
5 に答える
最初のアプローチ
事前に割り当てられた data.frame の各要素にアクセスしてみました:
res <- data.frame(x=rep(NA,1000), y=rep(NA,1000))
tracemem(res)
for(i in 1:1000) {
res[i,"x"] <- runif(1)
res[i,"y"] <- rnorm(1)
}
しかし、tracemem はおかしくなります (たとえば、data.frame が毎回新しいアドレスにコピーされます)。
代替アプローチ(どちらも機能しません)
1 つのアプローチ (まだベンチマークを行っていないため、高速かどうかはわかりません) は、data.frames のリストを作成し、stack
それらをすべてまとめて作成することです。
makeRow <- function() data.frame(x=runif(1),y=rnorm(1))
res <- replicate(1000, makeRow(), simplify=FALSE ) # returns a list of data.frames
library(taRifx)
res.df <- stack(res)
残念ながら、リストを作成する際に、事前に割り当てるのは難しいと思います。例えば:
> tracemem(res)
[1] "<0x79b98b0>"
> res[[2]] <- data.frame()
tracemem[0x79b98b0 -> 0x71da500]:
つまり、リストの要素を置き換えると、リストがコピーされます。リスト全体を想定していますが、リストのその要素のみである可能性があります。私は R のメモリ管理の詳細に精通していません。
おそらく最良のアプローチ
最近の多くの速度またはメモリ制限のあるプロセスと同様に、最良のアプローチdata.table
はdata.frame
. data.table
参照による割り当て演算子があるため:=
、再コピーせずに更新できます。
library(data.table)
dt <- data.table(x=rep(0,1000), y=rep(0,1000))
tracemem(dt)
for(i in 1:1000) {
dt[i,x := runif(1)]
dt[i,y := rnorm(1)]
}
# note no message from tracemem
しかし、@MatthewDowle が指摘しているset()
ように、ループ内でこれを行う適切な方法です。そうすることで、さらに高速になります。
library(data.table)
n <- 10^6
dt <- data.table(x=rep(0,n), y=rep(0,n))
dt.colon <- function(dt) {
for(i in 1:n) {
dt[i,x := runif(1)]
dt[i,y := rnorm(1)]
}
}
dt.set <- function(dt) {
for(i in 1:n) {
set(dt,i,1L, runif(1) )
set(dt,i,2L, rnorm(1) )
}
}
library(microbenchmark)
m <- microbenchmark(dt.colon(dt), dt.set(dt),times=2)
(以下に結果を示します)
ベンチマーク
ループを 10,000 回実行すると、データ テーブルはほぼ 1 桁速くなります。
Unit: seconds
expr min lq median uq max
1 test.df() 523.49057 523.49057 524.52408 525.55759 525.55759
2 test.dt() 62.06398 62.06398 62.98622 63.90845 63.90845
3 test.stack() 1196.30135 1196.30135 1258.79879 1321.29622 1321.29622
:=
との比較set()
:
> m
Unit: milliseconds
expr min lq median uq max
1 dt.colon(dt) 654.54996 654.54996 656.43429 658.3186 658.3186
2 dt.set(dt) 13.29612 13.29612 15.02891 16.7617 16.7617
n
上記のベンチマークのように、ここでは 10^5 ではなく 10^6 であることに注意してください。そのため、桁違いに多くの作業があり、結果は秒単位ではなくミリ秒単位で測定されます。確かに印象的です。
要素がデータフレームで満たされている空のリスト オブジェクトを持つこともできます。最後に、sapply などを使用して結果を収集します。例はここにあります。これにより、オブジェクトの成長によるペナルティは発生しません。
ええと、行列への変換についてまだ誰も言及していないことに非常に驚いています...
Ari B. Friedmanによって定義されたdt.colonおよびdt.set関数と比較すると、行列への変換の実行時間が最適です ( dt.colonよりもわずかに高速です)。マトリックス内のすべての影響は参照によって行われるため、このコードでは不要なメモリ コピーは実行されません。
コード:
library(data.table)
n <- 10^4
dt <- data.table(x=rep(0,n), y=rep(0,n))
use.matrix <- function(dt) {
mat = as.matrix(dt) # converting to matrix
for(i in 1:n) {
mat[i,1] = runif(1)
mat[i,2] = rnorm(1)
}
return(as.data.frame(mat)) # converting back to a data.frame
}
dt.colon <- function(dt) { # same as Ari's function
for(i in 1:n) {
dt[i,x := runif(1)]
dt[i,y := rnorm(1)]
}
}
dt.set <- function(dt) { # same as Ari's function
for(i in 1:n) {
set(dt,i,1L, runif(1) )
set(dt,i,2L, rnorm(1) )
}
}
library(microbenchmark)
microbenchmark(dt.colon(dt), dt.set(dt), use.matrix(dt),times=10)
結果:
Unit: milliseconds
expr min lq median uq max neval
dt.colon(dt) 7107.68494 7193.54792 7262.76720 7277.24841 7472.41726 10
dt.set(dt) 93.25954 94.10291 95.07181 97.09725 99.18583 10
use.matrix(dt) 48.15595 51.71100 52.39375 54.59252 55.04192 10
マトリックスを使用する利点:
- これが今のところ最速の方法です
- data.table オブジェクトを学習/使用する必要はありません
マトリックスを使用することの短所:
- 行列で処理できるデータ型は 1 つだけです (特に、data.frame の列に型が混在している場合、それらはすべて次の行で文字に変換されます: mat = as.matrix(dt) # conversionマトリックスへ)
私はそれが好きRSQLite
です:dbWriteTable(...,append=TRUE)
収集中のdbReadTable
ステートメント、および最後にステートメント。
データが十分に小さい場合は ":memory:" ファイルを使用でき、大きい場合はハードディスクを使用できます。
もちろん、速度の面では競合できません。
makeRow <- function() data.frame(x=runif(1),y=rnorm(1))
library(RSQLite)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
collect1 <- function(n) {
for (i in 1:n) dbWriteTable(con, "test", makeRow(), append=TRUE)
dbReadTable(con, "test", row.names=NULL)
}
collect2 <- function(n) {
res <- data.frame(x=rep(NA, n), y=rep(NA, n))
for(i in 1:n) res[i,] <- makeRow()[1,]
res
}
> system.time(collect1(1000))
User System verstrichen
7.01 0.00 7.05
> system.time(collect2(1000))
User System verstrichen
0.80 0.01 0.81
data.frame
ただし、 s に複数の行がある場合は、見栄えが良くなる可能性があります。また、事前に行数を知る必要はありません。