R

トップページ
Google
WWWを検索
サイト内を検索

lapply, sapply, vapply, apply, tapplyの順番に説明していきます。

1 データセットの作成

applyを試す前に、処理するデータをつくります。

df01 <- with(list(n=100), {
    x <- 1:n
    p <- runif(n)
    d <- ifelse(p<0.3, "A", ifelse(p<0.7, "B", "C"))
    z <- rnorm(n) + p
    y <- 1 + 2*x - z + rnorm(n)
    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" 

以下のようにオプションをつけると、sapplylapplyと同じ動作になります。

sapply(unique(df01$d), function(n){ sprintf("列名%s", n)}, simplify = FALSE, USE.NAMES = TRUE)

2.3 vapply

vapplysapplyと同じ目的に使いますが、戻り値の型をチェックするところがsapplyと異なります。

sapply(df01, max) # エラーにならない
vapply(df01, max, numeric(1)) # エラーになる

利用頻度は低いようです。

3 引数が行列もしくはデータフレーム

引数が行列もしくはデータフレームのときはapply関数を用います。行ごと、もしくは列ごとに関数を用いることができます。 まずはmean関数と行列にする都合で、numeric型の列だけ選んでおきます。

df02 <- df01[,c("y", "x", "z")] 

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 引数が行列

行列でもデータフレームと同様に処理できます。

X <- as.matrix(df02) # データフレームを行列にする
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を含むデータセットをつくる
df02 <- df01[, 1:3]; df02[1, 1] <- NA

# デフォルトでは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 慣れたら便利

最近は他のプログラミング言語でもラムダ式は一般化したのでそうでもないでもですが、慣れるまで違和感があるかも知れません。しかし、覚えたら便利なので、積極的に使っていきましょう。