magrittrのパイプとbaseのパイプ

パイプでつないだ関数が評価されるframeがちょっと異なる
R
公開

2023年1月15日

R4.1.0からbaseパッケージにパイプ演算子|>が追加されました。

これまでmagrittrパッケージの%>%で実現していたことを、base Rで書けるようになったのは嬉しいことです。 ただし、両者には微妙な違いがありますのでそれをご紹介します。

magrittrパッケージを呼び出しておきましょう。

library(magrittr)

magrittrのパイプは右辺の表現を前もって評価する

magrittr%>%では、右辺をnon-standard evaluationで評価するのでtidyverseらしく拡張された記述が可能です。 単純な例で言うと、引数が1つだけならば関数fの名前だけでもうまく動きます。

x %>% f

これがbase|>だと、Rが認識できる関数の形にするためにカッコが必要になります。

x |> f()

このx |> f()f(x)とまったく同じなのに対して、x %>% fは先にfが何なのかを判定してからf(x)が実行されています。 つまりそこには2段階のステップが内包されているということです。 それはsys.callsなどを使うと確認できます。

f <- function(x){ print(sys.calls()) }
x <- 1

x %>% f
[[1]]
x %>% f

[[2]]
f(.)
x |> f()
[[1]]
f(x)

このように|>は1段階で実行されていますが、%>%は2段階で実行されていることが分かります。

環境(environment)に依存した処理に注意

パイプで結んだf(x)の前に1ステップあるか否かは、呼び出し元の環境を変更するときなどに影響してきます。 例えばassignを使って変数を作成する場合を考えましょう。assignは通常は「現在の環境」に変数を作成します。

assign("y", 2)
y
[1] 2

これは「assignが呼び出された環境」に変数を作成しているとも言えます。 base|>を使った場合はグローバル環境(通常の環境)から直接関数を呼び出したのと同じになるため、assignは想定どおり動きます。

3 |> assign("y", value=_)
y
[1] 3

ここで、セットしたい値はassignの2番目の引数に渡すので、パイプでつなぐためにR4.2.0から実装されたplaceholderの_を使いました。

yの値が3に更新されたことが分かります。しかしmagrittr%>%では異なった動きをします。

# yを削除
rm("y")

4 %>% assign("y", .)
y
Error in eval(expr, envir, enclos): object 'y' not found

変数yが作成されません。先ほどの%>%の2段階ステップのうちassignは2段階目で実行されます。 つまりassignの呼び出し元は1段階目のステップになります。(ここまでステップと呼んでいるものはRの中ではframeと言います)

1段階目の環境は一時的なもので、処理が終わると消えてしまいます。 そこに作られた変数yは、一緒に消えてしまったというわけです。

そこで、一時的な環境でなくグローバル環境に変数を作成するには、それを明示的に指定することが必要になります。

# グローバル環境(.GlobalEnv)を引数に追加
4 %>% assign("y", ., pos=.GlobalEnv)
y
[1] 4

今度はちゃんと変数yが作成されました。

このようにmagrittrbaseのパイプには微妙な違いがあります。 ただ、それが問題になるような場面はほとんどないと思うので、あまり気にする必要はないでしょう。