lapply
, sapply
, vapply
,
apply
, tapply
の順番に説明していきます。
1 データセットの作成
apply
を試す前に、処理するデータをつくります。
<- with(list(n=100), {
df01 <- 1:n
x <- runif(n)
p <- ifelse(p<0.3, "A", ifelse(p<0.7, "B", "C"))
d <- rnorm(n) + p
z <- 1 + 2*x - z + rnorm(n)
y data.frame(y, x, z, d)
})
中はhead(df01)
とでもして確認してください。
2 引数がベクターもしくはリスト
ベクターもしくはリストの引数を取れるsapply
/lapply
は、名前からすると簡素版と言った感じですが、(ベクターに二次元構造を持たせた)行列や、(リストに各要素の長さが等しいという制約のついた)データフレームを要求するapply
よりも、構造的にはむしろ基本です。
2.1 lapply
lapply
がもっとも単純な関数で、戻り値としてリストを返してきます。
lapply(unique(df01$d), function(n){ sprintf("列名%s", n)})
[[1]]
[1] "列名C"
[[2]]
[1] "列名A"
[[3]]
[1] "列名B"
第1引数がベクターもしくはリストで、第2引数がベクターもしくはリストのそれぞれの要素を処理していく関数となります。
lapply
に渡す関数の引数が複数ある場合は、lapply
の第3引数以降に、例えば以下のように指定します。
lapply(unique(df01$d), function(n, m){ sprintf("列名%s%s", n, m)}, "です")
[[1]]
[1] "列名Cです"
[[2]]
[1] "列名Aです"
[[3]]
[1] "列名Bです"
2.2 sapply
戻り値がリストのままでは使いづらいことが多いので、ラッパー関数sapply
が用意されていて、ベクトルで結果を得ることができます。
sapply(unique(df01$d), function(n){ sprintf("列名%s", n)})
C A B
"列名C" "列名A" "列名B"
以下のようにオプションをつけると、sapply
はlapply
と同じ動作になります。
sapply(unique(df01$d), function(n){ sprintf("列名%s", n)}, simplify = FALSE, USE.NAMES = TRUE)
2.3 vapply
vapply
はsapply
と同じ目的に使いますが、戻り値の型をチェックするところがsapply
と異なります。
sapply(df01, max) # エラーにならない
vapply(df01, max, numeric(1)) # エラーになる
利用頻度は低いようです。
3 引数が行列もしくはデータフレーム
引数が行列もしくはデータフレームのときはapply
関数を用います。行ごと、もしくは列ごとに関数を用いることができます。
まずはmean関数と行列にする都合で、numeric型の列だけ選んでおきます。
<- df01[,c("y", "x", "z")] df02
3.1 引数がデータフレーム
第2引数が1
だと行ごとの処理、2
だと列ごとの処理になります。
apply(df02, 1, mean) # 行ごとに合計
[1] 1.622697 2.320035 2.847841 4.920730 4.942744 6.051844
[7] 7.556722 8.507212 9.380771 10.868440 11.220000 12.531286
[13] 13.844127 14.211436 14.976604 16.225572 16.938343 18.131063
[19] 19.213633 20.590648 21.472268 22.512395 23.352893 24.460907
[25] 25.630032 26.237153 27.016952 28.263357 29.423972 29.682721
[31] 31.421741 31.788890 32.806602 34.515535 35.458216 36.597397
[37] 37.329736 38.151825 39.285260 40.075822 41.489289 42.223831
[43] 42.906151 44.592837 45.280584 46.083721 47.333785 48.289960
[49] 49.528833 51.237229 51.497108 52.029243 53.789747 53.928825
[55] 55.436622 56.317443 56.870172 57.954918 59.670863 60.560703
[61] 61.072663 62.143290 63.186900 64.292047 65.965263 65.860503
[67] 68.064520 67.864792 69.514983 69.845957 71.240878 72.104215
[73] 73.426081 73.998273 75.431412 76.245311 77.053099 78.871881
[79] 79.634876 80.151318 81.224910 82.817697 83.573518 84.723021
[85] 85.391262 85.979217 86.919064 88.990041 89.752592 90.377989
[91] 91.491730 92.441059 93.273185 93.677284 94.770796 96.719426
[97] 97.326837 97.997128 98.737980 100.334702
apply(df02, 2, mean) # 列ごとに合計
y x z
101.6166660 50.5000000 0.3401837
apply(df02, 2, mean, simplify=FALSE) # 結果をlistにする
$y
[1] 101.6167
$x
[1] 50.5
$z
[1] 0.3401837
行列やデータフレームの行ごと、列ごとの合計と平均をとるだけであれば、colSums
,
rowSums
, colMeans
,
rowMeans
を使う方が高速です。
3.2 引数が行列
行列でもデータフレームと同様に処理できます。
<- as.matrix(df02) # データフレームを行列にする
X apply(X, 1, max) # 行ごとに最大値
[1] 3.847575 3.062512 6.418936 9.581625 8.596115 11.366249
[7] 16.337799 17.045193 19.783599 22.336437 23.127082 25.411901
[13] 28.440671 29.793672 30.311656 31.902035 33.472837 35.857310
[19] 38.682433 41.180021 44.603397 43.951116 47.139572 51.188501
[25] 49.647442 50.851432 55.624767 56.013556 58.615314 60.094334
[31] 62.776680 62.943564 64.779635 70.481473 70.899317 73.851145
[37] 73.523761 74.551002 78.375004 81.627173 82.450327 83.904843
[43] 85.085176 89.744149 89.477028 90.997435 94.299640 95.453288
[49] 99.051369 102.945628 103.908389 104.491161 107.341749 107.437558
[55] 110.691864 113.474578 114.353597 116.839589 120.581582 121.461397
[61] 121.505125 122.946955 125.359917 129.070290 131.666723 131.068281
[67] 137.309810 136.912574 139.169890 139.643682 140.062601 143.750322
[73] 148.327422 147.130264 149.478071 153.796167 154.209097 158.296773
[79] 161.482807 159.813645 162.699196 167.116547 166.506777 170.007497
[85] 171.529285 171.710799 174.481273 178.520592 180.353453 179.634978
[91] 182.561867 184.692057 185.589805 185.622140 189.050503 192.427699
[97] 195.680312 194.755121 195.289238 200.352847
apply(X, 2, min) # 列ごとに最小値
y x z
3.062512 1.000000 -1.805779
4 カテゴリ変数ごとの関数に適応
カテゴリ変数ごとに平均値を求めるようなことはよくします。こういうときはtapply
が使えます。
with(df01, tapply(y, d, mean))
A B C
102.66530 108.98426 88.53903
これだけならばxtabs(y ~ d, data=df01)
を用いても同様ですが、2位グループのスコアを求めるような処理も同様にかけます。
with(df01, tapply(y, d, function(v){
unique(sort(v))[2]
}))
A B C
8.596115 17.045193 3.847575
5 引数の関数に渡す引数
apply
関数群は自分が利用しない引数を、そのまま引数の関数に受け渡します。以下の例では、sapply
に渡すmean
関数に第2引数na.rm = TRUE
を与えています。
# NAを含むデータセットをつくる
<- df01[, 1:3]; df02[1, 1] <- NA
df02
# デフォルトではNAがあると計算できない
sapply(df02, mean)
y x z
NA 50.5000000 0.3401837
# NAを無視させれば計算できる
sapply(df02, mean, na.rm = TRUE)
y x z
102.6042325 50.5000000 0.3401837
なお、sapply(df02, function(x) mean(x, na.rm = TRUE) )
と書いても同じ結果になります。
6 関数の省略表記
R
4.1から、function(x){ ... }
を\(x){ ... }
と書けるようになっていて、apply
関数群での利用が多くなっていくかも知れません。
7 慣れたら便利
最近は他のプログラミング言語でもラムダ式は一般化したのでそうでもないでもですが、慣れるまで違和感があるかも知れません。しかし、覚えたら便利なので、積極的に使っていきましょう。