1. ホーム
  2. r

[解決済み] 大規模なdata.tableのNAを置換する最速の方法

2022-04-24 13:07:53

質問

私は、大きな data.table 200k行200列の中に多くの欠損値が散らばっています。 これらの欠損値を可能な限り効率的にゼロに再コード化したい。

2つの選択肢がありますね。

1: data.frameに変換して使用する。 このような

2: ある種のクールなdata.tableサブ設定コマンド

タイプ1のかなり効率的な解決策で満足する。data.frameに変換してからdata.tableに戻しても、それほど時間はかからないでしょう。

どのように解決するのですか?

を使った解決策を紹介します。 データテーブル 's := 演算子で、AndrieとRamnathの回答に基づいています。

require(data.table)  # v1.6.6
require(gdata)       # v2.8.2

set.seed(1)
dt1 = create_dt(2e5, 200, 0.1)
dim(dt1)
[1] 200000    200    # more columns than Ramnath's answer which had 5 not 200

f_andrie = function(dt) remove_na(dt)

f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un)

f_dowle = function(dt) {     # see EDIT later for more elegant solution
  na.replace = function(v,value=0) { v[is.na(v)] = value; v }
  for (i in names(dt))
    eval(parse(text=paste("dt[,",i,":=na.replace(",i,")]")))
}

system.time(a_gdata = f_gdata(dt1)) 
   user  system elapsed 
 18.805  12.301 134.985 

system.time(a_andrie = f_andrie(dt1))
Error: cannot allocate vector of size 305.2 Mb
Timing stopped at: 14.541 7.764 68.285 

system.time(f_dowle(dt1))
  user  system elapsed 
 7.452   4.144  19.590     # EDIT has faster than this

identical(a_gdata, dt1)   
[1] TRUE

f_dowleは参照によってdt1を更新していることに注意してください。ローカルコピーが必要な場合は、明示的に copy 関数は、データセット全体のローカルコピーを作成するために必要である。 setkey , key<-:= はコピーオンライトしない。

次に、f_dowleがどこに時間を費やしているのかを見てみましょう。

Rprof()
f_dowle(dt1)
Rprof(NULL)
summaryRprof()
$by.self
                  self.time self.pct total.time total.pct
"na.replace"           5.10    49.71       6.62     64.52
"[.data.table"         2.48    24.17       9.86     96.10
"is.na"                1.52    14.81       1.52     14.81
"gc"                   0.22     2.14       0.22      2.14
"unique"               0.14     1.36       0.16      1.56
... snip ...

そこで、私が注目するのは na.replaceis.na ここで、いくつかのベクターコピーとベクタースキャンがあります。これらの問題は,小さなna.replace C関数を書くことでかなり簡単に解決できます。 NA ベクター内の参照によって そうすれば、少なくとも20秒は半分になると思います。そのような関数はRのパッケージに存在するのでしょうか?

その理由 f_andrie の全体をコピーしてしまうため、失敗する可能性があります。 dt1 の全体と同じ大きさの論理的な行列を作るか、あるいは dt1 を数回繰り返す。他の2つのメソッドは、一度に1つの列に対して動作します。 NAToUnknown ).

EDIT (コメントでRamnathから要求された、よりエレガントな解決策) :

f_dowle2 = function(DT) {
  for (i in names(DT))
    DT[is.na(get(i)), (i):=0]
}

system.time(f_dowle2(dt1))
  user  system elapsed 
 6.468   0.760   7.250   # faster, too

identical(a_gdata, dt1)   
[1] TRUE

最初からそのようにすればいいのに!と思いました。

EDIT2 (1年以上経った現在)

また set() . を呼び出すことによる(小さな)オーバーヘッドを避けることができるので、ループスルーされるカラムが多い場合は、この方が高速になることがあります。 [,:=,] をループ内で使用します。 set は、ループ可能な := . 参照 ?set .

f_dowle3 = function(DT) {
  # either of the following for loops

  # by name :
  for (j in names(DT))
    set(DT,which(is.na(DT[[j]])),j,0)

  # or by number (slightly faster than by name) :
  for (j in seq_len(ncol(DT)))
    set(DT,which(is.na(DT[[j]])),j,0)
}