R の ave 函数

概要
Rにおける ave 函数の使用法についての説明。この函数は、グループ分けした上で、何か計算をするときに有用である。ただし dplyr パッケージの方が同様のことができる上、分かりやすく書ける。

ave のデータ出力

R には ave という函数がある。これは、与えたデータに対して何らかの計算をした上で、その計算結果を元々のデータの回数だけ繰り返すというものだ。こう言われても、多くの人にとってはどんな函数かよく分からないだろう。

具体例を見てみよう。2, 6, 7, 13, 17という5つの数値から成り立つデータがあるとする。このデータの中央値(メディアン)は7である。Rを使って、これを計算してみよう。

Rで中央値を求めるには、median 函数を使う。この函数を使うと、7という値が1つだけ出てくる。1つしか出てこないのは当然と言えば当然の話である。ここのデータから求められる中央値は1つの値に定まるのだから、1つ出てくれば十分なのである。

x <- c(2, 6, 7, 13, 17)
median(x)
# [1] 7

だが、場合によっては、与えたデータと同じ回数だけ値が繰り返された方が便利なこともある。どのように便利なのかは後で述べることにするが、この例について言えば、データが5つの数値から成っているので、中央値の7が5回繰り返されたものが出てくると便利なこともある。そのように出力したい場合、ave 函数を用いればよい。

x <- c(2, 6, 7, 13, 17)
ave(x, FUN = median)
# [1] 7 7 7 7 7

つまり、単に median を使うだけでは1つしか出てこなかったものが、ave を使うことによって元々のデータと同じ回数だけ出てくるようになる。単に中央値を知りたければ、別に ave を使う必要はない。しかし、算出された中央値をもとに何か次の計算をしたい場合、ave が便利である。

先ほどの事例において、各々のデータが中央値からどれだけずれているか(中央値からの偏差)を計算したいという状況を考えてみよう。これを求めるのは単純な話で、各々のデータから中央値を引くだけである。つまり、データが、2, 6, 7, 13, 17であるから、2-7, 6-7, 7-7, 13-7, 17-7を計算すれば済む。ただ、1回ずつ引き算をするのは手間である。

幸い、R ではベクトルをまとめて計算できる。例えば、a, bという2つのベクトルがあるとき、a-b という数式で、aの1番目の要素からbの1番目の要素を引いたもの、aの2番目の要素からbの2番目の要素を引いたもの、aの3番目の要素からbの3番目の要素を引いたもの……を求めることができる。

これを使えば、中央値からの偏差の計算が簡単な式で表現できる。

x <- c(2, 6, 7, 13, 17)
y <- ave(x, FUN = median)
# yの中身は、 7, 7, 7, 7, 7 である
x - y
# [1] -5 -1  0  6 10
中央値からの偏差の計算
1番目 2番目 3番目 4番目 5番目
x 2 6 7 13 17
y 7 7 7 7 7
x-yの途中式 2-7 6-7 7-7 13-7 17-7
x-yの結果 -5 -1 0 6 10

ただし、実は、今回の例では別に ave を使わなくても、x – median(x) という式で中央値からの偏差を求めることができる。

x <- c(2, 6, 7, 13, 17)
x - median(x)
# [1] -5 -1  0  6 10

なぜかというと、Rでは、引き算などで左項と右項のベクトルの長さが違う場合は適宜補うというルールがあるためだ。今の例で言うと、x は5つのデータが入っている長さ5のベクトルである。これに対して、median(x) は1つの値しか入っていない長さ1のベクトルである。このままだとベクトルの長さが合わないが、R は適宜 median(x) の値が5個あるように計算してくれる。

ave とグループ分け

ave が威力を発揮するのは、元々のデータに対してグループごとに何らかの計算をする場合である。

以下のように、性別 (Sex) と身長 (Height) のデータがあるとしよう。なお、ここで性別 (Sex) の“M”は男性を、“F”は女性を示す。

10人の男女の身長データ
ID Sex Height
1 M 168
2 M 175
3 F 153
4 F 162
5 M 171
6 F 148
7 F 172
8 M 182
9 M 158
10 F 158

このデータについて、男女それぞれの中央値からの偏差を求めてみよう。

まずはデータをRのデータフレームの形にしよう。以下のコードを実行することで、性別と身長についてのデータが入った physical というデータフレームができる。

sex <- c("M", "M", "F", "F", "M",
         "F", "F", "M", "M", "F")
height <- c(168, 175, 153, 162, 171,
            148, 172, 182, 158, 158)
physical <- data.frame(Sex = sex, Height = height)

このデータから、男女それぞれの身長の中央値を求めるにはどうすればよいだろうか? 単に中央値を求めるだけならば、tapply 函数を使うだけである。以下のコードでは、第1の引数である physical$Height を計算の対象とし、第2の引数である physical$Sex をグループ分けの基準とし、median 函数で計算している。つまり、physical$Sex に入っている値を基準としてグループ分けした上で、physical$Height の中央値を median で計算しているのである。

tapply(physical$Height, physical$Sex, median)
#   F   M 
# 158 171 

さて、tapply を使えば、女性 (F) の身長の中央値が158であり、男性 (M) の身長の中央値が171であることが分かる。

しかし、tapply が出した結果は、中央値からの偏差を求める際にそのまま使えない。先ほど x – median(x) と計算したように、physical$Height – tapply(physical$Height, physical$Sex, median) という形で計算することはできないのだ。

ここで役立つのが ave だ。ave を使うと、算出された結果が元々のデータと同じ回数だけ出てくるようにすることができる。以下のコードを実行すると、性別ごとの中央値が、元々のデータと同じ回数だけ出てくる。

ave(physical$Height, physical$Sex, FUN = median)
# [1] 171 171 158 158 171 158 158 171 171 158

上のコードでは、ave を使って、第1の引数である physical$Height を計算の対象とし、第2の引数である physical$Sex をグループ分けの基準とし、最後に FUNを median と指定して中央値を計算している。出力されるのは、長さが10のベクトル、すなわち計算の対象となった physical$Height と同じ長さのベクトルである。しかも、元となったデータで性別が男性のところには171という男性の中央値が出ていて、女性のところには158という女性の中央値が出ている。

ave を使ってここまで求めることができれば、後は普通に引き算をするだけで、中央値からの偏差を求めることができる

medians      <- ave(physical$Height, physical$Sex, FUN = median)
deviations <- physical$Height - medians
physical_rev   <- cbind(physical, Dev = deviations)

上のコードでは、まず ave を使ってグループごとの中央値を元となったデータと同じ長さだけ並べたものを medians に代入している。その次に、physical$Height から medians を引いて偏差を計算し、deviations に代入している。最後に、元のデータフレームに中央値からの偏差の列を加えたものを、physical_rev に代入している。

なお、ここでは1ステップごとに計算結果を何らかの変数に代入しているが、そうしたことをせずとも、以下のように一気に処理することもできる。

physical_rev   <- cbind(physical,
                Dev = ave(physical$Height,
                          physical$Sex,
                          FUN = function(x){x - median(x)}
                         )
                   )

dplyr パッケージによる代用

今まで aveを使ってきたが、dplyr パッケージを使っても同様のことができる。多くの場合、dplyr パッケージを使った方が分かりやすく書けるだろう。

library(dplyr)
physical_rev <- physical %>% group_by(Sex) %>% mutate(Dev = Height - median(Height))

上記のコードを簡単に解説しよう。まず、physical というデータフレームを対象として、group_by 函数を使って性別 (Sex) を基準としてグループ分けをする。そのグループ分けの結果に基づき、mutate 函数を使って元々のデータフレームに列を付け加える。付け加える列の名前は Dev であり、その値としては Height – median(Height) によって、すなわち身長 (Height) の値から身長の中央値を引いたものが入る。そうやってできあがったデータフレームが physica_rev に代入される。

ave がとる引数について

ave 函数がとる引数について簡単にまとめておこう。ave 函数は3種類の引数をとる [1]

  1. 計算の対象となるもの
  2. グループ分けの基準(複数あってよい)
  3. 計算に用いる函数(FUNオプションで指定する)

なお、2番目のグループ分けの基準を指定しなければ、計算の対象となったものはグループ分けされず、全体で計算される。また、3番目の計算に用いる函数に何も指定しなければ、平均を求める函数 mean が計算に用いられる。

いくつか具体例を挙げれば以下の通りになる。

z <- c(11, 12, 4, 15, 5, 8, 6, 10, 3)
ref1 <- c("a", "a", "a", "a", "b", "b", "b", "b", "b")
ref2 <- c("c", "c", "d", "d", "c", "c", "d", "d", "d")

# 全体の平均値
ave(z)
# [1] 8.222222 8.222222 8.222222 8.222222 8.222222 8.222222 8.222222 8.222222
# [9] 8.222222
# 全体の中央値
ave(z, FUN = median)
# [1] 8 8 8 8 8 8 8 8 8

# 全体の最大値
ave(z, FUN = max)
# [1] 15 15 15 15 15 15 15 15 15

# 全体の標準偏差
ave(z, FUN = sd)
# [1] 4.055175 4.055175 4.055175 4.055175 4.055175 4.055175 4.055175 4.055175
# [9] 4.055175

# ref1によってグループ分けしたときの平均値
ave(z, ref1)
# [1] 10.5 10.5 10.5 10.5  6.4  6.4  6.4  6.4  6.4

# ref1によってグループ分けしたときの中央値
ave(z, ref1, FUN = median)
# [1] 11.5 11.5 11.5 11.5  6.0  6.0  6.0  6.0  6.0

# ref1とref2を組み合わせてグループ分けしたときの平均値
ave(z, ref1, ref2)
# [1] 11.500000 11.500000  9.500000  9.500000  6.500000  6.500000  6.333333
# [8]  6.333333  6.333333

# ref1とref2を組み合わせてグループ分けしたときの中央値
ave(z, ref1, ref2, FUN = median)
# [1] 11.5 11.5  9.5  9.5  6.5  6.5  6.0  6.0  6.0
脚注
  1. 3個の引数ではなく、3種類の引数であることに注意。 []