Rで見かける %>% は見た目がいかついですが、 慣れるとコードが読みやすくなったり デバッグが楽になったり、なにかと非常に便利です。 主な役割はちょっとひねった関数の呼び出しができる… というものです。

library("magrittr")
# ctrl + shift + m とかで %>% をだせる(OS依存)
iris %>% head

これによく似た形の関数の呼び出しには 1 + 1 なんかがあります。 こうした 項と項の間に関数を置く記法を 「中置記法」と呼び、 中置記法に使う演算子(+%>%)を中置演算子(Infix Operator)と呼びます1

さて、こうした中置演算子には2つの種類、つまり デフォルトで埋め込まれている中置演算子ユーザーが定義する中置演算子 があります。 そして一般的に(あるいは慣習的に?)、 後者は %>% のように % で挟む形式を取るのです。

ちなみにデフォルトで用意されている中置演算子は “infix operators in R” とかでググるとどっさり でてきます。マニアックなのだと <<- とか とか -> とかですかね。

したがって、 「Rで見かける %>%ユーザー定義の 中置記法だよー」 というのがタイトルの答えになります。

記法の幅が増えると表現力が増すのですが、 どうにもこの%に挟まれる演算子は見た目がイカツすぎるんですよね。 最初に見た時は「いやそんなんつかわんでもかけるやん」となってモヤモヤしてました。

残りは余談なのですが、 今回はRでよく見る %>% の使い方を見ていきます。 なお、今後%>%をpipeと呼びます。

そもそもpipeとは

pipe

ルネ・マグリットの作品に「これはパイプではない」というのがありますよね。 pipe(%>%)が入っているパッケージの名前はmagrittrで、 この画家の名前に由来します。 もっと言うと magrittrtidyverse という データコネコネパッケージの一部… なのですが、それはさておき。

最初はRのコード中に magrittr package のpipe(%>%)を見る度に 「いやいや打ちづらいし、それって見づらくなっているだけでは?」 と思っていました。

ただ使い始めると楽しくて、 最近はどっぷりmagrittrにハマってしまいました。 気づいた頃にはhead()にもpipeしだす始末…。

# みづらい。よくない。
data %>% head()
# みやすい
head(data)

しかし他の方にコード共有する段階になってから コード可読性の低さに気づいて pipeとかmagrittrの良さを自分なりに整理しようかなと思った次第です。 しばらくコードとにらめっこした結果、 magrittrを使うメリットは以下の4つ2なのでは、 と暫定ですが考えています。

  1. ネストを簡略化
  2. 一時変数の駆逐
  3. 一部の関数(is系など)の可読性を改善
  4. 作業工程の編集

pipeのメリット

n重ネスト(n>=2)を簡略化と一時変数の駆逐

まずfile名から任意の操作をしてdataframeを返す fn2dfという関数があったとします。 その関数にfilenameの文字列を入れた結果の出力が持つ クラスをclass()で確認したい状況を仮定します。

ストレートに考えると以下の2パターンが存在します。 前者はネームスペース を無駄に消費して変数名の衝突頻度が増えますし、 スクリプトの変数名を変える回数が増えます。 後者は関数がネストしていて読みづらいです。

# 一時変数量産
data1 = fn2df(file_name)
data1_class = class(data1)
print(data1_class)

# 関数ネスト
print(class(fn2df(file_name)))

じゃあどうするんだ、という話でmagrittrがでてきます。 下のコードは左からそのまま「file_namefn2dfに入れて、 その結果のclassをprintする」と読めます。 パイプは左の式を右の関数に代入してオブジェクトを返すのですが、 更にそのオブジェクトをパイプして、とたらいまわしにできるわけです。 その結果、一時変数の利用を避け可読性を保持しています。

# pipe
library(magrittr)
file_name %>% fn2df %>% class %>% print

逆に先ほどの繰り返しになりますが、 一時変数もネストもない関数にはパイプはなしということになります。 隙あらばpipeしたくなる気持ちを抑えるのが大変です。

# OK
head(data)

# NG
data %>% head

# Viewを使う場合は好みの問題?
View(head(data))
head(data) %>% View
data %>% head %>% View

一部の関数(is系など)の可読性を改善と作業工程の編集

上では引数が一つの関数にはpipeは向かないと書きましたが、 例外的にis.na系の関数はpipeしたほうが読みやすい場合がありそうです。

# returns boolean values
is.na(data1)
data1 %>% is.na

また、関数の括弧って意外と軽快な編集を妨げるので、 pipeでガリガリ足したり削ったりしたほうがネストしている場合などは 編集が楽になりそうです。 例えば下の関数の一番外側の関数Viewを削りたい場合なんかが顕著です。

ネストしている場合、詰めVimなら4手(fh,di),<ctrl>v,p)必要な一方、 magrittrなら2手(3f<space>,D)で終わります。

View(head(data))
# fh di) <ctrl>v p
head(data)

data %>% head %>% View
# 3f<space> D
data %>% head
View(head(data))
data %>% head %>% View

ちなみにRStudioを使っている場合はctrl+shift+m%>%が出ます。

おまけ

あとはさらに余談なのですが、 Pythonみたいに中置関数で文字列連結したい場合なんかには 下のように定義できます。

"%+%" = function(a,b) paste0(a,b)
ggsave(file="../source/" %+% "region" %+% i %+% ".png", plot=graph)

いつか%$%などの他の演算子やdplyrなどの他のライブラリとのシナジーに関しても書けたらと思います。

参考文献


  1. 余談ですが、Rにおける中置記法は糖衣構文です。1 + 1"+"(1,1)と書くのが正式だそうです。 

  2. 公式のgithubでは可読性の保証を4つの手段で実現すると言っていて、1. データを読む時に関数の中から外ではなく、左から右と自然に読めること、2. ネストした関数適用の回避、3. ローカル変数と関数定義の最少化、4. 手順編集の簡易化、が挙げられています。ただ1はネストした関数の場合のみの話であり、一つの関数適用の際は例外を除きmgrittrを使わない方がむしろ読みやすい気すらしています(is系関数は一つの関数適用でも読みやすいかも)。なんでもかんでもpipeしてしまう自分への戒めも込めたエントリーになります。