dplyrでdata frameの一部を書き換える

base Rとは異なる発想
R
公開

2023年10月22日

データの中の一部の行だけを書き換える場合を考えてみましょう。

例えば、次のような簡単なデータがあったとします。

example
  class value
1     A     1
2     A     1
3     B     2
4     B     2
5     C     3

この中のクラスBの値を4に書き換えたい場合、Rでは次のように書きます。

example[example$class == "B", "value"] <- 4
example
  class value
1     A     1
2     A     1
3     B     4
4     B     4
5     C     3

対象となる値だけをまず取り出して、そこに更新する値を代入するというイメージですね。

dplyrの場合

これをdplyrパッケージを使って書くとしたらどのようになるでしょうか。

同じように考えると、まずfilter()で書き換えたい行だけを取り出してからmutate()で値を更新すれば良さそうに思えます。

library(dplyr)

example |>
  filter(class == "B") |>
  mutate(value = 4)
  class value
1     B     4
2     B     4

しかしこれでは取り出した行だけしか残りません。データは全て残しつつ一部の行だけ書き換える方法はあるでしょうか。

このためにはもともとのRのやり方から発想を転換し、一部を取り出さずに全体を書き換えつつ、ifelseでクラスB以外のデータは元の値のままとすることで実現できます。

mutate(example, value = ifelse(class == "B", 4, value))
  class value
1     A     1
2     A     1
3     B     4
4     B     4
5     C     3

base Rのやり方をイメージしているとこの書き方は違和感があるかも知れませんが、mutate()で列全体を書き換えることを前提に考えれば自然な書き方に思えてきます。 実際、Hadley Wickham氏もこの書き方になるだろうとコメントしています。Github Issue #425

dplyrパッケージではif_else()case_when()というbase Rよりも頑健な設計がされたコントロールが提供されているので、データの一部を書き換えるにはmutate()の中でこれらを駆使していくのがよいでしょう。