パイプ演算子の種類
Rでは複数の種類のパイプ演算子が知られている。 ここではいくつかのパイプ演算子について紹介したい。
現在は主に2つのパイプ演算子が使われている(|>
と%>%
)。 それぞれのパイプ演算子の由来は以下の通り。
|>
R4.1から追加されたパイプ演算子。Rの文法要素として追加され、なにもパッケージを読み込まなくても使用できる。Native pipeとも呼ばれる。
%>%
magrittr
パッケージで提供されている。おそらく最初にRでパイプ演算子を導入したもの。
%>>%
pipeR
パッケージで提供されている。現在ではあまり使われていないかもしれない。
以降では、|>
と%>%
の機能面での違いについて紹介する。
実行速度
library (ggplot2)
library (magrittr)
# ネストさせる用の関数。第一引数をそのまま返す
f <- function (x) x
# ベンチマーク
benchmark <- bench:: mark (
# `%>%`のベンチマーク(1, 5, 10回ネスト)
magrittr_nest01 = NULL %>% f (),
magrittr_nest05 = NULL %>% f () %>% f () %>% f () %>% f () %>% f (),
magrittr_nest10 = NULL %>% f () %>% f () %>% f () %>% f () %>% f () %>% f () %>% f () %>% f () %>% f () %>% f (),
# `|>`のベンチマーク(1, 5, 10回ネスト)
native_nest01 = NULL |> f (),
native_nest05 = NULL |> f () |> f () |> f () |> f () |> f (),
native_nest10 = NULL |> f () |> f () |> f () |> f () |> f () |> f () |> f () |> f () |> f () |> f (),
# パイプなしのベンチマーク(10回ネスト)
no_pipe_nest10 = f (f (f (f (f (f (f (f (f (f (NULL ))))))))))
)
summary (benchmark)
# A tibble: 7 × 6
expression min median `itr/sec` mem_alloc `gc/sec`
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
1 magrittr_nest01 819.91ns 943.08ns 961273. 5.15KB 96.1
2 magrittr_nest05 1.84µs 2.13µs 442390. 2.86KB 44.2
3 magrittr_nest10 3.03µs 3.44µs 279000. 0B 55.8
4 native_nest01 41.09ns 122.94ns 8464304. 0B 0
5 native_nest05 532.95ns 615.02ns 1453842. 0B 145.
6 native_nest10 1.31µs 1.48µs 639918. 0B 64.0
7 no_pipe_nest10 1.27µs 1.48µs 644494. 0B 64.5
Code
benchmark %>%
plot () +
scale_x_discrete (limits = rev) +
bench:: scale_y_bench_time (breaks = c (1e-7 , 1e-6 , 1e-5 , 1e-4 , 1e-3 , 1e-2 )) +
theme_linedraw ()
|>
の方が高速だが、多くの場合違いを感じられるほどではない。 %>%
は回数の多いループの中で沢山使うのは避けたほうがいいかもしれない。
Place holder
パイプ演算子はLHSをRHSの第一引数に渡す。 しかし、第一引数以外に渡したいという場合も考えられる。
# `grepl()`の第二引数に渡すことで、c(TRUE, TRUE, FALSE)にしたい。
c ("apple" , "pineapple" , "banana" ) %>% grepl ("apple" )
## Warning in grepl(., "apple"): argument 'pattern' has length > 1 and only the
## first element will be used
## [1] TRUE
c ("apple" , "pineapple" , "banana" ) |> grepl ("apple" )
## Warning in grepl(c("apple", "pineapple", "banana"), "apple"): argument
## 'pattern' has length > 1 and only the first element will be used
## [1] TRUE
これはPlace holder(引数の位置を指定できる変数のようなもの)を使うと実現できる。 Place holderは%>%
の場合は.
、|>
の場合は_
を使う。 また、|>
の場合は_
を名前付き引数に渡す必要がある。
# place holderは`.`。引数名を指定しなくても良い。
c ("apple" , "pineapple" , "banana" ) %>% grepl ("apple" , .)
## [1] TRUE TRUE FALSE
# place holderは`_`。引数名を指定しなければいけない。
c ("apple" , "pineapple" , "banana" ) |> grepl ("apple" , x = _)
## [1] TRUE TRUE FALSE
# 引数名を指定しないとエラーになる。
c ("apple" , "pineapple" , "banana" ) |> grepl ("apple" , _)
Error: pipe placeholder can only be used as a named argument (<text>:2:38)
RHSの第一引数以外にLHSを渡す場合は、place holder (_
または.
)を使用する。 ただし、|>
の場合(_
を使う場合)は引数名を指定する必要がある。
引数名を指定できない場合はどうしたら良いだろうか? %>%
は特に何も考えず、place holderを目的の位置引数に指定できる。
# `paste()`関数はいくらでも引数名なしで引数を受け取る。(`...`の部分)
paste
#> function (..., sep = " ", collapse = NULL, recycle0 = FALSE)
#> .Internal(paste(list(...), sep, collapse, recycle0))
#> <bytecode: 0x1402705b0>
#> <environment: namespace:base>
# `%>%`は問題なく渡せる
"world" %>% paste ("hello" , .)
#> [1] "hello world"
一方で、|>
は引数名なしではplace holderを使うことはできないのでエラーになる。。
# `|>`ではエラーになり実行できない。
"world" |> paste ("hello" , _)
Error: pipe placeholder can only be used as a named argument (<text>:2:12)
このようなとき、|>
では無名関数を使うことで実現できる。 しかし、記述が多少煩雑になるため、可読性が低下するかもしれない。
# 引数の位置を指定する無名関数を書いて、その無名関数に渡す。
"world" |> (function (x) paste ("hello" , x))()
#> [1] "hello world"
"world" |> (\(x) paste ("hello" , x))()
#> [1] "hello world"
quote ( "world" |> (\(x)paste ("hello" , x))() )
#> (function(x) paste("hello", x))("world")
Code
# 実はこの例だと、`|>`でも`x = _`のように適当な引数名をつけることで同じ挙動にできる。
c ("world" ) |> paste ("hello" , x = _)
#> [1] "hello world"
Place holderの複数回使用
Place holderを複数回使用するなど、少し変わった使い方をする場合に|>
と%>%
の柔軟性の違いが大きく現れる。
# `%>%`はそのまま複数回使うことができる。
"Hi!" %>% paste (., .)
#> [1] "Hi! Hi!"
# `|>`はエラーが出て実行できない。Place holderは一度しか指定することはできない。
"Hi!" |> paste (x = _, y = _)
Error: pipe placeholder may only appear once (<text>:2:10)
# 同じことを`|>`でも実現するには無名関数を利用する。
"Hi!" |> (\(x) paste (x, x))()
#> [1] "Hi! Hi!"
%>%
は複数回place holder(.
)を使用できる。
RHSが入れ子になった関数で、内側の関数にLHSを渡したい場合はどうすれば良いだろうか?
# 入れ子にする用の関数。第一引数に受け取った文字列を自分の関数名で包んで返す。第二引数以降は無視する。
outer <- function (x = "" , ...) glue:: glue ("outer({x})" )
inner <- function (x = "" , ...) glue:: glue ("inner({x})" )
# RHSが入れ子構造の関数になっている。
# 内側の関数`inner`にLHSを渡して、"outer(inner(hello))"と出力したいがそうならない。
"hello" |> outer (inner ())
#> outer(hello)
"hello" %>% outer (inner ())
#> outer(hello)
# これは実行されるときに以下のように、`outer`関数の第一引数にLHSが渡されているためである。
quote ("hello" |> outer (inner ()))
#> outer("hello", inner())
%>%
では、RHSを{
と}
で囲むことで任意の位置の引数に渡すことができる。 |>
では、無名関数を使う必要がある。
"hello" %>% {outer (inner (.))}
#> outer(inner(hello))
"hello" |> (\(x) outer (inner (x)))()
#> outer(inner(hello))
まとめると、%>%
を使う場合、RHSを{
と}
で囲むだけでかなり柔軟にplace holder (.
)を利用することができる。 一方で|>
は制限が強いため、place holder (_
)を柔軟に利用することは難しい。
%>%
は入れ子や複数回place holderを使うときには、RHSを{
と}
で囲むだけで良い。 |>
は、place holderの利用に強い制限があるため、無名関数などを利用する必要がある。
Sessioninfo
R version 4.3.2 (2023-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.1
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Asia/Tokyo
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] magrittr_2.0.3 ggplot2_3.5.1
loaded via a namespace (and not attached):
[1] gtable_0.3.5 jsonlite_1.8.8 dplyr_1.1.4 compiler_4.3.2
[5] crayon_1.5.2 tidyselect_1.2.1 ggbeeswarm_0.7.2 tidyr_1.3.1
[9] scales_1.3.0 yaml_2.3.9 fastmap_1.1.1 R6_2.5.1
[13] generics_0.1.3 knitr_1.48 htmlwidgets_1.6.4 tibble_3.2.1
[17] munsell_0.5.1 pillar_1.9.0 rlang_1.1.4 utf8_1.2.4
[21] xfun_0.46 cli_3.6.3 withr_3.0.0 digest_0.6.34
[25] grid_4.3.2 beeswarm_0.4.0 lifecycle_1.0.4 vipor_0.4.7
[29] vctrs_0.6.5 bench_1.1.3 evaluate_0.24.0 glue_1.7.0
[33] farver_2.1.2 profmem_0.6.0 fansi_1.0.6 colorspace_2.1-1
[37] rmarkdown_2.25 purrr_1.0.2 tools_4.3.2 pkgconfig_2.0.3
[41] htmltools_0.5.7