以下は{tidyr}
パッケージのgather()関数とspread()関数のヘルプページを改変・再構成したメモ書きです。
{tidyr}
パッケージとTidy Dataについてより詳しく知りたい方は R for Data Science - 12 Tidy data をぜひ御覧ください。
基本的な使い方
説明のために、まず次のようにしてstocks
というデータフレームを作ります。
set.seed(71)
day_length <- 4
stocks <- data.frame(
time = seq.Date(as.Date('2009-01-01'), by = "day", length.out = day_length),
X = rnorm(n = day_length, mean = 0, sd = 1),
Y = rnorm(n = day_length, mean = 0, sd = 2),
Z = rnorm(n = day_length, mean = 0, sd = 4)
)
X, Y, Zの3つの銘柄について前日の株価との値段の増減比(%)を取ったもの、とでも思ってください。
time | X | Y | Z |
---|---|---|---|
2009-01-01 | -0.4318422 | -0.8358013 | -4.433913 |
2009-01-02 | -0.4471872 | -2.3743278 | 5.053723 |
2009-01-03 | -0.4785726 | -2.1445711 | 1.171803 |
2009-01-04 | 0.4171454 | 2.0065677 | 2.193100 |
分析時に読み込むデータは、このように1つの列が1つの系列になっていることが多々あります。
しかしこの形だと、銘柄(X, Y, Z...)が増えるたびに列を増やさなければいけなくなってしまいます。
これでは分析用のコードを書く上で不都合なので、分析対象が増えても行だけが増えるようにデータの構造を変更したいことがあります。
gather
そんなときにtidyr::gather()
を使うとデータフレームの構造を簡単に変更できて便利です。
tidyr::gather()関数は以下の様に引数を指定します。...(ドット3つ)は可変長の引数を取れることを意味します。
data %>%
tidyr::gather(key, value, ...)
さきほどのstocksデータについて、X, Y, Z
をstock列の"ラベル"にして、付随する数値はprice列に放り込みたいときは、以下のようにします。
※パイプ演算子(%>%)を使うためにdplyrパッケージを読み込んでいます
library(dplyr)
stocksm <- stocks %>%
tidyr::gather(key = stock, value = price, X, Y, Z)
stocksm
time | stock | price |
---|---|---|
2009-01-01 | X | -0.4318422 |
2009-01-02 | X | -0.4471872 |
2009-01-03 | X | -0.4785726 |
2009-01-04 | X | 0.4171454 |
2009-01-01 | Y | -0.8358013 |
2009-01-02 | Y | -2.3743278 |
2009-01-03 | Y | -2.1445711 |
2009-01-04 | Y | 2.0065677 |
2009-01-01 | Z | -4.4339130 |
2009-01-02 | Z | 5.0537229 |
2009-01-03 | Z | 1.1718029 |
2009-01-04 | Z | 2.1931001 |
ここで、tidyr::gather()
の可変長引数はdplyr::select()
に準ずる書き方ができるため、普通は以下のように「time列を除いて全部」というように書くことが多いです。
stocks %>%
tidyr::gather(key = stock, value = price, -time)
あるいは、「X列からZ列」という意味で、以下のように書いても同じ結果が得られます。
stocks %>%
tidyr::gather(key = stock, value = price, X:Z)
spread
さきほどの構造を元の形に戻したくなったら、tidyr::spread()
を使います。
key 引数で指定した変数が列名になり、
value 引数に指定した変数が各列の中身になります。こんどは可変長引数はありません。
stocksm %>%
tidyr::spread(key = stock, value = price)
time | X | Y | Z |
---|---|---|---|
2009-01-01 | -0.4318422 | -0.8358013 | -4.433913 |
2009-01-02 | -0.4471872 | -2.3743278 | 5.053723 |
2009-01-03 | -0.4785726 | -2.1445711 | 1.171803 |
2009-01-04 | 0.4171454 | 2.0065677 | 2.193100 |
key引数に日付を指定することで、「行と列を入れ替える」こともできます。
stocksm %>%
tidyr::spread(key = time, value = price)
stock | 2009-01-01 | 2009-01-02 | 2009-01-03 | 2009-01-04 |
---|---|---|---|---|
X | -0.4318422 | -0.4471872 | -0.4785726 | 0.4171454 |
Y | -0.8358013 | -2.3743278 | -2.1445711 | 2.0065677 |
Z | -4.4339130 | 5.0537229 | 1.1718029 | 2.1931001 |
便利なオプション
ここからは便利なオプションの使い方(抜粋)です。
spreadすると欠損値が生じる場合
次のようなデータを考えます。
df <- data.frame(
time = as.Date(c("2016-09-01", "2016-09-02")),
label = c("a", "b"),
val = c(3, 4)
)
time | label | val |
---|---|---|
2016-09-01 | a | 3 |
2016-09-02 | b | 4 |
上のようなデータフレームをlabelでspreadすると次のように欠損値が挿入されます。
df %>%
tidyr::spread(key = label, value = val)
time | a | b |
---|---|---|
2016-09-01 | 3 | NA |
2016-09-02 | NA | 4 |
これをそのままgatherしてしまうと
time | label | val |
---|---|---|
2016-09-01 | a | 3 |
2016-09-02 | a | NA |
2016-09-01 | b | NA |
2016-09-02 | b | 4 |
こうなってしまうので、gatherした後にNAをわざわざ表示したくない場合はna.rm = TRUEオプションを指定します。
df %>%
tidyr::spread(key = label, value = val1) %>%
tidyr::gather(key = label, value = val1, a:b, na.rm = TRUE)
time | label | val | |
---|---|---|---|
1 | 2016-09-01 | a | 3 |
4 | 2016-09-02 | b | 4 |
暗黙の型変換を起こしてしまう場合
kvs_iris <- data.frame(
id = rep(c(1, 51), each = 3),
name = c("Species", "Species_num", "Sepal.Length"),
value = c("setosa", 1, 5.1, "versicolor", 7.0, 2)
)
Key-Value Storeのようなキーと値がペアになったデータベースからデータをダンプすると以下の様な形式になることがあります。
id | name | val |
---|---|---|
1 | Sepal.Length | 5.1 |
1 | Species | setosa |
1 | Species_num | 1 |
51 | Sepal.Length | 7 |
51 | Species | versicolor |
51 | Species_num | 2 |
このとき、データフレーム内部では列ごとに、一番表現力の大きい形式へと暗黙のうちに変換がなされています。name列とvalue列は因子になっています。
> str(kvs_iris)
'data.frame': 6 obs. of 3 variables:
$ id : num 1 1 1 51 51 51
$ name : Factor w/ 3 levels "Sepal.Length",..: 2 3 1 2 3 1
$ value: Factor w/ 6 levels "1","2","5.1",..: 5 1 3 6 4 2
上のデータフレームを、単純にspreadすると次のようになります。
kvs_iris %>%
tidyr::spread(key = name, value = val)
id | Sepal.Length | Species | Species_num |
---|---|---|---|
1 | 5.1 | setosa | 1 |
51 | 7 | versicolor | 2 |
一見問題なさそうですが、str()関数で各列を見てみると、もとのデータフレームの因子が保持されてしまっています。
kvs_iris %>%
spread(key = name, value = val) %>%
str()
'data.frame': 2 obs. of 4 variables:
$ id : num 1 51
$ Sepal.Length: Factor w/ 6 levels "1","2","5.1",..: 3 4
$ Species : Factor w/ 6 levels "1","2","5.1",..: 5 6
$ Species_num : Factor w/ 6 levels "1","2","5.1",..: 1 2
これでは困るので、convert = TRUE オプションを指定すると、spread後に適切な型に変換されています。
df %>%
tidyr::spread(var, value, convert = TRUE) %>%
str()
'data.frame': 2 obs. of 4 variables:
$ id : num 1 51
$ Sepal.Length: num 5.1 7
$ Species : chr "setosa" "versicolor"
$ Species_num : int 1 2
データフレームを作るときにstringAsFactors = FALSEを指定していても、暗黙の型変換によってid列以外が文字列になってしまいますが、その場合も同様に変換してくれます。ちょうべんり。
実行環境
> devtools::session_info()
Session info --------------------------------------------------------------------------
setting value
version R version 3.3.1 (2016-06-21)
system x86_64, darwin13.4.0
ui RStudio (0.99.491)
language (EN)
collate ja_JP.UTF-8
tz Asia/Tokyo
date 2016-09-03
Packages ------------------------------------------------------------------------------
package * version date source
assertthat 0.1 2013-12-06 cran (@0.1)
devtools 1.12.0 2016-06-24 CRAN (R 3.3.0)
digest 0.6.10 2016-08-02 CRAN (R 3.3.0)
magrittr 1.5 2014-11-22 cran (@1.5)
memoise 1.0.0 2016-01-29 CRAN (R 3.3.0)
Rcpp 0.12.6 2016-07-19 CRAN (R 3.3.0)
tibble 1.1 2016-07-04 cran (@1.1)
tidyr * 0.6.0 2016-08-12 CRAN (R 3.3.0)
withr 1.0.2 2016-06-20 CRAN (R 3.3.0)
Enjoy!