読者です 読者をやめる 読者になる 読者になる

コーパス間で頻度差の大きい単語を特定する

 複数コーパスにおける単語の出現頻度を比較する場合、比較対象となる語は、分析者によってあらかじめ決められていることが多いでしょう。そのような比較は、分析者の仮説を統計的に検証するアプローチと言えます。しかし、実際の分析では、どの単語に注目すればよいかが事前に分かっているとは限りません。そのような場合は、コーパスに出現する全ての単語の頻度を比較し、コーパス間で頻度の差が大きい単語を探索することがあります。
 頻度差を統計的に比較するには、カイ自乗検定や対数尤度比検定などの検定手法がよく用いられます。以下は、フィッシャーの正確検定を用いた例です。分析データは、金明哲先生が公開しておられる名詞の頻度表を使わせて頂きます。*1

# 分析データ
nouns <- read.csv("http://mjin.doshisha.ac.jp/iwanami/data/AFAmeishi.csv", row.names = 1, header = TRUE)
# 分析データの確認
head(nouns)

 上記のように、読み込んだデータの中身をhead関数で確認すると、以下のような結果が表示されます。

             安倍 福田 麻生
こと<名詞>   27   27   23
国民<名詞>   15   16   22
ため<名詞>   26   13   13
よう<名詞>   13   21    6
国<名詞>     29    4    4
日本<名詞>   23    5    9

 このデータにおける全ての名詞にフィッシャーの正確検定を実行し、p値の小さい(=コーパス間の頻度差が大きい)順で名詞を並び替えてみましょう。まずは、この処理に用いる関数を定義します。*2

# 関数の定義
# フィッシャーの正確検定
fisher.bat <- function(x){
    sum <- apply(x, 2, sum)
    nr <- nrow(x)
    nc <- ncol(x)
    mat <- matrix(0, nr, 1)
    mat2 <- cbind(x, p.value = mat)
    for(i in 1 : nr){
        temp <- rbind(x[i, ], sum - x[i, ])
        mat2[i, nc + 1] <- fisher.test(temp)$p.value
    }
    mat3 <- mat2[sort.list(mat2[, nc + 1]), ]
    list(results = mat3)
}

 次に、定義した関数を実行します。

# 定義した関数の実行
fisher.bat(nouns)

 すると、以下のような結果が得られます。3人の政治家による演説において、最も頻度差の大きい名詞は「わたし」で、2番目は「民主党」、3番目は「国」でした。

$results
                       安倍 福田 麻生      p.value
わたし<名詞>            0    0   24 2.398409e-14
民主党<名詞>            0    0   12 1.636454e-07
国<名詞>               29    4    4 2.746677e-05
OTHERS                  661  430  454 8.228468e-05
もの<名詞>              3   10   16 3.250653e-04
不安<名詞>              0    4    9 3.373269e-04
 (以下省略)

 今回の分析に使ったフィッシャーの正確検定は、頻度表に小さい値(特に0)が多く含まれていても、文字通り「正確」な結果が得られるという長所があります。*3 しかし、フィッシャーの正確検定は、大きな頻度表を分析する場合には計算量が莫大なものとなり、普通のコンピューターでは結果が得られないこともあります。そのような問題に対しては、モンテカルロ・シミュレーションによる近似計算で対応することが可能です。その処理は、先ほどの関数を少し修正することで実行することができます。*4

# 関数の定義
# フィッシャーの正確検定(モンテカルロ・シミュレーションで近似計算)
fisher.mc.bat <- function(x){
    sum <- apply(x, 2, sum)
    nr <- nrow(x)
    nc <- ncol(x)
    mat <- matrix(0, nr, 1)
    mat2 <- cbind(x, p.value = mat)
    for(i in 1 : nr){
        temp <- rbind(x[i, ], sum - x[i, ])
        mat2[i, nc + 1] <- fisher.test(temp, simulate.p.value = TRUE, B = 10000)$p.value
    }
    mat3 <- mat2[sort.list(mat2[, nc + 1]), ]
    list(results = mat3)
}

# 定義した関数の実行
fisher.mc.bat(nouns)

 以下は、得られた結果です。モンテカルロ・シミュレーションを実行しなかった場合と比べて、結果に若干の違いが生じています。

$results
                       安倍 福田 麻生    p.value
国<名詞>               29    4    4 0.00009999
わたし<名詞>            0    0   24 0.00009999
民主党<名詞>            0    0   12 0.00009999
OTHERS                  661  430  454 0.00019998
行政<名詞>              0    9    4 0.00039996
 (以下省略)

(2014年5月16日追記)

 裏RjpWikiの方より、以下のように書いた方が効率的であると教えて頂きました。*5 いつもありがとうございます。

# forを使わずに、applyを使う
fisher.bat2 = function(x) {
    sum = colSums(x)
    y = t(sum - t(x))
    p.values = apply(cbind(x, y), 1, function(temp) fisher.test(matrix(temp, 3))$p.value)
    cbind(x, p.values)[order(p.values), ]
}

# 定義した関数の実行
fisher.bat2(nouns)

*1:このデータは、安倍晋三福田康夫麻生太郎の3氏による所信表明演説文を形態素解析し、名詞のみを集計したものです。

*2:記事を公開した後で、スクリプトを微修正しました。

*3:それに対して、カイ自乗検定では、標本数が小さい場合や、表中の数値の偏りが大きい場合には、検定結果が不正確になると言われています。

*4:具体的には、fisher.test関数の引数でsimulate.p.valueとBを指定しています。

*5:詳しくは、 無駄をなくし,短くすると,速くなる(本当か?)を参照。