第24章 パイプ演算子%>%について

こいつはtidyverse内のmagrittrというパッケージの機能で、パイプ演算子あるいは単にパイプと呼ばれたりします。

以下のサイトが参考になりました。

library(tidyverse)
library(magrittr)
## 
## Attaching package: 'magrittr'
## The following object is masked from 'package:purrr':
## 
##     set_names
## The following object is masked from 'package:tidyr':
## 
##     extract

24.1 基本的な使い方

基本的に%>%は左辺の出力を右辺の関数の第1引数にします。 つまり、f %>% gg(f)と同値です。 ちなみに、RStudioではShift + Ctrl + Mで入力します。

summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 
iris %>% summary()
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 

これだけだとパイプ演算子のご利益は分かりにくいですが、コードが長くなるにつれてその力を発揮します。 例えば、irisの各品種について各変数の平均と中央値を求めてみます。

パイプ演算子を使うと次のように一行のコードで書くことができます。

iris %>% 
  pivot_longer(-Species, names_to = "var", values_to = "val") %>% 
  group_by(var, Species) %>% 
  summarise(mean = mean(val),
            median = median(val))
iris %>% 
  pivot_longer(-Species, names_to = "var", values_to = "val") %>% 
  group_by(var, Species) %>% 
  summarise(mean = mean(val),
            median = median(val)) %>% 
  knitr::kable()
## `summarise()` regrouping output by 'var' (override with `.groups` argument)
var Species mean median
Petal.Length setosa 1.462 1.50
Petal.Length versicolor 4.260 4.35
Petal.Length virginica 5.552 5.55
Petal.Width setosa 0.246 0.20
Petal.Width versicolor 1.326 1.30
Petal.Width virginica 2.026 2.00
Sepal.Length setosa 5.006 5.00
Sepal.Length versicolor 5.936 5.90
Sepal.Length virginica 6.588 6.50
Sepal.Width setosa 3.428 3.40
Sepal.Width versicolor 2.770 2.80
Sepal.Width virginica 2.974 3.00

これをパイプ演算子を使わないと、次のようになります。

summarise(group_by(pivot_longer(iris, -Species, names_to = "var", values_to = "val"), var, Species), mean = mean(val), median = median(val))
summarise(group_by(pivot_longer(iris, -Species, names_to = "var", values_to = "val"), var, Species), mean = mean(val), median = median(val)) %>% 
  knitr::kable()
## `summarise()` regrouping output by 'var' (override with `.groups` argument)
var Species mean median
Petal.Length setosa 1.462 1.50
Petal.Length versicolor 4.260 4.35
Petal.Length virginica 5.552 5.55
Petal.Width setosa 0.246 0.20
Petal.Width versicolor 1.326 1.30
Petal.Width virginica 2.026 2.00
Sepal.Length setosa 5.006 5.00
Sepal.Length versicolor 5.936 5.90
Sepal.Length virginica 6.588 6.50
Sepal.Width setosa 3.428 3.40
Sepal.Width versicolor 2.770 2.80
Sepal.Width virginica 2.974 3.00

あるいは適当なオブジェクトを作成して次のようにします。

temp <- pivot_longer(iris, -Species, names_to = "var", values_to = "val")
temp <- group_by(temp, var, Species)
temp <- summarise(temp, mean = mean(val), median = median(val))
temp
temp <- pivot_longer(iris, -Species, names_to = "var", values_to = "val")
temp <- group_by(temp, var, Species)
temp <- summarise(temp, mean = mean(val), median = median(val))
## `summarise()` regrouping output by 'var' (override with `.groups` argument)
knitr::kable(temp)
var Species mean median
Petal.Length setosa 1.462 1.50
Petal.Length versicolor 4.260 4.35
Petal.Length virginica 5.552 5.55
Petal.Width setosa 0.246 0.20
Petal.Width versicolor 1.326 1.30
Petal.Width virginica 2.026 2.00
Sepal.Length setosa 5.006 5.00
Sepal.Length versicolor 5.936 5.90
Sepal.Length virginica 6.588 6.50
Sepal.Width setosa 3.428 3.40
Sepal.Width versicolor 2.770 2.80
Sepal.Width virginica 2.974 3.00

このように考えるとパイプ演算子のご利益は次のようにまとめられます。

  1. 処理をする順番に関数が登場するので可読性を高めることができる。
  2. 一行でコードを書くことができるので無駄なオブジェクトを作らなくてよい。

24.2 データの代入

データを加工する際、パイプを使うと基本的にはこのようになります。

iris <- iris %>% 
  mutate(species = case_when(Species == "setosa" ~ 0,
                             Species == "versicolor" ~ 1,
                             Species == "virginica" ~ 2))
summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species      species 
##  setosa    :50   Min.   :0  
##  versicolor:50   1st Qu.:0  
##  virginica :50   Median :1  
##                  Mean   :1  
##                  3rd Qu.:2  
##                  Max.   :2

しかし、左から右に流れるべきと思う場合は次のように書くこともできます。

iris %>% 
  mutate(species = case_when(Species == "setosa" ~ 0,
                             Species == "versicolor" ~ 1,
                             Species == "virginica" ~ 2)) -> iris
summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species      species 
##  setosa    :50   Min.   :0  
##  versicolor:50   1st Qu.:0  
##  virginica :50   Median :1  
##                  Mean   :1  
##                  3rd Qu.:2  
##                  Max.   :2

あるいは%<>%という演算子を使うこともできます。

iris %<>% 
  mutate(species = case_when(Species == "setosa" ~ 0,
                             Species == "versicolor" ~ 1,
                             Species == "virginica" ~ 2))
summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species      species 
##  setosa    :50   Min.   :0  
##  versicolor:50   1st Qu.:0  
##  virginica :50   Median :1  
##                  Mean   :1  
##                  3rd Qu.:2  
##                  Max.   :2

24.3 左辺の参照

基本的にはパイプ演算子の左辺を右辺の第1引数にしますが、.を使うことで任意の引数にすることができます。 例えば、irissetosaを除外して回帰分析をしたい場合、第1引数はformulaなので普通はパイプで繋げることはできないが、以下のように書くことができます。

iris %>% 
  filter(Species != "setosa") %>% 
  lm(Sepal.Length ~ Sepal.Width, data = .) %>% 
  summary()
## 
## Call:
## lm(formula = Sepal.Length ~ Sepal.Width, data = .)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.0032 -0.3877 -0.0774  0.3200  1.7381 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   3.0934     0.4844   6.387 5.70e-09 ***
## Sepal.Width   1.1033     0.1675   6.585 2.27e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.5547 on 98 degrees of freedom
## Multiple R-squared:  0.3068, Adjusted R-squared:  0.2997 
## F-statistic: 43.36 on 1 and 98 DF,  p-value: 2.27e-09

また、%$%という演算子は右辺において左辺のデータを参照せずに変数名を指定することができます。 したがって、次のように書くこともできます。

iris %>% 
  filter(Species != "setosa") %$% 
  lm(Sepal.Length ~ Sepal.Width) %>% 
  summary()
## 
## Call:
## lm(formula = Sepal.Length ~ Sepal.Width)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.0032 -0.3877 -0.0774  0.3200  1.7381 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   3.0934     0.4844   6.387 5.70e-09 ***
## Sepal.Width   1.1033     0.1675   6.585 2.27e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.5547 on 98 degrees of freedom
## Multiple R-squared:  0.3068, Adjusted R-squared:  0.2997 
## F-statistic: 43.36 on 1 and 98 DF,  p-value: 2.27e-09

これは、次のコードと同値です。

iris %>% 
  filter(Species != "setosa") %>% 
  {
    lm(.$Sepal.Length ~ .$Sepal.Width)
  } %>% 
  summary()
## 
## Call:
## lm(formula = .$Sepal.Length ~ .$Sepal.Width)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.0032 -0.3877 -0.0774  0.3200  1.7381 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)     3.0934     0.4844   6.387 5.70e-09 ***
## .$Sepal.Width   1.1033     0.1675   6.585 2.27e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.5547 on 98 degrees of freedom
## Multiple R-squared:  0.3068, Adjusted R-squared:  0.2997 
## F-statistic: 43.36 on 1 and 98 DF,  p-value: 2.27e-09

24.4 複数の出力

.を使うことで複数の出力を行うことも可能です。

iris$Sepal.Length %>% 
  {
    mean(.) %>% print()
    median(.) %>% print()
    var(.) %>% print()
  }
## [1] 5.843333
## [1] 5.8
## [1] 0.6856935

$T>$という演算子を使うと右辺は評価されるが、返り値は左辺のままになります。 例えば、plot()に流すことで図を出力しつつ、irissummary()にも流すことができます。

iris %T>%
  plot() %>% 
  summary()

##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species      species 
##  setosa    :50   Min.   :0  
##  versicolor:50   1st Qu.:0  
##  virginica :50   Median :1  
##                  Mean   :1  
##                  3rd Qu.:2  
##                  Max.   :2

これは、以下のコードと同値です。

iris %>% 
  {
    plot(.)
    summary(.)
  }

##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species      species 
##  setosa    :50   Min.   :0  
##  versicolor:50   1st Qu.:0  
##  virginica :50   Median :1  
##                  Mean   :1  
##                  3rd Qu.:2  
##                  Max.   :2