以下の文章はmagrittr - Ceci n'est pas un pipeを訳したものです( ゚Д゚)y─┛~~
Abstract
magrittr(洗練されたフランス語のアクセントで発音する)パッケージは、2つの目的をもつ。1つは開発時間を短縮すること、もう1つはコードの可読性と保守性を向上させることだ。要はコードがイケてる感じになるのだ( ゚Д゚)y─┛~~
この謙虚な目的を達成するために、magrittr(アクセントを忘れないでもらいたい)は新しくパイプ風演算子である%>%
を導入する。この演算子を使うことで、値を演算子の先の式や関数呼び出しに渡すことができるようになる。例えばf(x)
はx %>% f
と書ける。これは他の言語を考えてみると、決して新しい概念というわけでもない。F#で広く使われている|>
演算子や、(言うまでもないことだが)Unixのパイプはmagrittrパッケージを開発する動機となっている。
このvignetteではmagrittrの主な機能を解説するとともに、初期のリリースに追加したいくつかの機能も紹介する。
入門と基本
はじめのうちは%>%
が本当に役に立つのかどうか疑問に思うかもしれない。しかし、この文法の変更がコードの読み書きをより直感的にするということをきっと気付いてもらえることと思う。
例を考えてみよう。以下ではRに付属しているmtcars
データセットを軽く集計・変更している。
library(magrittr)
car_data <-
mtcars %>%
subset(hp > 100) %>%
aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>%
transform(kpl = mpg %>% multiply_by(0.4251)) %>%
print
cyl | mpg | disp | hp | drat | wt | qsec | vs | am | gear | carb | kpl |
---|---|---|---|---|---|---|---|---|---|---|---|
4 | 25.90 | 108.05 | 111.00 | 3.94 | 2.15 | 17.75 | 1.00 | 1.00 | 4.50 | 2.00 | 11.010090 |
6 | 19.74 | 183.31 | 122.29 | 3.59 | 3.12 | 17.98 | 0.57 | 0.43 | 3.86 | 3.43 | 8.391474 |
8 | 15.10 | 353.10 | 209.21 | 3.23 | 4.00 | 16.77 | 0.00 | 0.14 | 3.29 | 3.50 | 6.419010 |
データであるmtcars
(これはデータフレームだ)から初め、サブセットを抽出し、シリンダー数毎に情報を集約し、mile/gallonを補う情報としてkm/Lの列を追加した。最後に結果を代入する前にprintした。手順が問題を考える際の論理的な順序になっているという点に注目して欲しい。つまり、「データ -> 変換 -> 集約」の順だ。そしてコードはこの順序で実行される。これはレシピのように読みやすく、簡単に追うことができる。
上記コードに対する恐ろしい代案はこうだ。
car_data <-
transform(aggregate(. ~ cyl,
data = subset(mtcars, hp > 100),
FUN = function(x) round(mean(x, 2))),
kpl = mpg * 0.4251)
多数の括弧が混乱をもたらしているこのコードの解読は難しい問題となるだろう。これを書いたのが自分ではない場合は尚更だ。
magrittrを使えば、aggregate
で使用する集約用の関数を組み立てるのも簡単になるという点にも注目して欲しい。パイプラインを.
から始めるだけで関数定義になるのだ。これは*aaply
族の関数を利用する際にも便利だ。
2番目の例は、一時変数を使うことによって(magrittrを使えば一時変数作成も回避できるのだが)、もう少し良くできるかもしれない。しかし、例として提示したような煩雑なコードはしばしば見かけるものだ。
他にも利点はある。何か別の手順を途中に追加したくなったとしよう。パイプラインを使っていれば、これは非常に簡単にできる。しかし、標準的なRのコードで同じことをするのは少し大変だろう。
先程の例が示すmagrittrの特徴はまとめるとこうだ。
- 標準では、パイプ演算子の左側の結果(LHS: left-hand side)は、右側(RHS: right-hand side)の第一引数として使われる。例中では、
subset
とtransform
で使用されている。 -
%>%
は関数の内部で入れ子にして使うこともできる。例えば引数の指定部分などだ。例ではmpg
からkpl
を計算する部分で使用している。 - LHSが第一引数以外の部分で必要な場合は、ドット
.
をプレースホルダとして使用できる。例ではaggregate
の中で使用した。 - formula中のドットとプレースホルダとしてのドットが混同されることはない。
aggregate
の中では2つの使用法が併存している。 - 必要な引数がLHSただ一つである場合には、空の括弧を省略することができる。これは
print
の部分で使用している。print
はLHS %>% print()
とも書けるし、もちろんLHS %>% print(.)
とも書ける。 -
.
から始めるパイプラインは無名関数を作成する。これはaggregate
内で集約関数を定義する部分で使用した。
例に含まれていない機能として、無名関数(あるいはlambda)へのパイプがある。標準の関数定義機能を用いて、以下のように実行できる。
car_data %>%
(function(x){
if(nrow(x) > 2)
rbind(head(x, 1), tail(x, 1))
else x
})
しかし、magrittrでは短縮形として以下の書き方もできるようにしている。
car_data %>%
{
if(nrow(.) > 2)
rbind(head(., 1), tail(., 1))
else .
}
## cyl mpg disp hp drat wt qsec vs am gear carb kpl
## 1 4 26 108 111 4 2 18 1 1 4 2 11.0526
## 3 8 15 350 192 3 4 17 0 0 3 4 6.3765
右辺は無名関数の本体だけになり、右辺の自然な拡張になっていることが分かるだろう。もちろん、もっと長い、複雑な関数を使うことだってできる。
最初の例では無名関数を括弧で括っていた。パイプを行う前に右辺を評価したい場合には、このように括弧を使用する。
あまり役に立たないだろうが、例を示しておこう。
1:10 %>% (substitute(f(), list(f = sum)))
## [1] 55
その他のパイプ演算子
magrittrは他に3つのパイプ演算子を提供している。これらは%>%
ほど汎用性は無いが、特定の状況では役に立つこともあるだろう。
まずは"tee"演算子こと%T>%
だ。これは%>%
のように機能するが、右辺の結果を返す代わりに左辺の結果をそのまま返すという特徴がある。これは、パイプライン中で副作用のみを利用したい場合(printやplot、loggingなど)に便利だ。以下に例を示そう(plot結果は省略した)。
rnorm(200) %>%
matrix(ncol = 2) %T>%
plot %>% # plotは通常返り値が無い
colSums
set.seed(1)
rnorm(200) %>%
matrix(ncol = 2) %>%
colSums
## [1] 10.888737 -3.780808
plotは通常返り値が無いので、パイプラインの途中に挟むとそこで処理が止まってしまう。しかし、%T>%
を使うことでcolSums
にはplot
の結果ではなくmatrix(ncol = 2)
の結果が受け渡されるので、処理を継続できるのだ。
2つめの演算子は"exposition"演算子こと%$%
だ。この演算子を使うと、左辺のオブジェクトに含まれる名前を右辺で参照できるようになる。実質的には、これはwith
関数の短縮形のようなものだ。これは、lm
やaggregate
が持っているようなdata引数を持たない関数を使う場合に便利だ。例を示そう。
iris %>%
subset(Sepal.Length > mean(Sepal.Length)) %$%
cor(Sepal.Length, Sepal.Width)
## [1] 0.3361992
data.frame(z = rnorm(100)) %$%
ts.plot(z)
最後に複合代入演算子%<>%
の説明をしよう。これはパイプラインの最初で使用することができる。この演算子を使うと、パイプラインの結果は左辺のオブジェクトに代入される。つまり、foo <- foo %>% bar %>% baz
をfoo %<>% bar %>% baz
のように書けるということだ。もう一つ例を示しておこう。
iris$Sepal.Length %<>% sqrt
%<>%
は、代入演算子<-
を使う場面ならどこでも使える。
x %<>% foo %>% bar
x[1:10] %<>% foo %>% bar
x$baz %<>% foo %>% bar
Aliases
magrittrは%>%
演算子だけでなく、加算や乗算などの操作をmagrittrの構文に馴染ませるためのエイリアスをいくつか提供している。
rnorm(1000) %>%
multiply_by(5) %>%
add(5) %>%
{
cat("平均: ", mean(.),
"分散: ", var(.), "\n")
head(.)
}
## 平均: 4.825097 分散: 27.5386
## [1] 7.047009 13.444366 12.932942 3.345461 -6.426178 17.488308
上記の例は以下の書き方と同じ意味になる。
rnorm(1000) %>% `*`(5) %>% `+`(5) %>%
{
cat("平均: ", mean(.), "分散: ", var(.), "\n")
head(.)
}
詳しい説明は?multiply_by
を確認してほしい。