46

Creation an R dataframe row-by-rowによると、毎回 data.frame 全体のコピーを作成するため、 data.frameusingに追加するのは理想的ではありません。このペナルティを受けずにデータを蓄積rbindするにはどうすればよいですか? 中間形式は . である必要はありません。Rdata.framedata.frame

4

5 に答える 5

45

最初のアプローチ

事前に割り当てられた 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.tabledata.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 であることに注意してください。そのため、桁違いに多くの作業があり、結果は秒単位ではなくミリ秒単位で測定されます。確かに印象的です。

于 2012-07-14T18:43:29.807 に答える
9

要素がデータフレームで満たされている空のリスト オブジェクトを持つこともできます。最後に、sapply などを使用して結果を収集します。例はここにあります。これにより、オブジェクトの成長によるペナルティは発生しません。

于 2012-07-14T20:09:02.730 に答える
7

ええと、行列への変換についてまだ誰も言及していないことに非常に驚いています...

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マトリックスへ
于 2014-02-13T23:57:56.903 に答える
6

私はそれが好き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 に複数の行がある場合は、見栄えが良くなる可能性があります。また、事前に行数を知る必要はありません。

于 2012-07-14T19:46:24.763 に答える