今回はR言語におけるapplyファミリーをみていきます。
applyファミリーとは、関数apply、mapply、lapply、sapply、tapplyを含む関数族のことです。
これらの関数を用いることで、ベクトル、行列・データフレーム、リストの各要素に対して、高速に演算を行うことが可能となります。
Rを使ったことがあるなら誰しもが「for文やwhile文が遅すぎて、実行できない!」、「ベクトルや行列単位の演算が高速で優れているのに、各要素の探索・演算に弱すぎる!」と不満を抱いたことがあると思います。
今回紹介するapplyファミリーを用いることで、これらの実行時間を解消することができます。
ぜひ例を参考にして、applyファミリーを使ってみてください。
今回紹介するコードも下のリンクからダウンロードすることができます。自由に使ってください。
R言語 applyファミリー Rスクリプト
applyファミリー
applyファミリーはapply、mapply、lapply、sapply、tapplyで構成されます。
ベクトル、行列・データフレーム、リストの各要素に対して、高速に同じ演算を適用することができます。
では、applyファミリーの各関数についてみていきます。
apply
関数applyは次のように使用します。
関数applyの引数を以下に示します。
X | ベクトル、行列・データフレーム |
MARGIN | MARGIN = 1のときは、Xの行ベクトルに対して関数を適用する。MARGIN = 2のときは、Xの列ベクトルに対して適用する。 |
FUN | Xの各要素に対して適用する関数 |
関数applyの簡単な使用例を見ていきます。
引数Xとして次の行列を与えます。
1 2 3 4 5 | > matrix(1 : 9, nrow = 3) [,1] [,2] [,3] [1,] 1 4 7 [2,] 2 5 8 [3,] 3 6 9 |
この行列を次で、Aに代入し、apply関数によってAの行ベクトルに対して関数sumを適用します。
1 2 3 | > A <- matrix(1 : 9, nrow = 3) > apply(A, 1, sum) [1] 12 15 18 |
次に示すデータフレームをみると、apply関数の意味をつかみやすいです。data.frameを用いて、4列目にapplyの結果を持つsumAboutRowという列を 追加してみましょう。
1 2 3 4 5 | > data.frame(A, sumAboutRow = apply(A, 1, sum)) X1 X2 X3 sumAboutRow 1 1 4 7 12 2 2 5 8 15 3 3 6 9 18 |
上の実行結果からわかる通り、X1、X2、X3までの行列Aの各行の和が4列目のsumAboutRowとなっていることが分かります。行方向について、関数sumが適用されていることが確認できます。
また、引数MARGINを2に変更すると、今度は列方向に対して和をとることができます。
1 2 | > apply(A, 2, sum) [1] 6 15 24 |
mapply
関数mapplyは次のように使用します。
関数mapplyの引数を以下に示します。
FUN | 適用する関数 |
... | 関数に適用するベクトル、行列・データフレーム、リスト |
MoreArgs | FUNの引数を要素に持つリスト |
SIMPLIFY | logicalまたはcharacter型。結果をベクトルにするかリストのままにするか。 |
USE.NAMES | 演算結果にnames属性(行名、列名、次元名)を持たせるか。 |
関数mapplyはapplyの多変量版といえます。引数...部分にベクトルX, Y , Z, ...を代入することで、X, Y, Zの3つに対して演算を行うことができます。
関数mapplyは次のように使用します。
1 2 3 4 5 | > mapply(paste, data.frame(cOne = 1 : 3, cTwo = 4 : 6, cThree = 7 : 9), 3 : 5) cOne cTwo cThree [1,] "1 3" "4 4" "7 5" [2,] "2 3" "5 4" "8 5" [3,] "3 3" "6 4" "9 5" |
引数USE.NAMESをFALSEにすると列名cOne、cTwo、cThreeが結果に含まれていないことが分かります。
1 2 3 4 5 | > mapply(paste, data.frame(cOne = 1 : 3, cTwo = 4 : 6, cThree = 7 : 9), 3 : 5, USE.NAMES = FALSE) [,1] [,2] [,3] [1,] "1 3" "4 4" "7 5" [2,] "2 3" "5 4" "8 5" [3,] "3 3" "6 4" "9 5" |
lapply
関数lapplyは次のように使用します。
関数lapplyの引数を以下に示します。
X | リスト |
FUN | リストXの各要素に対して適用する関数 |
関数lapplyはリストの要素に対して、関数を適用することができます。
lapplyの使用例を見ていきましょう。
1 2 3 4 5 6 7 8 9 | > lapply(list(c(1 : 10), NULL, c("ウサギ", "さんの", "統計学", "サロン")), length) [[1]] [1] 10 [[2]] [1] 0 [[3]] [1] 4 |
上の例では、c(1 : 10)、NULL、c("ウサギ", "さんの", "統計学", "サロン")の3つのベクトルを要素にもつリストに関数lengthを適用しています。
各要素のベクトルの長さ10,0,4をリストで返しているのが分かります。
sapply
関数sapplyは次のように使用します。
関数sapplyの引数を以下に示します。
X | 関数FUNに適用するベクトルまたはリスト |
FUN | 適用する関数 |
... | FUNの引数 |
SIMPLIFY | logicalまたはcharacter型。結果をベクトルにするかリストのままにするか。 |
USE.NAMES | 演算結果にnames属性(行名、列名、次元名)を持たせるか。 |
関数sapplyの使用例を見ていきます。
lapplyと比較したいので、先ほど用いたリストを用います。
1 2 3 4 5 6 7 8 9 | > list(numeric = c(1 : 10), null = NULL, character = c("ウサギ", "さんの", "統計学", "サロン")) $numeric [1] 1 2 3 4 5 6 7 8 9 10 $null NULL $character [1] "ウサギ" "さんの" "統計学" "サロン" |
次に、簡便のためこのリストをlistAに代入します。
1 | > listA <- list(numeric = c(1 : 10), null = NULL, character = c("ウサギ", "さんの", "統計学", "サロン")) |
関数sapplyを用いて、このリストの各要素に対して関数pasteを適用してみます。
1 2 3 4 5 6 7 8 9 | > sapply(listA, paste) $numeric [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" $null character(0) $character [1] "ウサギ" "さんの" "統計学" "サロン" |
sapplyの引数...の部分に関数pasteの引数を指定することで、pasteの出力を変更することもできます。
1 2 3 | > sapply(listA, paste, collapse = "") numeric null character "12345678910" "" "ウサギさんの統計学サロン" |
上の例では、関数pasteの引数collapse = ""とすることで、各3つのベクトルの要素を 結合させることができます。
tapply
関数tapplyは次のように使用します。
関数tapplyの引数を以下に示します。
X | ベクトル(levelsなどのグループを表すベクトル)。 |
INDEX | 1つまたは、それ以上のfactor型のオブジェクトをもつリスト。Xと同じ長さを持つ必要がある。 |
FUN | 適用する関数 |
default | (simplify = TRUEの場合のみ)出力する配列の初期値となる。 |
simplify | 演算結果にnames属性(行名、列名、次元名)を持たせるか。 |
関数tapplyのつかいかたを紹介します。
統計解析のfactor型の各levelsごとの演算に対して、tapplyが役に立ちます。
例として、データセットToothGrowthを用います。ToothGrowthはsupp、doseという2つのfactorを持ちます。この2つのfactorの各levelsの組み合わせについて、平均を求めるには次のようにtapply関数を用います。
1 2 3 4 5 | > tapply(ToothGrowth$len, ToothGrowth[,-1], mean) dose supp 0.5 1 2 OJ 13.23 22.70 26.06 VC 7.98 16.77 26.14 |
applyファミリーの使用例
上の簡単な使用例だけでは、applyファミリーをどのように扱えばよいか想像しにくいので、具体的な使用例を紹介します。
ここでは、applyとlapplyのみを解説します。
applyはベクトルや行列・データフレームの演算が可能であるのに対し、lapplyはリストの演算が可能であるので、
2つの使いかたが分かれば、ほとんどのオブジェクト(ベクトル、行列・データフレーム、リスト)に対し、高速な繰り返し処理を行うことが可能となります。
apply
実際に、データセットを用いたapplyの使用例をみていきます。次のRに標準で入っている集計表のデータセットoccupationalStatusを用います。
1 2 3 4 5 6 7 8 9 10 11 | > occupationalStatus destination origin 1 2 3 4 5 6 7 8 1 50 19 26 8 7 11 6 2 2 16 40 34 18 11 20 8 3 3 12 35 65 66 35 88 23 21 4 11 20 58 110 40 183 64 32 5 2 8 12 23 25 46 28 12 6 12 28 102 162 90 554 230 177 7 0 6 19 40 21 158 143 71 8 0 3 14 32 15 126 91 106 |
このデータセットの各行の和、各列の和、総計を求めてみましょう。
次のように関数applyを用いることで各行の和を計算することができます。
1 2 | data <- occupationalStatus #データセット rowSum <- apply(data, 1, sum) #行の和 |
関数cbindを用いて、これらの和のベクトルをdataに結合させます。
1 2 3 4 5 6 7 8 9 10 11 | data <- cbind(data, rowSum) > data 1 2 3 4 5 6 7 8 rowSum 1 50 19 26 8 7 11 6 2 129 2 16 40 34 18 11 20 8 3 150 3 12 35 65 66 35 88 23 21 345 4 11 20 58 110 40 183 64 32 518 5 2 8 12 23 25 46 28 12 156 6 12 28 102 162 90 554 230 177 1355 7 0 6 19 40 21 158 143 71 458 8 0 3 14 32 15 126 91 106 387 |
次に、applyを用いて列方向に関数sumを適用することで、各列の和と総計を計算することができます。
1 | colSum <- apply(data, 2, sum) #列の和と総計 |
最後にこのcolSumをrbindでdataに結合させることで、各行、各列の和と総計を集計表に加えることができます。
1 2 3 4 5 6 7 8 9 10 11 12 | data <- rbind(data, colSum) > data 1 2 3 4 5 6 7 8 rowSum 1 50 19 26 8 7 11 6 2 129 2 16 40 34 18 11 20 8 3 150 3 12 35 65 66 35 88 23 21 345 4 11 20 58 110 40 183 64 32 518 5 2 8 12 23 25 46 28 12 156 6 12 28 102 162 90 554 230 177 1355 7 0 6 19 40 21 158 143 71 458 8 0 3 14 32 15 126 91 106 387 colSum 103 159 330 459 244 1186 593 424 3498 |
また、次に示す例のように関数を自分で定義することもできます。
apply関数の引数FUNにfunction(x) {...}を代入することでapply内にローカルの関数を定義することができます。
次のコードは、xy_coordinatesというxy座標が格納されたデータフレームの異なるi番目とj番目の座標のユークリッド距離が整数であるかを判定する関数となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | xy_coordinates <- data.frame(x = c(4, 1, 5, -2), y = c(2, 2, 5, 8)) apply(combn(nrow(xy_coordinates), 2), 2, function(x){ diff_x <- xy_coordinates$x[x[1]] - xy_coordinates$x[x[2]] diff_y <- xy_coordinates$y[x[1]] - xy_coordinates$y[x[2]] distance <- sqrt(diff_x ^ 2 + diff_y ^ 2) if (distance %% 1 == 0) { return(paste0("i:", x[1], ", j:", x[2], " isInt")) } else { return(paste0("i:", x[1], ", j:", x[2], " notInt")) } }) |
apply関数の最初の引数には、i番目とj番目の全組み合わせcombn(nrow(xy_coordinates), 2)を与え、そのデータフレームの各列のインデックスについて距離を計算しています。
また、距離が整数であるかどうかは、distance %% 1 == 0のように1で割った余りが0であるという論理演算を行うことで判定可能です。
上では、xy座標は次のように与えられています。
1 2 3 4 5 6 | > xy_coordinates x y 1 4 2 2 1 2 3 5 5 4 -2 8 |
このxy座標に対し、先ほどのapply関数を用いると、次のようにi番目とj番目の座標の組み合わせについて、結果が出力されます。
1 2 | [1] "i:1, j:2 isInt" "i:1, j:3 notInt" "i:1, j:4 notInt" [4] "i:2, j:3 isInt" "i:2, j:4 notInt" "i:3, j:4 notInt" |
lapply
次にlapplyの使用例を見ていきます。
次のlistAには、int型のc(3, 8, 1, 6, 3, 9, 2)とfloat型のsqrt(c(7, 5, 13, 17, 3, 10, 19))とstring型のc("j", "d", "y", "l", "m", "s", "a")が格納されています。
1 2 3 | listA <- list(int = c(3, 8, 1, 6, 3, 9, 2), float = sqrt(c(7, 5, 13, 17, 3, 10, 19)), string = c("j", "d", "y", "l", "m", "s", "a")) |
このリストの各要素をソートするには、次のようにlapplyを用います。
1 | lapply(listA, sort) |
実行すると、各要素についてソートされたベクトルが出力されているのが確認できます。
1 2 3 4 5 6 7 8 9 | > lapply(listA, sort) $int [1] 1 2 3 3 6 8 9 $float [1] 1.732051 2.236068 2.645751 3.162278 3.605551 4.123106 4.358899 $string [1] "a" "d" "j" "l" "m" "s" "y" |
また、正規表現に関するlapplyの使用例もみていきます。
次のように"Hello Worlrd !!!"と"ウサギさんの統計学サロン"に数字が混在しているベクトルを与えます。
1 2 | strings <- c("14H4e65l41l4o123 097W345o780r1l34d28 34!5!90!1", "1ウ34サ53ギ11 5さ098んの 9098統5計学452サロ343ン1") |
スペース区切りで数字を取り除いた元の文字列を抽出したい場合、関数strsplitを用いて、分割後のリストをlapplyに適用します。
1 2 3 4 5 | splitedStr <- strsplit(strings, " ") lapply(splitedStr, function(x) { return(gsub("[0-9]", "", x)) }) |
実行すると、数字を除去した文字列が節ごとに出力されているのが確認できます。
1 2 3 4 5 | [[1]] [1] "Hello" "World" "!!!" [[2]] [1] "ウサギ" "さんの" "統計学サロン" |
まとめ
applyファミリーの使用法についてみていきました。
Rでは大きなデータフレームを用いる際にfor 文などの繰り返し処理を行うと、計算時間が膨大になることがほとんどです。
applyファミリーを用いることでこれらの実行時間を大幅に減らすことができます。
for文を使わず、積極的にapplyファミリーを使っていきましょう。