1. 程式人生 > >卡方檢驗值轉換為P值

卡方檢驗值轉換為P值

卡方檢驗作為一種常見的假設檢驗,在統計學中的地位是顯而易見的,如果你還不太清楚可以參看這篇博文:卡方檢驗用於特徵選擇,寫的非常的淺顯易懂,如果你還想再擴充套件點卡方檢驗方面的知識,可以參看這篇博文卡方檢驗基礎,寫的也很有意思。前輩的功底都很深厚,小弟就就不再闡述卡方檢驗的原理、意義及如何計算了,理解了其實很簡單就那麼個公式,再根據實際業務場景關鍵看你選擇哪一個。從chi-squared value 到p-value,相信大多數同學和我一樣,查表,因為大學課本上就是這麼寫的。假如在實際業務場景中,自由度和顯著性水準都不確定的情況下,怎麼辦呢?查表就顯得不那麼地道了。

這時可能很多同學想到了著名的fisher精確檢驗,因為這個檢驗能直接求出的精確的p-value,但是在檢驗資料樣本比較大的情況下,fisher精確檢驗的計算複雜度會讓你顯得那麼的力不從心,本系列的後面將會講到fisher精確檢驗的原理並給出其原始碼及與chi-squared的效率對比。還是抓緊時間侃侃怎麼通過chi-squared計算p-value吧,估計心急的同學就等不及了。ok,咱們攻城師還是用程式碼說話,先上程式碼:

public double chisqr2pValue(int dof, double chi_squared) {
        if (chi_squared < 0 || dof < 1) {
            return 0.0;
        }
        double k = ((double) dof) * 0.5;
        double v = chi_squared * 0.5;
        if (dof == 2) {
            return Math.exp(-1.0 * v);
        }
        double incompleteGamma = IncompleteGamma.log_igf(k,v);
        // 如果過小或者非數值或者無窮
        if (Math.exp(incompleteGamma) <= 1e-8
               || Double.isNaN(Math.exp(incompleteGamma))
               || Double.isInfinite(Math.exp(incompleteGamma))) {
            return 1e-14;
        }
        double gamma = Math.log(Gamma.getApproxGamma(k));
       incompleteGamma -= gamma;
        if(Math.exp(incompleteGamma) > 1){
            return 1e-14;
        }
        double pValue = 1.0 - Math.exp(incompleteGamma);
        return (double) pValue;
}

這個chisqr2pValue函式引用到了兩個函式,一個為getApproxGamma(k) 一個為log_igf(k,v),如果你對該函式的實現原理不太清楚,wiki中侃的很清楚:卡方分佈,本文也不再闡述卡方分佈的密度函式、自由度等等;具體求P-value說白了就是計算卡方分佈的分佈函式值,如下的公式:


其中,分子為不完全伽馬函式,分母為伽馬函式;那麼上述chisqu2pvalue就是實現了上述公式。Ok,現在的問題轉換為怎麼求分子與分母。

在兩年前我曾為了實現伽馬函式的功能程式碼敲個積分,但是效果不太理想。但,今天發現個利器:斯特靈公式,估計大多數同學跟我一樣,第一感覺斯特靈公式不是用來求階乘的嗎?不錯,確實是,在實現fisher時我也用到了斯特靈公式;可是哥們確實有點孤陋寡聞,斯特靈還有個求伽馬函式的近似公式,如下:


如果你想了解詳細的斯特靈公式的推導資訊,也是參考wiki:斯特林公式 是不是覺得維基太牛逼了?好吧,還是趕緊上程式碼:

public static double getApproxGamma(double n) {
        // RECIP_E = (E^-1) = (1.0 / E)
        double RECIP_E = 0.36787944117144232159552377016147;
        // TWOPI = 2.0 * PI
        double TWOPI = 6.283185307179586476925286766559;
        double d = 1.0 / (10.0 * n);
        d = 1.0 / ((12* n) - d);
        d = (d + n) *RECIP_E;
        d = Math.pow(d,n);
        d *= Math.sqrt(TWOPI/ n);
        return d;
    }


好,剩下的就差不完全伽馬函式沒有解了,怎麼求?問大神wiki:不完全伽馬函式 哎呦,在裡面又發現個牛逼的不用計算積分的公式:


而M函式為什麼呢?就是傳聞中的合連幾何函式,如果對這個函式感興趣的可以繼續參考wiki:合連幾何函式 ,不過這裡我們依舊只使用它的一個公式:


Ok,公式知道了,程式碼實現就很簡單了,只不過在這裡為了便於大數的計算,我採用了計算其log值,程式碼如下:

 public static double log_igf(double s, double z) {
        if (z < 0.0) {
            return 0.0;
        }
        double sc = (Math.log(z) * s) - z - Math.log(s);
        double k = KM(s, z);
        return Math.log(k) + sc;
    }
   
    private static double KM(double s, double z) {
        double sum = 1.0;
        double nom = 1.0;
        double denom = 1.0;
        double log_nom = Math.log(nom);
        double log_denom = Math.log(denom);
        double log_s = Math.log(s);
        double log_z = Math.log(z);
        for (int i = 0; i < 1000; ++i) {
           log_nom += log_z;
           s++;
           log_s = Math.log(s);
           log_denom += log_s;
            double log_sum = log_nom - log_denom;
           sum += Math.exp(log_sum);
        }
        return sum;
    }

Ok,簡單吧?!以後你再也不用坑爹地查表了。下一篇再介紹下fisher精確檢驗吧。