この文章は、pipeR 0.5 README を適当に翻訳したものです。
https://github.com/renkun-ken/pipeR/tree/0.5
pipeR の最新バージョン(v0.6)の翻訳は下記にあります。
pipeR
pipeR は、パイプ演算子(%>>%
)およびパイプ関数(Pipe()
)を提供する R パッケージです。
シンタックスとして、
- 関数の最初の引数にパイプ
- 表現式中のドット(
.
)にパイプ - ラムダ式でパイプ
- 副作用ありパイプ
- 代入ありパイプ
をサポートしています。
これらのシンタックスは、パイプラインの可読性を上げるためにデザインされています。
インストール
CRAN からインストール(v0.4-2):
install.packages("pipeR")
開発版を GitHub からインストール(v0.5):
devtools::install_github("pipeR","renkun-ken","0.5")
モチベーション
次のコードは従来のアプローチで書かれたものです:
plot(density(sample(mtcars$mpg, size = 10000, replace = TRUE),
kernel = "gaussian"), col = "red", main="density of mpg (bootstrap)")
R の組み込みデータセットである mtcars
の変数 mpg
に対して、ブートストラップ法でリサンプリングし、ガウシアンカーネルを用いて推定した密度関数をプロットしています。
このコードは深くネストされているため、読むのもコードをメンテナンスするのも一苦労です。
パイプ演算子を使えば、次のように書くことができます:
mtcars$mpg %>>%
sample(size = 10000, replace = TRUE) %>>%
density(kernel = "gaussian") %>>%
plot(col = "red", main = "density of mpg (bootstrap)")
このコードは、よりクリーンになりました。読むのもメンテナンスするのも容易です。
使い方
%>>%
パイプ演算子 %>>%
は、基本的に、左辺の値を右辺の式に渡します。右辺の式は、そのシンタックスに従って評価されます。
関数の最初の引数として渡す
多くの R 関数は、パイプとの親和性が高いです。データを最初の引数として受け取る関数が多いです。このような引数配置であれば、パイプにより操作の流れを作ることができます。つまり、一つのデータソースを関数の最初の引数として入力し、関数を適用し、次の関数の最初の引数として渡すことができます。このようなコマンドの連鎖をパイプラインと呼びます。
%>>%
の右側には、常に関数名か関数呼び出しが来ますが、左側の値は常にその関数の最初の引数になります。
rnorm(100) %>>%
plot
rnorm(100) %>>%
plot(col="red")
左辺の値が複数の箇所で必要となる場合もあります。その場合、関数呼び出しの中で .
を使うことで表現することができます。
rnorm(100) %>>%
plot(col="red", main=length(.))
関数を名前空間を指定して呼び出す場合、関数の終わりには必ず ()
をつけて呼び出して下さい。
rnorm(100) %>>%
stats::median()
rnorm(100) %>>%
graphics::plot(col = "red")
表現式中の .
にパイプする
R の全ての関数がパイプに適合するとは限りません。いくつかの関数は、データを最初の引数として受け取りません。そのような場合には、関数呼び出しを {}
もしくは ()
で囲み、表現式にしてから .
を使ってパイプするとよいでしょう。
mtcars %>>%
{ lm(mpg ~ cyl + wt, data = .) }
mtcars %>>%
( lm(mpg ~ cyl + wt, data = .) )
ラムダ式でパイプする
.
を使ったパイプでは、時々混乱が起こります。例えば:
mtcars %>>%
(lm(mpg ~ ., data = .))
これはちゃんと動きますが、二つの .
が別の意味を持っているため、紛らわしく感じます。
このような場合、pipeR ではラムダ式を使うことができます。ラムダ式は ()
の中にモデル式を書くことで表現されます。例えば、(x ~ f(x))
というラムダ式を書けば、パイプで受け取った値を変数 x
に入れ、関数 f(x)
を評価して次に受け渡すという意味になります。
mtcars %>>%
(df ~ lm(mpg ~ ., data = df))
mtcars %>>%
subset(select = c(mpg, wt, cyl)) %>>%
(x ~ plot(mpg ~ ., data = x))
副作用をパイプに含める
パイプラインでは、最終的な結果だけを必要とするのではなく、中間結果も出したい場合があります。中間結果をプリントしたり、プロットしたり、保存したりするためには、メインストリームのパイプラインを壊さずに、副作用を持たせる必要があります。例えば、散布図を描くために plot()
を呼び出すと、結果として NULL
が返されます。もし、パイプラインの中で何の工夫もなく plot()
を呼び出せば、次に渡すべき結果が NULL
になってしまい、パイプラインが壊れてしまいます。
パイプラインを壊さずに副作用を持たせるには、~
で始まる片側モデル式 (~ f(x))
を使います。これにより、右辺の式 f(x)
は副作用のために評価されるだけであり、その結果は無視され、もともと入力された値が結果としてパイプラインに渡されるという意味になります。
mtcars %>>%
subset(mpg >= quantile(mpg, 0.05) & mpg <= quantile(mpg, 0.95)) %>>%
(~ cat("rows:",nrow(.),"\n")) %>>% # cat() returns NULL
summary
mtcars %>>%
subset(mpg >= quantile(mpg, 0.05) & mpg <= quantile(mpg, 0.95)) %>>%
(~ plot(mpg ~ wt, data = .)) %>>% # plot() returns NULL
(lm(mpg ~ wt, data = .)) %>>%
summary()
~
によって、メインストリームパイプラインと副作用操作を容易に区別することができます。
中間結果をプリントする糖衣構文も用意されています。(? expr)
は (~ print(expr))
と同様の動きをします。
mtcars %>>%
(? ncol(.)) %>>%
summary
代入ありパイプ
プリントやプロットに加えて、途中結果を変数に代入したい場合もあります。
パイプした結果を変数 x
に代入したい場合は、(~ x)
と書けばいいだけです。
mtcars %>>%
(lm(formula = mpg ~ wt + cyl, data = .)) %>>%
(~ lm_mtcars) %>>%
summary
パイプした値をそのまま変数に代入するのでなく、何か変換をかけてから代入したい場合は、=
や <-
が使えます。より自然に、ラムダ式風に書くには、->
を使います。
mtcars %>>%
(~ summ = summary(.)) %>>% # side-effect assignment
(lm(formula = mpg ~ wt + cyl, data = .)) %>>%
(~ lm_mtcars) %>>%
summary
mtcars %>>%
(~ summary(.) -> summ) %>>%
mtcars %>>%
(~ summ <- summary(.)) %>>%
中間結果を変数に代入しつつ、パイプで次に送るには、単に (x = f(x))
のように書けばできます。
mtcars %>>%
(~ summ = summary(.)) %>>% # side-effect assignment
(lm_mtcars = lm(formula = mpg ~ wt + cyl, data = .)) %>>% # continue piping
summary
もしくは (f(x) -> x)
と書けばより自然です。
mtcars %>>%
(~ summary(.) -> summ) %>>% # side-effect assignment
(lm(formula = mpg ~ wt + cyl, data = .) -> lm_mtcars) %>>% # continue piping
summary
オブジェクトから要素を抽出する
x %>>% (y)
と書けば、オブジェクト x
から要素 y
を抜き出すことができます。x
としては、ベクトルでもリストでも環境でも、[[]]
が定義されているものならなんでも OK です。さらには、S4 オブジェクトでも OK です。
mtcars %>>%
(lm(mpg ~ wt + cyl, data = .)) %>>%
(~ lm_mtcars) %>>%
summary %>>%
(r.squared)
Compatibility
- dplyr と一緒に使えます:
library(dplyr)
mtcars %>>%
filter(mpg <= mean(mpg)) %>>%
select(mpg, wt, cyl) %>>%
(~ plot(.)) %>>%
(model = lm(mpg ~ wt + cyl, data = .)) %>>%
(summ = summary(.)) %>>%
(coefficients)
- ggvis と一緒に使えます:
library(ggvis)
mtcars %>>%
ggvis(~mpg, ~wt) %>>%
layer_points()
- rlist と一緒に使えます:
library(rlist)
1:100 %>>%
list.group(. %% 3) %>>%
list.mapv(g ~ mean(g))
Pipe()
Pipe()
creates a Pipe object that supports light-weight chaining without any external operator. Typically, start with Pipe()
and end with $value
or []
to extract the final value of the Pipe.
Pipe object provides an internal function .(...)
that work exactly in the same way with x %>>% (...)
, and it has more features than %>>%
.
NOTE:
.()
only supports assignment with<-
or->
.
Piping
Pipe(rnorm(1000))$
density(kernel = "cosine")$
plot(col = "blue")
Pipe(mtcars)$
.(mpg)$
summary()
Pipe(mtcars)$
.(~ summary(.) -> summ)$
lm(formula = mpg ~ wt + cyl)$
summary()$
.(coefficients)
Subsetting and extracting
pmtcars <- Pipe(mtcars)
pmtcars[c("mpg","wt")]$
lm(formula = mpg ~ wt)$
summary()
pmtcars[["mpg"]]$mean()
Assigning values
plist <- Pipe(list(a=1,b=2))
plist$a <- 0
plist$b <- NULL
Side effect
Pipe(mtcars)$
.(? ncol(.))$
.(~ plot(mpg ~ ., data = .))$ # side effect: plot
lm(formula = mpg ~ .)$
.(~ lm_mtcars)$ # side effect: assign
summary()$
Compatibility
- Working with dplyr:
Pipe(mtcars)$
filter(mpg >= mean(mpg))$
select(mpg, wt, cyl)$
lm(formula = mpg ~ wt + cyl)$
summary()$
.(coefficients)$
value
- Working with ggvis:
Pipe(mtcars)$
ggvis(~ mpg, ~ wt)$
layer_points()
- Working with rlist:
Pipe(1:100)$
list.group(. %% 3)$
list.mapv(g ~ mean(g))$
value
Vignettes
The package also provides the following vignettes:
Help overview
help(package = pipeR)
License
This package is under MIT License.