15  apply関数群

Rを代表する便利な関数やライブラリは何か?、と聞かれた時、2010年ごろまではapply関数群を挙げるのが一般的でした。apply関数群を用いれば、他の言語では繰り返し計算で数行プログラムを書かないといけないような状況でも、たった1つの関数で高速に計算ができます。Rでは繰り返し計算をなるべく使わず、apply関数群を使うのが標準的な方法でした。現在では、2010年以降に開発されたdplyr (Wickham et al. 2023)tidyrパッケージ (Wickham, Vaughan, and Girlich 2023)を用いて計算を行うのが一般的となり、apply関数群は使われなくなってきてはいますが、うまく用いれば便利な関数群です。

この章では、まず繰り返し計算によるベクター・データフレームの計算について解説し、次に繰り返しを用いず計算を行うapply関数群について説明していきます。

15.1 繰り返し計算

ベクターのような、多数の要素を含むオブジェクトを用いて演算を行う場合、R以外の言語では通常繰り返し計算を用います。繰り返し計算とは5章で説明した通り、for文やwhile文、repeat文などを用いた計算です。プログラミング言語によって繰り返し計算の式は異なりますが、たくさんの要素を持つオブジェクトの演算では、繰り返し計算は有効な計算方法の一つです。

15.2 繰り返し計算とベクター

では、Rでの繰り返し計算を見てみましょう。下の例では、ベクターの要素に演算を行い、結果をベクターで返す繰り返し計算を行っています。

for文でベクターの演算を行う
v <- 1:10 # vは1~10の整数ベクター

v_new <- NULL # 計算結果を代入するNULLが入った変数

for(i in 1:length(v)){ # vの長さ分計算する
  temp <- v[i] * 3 + 5
  v_new <- c(v_new, temp) # v_newにv[i]を演算した結果をつなげる
}

v_new # 演算結果
##  [1]  8 11 14 17 20 23 26 29 32 35

Rでfor文を用いてベクターの要素に演算を行う場合には、

  • NULLを代入しただけの空っぽの変数を作る
  • 繰り返し回数を演算に用いるベクターの長さで指定する
  • ベクターの要素をインデックスで取り出し、演算する
  • 空っぽの変数に、c関数で演算結果を付け足す

といった計算を行うことがあります。

上の例では、変数vは1~10の連続する長さ10の整数ベクター、v_newNULL(空)のオブジェクトです。

for文では、i1:length(v)の要素が繰り返し計算ごとに代入されます。つまり1回目の繰り返し計算ではiは1、2回目ではiは2…となり、ilength(v)、つまり10が代入された計算が終わると、繰り返し計算が終了します。

さらに、for文中では、v[i]、つまりiをインデックスにしてvの要素を取り出しています。1回目の繰り返し計算ではiが1ですので、v[i]vの1つ目の要素、2回目の繰り返し計算ではv[i]vの2つ目の要素となります。v[i]を3倍して5を足した結果を一時的にtempという変数に代入しています。

for文の中では、v_new <- c(v_new, temp)という形で、空の変数であるv_newに、計算結果をc関数で繋いだものを、v_newに代入する、という変な演算を行っています。この演算では、1週目ではc(NULL, temp)v_newに代入することで、v_newtempを1つだけ持つベクターに変化しています。2週目以降は、v_newのベクターの要素の後に新しく計算したtempが付け加えられます。これを繰り返すことで、v_newにはtempの計算結果がベクターとして記録されていきます。

最終的に、v_newvの各要素を3倍して5を足したベクターとして演算が終了します。

上記のようなfor文の演算は、Rでは非常によろしくない、典型的なバッドノウハウであるとされています。そもそもRでのベクターの演算に繰り返し文を使う必要がないので、上記のfor文は下のように書き換えることができます。ベクターの演算の方が、for文を用いた演算よりずっと速いため、Rでベクターの要素を演算に用いる場合には、繰り返し計算ではなく、ベクターの演算を用いることが推奨されています。

上のfor文と同じ計算をベクターで行う
v * 3 + 5
##  [1]  8 11 14 17 20 23 26 29 32 35

上のfor文にはもう一点問題があります。v_newというベクターに対して、数値を付け足すという演算を繰り返しています。このような場合、v_newの長さが伸びていくため、Rの内部では、要素が付け加えられる度にv_newというベクターを新しく作り直し、古いものは削除するという演算が行われることになります。この「作り直して」「削除する」という演算に時間がかかるため、上記のような書き方では演算速度に問題が生じます。

このような「作り直して」「削除する」プロセスを省くためには、インデックスに代入する方法を用いるとよいでしょう。あらかじめ結果と同じ長さのベクターを準備しておき、このベクターの要素に演算結果を代入します。このような形にすると、v_new自体を作り直すプロセスがなくなり、演算速度が速くなるとされています。

この、結果と同じ長さのベクターを準備するときには、numeric関数を用います。numeric関数は数値を1つ引数に取り、数値に応じた長さの、要素が0だけのベクターを作成する関数です。このnumeric関数を用いて演算結果と同じ長さのベクターを作成しておくことで、そのベクターにインデックスを指定して演算結果を代入していくことができます。

numeric関数を用いてインデックスに代入する
numeric(5) # 0が5つ入ったベクター
## [1] 0 0 0 0 0

v_new <- numeric(length(v)) # v_newは0がvと同じ長さだけ並んだベクター

for(i in 1:length(v)){ # vの長さ分計算する
  v_new[i] <- v[i] * 3 + 5 # v_new[i]にv[i]を演算した結果を代入
}

v_new # 演算結果
##  [1]  8 11 14 17 20 23 26 29 32 35

15.3 繰り返し計算とデータフレーム

データフレームの要素に対して繰り返し計算をする場合にも、上のベクターでの繰り返し計算と同様の手法が使えます。下の計算では、iris_editedという変数にNULLを代入し、この空のiris_editedにベクターをrbind関数で結合したものをiris_editedに代入するという計算をしています。rbind関数は行を追加する関数ですので、計算結果を含むベクターはiris_editedの一番下の行に追加されます。

このような繰り返し計算を行うと、iris_edited自動的にNULLから行列(matrix)に変換されます。また、計算途中でiris$Species(因子)を文字列に変換し、文字列と数値の計算結果をベクターにまとめているため、数値計算結果は自動的に文字列に変換されています。その結果、繰り返し計算後に得られるiris_edited文字列の行列になっています。

行列は、as.data.frame関数でデータフレームに変換できます。ただし、文字列の行列の要素は文字列型のまま変換されるため、結果が数値に見えても文字列になっている場合があります。

このように、データフレームを直接繰り返し計算に用いると、データ型の変換が頻繁に起こり、計算結果を予測するのが難しくなります。繰り返し計算時には、取り扱っている変数の型がどのように変化しているのか、常に注意が必要です。

for文でデータフレームに行を追加する
head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

iris_edited <- NULL

for(i in 1:nrow(iris)){
  # iris$Speciesは因子で、そのままだと数値に変換されるため、文字列に変換しておく
  species <- as.character(iris$Species[i]) 
  
  # Sepal.LengthとSepal.Widthの積を計算
  Sepal_multiple <- iris$Sepal.Length[i] * iris$Sepal.Width[i]
  
  # species(文字列)と計算結果をベクターにまとめる(文字列のベクターに変換)
  temp_vec <- c(species, Sepal_multiple)
  
  # ベクターをiris_editedの行として追加(iris_editedは行列になる)
  iris_edited <- rbind(iris_edited, temp_vec)
}

dim(iris_edited) # iris_editedは150行2列
## [1] 150   2

class(iris_edited) # iris_editedは行列(matrix)
## [1] "matrix" "array"

head(iris_edited) # 文字列の行列になっている
##          [,1]     [,2]   
## temp_vec "setosa" "17.85"
## temp_vec "setosa" "14.7" 
## temp_vec "setosa" "15.04"
## temp_vec "setosa" "14.26"
## temp_vec "setosa" "18"   
## temp_vec "setosa" "21.06"

ベクターの繰り返し計算で述べたように、変数に要素を追加して、サイズが変化すると、変数を「作り直して」「削除する」プロセスが繰り返され、計算のコストが大きくなります。計算のコストが大きくなると演算に時間がかかるため、上の例のようにNULLに要素を追加するのは避けた方がよいとされています。ですので、上のような計算では、あらかじめ結果と同じサイズの行列を準備し、その行列の要素に計算結果を追加していくのが良いとされています。

インデックスへの代入で計算結果を保存する
head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

# あらかじめ0が埋まっている行列を準備しておく
iris_edited <- matrix(0, nrow=150, ncol=2)

for(i in 1:nrow(iris)){
  species <- as.character(iris$Species[i])
  Sepal_multiple <- iris$Sepal.Length[i] * iris$Sepal.Width[i]
  temp_vec <- c(species, Sepal_multiple)
  iris_edited[i, ] <- temp_vec # 行列のインデックスに行の計算結果を追加する
}

dim(iris_edited) # iris_editedは150行2列
## [1] 150   2

class(iris_edited) # iris_editedは行列(matrix)
## [1] "matrix" "array"

head(iris_edited) # 文字列の行列になっている
##      [,1]     [,2]   
## [1,] "setosa" "17.85"
## [2,] "setosa" "14.7" 
## [3,] "setosa" "15.04"
## [4,] "setosa" "14.26"
## [5,] "setosa" "18"   
## [6,] "setosa" "21.06"

上記のように、Rでは繰り返し計算で変数の「作り直し」が起きたり、データ型がころころ変わったりするため、繰り返し計算でベクターやデータフレームの要素を取り扱うのは推奨されません。ただし、繰り返し計算は演算の過程が捉えやすく、後から読んで理解しやすい構造をしています。NULLオブジェクトにデータを追加していくと、結果として得られるオブジェクトのサイズがわかっていなくても演算できるという利点があります。

2000年頃のPCは性能が低く、計算コストに気を払う必要がありましたが、現在では繰り返し計算を回しても大して時間がかからなくなりました。よほど大きいデータフレーム(数万~数十万行)を取り扱う場合を除けば、繰り返し計算が問題となることは少なくなったと感じます。データの取り扱いが一度きり(ad hoc)の場合には以下のapply関数群を使っても、繰り返し計算を使っても大差ないため、好みの、わかりやすい方法を用いればよいでしょう。

15.4 apply関数群

Rでのデータフレーム演算の「お作法」では、繰り返し計算ではなく、apply関数群を用いるのが望ましいとされています。apply関数群にはapply, mapply, lapply, sapply, tapply, aggregate, byなど、かなりたくさんの関数があり、それぞれ少し癖のある使い方が求められます。以下の表1にapply関数群についてまとめます。

表1 apply関数群(funは関数、factは因子、marginは方向を指す)
関数名 計算の内容
apply(x, margin, fun) marginの方向に関数を適用する
mapply(fun, 引数1, 引数2…) 引数に指定した複数の値を関数に代入する
lapply(list, fun) listの要素に関数を適用する
sapply(list, fun) listの要素に関数を適用する
vapply(x, fun, fun.value) xに関数を適用し、fun.valueに指定した形で返す
replicate(n, calc) calcで指定した演算をn回繰り返す
tapply(x, fact, fun) xを因子で切り分け、関数を適用する
sweep(x, margin, y) marginの方向にyの要素を引く
aggregate(x, by, fun) byで指定した要素ごとに関数を適用する
aggregate(formula, data) formulaに従い、要素ごとに関数を適用する
by(x, by, fun) byで指定した要素ごとに関数を適用する

15.4.1 apply関数

apply関数群の中で最も基本的なものが、apply関数です。apply関数は第一引数にデータフレーム(もしくは行列)を取り、第二引数にMARGIN第三引数に関数を取ります。第二引数のMARGINは1か2を取り、1を取ると行(横)方向、2を取ると列(縦)方向のベクターを計算対象とします。apply関数はデータフレームの要素に、MARGINで指定した方向に、第三引数で指定した関数を適用します。第三引数に指定する関数には、Rに備わっている関数、自作した関数の両方を用いることができます。

apply関数の演算では、(特に行方向、MARGIN = 1の時には)データ型が一致していることが必要となります。apply関数の使い方を以下の図1に示します。

図1:apply関数の使い方

図1:apply関数の使い方
apply関数の演算
apply(iris[, 1:4], 1, sum) # 行方向に和を求める
##   [1] 10.2  9.5  9.4  9.4 10.2 11.4  9.7 10.1  8.9  9.6 10.8 10.0  9.3  8.5 11.2
##  [16] 12.0 11.0 10.3 11.5 10.7 10.7 10.7  9.4 10.6 10.3  9.8 10.4 10.4 10.2  9.7
##  [31]  9.7 10.7 10.9 11.3  9.7  9.6 10.5 10.0  8.9 10.2 10.1  8.4  9.1 10.7 11.2
##  [46]  9.5 10.7  9.4 10.7  9.9 16.3 15.6 16.4 13.1 15.4 14.3 15.9 11.6 15.4 13.2
##  [61] 11.5 14.6 13.2 15.1 13.4 15.6 14.6 13.6 14.4 13.1 15.7 14.2 15.2 14.8 14.9
##  [76] 15.4 15.8 16.4 14.9 12.8 12.8 12.6 13.6 15.4 14.4 15.5 16.0 14.3 14.0 13.3
##  [91] 13.7 15.1 13.6 11.6 13.8 14.1 14.1 14.7 11.7 13.9 18.1 15.5 18.1 16.6 17.5
## [106] 19.3 13.6 18.3 16.8 19.4 16.8 16.3 17.4 15.2 16.1 17.2 16.8 20.4 19.5 14.7
## [121] 18.1 15.3 19.2 15.7 17.8 18.2 15.6 15.8 16.9 17.6 18.2 20.1 17.0 15.7 15.7
## [136] 19.1 17.7 16.8 15.6 17.5 17.8 17.4 15.5 18.2 18.2 17.2 15.7 16.7 17.3 15.8

apply(iris[, 1:4], 2, sum) # 列方向に和を求める
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
##        876.5        458.6        563.7        179.9

# 自作の関数
func1 <- function(x){sum(sqrt(x) * log(x))}

apply(iris[, 1:4], 2, func1)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
##    638.50578    292.37383    373.96812     32.26917

apply(iris[1:5, 1:4], 2, \(x){sum(sqrt(x) * log(x))}) # 無名関数も利用できる
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
##    17.424140    10.749705     1.990091    -3.598813

15.4.1.1 3次元以上のarrayにapplyを適用する

apply関数は3次元以上のarrayを引数に取ることもできます。3次元以上のarrayを引数に取る場合には、MARGINの設定がやや複雑になります。MARGINに1つの値を設定した場合には、その次元を残す形で関数を適用します。MARGINに2つの値を設定した場合には、その2つの次元を残して関数を適用します。MARGINに2つ以上の値を指定する場合には、ベクターを用います。

apply関数でarrayを演算する
UCBAdmissions # 3次元アレイ
## , , Dept = A
## 
##           Gender
## Admit      Male Female
##   Admitted  512     89
##   Rejected  313     19
## 
## , , Dept = B
## 
##           Gender
## Admit      Male Female
##   Admitted  353     17
##   Rejected  207      8
## 
## , , Dept = C
## 
##           Gender
## Admit      Male Female
##   Admitted  120    202
##   Rejected  205    391
## 
## , , Dept = D
## 
##           Gender
## Admit      Male Female
##   Admitted  138    131
##   Rejected  279    244
## 
## , , Dept = E
## 
##           Gender
## Admit      Male Female
##   Admitted   53     94
##   Rejected  138    299
## 
## , , Dept = F
## 
##           Gender
## Admit      Male Female
##   Admitted   22     24
##   Rejected  351    317

dimnames(UCBAdmissions) # 次元の順番はAdmit, Gender, Deptの順
## $Admit
## [1] "Admitted" "Rejected"
## 
## $Gender
## [1] "Male"   "Female"
## 
## $Dept
## [1] "A" "B" "C" "D" "E" "F"

apply(UCBAdmissions, 1, mean) # 1次元目(Admit)の平均を求める
## Admitted Rejected 
## 146.2500 230.9167

apply(UCBAdmissions, 2, mean) # 2次元目(Gender)の平均を求める
##     Male   Female 
## 224.2500 152.9167

apply(UCBAdmissions, 3, mean) # 3次元目(Dept)の平均を求める
##      A      B      C      D      E      F 
## 233.25 146.25 229.50 198.00 146.00 178.50


apply(UCBAdmissions, 1:2, mean) # 3次元目(Dept)方向に平均を求める
##           Gender
## Admit          Male    Female
##   Admitted 199.6667  92.83333
##   Rejected 248.8333 213.00000

apply(UCBAdmissions, c(1, 3), mean) # 2次元目(Gender)方向に平均を求める
##           Dept
## Admit          A     B   C     D     E   F
##   Admitted 300.5 185.0 161 134.5  73.5  23
##   Rejected 166.0 107.5 298 261.5 218.5 334

apply(UCBAdmissions, 2:3, mean) # 1次元目(Admit)方向に平均を求める
##         Dept
## Gender       A     B     C     D     E     F
##   Male   412.5 280.0 162.5 208.5  95.5 186.5
##   Female  54.0  12.5 296.5 187.5 196.5 170.5

15.4.2 mapply関数

mapply関数は、関数が2種類以上の引数を取る場合に用います。mapply関数はapply関数とは引数の順番が異なり、適用する関数が第一引数になります。続いて適用する関数の引数をベクターで取ります。mapply関数の返り値は適用する関数により異なり、関数の返り値が1つならmapply関数の返り値はベクター、返り値が2つ以上ならmapply関数の返り値はリストになります。

図2:mapply関数の使い方

図2:mapply関数の使い方
mapply関数
mapply(mean, 1:4, 2:5)
## [1] 1 2 3 4

lst <- mapply(rep, times = 1:4, x = 4:1) # 関数の引数を指定して、ベクターで与える 複数の引数を取れる
lst
## [[1]]
## [1] 4
## 
## [[2]]
## [1] 3 3
## 
## [[3]]
## [1] 2 2 2
## 
## [[4]]
## [1] 1 1 1 1

# 無名関数も使える
mapply(\(x, y){x / y}, x = 1:5, y = 5:1)
## [1] 0.2 0.5 1.0 2.0 5.0

15.4.3 lapply関数/sapply関数

lapply関数はリストを引数に取る関数です。lapply関数は第一引数にリスト、第二引数に関数を取り、リストの各要素に関数を適用します。lapply関数は返り値にリストを取ります。データフレームはリストですので、lapply関数はapply(x, 2, FUN)、つまり列方向に関数を適用するのと同じ計算を行うことができます。

sapply関数は返り値がベクターになったlapply関数です。関数の返り値が2つ以上の値であれば、sapply関数は行列を返します。

図3:lapply/sapply関数の使い方

図3:lapply/sapply関数の使い方
lapply関数とsapply関数
lapply(lst, sum) # リストの各要素に関数を適用する(返り値はリスト)
## [[1]]
## [1] 4
## 
## [[2]]
## [1] 6
## 
## [[3]]
## [1] 6
## 
## [[4]]
## [1] 4

lapply(iris[, 1:4], sum) # データフレームは列方向のリストなので、適用可能
## $Sepal.Length
## [1] 876.5
## 
## $Sepal.Width
## [1] 458.6
## 
## $Petal.Length
## [1] 563.7
## 
## $Petal.Width
## [1] 179.9

lapply(lst, summary)
## [[1]]
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       4       4       4       4       4       4 
## 
## [[2]]
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       3       3       3       3       3       3 
## 
## [[3]]
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       2       2       2       2       2       2 
## 
## [[4]]
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       1       1       1       1       1       1


sapply(lst, sum) # 返り値がベクターのlapply
## [1] 4 6 6 4

sapply(iris[, 1:4], sum)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
##        876.5        458.6        563.7        179.9

15.4.4 vapply関数

vapply関数は関数が複数の返り値を持つときに用いる関数です。vapply関数の第一引数はベクターやリスト、第二引数は複数の返り値を取る関数です。vapply関数はこの返り値を行列に変換するのですが、この変換時の行名をFUN.VALUEという第三引数で設定します。FUN.VALUEの設定は名前付きベクターで、値は0とします。かなり癖が強いので、使われているところをほとんど見たことがない関数です。

図3:vapply関数の使い方

図3:vapply関数の使い方
vapply関数
fivenum(1:10) # データを5つに集約する関数(最小値、第一四分位、中央値、第三四分位、最大値)
## [1]  1.0  3.0  5.5  8.0 10.0

vapply(iris[, 1:4], fivenum, c(Min. = 0, "1st Qu" = 0, Median = 0, "3rd Qu" = 0, Max. = 0)) # 集約値をそれぞれ表示
##        Sepal.Length Sepal.Width Petal.Length Petal.Width
## Min.            4.3         2.0         1.00         0.1
## 1st Qu          5.1         2.8         1.60         0.3
## Median          5.8         3.0         4.35         1.3
## 3rd Qu          6.4         3.3         5.10         1.8
## Max.            7.9         4.4         6.90         2.5

15.4.5 replicate関数

replicate関数は、第一引数に繰り返し計算の回数、第二引数に関数を取ります。replicate関数は関数の計算を繰り返し計算の回数だけ繰り返し、結果をベクターで返します。指定する関数の引数を変えることはできないので、一見あまり意味がなさそうに見えます。しかし、Rでは乱数(ランダムな数値)を用いた計算を行うことが多く、乱数計算を繰り返すと同じ関数の返り値でも変化することがあります。したがって、replicate関数は乱数を使った計算で利用すると生きる関数となっています。

図5:replicate関数の使い方

図5:replicate関数の使い方
replicate関数
sample(1:6, 10, replace=T) # サイコロを10回ふる
##  [1] 6 1 4 1 2 5 3 6 2 3

sum(sample(1:6, 10, replace=T)) # サイコロを10回ふり、合計値を求める
## [1] 36

replicate(20, sum(sample(1:6, 10, replace=T))) # 上の試行を20回繰り返す
##  [1] 34 36 31 44 36 41 34 30 35 36 41 33 28 26 30 34 37 34 32 49

15.4.6 tapply関数

tapply関数はベクターと因子を引数に取り、因子のグループごとに関数をベクターに適用します。ベクターに測定値、因子にカテゴリ(たとえば男性・女性など)を取っておけば、カテゴリごとの集計値を計算するのに使えます。

図6:tapply関数の使い方

図6:tapply関数の使い方
tapply関数
v <- 1:10
cutv <- factor(c(1, 1, 1, 1, 2, 2, 2, 3, 3, 4))
tapply(v, cutv, sum) # ベクターを因子で切り分け、関数を適用する
##  1  2  3  4 
## 10 18 17 10

15.4.7 sweep関数

sweep関数は、apply関数に似ていますが、第三引数が関数ではなく、ベクターであるところが異なります。MARGINは1が行方向、2が列方向であるのはapply関数と同じですが、第三引数が引き算に使われるのが特徴です。

sweep関数
matrix(1:15, nrow=3)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    4    7   10   13
## [2,]    2    5    8   11   14
## [3,]    3    6    9   12   15

sweep(matrix(1:15, nrow=3), 1, 1:3) # 列方向に1, 2, 3を引く
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    0    3    6    9   12
## [2,]    0    3    6    9   12
## [3,]    0    3    6    9   12

sweep(matrix(1:15, nrow=3), 2, 1:5) # 行方向に1, 2, 3, 4, 5を引く
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    0    2    4    6    8
## [2,]    1    3    5    7    9
## [3,]    2    4    6    8   10

15.4.8 aggregate関数

aggregate関数は、dplyrが出てくるまではデータフレームの結果を集計するのによく用いられてきた関数です。aggregate関数の第一引数はデータフレーム、第二引数にはby(因子のリスト)、第三引数に関数を取ります。aggregate関数はbyに指定した因子に従い、各列に関数を適用します。因子にカテゴリ(下の例ではirisの種、Species)を指定することで、因子ごとにデータを集計するのに用いることができます。

aggregate関数は引数にデータフレームだけでなく、formula(式)というものを取ることもできます。このformulaはRでは統計でよく用いられる表現で、~(チルダ)を演算子として用いるものです。~の左側には目的変数、右側には説明変数を足し算・掛け算で記入する形をとるのが最も一般的です。aggregate関数では、左側に関数を適用する列名、右側にbyにあたる因子を指定します。また、formulaでは、~の左側または右側に.(ピリオド)を置くことがあります。このピリオドは、「従属変数または独立変数に使用しなかったすべての列」を表す表現です。このformulaについては、統計の章で詳しく説明します。

by関数もaggregate関数と類似した関数ですが、byは関数の引数に各列のベクターではなく、因子で区切ったデータフレームを指定します。ですので、データフレームを処理できない関数を用いると、計算ができない場合があります。

図7:aggregate関数の使い方

図7:aggregate関数の使い方
aggregate関数とby関数
aggregate(iris[,1:4], by=list(iris$Species), FUN = "mean") # byはリスト、Speciesごとの平均値
##      Group.1 Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1     setosa        5.006       3.428        1.462       0.246
## 2 versicolor        5.936       2.770        4.260       1.326
## 3  virginica        6.588       2.974        5.552       2.026

aggregate(iris[,1:4], by=list(iris$Species), FUN = "min") # Speciesごとの最小値
##      Group.1 Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1     setosa          4.3         2.3          1.0         0.1
## 2 versicolor          4.9         2.0          3.0         1.0
## 3  virginica          4.9         2.2          4.5         1.4

# formulaでも演算ができる(.はirisのSpecies以外の列)
aggregate(.~Species, data = iris, max) 
##      Species Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1     setosa          5.8         4.4          1.9         0.6
## 2 versicolor          7.0         3.4          5.1         1.8
## 3  virginica          7.9         3.8          6.9         2.5

head(ToothGrowth) # ラットの歯の成長のデータ
##    len supp dose
## 1  4.2   VC  0.5
## 2 11.5   VC  0.5
## 3  7.3   VC  0.5
## 4  5.8   VC  0.5
## 5  6.4   VC  0.5
## 6 10.0   VC  0.5

# 与えるサプリの種類と量ごとの歯の長さの平均値
aggregate(len~., data = ToothGrowth, mean) 
##   supp dose   len
## 1   OJ  0.5 13.23
## 2   VC  0.5  7.98
## 3   OJ  1.0 22.70
## 4   VC  1.0 16.77
## 5   OJ  2.0 26.06
## 6   VC  2.0 26.14

by(iris[, 1], list(iris$Species), mean) # 1列目のSpeciesごとの平均値
## : setosa
## [1] 5.006
## ------------------------------------------------------------ 
## : versicolor
## [1] 5.936
## ------------------------------------------------------------ 
## : virginica
## [1] 6.588

by(iris[, 1:2], list(iris$Species), mean) # meanは2列のデータを処理できない
## : setosa
## [1] NA
## ------------------------------------------------------------ 
## : versicolor
## [1] NA
## ------------------------------------------------------------ 
## : virginica
## [1] NA

by(iris[, 1:4], list(iris$Species), summary) # summaryはデータフレームを引数にできるので、計算できる
## : setosa
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100  
##  1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200  
##  Median :5.000   Median :3.400   Median :1.500   Median :0.200  
##  Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246  
##  3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300  
##  Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600  
## ------------------------------------------------------------ 
## : versicolor
##   Sepal.Length    Sepal.Width     Petal.Length   Petal.Width   
##  Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000  
##  1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200  
##  Median :5.900   Median :2.800   Median :4.35   Median :1.300  
##  Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326  
##  3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500  
##  Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800  
## ------------------------------------------------------------ 
## : virginica
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400  
##  1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800  
##  Median :6.500   Median :3.000   Median :5.550   Median :2.000  
##  Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026  
##  3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300  
##  Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500