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] "列名B"
[[2]]
[1] "列名C"
[[3]]
[1] "列名A"
第1引数がベクターもしくはリストで、第2引数がベクターもしくはリストのそれぞれの要素を処理していく関数となります。
lapply
に渡す関数の引数が複数ある場合は、lapply
の第3引数以降に、例えば以下のように指定します。
lapply(unique(df01$d), function(n, m){ sprintf("列名%s%s", n, m)}, "です")
[[1]]
[1] "列名Bです"
[[2]]
[1] "列名Cです"
[[3]]
[1] "列名Aです"
2.2 sapply
戻り値がリストのままでは使いづらいことが多いので、ラッパー関数sapply
が用意されていて、ベクトルで結果を得ることができます。
sapply(unique(df01$d), function(n){ sprintf("列名%s", n)})
B C A
"列名B" "列名C" "列名A"
以下のようにオプションをつけると、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.355528 2.617994 3.161656 4.073573 5.164650 6.907851
[7] 7.469364 8.505914 9.286480 10.064940 11.478411 12.018665
[13] 13.262795 14.582559 15.865726 16.196913 17.232445 17.328222
[19] 19.109778 20.281572 20.934057 23.106776 23.828969 24.178164
[25] 25.705522 26.302219 27.656999 28.611033 29.965415 30.226928
[31] 31.691549 32.388729 33.620194 34.146871 35.435995 36.554902
[37] 37.268276 38.976736 39.602202 40.365403 41.198490 42.825710
[43] 43.355750 44.816395 45.524283 46.191651 47.424782 47.938282
[49] 49.726528 49.983700 51.406084 52.193979 53.412468 54.559747
[55] 55.534200 56.284943 57.875156 58.643521 59.287106 60.583292
[61] 60.968064 62.640513 63.758952 64.141365 65.172695 66.455664
[67] 66.870031 68.364434 69.054198 70.087444 71.335276 71.963958
[73] 73.326658 74.312858 75.244567 75.580207 76.894802 78.484507
[79] 79.395963 80.350422 81.082950 82.533563 83.495236 84.182246
[85] 84.943941 86.410144 87.126241 88.563003 89.358945 90.778945
[91] 90.974983 92.167414 93.521326 94.397707 95.225568 96.739741
[97] 96.938734 98.303338 98.690728 100.501317
apply(df02, 2, mean) # 列ごとに合計
y x z
101.5933464 50.5000000 0.4757544
apply(df02, 2, mean, simplify=FALSE) # 結果をlistにする
$y
[1] 101.5933
$x
[1] 50.5
$z
[1] 0.4757544
3.2 引数が行列
行列でもデータフレームと同様に処理できます。
<- as.matrix(df02) # データフレームを行列にする
X apply(X, 1, max) # 行ごとに最大値
[1] 1.881450 3.517223 6.185505 7.939745 9.576081 15.368247
[7] 14.726247 17.284078 17.718266 20.630706 24.185139 23.018330
[13] 25.680914 29.930005 32.248988 32.042508 33.090757 33.346180
[19] 37.589240 40.221239 41.379827 47.325193 47.560091 48.390336
[25] 51.858013 53.780560 55.311155 57.050228 59.315184 58.277747
[31] 63.719103 65.852079 66.540909 68.654030 73.029298 73.352680
[37] 74.493069 77.892559 80.130888 82.241111 81.346687 87.480374
[43] 86.037225 89.667072 90.022841 91.372677 95.124914 94.892906
[49] 97.656394 100.183337 101.226893 104.582841 108.349871 109.168084
[55] 113.090212 113.416455 116.802127 116.962988 118.046430 121.098256
[61] 120.312386 125.012528 126.811150 125.686391 129.500990 132.029810
[67] 132.812198 137.086106 137.395844 139.495826 142.068496 144.426688
[73] 148.474615 149.884316 149.770468 150.450782 154.407974 158.387042
[79] 160.579413 160.886522 163.875901 166.308326 167.306475 166.359378
[85] 170.390890 171.421469 174.199394 176.300963 179.043514 181.451979
[91] 182.745988 181.426185 186.118315 188.291850 187.908695 192.812300
[97] 194.999043 197.422805 195.826492 200.781639
apply(X, 2, min) # 列ごとに最小値
y x z
1.881450 1.000000 -1.721315
4 カテゴリ変数ごとの関数に適応
カテゴリ変数ごとに平均値を求めるようなことはよくします。こういうときはtapply
が使えます。
with(df01, tapply(y, d, mean))
A B C
97.47676 110.25179 94.93219
これだけならばxtabs(y ~ d, data=df01)
を用いても同様ですが、2位グループのスコアを求めるような処理も同様にかけます。
with(df01, tapply(y, d, function(v){
unique(sort(v))[2]
}))
A B C
9.576081 15.368247 6.185505
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.4757544
# NAを無視させれば計算できる
sapply(df02, mean, na.rm = TRUE)
y x z
102.6005373 50.5000000 0.4757544
なお、sapply(df02, function(x) mean(x, na.rm = TRUE) )
と書いても同じ結果になります。
6 関数の省略表記
R
4.1から、function(x){ ... }
を\(x){ ... }
と書けるようになっていて、apply
関数群での利用が多くなっていくかも知れません。
7 慣れたら便利
最近は他のプログラミング言語でもラムダ式は一般化したのでそうでもないでもですが、慣れるまで違和感があるかも知れません。しかし、覚えたら便利なので、積極的に使っていきましょう。