この内容は山田、杉澤、村井(2008)「R」によるやさしい統計学、およびErik Marsjaの記事を参考にしています。
なお、滋賀医科大学の小山先生のページはとてもよくまとまっていて、お勧めです。
この講義では、「分散分析」のうち、「一元配置分散分析」をとりあげます。 前回のt検定では2つの群の平均値を比較しましたが、分散分析は3つ以上の群の平均値を比較したり、ある条件の違いによって母平均が異なるかを検定します。
学習項目です:
分散分析(ANOVA, Analysis Of Variance) とは、3つ以上の群の平均値差の検定や、条件(の組み合わせ)の違いによって母平均が異なるかどうかを検定する方法です (注意:「分散」という名前に惑わされないように)
データにおいて変数がとる値は、以下の『要因』によるものと「誤差」によるものがあるとみなせます:
そして、 「データ全体のばらつき」は、『要因によるばらつき』と『誤差によるばらつき』の和とみなせます。分散分析は、データ全体のばらつきを「平方和」として表し、それを要因ごとに分け、誤差に比べて大きな影響を与えている要因を探し出し、推測に利用する方法です。
具体的には、ある要因の水準ごとに分けたデータ(群)について、それぞれの群の平均を求めます。そして全データに対し、その群の平均を引いた差の2乗を求めます。Pythonで書くと(「全データを表す」変数に行列データが入っているとします):
import numpy as np
import pandas as pd
# A, B, C, Dは「対応のない一元配置分散分析」の例からとった。1要因4水準
A = np.array([15,9,18,14,18])
B = np.array([13,8,8,12,7])
C = np.array([10,6,11,7,12])
D = np.array([10,7,3,5,7])
AllData = pd.DataFrame({'A':A, 'B':B, 'C':C, 'D':D}) # データフレームを作る:全データ
GroupAverage = AllData.mean() # 水準ごとの平均のベクトル:群平均
# 水準ごとの平均のベクトルを行数だけ並べた行列を作る
GroupAverageMatrix =np.ones(AllData.shape) # 群平均行列の形の生成
for i in range(AllData.shape[1]): # 要素をいれていく
GroupAverageMatrix[:,i] = AllData.mean().iloc[i] # 水準ごとの平均
InGroup = np.array(AllData - GroupAverageMatrix) # 各データと、その群内の平均との差
InGroupSquareSum = np.sum(InGroup**2) # 群内平方和
Divider = InGroupSquareSum /( ( len(AllData.index) -1.0)*len(AllData.columns)) # 分母
このように「それぞれのデータと群平均との差の2乗の和」が「群の中のデータのばらつき」を表し、これが(群内平方和)と呼ばれるものです。そして、これを群内の自由度(「各群のデータ数-1」の和)で割ったものが、各データの「誤差によるばらつき」を表しているとみなすことができます。
一方、「要因によるばらつき」は、群間のばらつきで表されます。それは「全データの平均と各群の平均の差」の2乗の和を、群間の自由度(群の数-1、つまり水準数-1)で割った値と計算されます:
# 全データの和の平均</font>
OverallMean = np.sum(AllData.mean())/len(AllData.columns) # 全体平均
# 各群ごと「群平均-全平均」からなる行列
InterGroup =GroupAverageMatrix - np.ones(AllData.shape)*OverallMean # 群間
InterGroupSquareSum = np.sum(InterGroup**2) # 群間平方和
Dividend = InterGroupSquareSum / (len(AllData.columns) - 1.0) # 分子
群間平方和を群間の自由度で割った値(「分子」の値)が『要因によるばらつき』です。これを「誤差によるばらつき」で割った値がFであり、Fの値によって要因のばらつきの影響を推定するのが分散分析です(この計算方法が「分散」という名前がついている理由です。なお、Fは統計学者Fisherに由来します)。
Dividend # 分子
⇒ 61.33333
Divider # 分母
⇒ 8.625
F = Dividend / Divider # F = 分子/分母
⇒ 7.111111
import numpy as np
# A, B, C, Dは「対応のない一元配置分散分析」の例からとった。1要因4水準
A = np.array([15,9,18,14,18])
B = np.array([13,8,8,12,7])
C = np.array([10,6,11,7,12])
D = np.array([10,7,3,5,7])
import pandas as pd
AllData = pd.DataFrame({'A':A, 'B':B, 'C':C, 'D':D})
AllData
AllData.mean()
GroupAverageMatrix =np.ones(AllData.shape)
for i in range(AllData.shape[1]):
GroupAverageMatrix[:,i] = AllData.mean().iloc[i]
GroupAverageMatrix
InGroup = np.array(AllData - GroupAverageMatrix)
InGroup
InGroupSquareSum = np.sum(InGroup**2)
InGroupSquareSum
#len(AllData.columns)
OverallMean = np.sum(AllData.mean())/len(AllData.columns)
OverallMean
InterGroup =GroupAverageMatrix - np.ones(AllData.shape)*OverallMean
InterGroup
InterGroupSquareSum = np.sum(InterGroup**2)
InterGroupSquareSum
Dividend = InterGroupSquareSum / (len(AllData.columns) - 1.0)
Dividend
Divider = InGroupSquareSum /( ( len(AllData.index) -1.0)*len(AllData.columns))
Divider
F = Dividend / Divider
F
変数の値に影響を及ぼす要因が1個の場合一元配置法、要因が2個の場合二元配置法、
3個以上の場合は多元配置法という。
また要因と水準の組み合わせごとに実験が繰り返されてデータがとられる場合を繰り返しのある二元配置法などと呼ぶ。
一般に検定の結果として表示される分散分析表の構成 (PythonではFとpの値のみ):
要因 | 自由度(Df) | 平方和(Sum Sq) | 平均平方(Mean Sum Sq) | F値(F value) | P値 | 評価 |
---|---|---|---|---|---|---|
要因名 | 水準数-1 | 群間平方和 | 群間平方平均 | F値 | p値 | 印(表の下に説明) |
残差(Residuals) | 水準数×(1群あたりのデータ数-1) | 群内平方和 | 群内平方平均 | |||
合計 | データ数-1 |
評価の印: 星印は有意(*は0.1%、は1%、*は5%水準)、.は10%(「有意傾向」という)を表す
m=len(AllData.columns) - 1.0
n= ( len(AllData.index) -1.0)*len(AllData.columns)
print(m,n)
%matplotlib inline
from __future__ import division
from scipy import stats
import numpy as np
import matplotlib.pyplot as plt
fig,ax = plt.subplots(1,1)
x = np.linspace(0,8, 1000)
ax.plot(x, stats.f.pdf(x, m,n), linestyle='-')
plt.xlabel('$x$')
plt.ylabel(r'$f(x)$')
plt.title(r'F-Distribution')
plt.axis([-0.2,8,-0.02,1.2])
plt.legend()
plt.grid()
stats.f.sf(7.11, m,n)
例題:以下の表は『教え方』ごとにみた統計学テストの成績である。この表から、4通りの教え方(A,B,C,D)によって統計学テストの母平均に差があるだろうか?
A | B | C | D |
---|---|---|---|
15 | 13 | 10 | 10 |
9 | 8 | 6 | 7 |
18 | 8 | 11 | 3 |
14 | 12 | 7 | 5 |
18 | 7 | 12 | 7 |
この標本が母平均の等しい4群から抽出される可能性が高いかどうかを検討するために分散分析を行う。ここで『教え方』が要因、A, B, C, Dの4つがその水準となる.
Pythonを用いた計算方法の紹介
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as st
x=np.linspace(0,8,1000)
plt.plot(x,st.f.pdf(x,3,16))
plt.xlabel('x')
plt.ylabel('st.f.pdf(x,3,16)')
plt.grid()
help(st.f_oneway)
Pythonを用いた計算:
import numpy as np
# 4つの指導法に違いがあるか?
A = np.array([15,9,18,14,18])
B = np.array([13,8,8,12,7])
C = np.array([10,6,11,7,12])
D = np.array([10,7,3,5,7])
f, p = st.f_oneway(A,B,C,D)
print("F=%f, p-value = %f"%(f,p))
F値は7.11、p値が0.003であるから、帰無仮説が棄却される。検定の結果、つまりA,B,C,Dの間に有意な平均値差が見られたことが導ける。
注意: 実際には分散分析を適用するには、データが独立で、それぞれ正規分布に従うものであり、分散はみな等しい(分散の等質性)という条件がある。分散の等質性の検定にはBartlett検定を用いる。
st.bartlett(A,B,C,D) # Bartlett検定
一元配置分散分析の結果「帰無仮説が棄却」されても、これだけでは「群の母平均が等しくない」ことしかわからない。
具体的に「どの群とどの群の間に差があるか」を知りたいことが多い。それには多重比較を行う。ここではTukey(テューキー)の方法を紹介する。
仮定: 各群のデータ数(サンプルサイズ)nが等しく、各群の母分散も等しい(分散の等質性)
検定統計量q:
Pythonでは次のようにstatsmodels.stats.multicompモジュールの pairwise_tukeyhsd (Tukey's Honest Significant Difference
)関数を用いれば、いろいろな指導方法による平均値差の多重比較が可能となる
from statsmodels.stats.multicomp import pairwise_tukeyhsd
help(pairwise_tukeyhsd)
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import numpy as np
# 4つの指導法に違いがあるか?
A = np.array([15,9,18,14,18])
B = np.array([13,8,8,12,7])
C = np.array([10,6,11,7,12])
D = np.array([10,7,3,5,7])
data_arr = np.hstack( (A,B,C,D) ) # すべてのデータを結合
# array([15, 9, 18, 14, 18, 13, 8, 8, 12, 7, 10, 6, 11, 7, 12, 10, 7, 3, 5, 7])
ind_arr = np.repeat(list('ABCD'),len(A)) # 名称をリスト
# array(['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'C', 'C', 'C', 'C', 'C', 'D', 'D', 'D', 'D', 'D'])
print(pairwise_tukeyhsd(data_arr,ind_arr)) # 結果を出力する
出力された表においてはp値が0.05以下のものに、reject列で True と記入がある。これに該当するのはAとCの間,およびAとDの間であり、これらには5%水準で有意差があることがわかった。
# 参考: 関数にしてみた
def tukey_hsd( lst, ind, n ):
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import numpy as np
data_arr = np.hstack( lst )
ind_arr = np.repeat(ind, n)
print(pairwise_tukeyhsd(data_arr,ind_arr))
tukey_hsd( (A,B,C,D), list('ABCD') , 5) # 第1引数:データをタプルで, 第2引数:名称のリスト, 第3引数: データの個数
# 別バージョン:個数がそれぞれ違う場合. 次を参考にした: http://qiita.com/keroplant/items/bb03375c55ca4b2f5943
A = [100, 96, 80, 92, 101, 99, 89, 93]
B = [83, 77, 86, 71, 70, 98, 80, 75, 74]
C = [90, 91, 92, 89, 88, 90, 91]
def tukey_hsd( ind, *args ):
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import numpy as np
data_arr = np.hstack( args )
ind_arr = np.array([])
for x in range(len(args)):
ind_arr = np.append(ind_arr, np.repeat(ind[x], len(args[x])))
print(pairwise_tukeyhsd(data_arr,ind_arr))
tukey_hsd(list('ABC') , A,B,C) # 第1引数:名称のリスト, 第2引数以降: データ
[参考: 対応のあるなしの違い]
データに『対応がある(反復、繰り返しのある、ともいう)』ということは、それだけ情報が多い(データだから、それを用いた検定が可能になります。前章で「対応のあるt検定」について述べ、そこで取り上げた例題では結論は変わらなかったものの、対応のある検定の方がp値が小さい、ということを見ました。ここで取り上げる例題でも同じようなことがわかります。以下ではデータに「対応がない」ものとして検定した結果を載せます。
import numpy as np
import scipy.stats as st
Algebra = np.array([7,8,9,5,6])
Calculus = np.array([5,4,7,1,3])
Statistics = np.array([8,6,7,2,5]) # 好き嫌い度
print( st.f_oneway(Algebra, Calculus, Statistics) )
⇒ F_onewayResult(statistic=2.6406250000000004, pvalue=0.11210798800331405)
『対応のない』データとしての検定は、F値が2.64で、p値は0.112なので、有意水準0.05よりも大きいため、 帰無仮説は棄却されない。
この結果と、本文で述べる「対応がある」場合の結果とを比べてみてください。
import numpy as np
import scipy.stats as st
Algebra = np.array([7,8,9,5,6])
Calculus = np.array([5,4,7,1,3])
Statistics = np.array([8,6,7,2,5]) # 好き嫌い度
print( st.f_oneway(Algebra, Calculus, Statistics) )
例題: 以下の表は5名の学生に、線形代数、微分積分、確率統計に対する好意度の評定データである。 これら3つの科目に対する好意度に差があるだろうか?(念のため:これは1要因3水準)
学生 | 線形代数 | 微分積分 | 確率統計 |
---|---|---|---|
田中 | 7 | 5 | 8 |
岸 | 8 | 4 | 6 |
大引 | 9 | 7 | 7 |
吉川 | 5 | 1 | 2 |
荻野 | 6 | 3 | 5 |
Pythonを用いた計算方法の紹介
[参考: 『人』がなぜ必要?] まず『人』を含めない場合は先に見たように p値は 0.112となり、科目の効果だけでは好意度の平均に有意な差が認められません。 そもそもこのデータにおいて『対応がある』としたのは、同じ人に対しそれぞれの科目の好意度のデータを集めたので、『対応関係がある』からです。 言い換えれば、「好意度」データを説明する要因として『科目』と『人』があり、『人』を考慮することでデータに「対応がある」と認められたのです。 </div>
# データの準備:DataFrameを作る
import statsmodels.api as sm
from statsmodels.formula.api import ols
import pandas as pd
Algebra=[7,8,9,5,6] ; Calculus = [5,4,7,1,3] ; Statistics=[8,6,7,2,5]
Students=list(range(5)) # ['Tanaka','Kisi','Obiki','Yosikawa','Ogino']
Favorness = np.array(Algebra+Calculus+Statistics)
Condition = np.repeat(['algebra','calculus','statistics'],len(Algebra))
Subjects = np.array(Students+Students+Students)
df = pd.DataFrame({'Favorness':Favorness,'Condition':Condition,'Subjects':Subjects})
df[:5]
# statsmodels.statsのanova.AnovaRMを用いる、インスタンスを作ってfit()する
import statsmodels.stats.anova as anova
aov=anova.AnovaRM(df, 'Favorness','Subjects',['Condition'])
result=aov.fit()
print(result)
# 有意水準は5%、つまり0.005とする
# 5%で有意差があるので、多重比較する
tukey_hsd(list('ABC') , A,B,C)
いま、A,B, Cという3つの群があったとします。そこで、2群間の検定を A-B間、B-C間、C-AC間で有意水準5%で行ったとします。
ここで、「この組み合わせの少なくとも1つは有意差がある」
となる確率を計算すると(実際にその間に有意差があるなしに拘らず)、
注: scipy.statsをst、statsmodels.stats.anovaをanovaと略記する
目的 | 関数名とモジュール | 使い方 |
---|---|---|
一元配置分散分析(対応なし) | st.f_oneway(配列$_1$,$\ldots$,配列$_n$) | st.f_oneway(Algebra,Caluculus,Statistics) ⇒ F値とp値 |
一元配置分散分析(対応あり) | anova.AnovaRM(データフレーム,依存変数名,人名, その他の属性のリスト]).fit() | anova.AnovaRM(df, 'Favorness','Subjects',['Condition'])).fit() |
テューキーの多重分析 | from statsmodels.stats.multicomp import pairwise_tukeyhsd | |
(続き) | pairwise_tukeyhsd(データをタプルで, 名称のリスト)もしくは自作関数tukey_hsd | pairwise_tukeyhsd(data_arr,ind_arr) |
分散の等質性の検定 | st.bartlett(データ$_1$,$\ldots$) | st.bartlett(A,B,C) |
import numpy as np
Hougakubu = np.array([75, 61, 68, 58, 66, 55, 65, 63])
Bungakubu = np.array([62, 60, 66, 63, 55, 53, 59, 63])
Rigakubu = np.array([65, 60, 78, 52, 59, 66, 73, 64])
Kougakubu = np.array([52, 59, 44, 67, 47, 53, 58, 49])
shidouhou.csvはCSV形式のファイルであり、20人の学生に対し、名前、性別、数学と統計の好き嫌い、心理学テスト、統計テスト1、統計テスト2のそれぞれの成績、そして指導方法の種別のデータが収められている。 このデータを読み込み、統計テスト1の成績が男女の違いで差があるかどうか、また統計の好き嫌いで差があるかどうかをそれぞれ検定せよ。
[参考: csvファイルを読み込む (再掲)</a>] 区切り記号がコンマのcsvファイルを読み込み、その内容をデータフレームとして取り込むには、pandasモジュールのread_csv関数を用いる。
もっとも中にはちゃんと読み込めないこともあるので、値を確認すること。そして読み込めない場合は、区切り記号を適切なものにセットしたりすることも考えよう。
#
import pandas as pd
下は小学校1年生から6年生まで、各学年から5人ずつ無作為抽出し、50メートル走のタイムを計測したデータである。学年による違いがあるか、検討せよ(出典: 長畑(2009)『Rで学ぶ統計学』共立出版)
import numpy as np
Grade1 = np.array([11.3, 12.4, 15.5, 13.6, 12.4])
Grade2 = np.array([12.3, 13.4, 11.5, 11.6, 10.8])
Grade3 = np.array([9.2, 10.4, 8.6, 9.8, 10.7])
Grade4 = np.array([9.5, 8.4, 9.8, 10.4, 7.8])
Grade5 = np.array([8.4, 7.5, 8.6, 7.9, 8.3])
Grade6 = np.array([7.8, 8.5, 7.6, 7.2, 7.5])
下は3種類の1500ccの乗用車を繰り返し4回走行させ、その1リットルあたりの走行距離(燃費)を計測したものである。 車種による違いがあるか、分散分析せよ。(出典: 長畑(2009)『Rで学ぶ統計学』共立出版)
import numpy as np
A = np.array([8.0, 9.0, 7.0, 6.0])
B = np.array([12.0, 11.0, 13.0, 12.0])
C = np.array([ 9.0, 10.0, 9.0, 8.0])