特定の行を条件にしながら複数の列を動的に変更する


11

この辺りに似たような質問がいくつかあることは知っていますが、私が抱えている正確な問題に対応しているようには見えません。

set.seed(4)
df = data.frame(
  Key = c("A", "B", "A", "D", "A"),
  Val1 = rnorm(5),
  Val2 = runif(5),
  Val3 = 1:5
)

Key == "A"である行の値列の値をゼロ化したいと思います。列名はgrep

cols = grep("Val", names(df), value = TRUE)

通常、この場合に必要data.tableなことを達成するには、次のようにします。

library(data.table)
df = as.data.table(df)
df[Key == "A", (cols) := 0]

そして、望ましい出力は次のようになります:

  Key      Val1       Val2 Val3
1   A  0.000000 0.00000000    0
2   B -1.383814 0.55925762    2
3   A  0.000000 0.00000000    0
4   D  1.437151 0.05632773    4
5   A  0.000000 0.00000000    0

ただし、今回はdplyr、みんなが使用するチームプロジェクトに取り組んでいるため、使用する必要があります。私が提供したばかりのデータは例示であり、実際のデータは500万行を超え、16の値列が更新されます。私が思いつくことができる唯一の解決策は次のmutate_atように使用することです:

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

しかし、これは私の実際のデータでは非常に遅いようです。よりエレガントで、より重要なことに、より高速なソリューションを見つけたいと思っていました。

私は使用してmap、引用符で囲まずに!!、使用してget、そして:=(これはうっとうしいこと:=にdata.tableでマスクされる可能性があります)などを使用して多くの組み合わせを試しましたが、これらの作業がどのように有効なソリューションを構築するのに十分なほど深くないかについての私の理解はないと思います。


6
これにはどれくらい時間がかかりますか?df [df $ Key == "A"、cols] <-0。ifelseを呼び出して列と行をループしているので、遅いことがわかります。
StupidWolf

StupidWolf、これは私のデータでは実際には非常に高速ですが、非常にコンパクトでエレガントです。ありがとう。必要に応じて、自由に回答として追加してください。
LiviusI

それを回避するための別の解決策をお見せしましょう
。– StupidWolf

回答:


9

このdplyrコマンドを使用すると、

df %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(df$Key == "A", 0, x))

実際には、ステートメントdf $ Key == "A"をn回評価しています。ここで、n =は列の数です。

回避策の1つは、変更する行を事前に定義することです。

idx = which(DF$Key=="A")
DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})

@IceCreamToucan(以下のコメントを参照)によって正しく指摘されている、よりクリーンでより良い方法は、関数replaceを使用する一方で、追加のパラメーターを渡すことです。

DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0)

私たちはこれらすべてのアプローチをテストすることができ、dplyrとdata.tableは比較可能だと思います。

#simulate data
set.seed(100)
Key = sample(LETTERS[1:3],1000000,replace=TRUE)
DF = as.data.frame(data.frame(Key,matrix(runif(1000000*10),nrow=1000000,ncol=10)))
DT = as.data.table(DF)

cols = grep("[35789]", names(DF), value = TRUE)

#long method
system.time(DF %>% mutate_at(.vars = vars(cols), .funs = function(x) ifelse(DF$Key == "A", 0, x)))
user  system elapsed 
  0.121   0.035   0.156 

#old base R way
system.time(DF[idx,cols] <- 0)
   user  system elapsed 
  0.085   0.021   0.106 

#dplyr
# define function
func = function(){
       idx = which(DF$Key=="A")
       DF %>% mutate_at(.vars = vars(cols), .funs = function(x){x[idx]=0;x})
}
system.time(func())
user  system elapsed 
  0.020   0.006   0.026

#data.table
system.time(DT[Key=="A", (cols) := 0])
   user  system elapsed 
  0.012   0.001   0.013 
#replace with dplyr
system.time(DF %>% mutate_at(.vars = vars(cols), replace, DF$Key == 'A', 0))
user  system elapsed 
  0.007   0.001   0.008

4
mutateへの追加の引数は1回評価され、提供された関数にパラメーターとして渡されます(たとえばlapplyと同様)。これにより、次のように一時変数idxを明示的に作成せずにこれを実行できますdf %>% mutate_at(vars(contains('Val')), replace, df$Key == 'A', 0)
IceCreamToucan

@IceCreamToucanを指摘してくれてありがとう、私はそれを知りませんでした。うん、置換機能はさらに優れており、私よりも不器用ではありません。よろしければ回答に含めますか?(もちろんあなたへの信用)。
StupidWolf

私のマシンでテストした後、このreplace方法は元のidx方法より少し遅いようです。
IceCreamToucan

1
またdplyr::if_else()、ベースよりも速いと思いますifelse()
sindri_baldur
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.