DOING THINGS RIGHT

彩りのある生産的な日々を。

【R言語】条件を満たす行の複数列に代入してくれるmutate_cond

下記のようなデータがあります。y=x _ 1 × x _ 2という関係です。

data<-data.frame(letter=c("a","a","c","b","a"),
           x1=c(20,30,15,25,20),
           x2=c(5,4,10,4,3)) %>% 
  mutate(y=x1*x2)
data
##   letter x1 x2   y
## 1      a 20  5 100
## 2      a 30  4 120
## 3      c 15 10 150
## 4      b 25  4 100
## 5      a 20  3  60

このデータに対して、
letterが“a”である行の、x1に5を足してx2には10を足し、yを計算しなおす。という操作を加えるとき、どういう操作が思いつくでしょうか。

%<>%を使った処理

magrittrパッケージのパイプライン演算子%<>%を使った処理がおそらく一般的だと思います。
x %<>% f()は、x <- x %>% f()と置き換えられるパイプライン演算子で、データフレームの置き換えを効率化してくれます(雑)。

data[data$letter=="a",] %<>% mutate(x1=x1+5,x2=x2+10,y=x1*x2)
data
##   letter x1 x2   y
## 1      a 25 15 375
## 2      a 35 14 490
## 3      c 15 10 150
## 4      b 25  4 100
## 5      a 25 13 325

しかしこの方法は行の抽出の仕方がスマートではありません。条件が複数あるような時にグチャグチャになってしまいます。
さらに、dplyrチェーンの中で代入したいな~なんて時にも困ります。

dplyrチェーンで繋げる

そこで考えられるのがもういっそ気合でdplyrチェーンで済ませてしまおうというやり方です。

data %>% 
  mutate(x1=if_else(letter=="a" , x1+5 , x1),
         x2=if_else(letter=="a" , x2+10 , x2)) %>% 
  mutate(y=x1*x2)
##   letter x1 x2   y
## 1      a 25 15 375
## 2      a 35 14 490
## 3      c 15 10 150
## 4      b 25  4 100
## 5      a 25 13 325

この処理だとパイプライン処理を崩さず代入できますが、
複数列置き換える場合、その数だけif_else()を書く必要があり若干煩雑です。

mutate_cond

filter()mutate()が合体したような関数があればいいんだけどな~などと考えているときに見つけたのがこの記事。
https://stackoverflow.com/questions/34096162/dplyr-mutate-replace-several-columns-on-a-subset-of-rows

どうやらmutate_cond()という関数を定義して使っているらしい。

Create a simple function for data frames or data tables that can be incorporated into pipelines. This function is like mutate but only acts on the rows satisfying the condition

パイプライン中に挿入でき、条件を満たす行にしか作用しないmutate()みたいなヤツらしい。
思わず「こういうのでいいんだよ」と言ってしまいそうなコンセプトですね、早速使ってみましょう。第一引数がデータ、第二が条件、第三以降はmutate()と同じです。

mutate_cond <- function(.data, condition, ..., envir = parent.frame()) {
  condition <- eval(substitute(condition), .data, envir)
  .data[condition, ] <- .data[condition, ] %>% mutate(...)
  .data
}

data %>% mutate_cond(letter=="a" , x1=x1+5 , x2=x2+10 ,y=x1*x2)
##   letter x1 x2   y
## 1      a 25 15 375
## 2      a 35 14 490
## 3      c 15 10 150
## 4      b 25  4 100
## 5      a 25 13 325

素晴らしい。引数の指定も簡単で、本家にあってもおかしくないようなdplyrライクな関数です。
これでみなさんも捗っていきましょう。