F検定(概要とpython実装)

F検定について

対象とする独立な2群の分散が等しい(等分散)であるかを調べる検定。
(下記図参照)


コード

※ scipyモジュールには、 F検定を直接実行するメソッドがないので一つ一つ計算する。

用いるデータは「対応のないデータ」と仮定し、有意水準を0.05帰無仮説を「2群間の分散に差がないこと」とする。

import numpy as np
from scipy import stats
import seaborn as sns

#用いるデータ(対応のないデータと仮定)
A = np.array([6.3, 8.1, 9.4, 10.4, 8.6, 10.5, 10.2, 10.5, 10.0, 8.8])
B = np.array([4.8, 2.1, 5.1, 2.0, 4.0, 1.0, 3.4, 2.7, 5.1, 1.4, 1.6])

#試しにバイオリンプロットを作成
sns.violinplot(x=['A' for i in range(len(A))]+['B' for i in range(len(B))],
y=np.concatenate([A, B]))

#有意水準を0.05、帰無仮説を「2群間の分散に差がないこと」とする。
A_var = np.var(A)
B_var = np.var(B)
A_df = len(A) - 1  # Aの自由度
B_df = len(B) - 1  # Bの自由度

#Fを求める場合は、数値が大きい方を分子にする。
bigger_var = np.max([A_var,B_var])
smaller_var = np.min([A_var,B_var])
f = smaller_var / bigger_var  # F比の値
one_sided_pval1 = stats.f.cdf(f, A_df, B_df)  # 片側検定のp値 1
one_sided_pval2 = stats.f.sf(f, A_df, B_df)   # 片側検定のp値 2
two_sided_pval = min(one_sided_pval1, one_sided_pval2) * 2  # 両側検定のp値
print('F:       ', round(f, 3))
print('p-value: ', round(two_sided_pval, 3))
# >>> F: 0.771
# >>> p-value: 0.707

p > 0.05なので帰無仮説は棄却されず、2群間の分散に差があると言えない

バイオリンプロット


検定の方法詳細

手順

  1. 仮説の設定
    帰無仮説(H0):「2群間の分散に差がない(等分散である)」と仮定する。
    対立仮説(H1):「2群間の分散に差がある(等分散でない)」と仮定する。
  2. 有意水準$\alpha$を設定する。
  3. 統計検定量$F$を求める。
    このときのFは、自由度が(分子の自由度, 分母の自由度)のF分布に従う。
  4. 以下の二通りのどちらかでp値を求め、判定する。

 

  • F分布から直接p値を算出する。(上のコードはこの方法)
    p > α:帰無仮説は棄却できない(=等分散である)
    p < α:帰無仮説を棄却する(不等分散である)
  • F分布表からを求める。
    1≦F≦Fα:P>0.05となり帰無仮説は棄却できない(=等分散である)
    F>Fα:P<0.05となり帰無仮説を棄却する(不等分散である)

$$ F = \frac{S_1^2}{S_2^2}  $$

詳細コード

以下の2パターンで検証した

  1. F値が棄却域に入るか否かで判定
  2. F値からp値を求めて判定

1. F値が棄却域に入るか否かで判定

import matplotlib.pyplot as plt

#自由度と先ほど求めたf値
A_df = 9
B_df = 10
f = 0.771

# x軸の等差数列を生成
X = np.linspace(start=0, stop=5, num=100)

# f分布を作成
f_sf = stats.f.pdf(x=X, dfn=A_df, dfd=B_df)

#パーセント点関数(ppf)を用いて、α=0.05の棄却域の境界の値を計算する
upper_005 = stats.f.ppf(q=0.95, dfn=A_df, dfd=B_df)
lower_005 = stats.f.ppf(q=0.05, dfn=A_df, dfd=B_df)
X_lower = np.linspace(start=0, stop=lower_005, num=100)
X_upper = np.linspace(start=upper_005, stop=5, num=100)
f_lower = stats.f.pdf(x=X_lower, dfn=A_df, dfd=B_df)
f_upper = stats.f.pdf(x=X_upper, dfn=A_df, dfd=B_df)
base = np.zeros(100)

# 可視化
#F分布を描画
plt.plot(X, f_sf)

#棄却域を塗りつぶす
plt.fill_between(X_lower, base, f_lower, facecolor='lime', alpha=0.5)
plt.fill_between(X_upper, base, f_upper, facecolor='lime', alpha=0.5)

#求めたF値をプロット
plt.plot(f, 0.01, 's',label='F value')
plt.title('F distribution({},{})'.format(A_df,B_df), fontsize=15)
plt.ylim(0,0.8)
plt.legend(fontsize=15)
plt.show()
print('下限点:',round(lower_005,3))
print('上限点:',round(upper_005,3))
"""output
下限点: 0.319
上限点: 3.02
"""


求めたF値(0.771)が棄却域(0.319〜3.02 | 黄緑)に入っていないので帰無仮説は棄却されず、2群間の分散に差があると言えない

2. F値からp値を求めて判定

import matplotlib.pyplot as plt

#自由度と先ほど求めたf値
A_df = 9
B_df = 10
f = 0.771

# x軸の等差数列を生成
X = np.linspace(start=0, stop=5, num=100)

#F分布を作成
f_sf = stats.f.pdf(x=X, dfn=A_df, dfd=B_df)

#累積確率関数(cdf)と生存関数(sf)でp値を求める。
one_sided_pval1 = stats.f.cdf(f, A_df, B_df)
one_sided_pval2 = stats.f.sf(f, A_df, B_df)
X_lower = np.linspace(start=0, stop=f, num=100)
X_upper = np.linspace(start=f, stop=5, num=100)
f_lower = stats.f.pdf(x=X_lower, dfn=A_df, dfd=B_df)
f_upper = stats.f.pdf(x=X_upper, dfn=A_df, dfd=B_df)
base = np.zeros(100)

# 可視化
#F分布を描画
plt.plot(X, f_sf)
#F値の両側を塗りつぶす

plt.fill_between(X_lower, base, f_lower, facecolor='lime', alpha=0.5)
plt.fill_between(X_upper, base, f_upper, facecolor='yellow', alpha=0.5)

#求めたF値をプロット
plt.plot(f, 0.01, 's',label='F value')
plt.title('F distribution({},{})'.format(A_df,B_df), fontsize=15)
plt.ylim(0,0.8)
plt.legend(fontsize=15)
plt.show()
print('p-value(lower side):',round(one_sided_pval1,3))
print('p-value(upper side):',round(one_sided_pval2,3))
"""output
p-value(lower side): 0.353
p-value(upper side): 0.647
"""


求めたp値(両側からF値までのそれぞれの面積 | 0.353と0.647 | 黄緑と黄)が、有意水準 0.05よりも大きいので、帰無仮説は棄却されず2群間の分散に差があるとは言えない

参考