付録 B — パイプ演算子 (R)

Rのコードを見ていると%>%|>といった謎の記号を見かけます。これらはパイプ演算子と呼びますが、その正体を考えたいと思います。

B.1 パイプ演算子の機能

まずは、パイプ演算子の機能を確認します。まずは、普通にgapminderのデータの冒頭を確認します。

library(gapminder)

head(gapminder)
# A tibble: 6 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.
6 Afghanistan Asia       1977    38.4 14880372      786.

これをパイプ演算子で書くと、このようになります。

gapminder |> 
    head()
# A tibble: 6 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.
6 Afghanistan Asia       1977    38.4 14880372      786.

|>はR 4.1.0以降で備わっているパイプ演算子です。%>%tidyverseに含まれているパイプ演算子です。

library(tidyverse)

gapminder %>% 
    head()
# A tibble: 6 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.
6 Afghanistan Asia       1977    38.4 14880372      786.

これらから分かるように、パイプ演算子は左辺のオブジェクトを右辺の関数に入れる機能を持っています。

  • RStudioではShift + Alt + Mでパイプ演算子を入力できます。

B.1.1 なにが嬉しいのか?

パイプ演算子を使うとなにが嬉しいのでしょうか。ご利益は再現性と可読性にあります。まず、パイプ演算子を使わずにコードを書いてみます。

  • 1990年以降のアジアの国を取り出し、GDPを計算し、国ごとに平均値を求めてみます。
gapminder <- filter(gapminder, year > 1990)
gapminder <- filter(gapminder, continent == "Asia")
gapminder <- mutate(gapminder, GDP = pop * gdpPercap)
gapminder <- group_by(gapminder, country)
gdp_mean <- summarise(gapminder, GDP = mean(GDP))

head(gdp_mean)
# A tibble: 6 × 2
  country              GDP
  <fct>              <dbl>
1 Afghanistan      1.85e10
2 Bahrain          1.47e10
3 Bangladesh       1.45e11
4 Cambodia         1.28e10
5 China            3.82e12
6 Hong Kong, China 2.03e11

十分可読性はあり、このような書き方が良くないというわけではありません。しかし、よくあるミスは、どこまで分析をやったのか忘れてしまうというものです。

  • 例えば、2行目までやって、少し集中が切れて、再開する時に4行目から実行してしまうと5行目でエラーが出ます。

また、何度も同じもの(今回の例ではgapminder)を書いていると、スペルミスをするリスクが上がります。

パイプ演算子を使うと、次のように書くことができ、可読性を保ちつつ、一気に全ての処理を実行できます。

gdp_mean <- gapminder |>
    filter(year > 1990) |> 
    filter(continent == "Asia") |> 
    mutate(GDP = pop * gdpPercap) |> 
    group_by(country) |> 
    summarise(GDP = mean(GDP))

head(gdp_mean)
# A tibble: 6 × 2
  country              GDP
  <fct>              <dbl>
1 Afghanistan      1.85e10
2 Bahrain          1.47e10
3 Bangladesh       1.45e11
4 Cambodia         1.28e10
5 China            3.82e12
6 Hong Kong, China 2.03e11

パイプ演算子を使うかどうかは個人の好みですが、どちらのコードを読めるようになっておかないと、他の人の分析が分からなくなります。

B.2 データの代入

パイプ演算子を使った場合、処理は上から下なのに、オブジェクトへの代入は再び上に戻るのは気持ち悪いかもしれません。その場合は、次のように書けます。ただし、あまり見かけない書き方です。

gapminder |>
    filter(year > 1990) |> 
    filter(continent == "Asia") |> 
    mutate(GDP = pop * gdpPercap) |> 
    group_by(country) |> 
    summarise(GDP = mean(GDP)) -> gdp_mean

head(gdp_mean)
# A tibble: 6 × 2
  country              GDP
  <fct>              <dbl>
1 Afghanistan      1.85e10
2 Bahrain          1.47e10
3 Bangladesh       1.45e11
4 Cambodia         1.28e10
5 China            3.82e12
6 Hong Kong, China 2.03e11

|>%>%のどちらを使っても大差はないですが、tiduverseのパイプ演算子には次のようなものもあります。最初のオブジェクトを上書きます。

gdp_mean <- gapminder

gdp_mean %<>% 
    filter(year > 1990) %>% 
    filter(continent == "Asia") %>% 
    mutate(GDP = pop * gdpPercap) %>% 
    group_by(country) %>% 
    summarise(GDP = mean(GDP))

head(gdp_mean)
# A tibble: 6 × 2
  country              GDP
  <fct>              <dbl>
1 Afghanistan      1.85e10
2 Bahrain          1.47e10
3 Bangladesh       1.45e11
4 Cambodia         1.28e10
5 China            3.82e12
6 Hong Kong, China 2.03e11

B.3 代入の位置

基本的に左辺のオブジェクトは右辺の関数の最初の位置に代入されますが、%>%の場合は>によって位置を指定することができます。

gapminder %>% 
    filter(year > 1990) %>% 
    lm(lifeExp ~ gdpPercap, data = .) %>% 
    summary()

Call:
lm(formula = lifeExp ~ gdpPercap, data = .)

Residuals:
    Min      1Q  Median      3Q     Max 
-22.200  -2.798   1.206   3.817   9.490 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 6.355e+01  6.836e-01   92.97   <2e-16 ***
gdpPercap   4.939e-04  4.421e-05   11.17   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.867 on 130 degrees of freedom
Multiple R-squared:  0.4898,    Adjusted R-squared:  0.4859 
F-statistic: 124.8 on 1 and 130 DF,  p-value: < 2.2e-16

次のようにも書けるそうです。

gapminder %>% 
    filter(year > 1990) %$% 
    lm(lifeExp ~ gdpPercap) %>% 
    summary()

Call:
lm(formula = lifeExp ~ gdpPercap)

Residuals:
    Min      1Q  Median      3Q     Max 
-22.200  -2.798   1.206   3.817   9.490 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 6.355e+01  6.836e-01   92.97   <2e-16 ***
gdpPercap   4.939e-04  4.421e-05   11.17   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.867 on 130 degrees of freedom
Multiple R-squared:  0.4898,    Adjusted R-squared:  0.4859 
F-statistic: 124.8 on 1 and 130 DF,  p-value: < 2.2e-16