静粛に、只今統計勉強中

仕事でデータ分析をすることになったバリバリ文系アラフィフのおっさんが、独学で統計の勉強を始めました。

Excel VBAでフィッシャーの正確検定ができる関数を作ってみた1

Excelで学ぶコレスポンデンス分析』の付録に、フィッシャーの正確検定の解説があります。

曰く、

「2行2列でなおかつ期待度数が5未満のマス目が存在するクロス表」において「2変数が関連しているかどうか」を検定したい場合は、独立性の検定よりもフィッシャーの正確検定のほうが適しているといわれています。
Excelで学ぶコレスポンデンス分析』高橋信(オーム社)198ページ

とのこと。 

これは、カイ二乗検定で信頼性のある結果を得るためには、

  • サンプルサイズが20を下回らない
  • 期待度数が5未満のマス目が20%以上にならない

というコクラン・ルールを、2×2表という条件下で説明したもののようです。

よって、3行以上または3列以上であっても、このルールに抵触してしまう場合は、カイ二乗検定を使うことは適切ではない、ということになります。

フィッシャーの正確検定も、2×2表に限らず、3行以上または3列以上であってもP値を求めることができるそうです *1 が、行または列が増えるごとに計算工程が膨大になっていくせいか、市販の統計ソフトであっても2×2表のみ可としていたりします。

 

そこで今回は、Excel VBAで2×2表専用のフィッシャーの正確検定ができる関数を作ってみました。

 

ソースコード

'フィッシャーの正確検定(2×2表専用)
Function Exact_Test(参照, Optional 検定の指定)

    Dim Acu, Min, Std, Tmp, Arr(1 To 4)
    Dim i, j, k, l

    If IsMissing(検定の指定) Then 検定の指定 = 1

    If 参照.Rows.Count = 2 And 参照.Columns.Count = 2 _
        And (検定の指定 = 1 Or 検定の指定 = 2) Then
        
        For i = 1 To 4
            Arr(i) = 参照(i)
        Next
        
        With Application.WorksheetFunction
            Min = .Min(Arr)
            For i = 1 To 4
                If Arr(i) = Min Then Exit For
            Next
            Select Case i
                Case 1: j = 2: k = 3: l = 4
                Case 2: j = 1: k = 4: l = 3
                Case 3: j = 1: k = 4: l = 2
                Case 4: j = 2: k = 3: l = 1
            End Select
            
            Std = (.Fact(Arr(1) + Arr(2)) * .Fact(Arr(3) + Arr(4)) _
                    * .Fact(Arr(1) + Arr(3)) * .Fact(Arr(2) + Arr(4))) _
                    / (.Fact(Arr(1)) * .Fact(Arr(2)) * .Fact(Arr(3)) _
                    * .Fact(Arr(4)) * .Fact(.Sum(Arr)))
            Do
                Acu = Acu + (.Fact(Arr(1) + Arr(2)) * .Fact(Arr(3) + Arr(4)) _
                    * .Fact(Arr(1) + Arr(3)) * .Fact(Arr(2) + Arr(4))) _
                    / (.Fact(Arr(1)) * .Fact(Arr(2)) * .Fact(Arr(3)) _
                    * .Fact(Arr(4)) * .Fact(.Sum(Arr)))
                If Arr(i) = 0 Then Exit Do
                Arr(i) = Arr(i) - 1
                Arr(j) = Arr(j) + 1
                Arr(k) = Arr(k) + 1
                Arr(l) = Arr(l) - 1
            Loop
            If 検定の指定 = 2 Then
                If Arr(j) < Arr(k) Then
                    Tmp = Arr(j)
                Else
                    Tmp = Arr(k)
                End If
                Arr(i) = Arr(i) + Tmp
                Arr(j) = Arr(j) - Tmp
                Arr(k) = Arr(k) - Tmp
                Arr(l) = Arr(l) + Tmp
                Do
                    Tmp = (.Fact(Arr(1) + Arr(2)) * .Fact(Arr(3) + Arr(4)) _
                        * .Fact(Arr(1) + Arr(3)) * .Fact(Arr(2) + Arr(4))) _
                        / (.Fact(Arr(1)) * .Fact(Arr(2)) * .Fact(Arr(3)) _
                        * .Fact(Arr(4)) * .Fact(.Sum(Arr)))
                    If Tmp > Std Then Exit Do
                    Acu = Acu + Tmp
                    Arr(i) = Arr(i) - 1
                    Arr(j) = Arr(j) + 1
                    Arr(k) = Arr(k) + 1
                    Arr(l) = Arr(l) - 1
                Loop
            End If
            Exact_Test = Acu
        End With

    Else
        Exact_Test = CVErr(xlErrNA)
    End If

End Function

 

コードの解説

f:id:cyclo-commuter:20180619150905p:plain

f:id:cyclo-commuter:20180619150604p:plain

ここは、ウィキペディアの以下の説明を参考にしました。

p値を求めるには(普通の検定と同じように)実際の観測データよりも極端な場合も含めて考えなければならない。フィッシャーは、そのためには小計が観測値と同じになるような場合だけを考慮すればよいことを示した。今の例では、その考慮に入れるべき場合は次の1つ(男はだれもダイエットしていない)だけである

フィッシャーの正確確率検定 - Wikipedia

f:id:cyclo-commuter:20180619151619p:plain