LoginSignup
64
65

More than 5 years have passed since last update.

tidyr::gather( )とtidyr::spread( )でデータフレームを自在に変形する

Last updated at Posted at 2016-09-03

以下は{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!

64
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
65