Help us understand the problem. What is going on with this article?

【tidyr】gather?, spread? もう古い。時代はpivot

tidyr 1.0.0公開

皆さん、Tidyverse使ってますか?網羅的な統計処理を遂行するためにR, 特にTidyverseは非常に有効なツールです。先日Tidyverseの作成者であるHadley(神)がtidyrのアップデートを行いました。その中でも一番大きい変化は今まで用いられてきたgather(), spread()関数がpivot_longer(), pivot_wider()に変更されたことではないでしょうか?
今回の記事ではこれらの関数が今までのものとどのように変わったかを簡単に解説します。また、次回以降の記事ではpivot_*()についてのユニークな機能についても書いていこうと思っています。

TL;DR

基本的な使い方に関して、大きな変更は無いよ!でも引数がよりわかりやすくなったからコードの可読性がアップしたよ!やったね!

gather(), spread() → pivot_longer(), pivot_wider()

gather()関数はみなさんもよくご存じの通り、messy data (雑然データ)をtidy data (整然データ)とするために必須と言える関数です(reshape2::melt()みたいなものもありますが)。例えば、

messy.R
library(tidyverse)

df <- tibble("country" = c("a", "b", "c"),
             "1999" = c(0.7, 0.3, 1.0),
             "2000" = c(1.0, 2.0, 4.8),
             "2001" = c(2.0, 5.0, 7.0))
df

# A tibble: 3 x 4
  country `1999` `2000` `2001`
  <chr>    <dbl>  <dbl>  <dbl>
1 a          0.7    1        2
2 b          0.3    2        5
3 c          1      4.8      7

というようなmessy dataを考えてみましょう。これをtidy dataとするには、

gather.R
library(tidyverse)

df %>%
  gather(-country, key = "year", value = "amount")

# A tibble: 9 x 3
  country year  amount
  <chr>   <chr>  <dbl>
1 a       1999     0.7
2 b       1999     0.3
3 c       1999     1  
4 a       2000     1  
5 b       2000     2  
6 c       2000     4.8
7 a       2001     2  
8 b       2001     5  
9 c       2001     7  

とすることで見事にデータ処理のしやすいtidy dataが出力されます。このgather()関数は引数として、data, key, valueを取ります。今回の場合ですと、「country列以外の列についてyear列を新たに設定し、表中の値をamount列に格納する」という処理を施しています。...が、これらの処理を引数から予測しづらいことが問題となっていました。実際に、公式のドキュメントにおいても、

For some time, it’s been obvious that there is something fundamentally wrong with the design of spread() and gather(). Many people don’t find the names intuitive and find it hard to remember which direction corresponds to spreading and which to gathering. It also seems surprisingly hard to remember the arguments to these functions, meaning that many people (including me!) have to consult the documentation every time.

テキトー訳

どう見ても明らかなようにspread(), gather()関数は根本的な設計ミスってたわ。関数名がわかりにくく、どのように表が変化していくかを予測しにくいし、引数なんて覚えらんないから自分含め多くの人がドキュメント見てるわ。

とまぁ、惨憺な言われようです。そこで神が作り出したのがpivot_*()関数です。従来のgather()に相当するものはpivot_longer() です。使い方を見てみましょう。

pivot_longer.R
library(tidyverse)

df_longer <- df %>%
  pivot_longer(col = -country, names_to = "year", values_to = "amount")

df_longer

# A tibble: 9 x 3
  country year  amount
  <chr>   <chr>  <dbl>
1 a       1999     0.7
2 a       2000     1  
3 a       2001     2  
4 b       1999     0.3
5 b       2000     2  
6 b       2001     5  
7 c       1999     1  
8 c       2000     4.8
9 c       2001     7 

基本的に、 pivot_longer()gather()と同様の書き方をすれば良いことがわかります。しかし引数の名前がdata, key, valueからcol, names_to, values_toという比較的容易に引数から内容を把握できるものに改善されています。もし、このtidy dataをもとに戻したい場合はpivot_wider()を使います。従来ではspread()がその役割を担っていました。しかし、その引数がまたもやdata, key, valueだったので、pivot_wider()ではcol, names_from, values_fromに変更となりました。

pivot_wider.R
library(tidyverse)

df_wider <- df_longer %>%
  pivot_wider(names_from = "year", values_from = "amount")

df_wider

# A tibble: 3 x 4
  country `1999` `2000` `2001`
  <chr>    <dbl>  <dbl>  <dbl>
1 a          0.7    1        2
2 b          0.3    2        5
3 c          1      4.8      7

このように、引数名がより明確になったことでコードの可読性が向上したことがわかります。基本的な記法は変化していないことから、従来からtidyrを使用している人たちも簡単に移行できると思います。次回pivot_longer()のユニークな機能について書いていこうと思います。

yanami
生命系の実験をしつつ、傍らでR, pythonを習得中
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away