PyLearnST 06: t検定によるデータ分析

二つの集団があったとして、特定の変数に差が生じているのかどうかを調べることは、仮説検証型の研究の基本です。例えば新しい教育手法を開発したとして、その教育効果があるのか、ないのかを検証するためには、一般的な教育手法を適用したグループ、新しい教育手法を適用したグループを用意し、学力の増減の平均値を2グループで算出し、その差を比較します。ここで、新しい教育手法を適用したグループの方が、学力の上昇量の平均値が高かったとき、その教育手法は一般的な方法よりも優れていることになります。

ところで、平均値同士を比較し、高い低いだけを論じることは本当に適切でしょうか。残念ながら、科学の世界では平均を比較するだけでは、その結果は信用のあるものとして受け入れられません。平均値同士の差がどの程度信頼できるのか、その結果も添えてあげる必要があります。平均値の差に対する信頼性として、多くの場合、t検定によるp値を用いることが普通です。p値は(あくまで簡単にいうと)「偶然そのような差が生じた確率」という意味があります(厳密には、自分で勉強するといいと思います)。偶然で差が生じた確率ですから、これが小さければ小さいほど「その平均値の差は、信頼できる」ことを示しています。本節では、t検定によるp値を算出する手法を説明します。


説明用の例として、以下の状況を考えてみます。

  • 中学校の2クラス(A組、B組、50人ずつ、合計100人)に同じ試験問題を解かせ、試験得点を記録
  • A組(50人分)の試験得点: ClassA
  • B組(50人分)の試験得点: ClassB

このデータを以下の乱数により生成します。

In [1]:
import numpy as np

# 毎回同じ乱数を生成するためのシード設定
np.random.seed(1)

# 乱数データの生成
ClassA=[]
ClassB=[]
for i in range(0,50):
    ClassA.append(np.random.normal(40, 20))
    ClassB.append(np.random.rand()*90)

この2クラスには、平均点に差が生じているでしょうか。ひとまず、平均と分散を計算してみます。

In [2]:
MeanClassA=np.mean(ClassA)
MeanClassB=np.mean(ClassB)
VarClassA=np.var(ClassA)
VarClassB=np.var(ClassB)
print("A組の平均値: ", round(MeanClassA, 2))
print("B組の平均値: ", round(MeanClassB, 2))
print("A組の分散: ", round(VarClassA, 2))
print("B組の分散: ", round(VarClassB, 2))
A組の平均値:  40.53
B組の平均値:  45.16
A組の分散:  351.01
B組の分散:  744.37

B組の方が5点くらい平均点が高いようです。ということは、B組の教師の方が優れた教育をしているのでしょうか。この5点の差が信頼できるものか、ヒストグラムを見てみます。

In [4]:
import matplotlib.pyplot as plt
plt.hist(x=[ClassA, ClassB], bins=20, rwidth=0.9,
         color=['red', 'blue'], label=['Class A', 'Class B'])
plt.xlabel("Academic Score")
plt.ylabel("Number of Students")
plt.title("histogram")
plt.legend()
plt.grid(True)

確かにB組の方が点数が高い人が多いです。しかし、点数が低い人も多いように見えます。A組とB組に生じていた平均の差が、信頼できるものか、t検定で調べてみます。このために、scipyのttestを使用します。

In [5]:
import scipy.stats as st
DefRes = st.ttest_ind(ClassA, ClassB, equal_var=False)
print("A組とB組の平均点の差をt検定により比較した結果、p値は", round(DefRes[1],3), "でした。")
A組とB組の平均点の差をt検定により比較した結果、p値は 0.33 でした。

p値が0.33、つまり、33.0%であることがわかりました。荒っぽくいうと、偶然その差が生じた可能性が33.0%ということです。低い感じはしますが、t検定のp値は5%未満(つまり、$p < 0.05$)のときに差があると解釈することが一般的です。 p値が0.05を上回っているので、信頼を持って差が生じているとは言えません。これは「A組とB組の平均点には差がある!」という現象は信用できません、ということを示唆しています。このような結果が出たときは、論文や報告書に以下のように記載します(ただの一例です。別の書き方もあります)。

試験得点の平均点を算出した結果、A組は40.53点、B組は45.16点であった。この結果をt検定で比較した結果、統計的に有意な差は認められなかった($p=0.33$)。

自分が作り出した新たな薬とか、治療方法とか、教育方法とか...が有効だと示したい場合には、それを使用した集団と使用していない集団で効果を比較し、p値が0.05を下回る必要があります。ただ、5%という基準には、科学的な妥当性はありません。ただ形式的によく使われるというだけです。

状況によって、p値の閾値は変更する場合もあります。基本的には、差が信頼できるほどp値が下がりますので、ゆるい比較をしたいときには、p値の基準を0.10(10%水準有意)とする場合もあります。逆に、医療現場などシビアな状況では、効果がないものを誤って効果があると言ってしまっては大変なので、p値の基準を0.01(1%水準有意)とする場合もあります。これは問題によりけりです。石を拾うけれど宝石を取り逃がしたくない場合には基準を緩くし、きれいな宝石だけをとりたい場合にはp値の基準を厳しくします。とはいえ、形式に倣うことも大事なことです。緩くする場合も、せいぜい10%水準くらいを限度にしてください。

色々な種類のt検定

t検定は、状況に応じてパラメータを変えます。基本的には以下の3種類の中から選択します。間違えないように、正しく読み取ってください。import scipy.stats as stを記載してから、以下のように記述するとp値が返ってきます。

  • 比較したい2群の分散が等しくないときのt検定( ← 先ほど使用しました

    st.ttest_ind(Dat01, Dat02, equal_var = False)

  • 比較したい2群の分散が等しいときのt検定

    st.ttest_ind(Dat01, Dat02, equal_var = True)

  • 比較したい2群が同じ対象のときのt検定

    st.ttest_rel(Dat01, Dat02)

上2つは異なる集団の平均値を比較したいとき、例えば、中学校1年A組と1年B組(異なる生徒)の試験得点の平均値を比較する、などです。この2つは、その集団同士の分散が等しいかどうかで設定する引数を変えます。分散を計算し、あまりにも離れていれば「分散が等しくない」と判断してください(「分散が等しい?」という検定もありますが、正規性を仮定するので、結局は主観的な閾値問題に落ち着くことが多いです。個人的には、わざわざやる必要性は感じません。。。)。上の例では、分散に2倍近い差があったので、equal_varにFalseを与えています。

一番下は同じ集団の平均値を比較したいとき、例えば、ある特別な教育を施し、同じ生徒について、施す前と施した後の学力を比較する、などです。

比較したい2群の分散が等しいときのt検定

1番目はすでに上の例でやったので、2番目の例を以下に示します。今度は分散が等しい場合です。乱数生成部分はコピペでokです。

In [6]:
# 毎回同じ乱数を生成するためのシード設定
np.random.seed(9)

# 乱数データの生成
ClassC=[]
ClassD=[]
for i in range(0,50):
    ClassC.append(np.random.normal(40, 20))
    ClassD.append(np.random.normal(50, 20))
    
# 平均値・分散の算出
MeanClassC=np.mean(ClassC)
MeanClassD=np.mean(ClassD)
VarClassC=np.var(ClassC)
VarClassD=np.var(ClassD)
print("C組の平均値: ", round(MeanClassC, 2))
print("D組の平均値: ", round(MeanClassD, 2))
print("C組の分散: ", round(VarClassC, 2))
print("D組の分散: ", round(VarClassD, 2))
C組の平均値:  39.01
D組の平均値:  50.47
C組の分散:  457.95
D組の分散:  414.99

10点くらい平均点に差が生じていますね。分散を見ると、C組の方がやや高いですが、同じくらいです。分散が等しいt検定(2番目)を使用して、p値を算出します。

In [7]:
DefResCD = st.ttest_ind(ClassC, ClassD, equal_var=True) # <- 分散が等しいので、True
print("C組とD組の平均点の差をt検定により比較した結果、p値は", round(DefResCD[1],3), "でした。")
C組とD組の平均点の差をt検定により比較した結果、p値は 0.008 でした。

p値が小さな値を示しました。0.05を下回り、0.01も下回っています。このような場合は、以下のように報告しましょう。

試験得点の平均点を算出した結果、C組は39.01点、D組は50.47点であった。この差をt検定により検証した結果、1%水準で有意な差が認められた($p=0.008$)。

1%水準の優位差なので、かなりの信頼性で差がありますよ、ということを意味します。D組にすごく優秀な教師がいたのかもしれません(あるいは元々勉強ができる生徒が集まっていたとか)。

比較したい2群が同じ対象のときのt検定

次に、3番目のt検定の例を示します。今度は、2集団が同じ人たちである必要があります。例として、教育実施前のE組と、教育実施後のE組の試験得点の比較を考えてみます。事前と事後で学力が変化したか調べたい、というイメージです。事前・事後の比較なので、同じ生徒でなければいけません。こういうときに3番目のt検定を使用します。これをイメージした乱数を生成します。ClassE_beforが事前、ClassE_afterが事後の得点だと考えてください。乱数生成の部分は未解説なのでコピペでいいです。

In [8]:
# 毎回同じ乱数を生成するためのシード設定
np.random.seed(9)

# 乱数データの生成
ClassE_befor=[]
ClassE_after=[]
for i in range(0,50):
    ClassE_befor.append(np.random.normal(60, 20))
    ClassE_after.append(np.random.normal(80, 20))
    
# 平均値の算出
MeanClassE_befor=np.mean(ClassE_befor)
MeanClassE_after=np.mean(ClassE_after)
print("事前の平均値: ", round(MeanClassE_befor, 2))
print("事後の平均値: ", round(MeanClassE_after, 2))

# t検定の実施
DefResE = st.ttest_rel(ClassE_befor, ClassE_after) # <- 同じ対象の比較なので、relを使用
print("E組の事前・事後の差をt検定により比較した結果、p値は", round(DefResE[1],5), "でした。")
事前の平均値:  59.01
事後の平均値:  80.47
E組の事前・事後の差をt検定により比較した結果、p値は 0.0 でした。

roundで5桁を指定して0ということは、p値を小数点以下5桁まで見た結果、全部ゼロでしたということです(p=0.00000...)。文句なしで有意な差が生じている、ということになります。

両側検定と片側検定

t検定には、両側検定と片側検定の2つがあります。それぞれ、以下の意味を持ちます。

  • 両側検定: 集団Aと集団Bに差がある、と主張したいとき
  • 片側検定: 集団Aよりも集団Bの方が高い、低い、を主張したいとき

今までは両側検定を実施していました。つまり、どちらが高いか低いかではなく、差があるかどうかだけを調べていたことになります。「C組とD組の試験得点には差がある」というのが両側検定で主張できることで、「C組よりもD組の方が試験得点が高い」というのが片側検定で主張できることです。片側検定のp値は、両側検定のp値を半分にすれば良いだけです。0.5をかければokです。上で計算したC組とD組で、両側検定と片側検定をしてみます(両側検定は先ほどやりましたが、もう一度やります)。

In [9]:
DefResCD = st.ttest_ind(ClassC, ClassD, equal_var=True) # <- 分散が等しいので、True
print("C組とD組の平均点の差をt検定により比較した結果、両側検定のp値は", round(DefResCD[1], 3), "でした。")
print("C組とD組の平均点の差をt検定により比較した結果、片側検定のp値は", round(DefResCD[1]*0.5, 3), "でした。")
C組とD組の平均点の差をt検定により比較した結果、両側検定のp値は 0.008 でした。
C組とD組の平均点の差をt検定により比較した結果、片側検定のp値は 0.004 でした。

ここで言えることは、以下の2つです。

試験得点の平均点を算出した結果、C組は39.01点、D組は50.47点であった。 1%水準で有意な差が認められた($p=0.008$)。 また、1%水準で有意にD組の方が高いことが確認された($p=0.004$)。

差があるだけではなくて、高い、低い、ということも主張できるようになります。当然のことながら、片側検定の方が有意差が生じやすくなります。

以下、確認事項です。

  • 2集団の平均値の差を比較したいときに使用する分析方法はわかりますか?
  • 分散が等しいとき、等しくないときに使用するパラメータはわかりますか?
  • 同一対象の2時点の差を比較したいとき、どうすれば良いかわかりますか?
  • 片側検定と両側検定を適切に使い分けられますか?

全部わかればokです。平均値同士を比較して差がある、ないを主張したい場合には、平均値だけではなく、p値も添えることを忘れないようにしてくださいね。