Apply() ファミリーまとめ

Rを使い始めて1年くらい。並列処理ではpythonを使っている名残でforを使ってきたが、今後apply系関数を活用していけるように、今回applyファミリーの知識を体系的にまとめてみようと思いたったのがきっかけ。

f:id:kokiando:20190503154238p:plain
Apply() Yourself!

applyファミリーとは

そもそもapplyファミリーとは、Rのベースパッケージに入っている関数群のことで、行列やリストに対して一括して演算を行いたいときに用いることができるもの。 forやwhile文と違うのはapplyを使うと、簡潔なコードで高速な処理をすることができる。

apply(X, MARGIN, FUN, ...)

行列かマトリクスのMARGINに対して、関数を適用し、ベクトルか行列、リストの形式で値を返す。MARGINが1であるときは列、2であるときは行に対して、c(1,2)に対しては各要素に対して関数を適用する。

f:id:kokiando:20190503163822p:plain
R tutorial on the apply familyより

この写真はかなりわかり易いです。このポストもかなりこちらのページを参考にしたので興味のある方は見てみてください。

set.seed(40)
A = sample(1:100, 1000, replace = TRUE)
B = sample(1:100, 1000, replace = TRUE)
C = sample(1:100, 1000, replace = TRUE)
dat = data.frame(A,B,C)
> dat
    A  B  C
1  69 10 48
2  88 71 31
3  70  8 12
4  12 24 58
5  20 96 80
6  47 80 64
7  21 39 32
8  60 98 70
9  38 10 68
10 15 59 76

それは上記で用意したdatで行ってみます。

MARGIN = 1

> apply(dat, MARGIN = 1, mean)
 [1] 42.33333 63.33333 30.00000 31.33333
 [5] 65.33333 63.66667 30.66667 76.00000
 [9] 38.66667 50.00000

1の場合は各列に対してのため合計10個の平均値が帰ってきてます。

MARGIN = 2

> apply(dat, MARGIN = 2, mean)
   A    B    C 
44.0 49.5 53.9 

2の場合は、各行のため合計三つの平均値。

MARGIN = c(1,2)

> apply(dat, MARGIN = c(1,2), mean)
       A  B  C
 [1,] 69 10 48
 [2,] 88 71 31
 [3,] 70  8 12
 [4,] 12 24 58
 [5,] 20 96 80
 [6,] 47 80 64
 [7,] 21 39 32
 [8,] 60 98 70
 [9,] 38 10 68
[10,] 15 59 76

c(1,2)の場合はそれぞれの値にたいして関数が適用されますが、今回はmean()のためそのままの値が帰ってきてます。

lapply(X, FUN, ...)

リストかベクトルに対して関数を適用し、同じ長さのリストを返す関数です。

dat2 = list(
   1:10,
   11:20,
   21:30)
> dat2
[[1]]
 [1]  1  2  3  4  5  6  7  8  9 10

[[2]]
 [1] 11 12 13 14 15 16 17 18 19 20

[[3]]
 [1] 21 22 23 24 25 26 27 28 29 30

基本形

> lapply(dat2, mean)
[[1]]
[1] 5.5

[[2]]
[1] 15.5

[[3]]
[1] 25.5

いたってシンプルです。それぞれのリストに対して関数が適用され、結果的に予想通り三つのデータがかえってきました。これはかなりシンプルな例です。ですが、これがマトリクスのリストであるときは少し使い方が変わってきます。

マトリクスのリストの場合。

f:id:kokiando:20190503170607p:plain
R tutorial on the apply familyより

こんな風に各リストのn行目やらm列目だけを取り出したいときは、リストを指定し、行か列をセットする必要があるようです。

> (dat3 = list(dat/2, dat, dat*2))
[[1]]
      A    B    C
1  34.5  5.0 24.0
2  44.0 35.5 15.5
3  35.0  4.0  6.0
4   6.0 12.0 29.0
5  10.0 48.0 40.0
6  23.5 40.0 32.0
7  10.5 19.5 16.0
8  30.0 49.0 35.0
9  19.0  5.0 34.0
10  7.5 29.5 38.0

[[2]]
    A  B  C
1  69 10 48
2  88 71 31
3  70  8 12
4  12 24 58
5  20 96 80
6  47 80 64
7  21 39 32
8  60 98 70
9  38 10 68
10 15 59 76

[[3]]
     A   B   C
1  138  20  96
2  176 142  62
3  140  16  24
4   24  48 116
5   40 192 160
6   94 160 128
7   42  78  64
8  120 196 140
9   76  20 136
10  30 118 152

最初に作ったdatを用いてやってみます。ちなみに見やすくするために一つ目のリスト要素はdat/2二つ目がdat、三つ目がdat*2となっております。

リスト内の各要素の一列目へのアクセス

> lapply(dat3, "[", 1, )
[[1]]
     A B  C
1 34.5 5 24

[[2]]
   A  B  C
1 69 10 48

[[3]]
    A  B  C
1 138 20 96

リスト内の各要素の一行目へのアクセス

> lapply(dat3, "[", , 1)
[[1]]
 [1] 34.5 44.0 35.0  6.0 10.0 23.5 10.5 30.0 19.0  7.5

[[2]]
 [1] 69 88 70 12 20 47 21 60 38 15

[[3]]
 [1] 138 176 140  24  40  94  42 120  76  30

リスト内の各要素の一列目、二行目へのアクセス

> lapply(dat3, "[", 1, 2)
[[1]]
[1] 5

[[2]]
[1] 10

[[3]]
[1] 20

かなり煩雑ですが、覚えておいたら使う時がくるかも…

sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)

基本的にはlapply()と使い方は同じで、出力がリスト型ではなく、ベクトル型となる。ちなみに以下が公式ドキュメントです。

sapply is a user-friendly version and wrapper of lapply by default returning a vector, matrix or, if simplify = "array", an array if appropriate, by applying simplify2array(). sapply(x, f, simplify = FALSE, USE.NAMES = FALSE) is the same as lapply(x, f).

ユーザーフレンドリーバージョンのlapply()… 。

では実際に再びdat2を用いて例を。

> lapply(dat2, mean)
[[1]]
[1] 5.5

[[2]]
[1] 15.5

[[3]]
[1] 25.5

> sapply(dat2, mean)
[1]  5.5 15.5 25.5

lapply()と比較すると一目瞭然でシンプルな出力です。

参考文献