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