plyrパッケージの使い方メモ

plyrパッケージはデータ操作を簡略化する関数が多数用意されている便利パッケージで、思想としては

という3プロセスにデータ処理を分けて考えるような作りになっている。特にその思想が反映されている関数群が

等の関数で、命名規則としてはx型を入力型としてy型を出力型としてデータ処理してくれる関数だとxyplyと書くことになる。めんどいので以下xyply系関数と書く事にする。入力・出力に応じた一覧で分類して書いておくと

入力 / 出力 array data.frame list なし
array aaply adply alply a_ply
data.frame daply ddply dlply d_ply
list laply ldply llply l_ply
整数 raply rdply rlply r_ply

となる。
まずはパッケージのロードと、よく使われるサンプルデータ(baseball)を表示

library(plyr)
head(baseball)
##            id year stint team lg  g  ab  r  h X2b X3b hr rbi sb cs bb so
## 4   ansonca01 1871     1  RC1    25 120 29 39  11   3  0  16  6  2  2  1
## 44  forceda01 1871     1  WS3    32 162 45 45   9   4  0  29  8  0  4  0
## 68  mathebo01 1871     1  FW1    19  89 15 24   3   1  0  10  2  1  2  0
## 99  startjo01 1871     1  NY2    33 161 35 58   5   1  1  34  4  2  3  0
## 102 suttoez01 1871     1  CL1    29 128 35 45   3   7  3  23  3  1  1  0
## 106 whitede01 1871     1  CL1    29 146 40 47   6   5  1  21  2  2  4  1
##     ibb hbp sh sf gidp
## 4    NA  NA NA NA   NA
## 44   NA  NA NA NA   NA
## 68   NA  NA NA NA   NA
## 99   NA  NA NA NA   NA
## 102  NA  NA NA NA   NA
## 106  NA  NA NA NA   NA

以下のコードで

するという流れになる。

head(daply(baseball, .(year), nrow))
## 1871 1872 1873 1874 1875 1876 
##    7   13   13   15   17   15 

基本的にはこの使い方が全てなので、後はこの枠組みに自分の解いている問題を当てはめるかがキーになるわけですが、そのためにxyply系関数以外にも便利関数が多数用意されているので、それらをうまく組み合わせると更に効率良くデータ処理する事が出来る。例えばcolwise関数は

と文字で書くと何を言っているのか分かりにくいが、要するに動作を観てみれば一発で、

# nmissing関数:欠損値の個数をカウント
nmissing <- function(x) sum(is.na(x))
# 各列に対して引数の関数を適用する関数を返す関数
colwise(nmissing)(baseball)
##   id year stint team lg g ab r h X2b X3b hr rbi  sb   cs bb   so  ibb hbp
## 1  0    0     0    0  0 0  0 0 0   0   0  0  12 250 4525  0 1305 7528 377
##    sh   sf gidp
## 1 960 7390 5272

と、各列に対してnmissing関数を適用した結果を抽出してくれる。これをxyply系関数と組み合わせると強力で

# 年毎の各列の欠損値の個数
head(ddply(baseball, .(year), colwise(nmissing)))
##   year id stint team lg g ab r h X2b X3b hr rbi sb cs bb so ibb hbp sh sf
## 1 1871  0     0    0  0 0  0 0 0   0   0  0   0  0  0  0  0   7   7  7  7
## 2 1872  0     0    0  0 0  0 0 0   0   0  0   0  0  0  0  0  13  13 13 13
## 3 1873  0     0    0  0 0  0 0 0   0   0  0   0  0  0  0  0  13  13 13 13
## 4 1874  0     0    0  0 0  0 0 0   0   0  0   0  0  0  0  0  15  15 15 15
## 5 1875  0     0    0  0 0  0 0 0   0   0  0   0  0  0  0  0  17  17 17 17
## 6 1876  0     0    0  0 0  0 0 0   0   0  0   0 15 15  0  0  15  15 15 15
##   gidp
## 1    7
## 2   13
## 3   13
## 4   15
## 5   17
## 6   15

のように短いコードで複雑な動作を記述することが出来るわけです。colwise関数は更に特定の列だけを指定する事も出来て

head(ddply(baseball, .(year), colwise(nmissing, .(sb, cs, so))))
##   year sb cs so
## 1 1871  0  0  0
## 2 1872  0  0  0
## 3 1873  0  0  0
## 4 1874  0  0  0
## 5 1875  0  0  0
## 6 1876 15 15  0
#
# 条件の指定はboolを返す関数でもOK。↓の場合は数値列のみ欠損値の個数をカウント
head(ddply(baseball, .(year), colwise(nmissing, is.numeric)))
##   year stint g ab r h X2b X3b hr rbi sb cs bb so ibb hbp sh sf gidp
## 1 1871     0 0  0 0 0   0   0  0   0  0  0  0  0   7   7  7  7    7
## 2 1872     0 0  0 0 0   0   0  0   0  0  0  0  0  13  13 13 13   13
## 3 1873     0 0  0 0 0   0   0  0   0  0  0  0  0  13  13 13 13   13
## 4 1874     0 0  0 0 0   0   0  0   0  0  0  0  0  15  15 15 15   15
## 5 1875     0 0  0 0 0   0   0  0   0  0  0  0  0  17  17 17 17   17
## 6 1876     0 0  0 0 0   0   0  0   0 15 15  0  0  15  15 15 15   15

こんな感じで書く事が出来る。

その他便利関数としてはcount関数ってのがあって、その名の通り指定した列の値ごとにデータの個数をカウントしてくれる。

# 第一引数の組み合わせの数を数える(=1でインクリメント)
x <- data.frame(id = c("a", "b", "a"), g = c(3, 6, 8))
count(x, "id")
##   id freq
## 1  a    2
## 2  b    1
# 第二引数指定した場合、その列の値でインクリメントされる
count(x, "id", "g")
##   id freq
## 1  a   11
## 2  b    6

each関数は引数に指定した関数をそれぞれ適用した結果を返す関数を返す関数

# それぞれの関数に引数1:10を実行。
each(min, max)(1:10)
## min max 
##   1  10 
# これは頻繁に使えそうな書き方
each(length, mean, var)(rnorm(100))
##   length     mean      var 
## 100.0000   0.2097   1.1945 

try-catchの簡略版的な書き方もあった。

x <- 100
f <- function(x) if (x == 1) stop("Error!") else 1
safef <- failwith(NULL, f)
x <- safef(1)
x
## NULL

頭にrが付くxyply系関数は整数を第一引数にとって、指定回数だけ以下の処理を反復してくれる。例えばこれで100回適当な回帰分析を行った結果をlist型として返却してくれる。

# 100回回帰分析する
x <- rlply(100, lm(y ~ x, data = data.frame(x = rnorm(100), y = rnorm(100))))
x[[1]]
## 
## Call:
## lm(formula = y ~ x, data = data.frame(x = rnorm(100), y = rnorm(100)))
## 
## Coefficients:
## (Intercept)            x  
##     -0.0741       0.0765  
## 

データの要約もラクラク作成

# ddply(baseball, .(year), function(x)mean(x$rbi, na.rm=TRUE)))と同じ
head(ddply(baseball, .(year), summarise, mean_rbi = mean(rbi, na.rm = TRUE)))
##   year mean_rbi
## 1 1871    22.29
## 2 1872    20.54
## 3 1873    30.92
## 4 1874    29.00
## 5 1875    31.59
## 6 1876    30.13

transform関数とmutate関数も便利。”データを変換する”という用途は同じだが、

*transform:全体を一括で変換
*mutate:引数の順序に沿って逐次的に結果を利用して変換

という点が大きく異なる。例で示すと↓は同じになる。

head(mutate(airquality, Ozone = -Ozone))
##   Ozone Solar.R Wind Temp Month Day
## 1   -41     190  7.4   67     5   1
## 2   -36     118  8.0   72     5   2
## 3   -12     149 12.6   74     5   3
## 4   -18     313 11.5   62     5   4
## 5    NA      NA 14.3   56     5   5
## 6   -28      NA 14.9   66     5   6
head(transform(airquality, Ozone = -Ozone))
##   Ozone Solar.R Wind Temp Month Day
## 1   -41     190  7.4   67     5   1
## 2   -36     118  8.0   72     5   2
## 3   -12     149 12.6   74     5   3
## 4   -18     313 11.5   62     5   4
## 5    NA      NA 14.3   56     5   5
## 6   -28      NA 14.9   66     5   6

↓これも同じになる。

head(mutate(airquality, new = -Ozone, Temp = (Temp - 32)/1.8))
##   Ozone Solar.R Wind  Temp Month Day new
## 1    41     190  7.4 19.44     5   1 -41
## 2    36     118  8.0 22.22     5   2 -36
## 3    12     149 12.6 23.33     5   3 -12
## 4    18     313 11.5 16.67     5   4 -18
## 5    NA      NA 14.3 13.33     5   5  NA
## 6    28      NA 14.9 18.89     5   6 -28
head(transform(airquality, new = -Ozone, Temp = (Temp - 32)/1.8))
##   Ozone Solar.R Wind  Temp Month Day new
## 1    41     190  7.4 19.44     5   1 -41
## 2    36     118  8.0 22.22     5   2 -36
## 3    12     149 12.6 23.33     5   3 -12
## 4    18     313 11.5 16.67     5   4 -18
## 5    NA      NA 14.3 13.33     5   5  NA
## 6    28      NA 14.9 18.89     5   6 -28

↓これだとOzT列の値が異なる。mutate関数は新しく定義したTempの値を使ってOzTを計算するので値が異なるということ。

head(mutate(airquality, new = -Ozone, Temp = (Temp - 32)/1.8, OzT = Ozone/Temp))
##   Ozone Solar.R Wind  Temp Month Day new    OzT
## 1    41     190  7.4 19.44     5   1 -41 2.1086
## 2    36     118  8.0 22.22     5   2 -36 1.6200
## 3    12     149 12.6 23.33     5   3 -12 0.5143
## 4    18     313 11.5 16.67     5   4 -18 1.0800
## 5    NA      NA 14.3 13.33     5   5  NA     NA
## 6    28      NA 14.9 18.89     5   6 -28 1.4824
head(transform(airquality, new = -Ozone, Temp = (Temp - 32)/1.8, 
    OzT = Ozone/Temp))
##   Ozone Solar.R Wind  Temp Month Day new    OzT
## 1    41     190  7.4 19.44     5   1 -41 0.6119
## 2    36     118  8.0 22.22     5   2 -36 0.5000
## 3    12     149 12.6 23.33     5   3 -12 0.1622
## 4    18     313 11.5 16.67     5   4 -18 0.2903
## 5    NA      NA 14.3 13.33     5   5  NA     NA
## 6    28      NA 14.9 18.89     5   6 -28 0.4242

・・・とかなり便利なパッケージなのでデータ整形の際には積極的に使っていこうと思う。