卡方檢驗是一種用途很廣的計數資料的假設檢驗方法。它屬於非參數檢驗的範疇,主要是比較兩個及兩個以上樣本率( 構成比)以及兩個分類變量的關聯性分析。其根本思想就是在於比較理論頻數和實際頻數的吻合程度或擬合優度問題。(更多參考:卡方檢驗、卡方分布)
不講過多理論,主要使用 python 實現卡方驗證。之前對於元素/特徵/屬性 異常值的選擇情況,可以使用直方圖、箱型圖、Z分數法等篩選。如 Python 探索性數據分析(Exploratory Data Analysis,EDA) ,數據探索的同時,也可以排除單個變量的異常值。而對於離散屬性(或離散化)的分類,可以使用 等距分類、等頻分類 等,但是這樣分類不能體現出與其他屬性或結果的相關性。
當前則使用卡方驗證來將屬性分類。如客戶的婚姻情況對於貸款後是否回款還是有影響的,所以當前校驗離散值怎麼分類更好。現在有一組數據,客戶的「婚姻情況」,更複雜點計算的話,「婚姻+年齡+性別」 相互作用還是比較強的,可同時將這幾個變量計算。此處只是有 婚姻情況測試。這些數據是歷史數據,有點客戶已經回款,有的未回款。
但是這些數據哪些是異常值,這些異常值是刪除,還是歸到其他類中呢?
首先進行粗分類,觀測他們的佔比情況:
表1,基於客戶婚姻情況的粗分類
比值較近的可以劃分為同一類,如把 「再婚」 歸為 「已婚」 ,「初婚」 歸為 「未婚」 。這時機器的算法,當然我們可以評經驗來分類 ,「再婚」、「初婚」 、「復婚」 其實都屬於「已婚」。
表2,該表格為各婚姻情況客戶是否回款的頻數
每個類別都是相互獨立,沒有交叉的,現在用另一種方法計算。假設是否回款的客戶的分布與總體保持一致。計算「已婚未回款的客戶數」 = 4124x5498/12405 = 1826(以所在行列的匯總值計算),最終結果如下:
表3,該表格為各婚姻情況客戶是否回款的期望頻數分布(理論推算值)
兩個表的數字差距越大,兩個表的獨立性越強,就表示兩個表的依賴度越高、粗分類結果越好。現在使用計算卡方距離:
Ai為i水平的實際的觀察頻數,Ei為i水平的期望頻數。代入公式:
這就是卡方值,再計算卡方的 自由度 v:
v=(行數-1)(列數-1)=(2-1)(3-1) = 2
表4,卡方臨界值表部分數據
卡方臨界值 為 (一般取 p=0.05):
該臨界值小於實驗中的卡方值 23,差異明顯,拒絕0假設。
使用 python 腳本實現:
# -*- coding: UTF-8 -*-# python 3.5.0# 卡方計算__author__ = 'HZC'import mathimport sqlalchemyimport numpy as npimport pandas as pdclass CHISQUARE: def __init__(self,d): self.engine = sqlalchemy.create_engine("mssql+pymssql://%s:%s@%s/%s" %(d['user'],d['pwd'],d['ins'],d['db'])) def get_df_from_query(self,sql): df = pd.read_sql_query(sql, self.engine) return df def get_variance(self,df): row_count = df.shape[0]-1 col_count = df.shape[1]-1 v = (row_count-1)*(col_count-1) return v #轉為矩陣求卡方距離 def get_chi_square_value(self,df1,df2): df1 = df1.drop(['col_total']) df2 = df2.drop(['col_total']) del df1['row_total'] del df2['row_total'] mtr1 = df1.astype(int).as_matrix() mtr2 = df2.astype(int).as_matrix() mtr = ((mtr1-mtr2)**2)/mtr2 return mtr.sum() #分類頻數 def get_classification(self,table_name,col_result,col_pred): sql = "select %s,%s from %s" % (col_result,col_pred,table_name) df = self.get_df_from_query(sql) df = df.groupby([col_result,col_pred]).agg({col_result:['count']}) df = df.reset_index() df.columns = [col_result,col_pred, 'count'] df = pd.pivot_table(df, values = 'count', index=col_result, columns = col_pred).reset_index() df['row_total'] = df.sum(axis=1) df.set_index(col_result, inplace=True) df.loc['ratio(%)'] = df.loc[0]*100/df.loc[1] print("==========================================================") print("原始數據粗分類:(百分比相近的可劃分為同一類)") print("==========================================================") print(df.astype(int)) df = df.drop(['ratio(%)']) df.loc['col_total']=df.sum(axis=0) print("==========================================================") print("分類頻數匯總:(實際值)") print("==========================================================") print(df.astype(int)) df2 = df.copy() total = df2[['row_total']].loc[['col_total']].values[0][0] for col in df2: df2[col] = df2[[col]].loc[['col_total']].values[0][0] * df2['row_total']/total df2 = df2.drop(['col_total']) df2.loc['col_total']=df2.sum(axis=0) print("==========================================================") print("期望頻數分布:(理論推算值)") print("與上表差距越大,兩表的獨立性越低、依存度越高,粗分類效果越好") print("==========================================================") print(df2.astype(int)) print("==========================================================") x = self.get_chi_square_value(df,df2)#順序:(實際df,推算df) v = self.get_variance(df2) # v=(行數-1)(列數-1) print("卡方值:χ2 = %s" % x) print("自由度:v = %s" % v) print("==========================================================")if __name__ == "__main__": conn = {'user':'用戶名','pwd':'密碼','ins':'實例','db':'資料庫'} cs = CHISQUARE(conn) cs.get_classification("V_ClientInfoAll","是否回款","婚姻狀況") #cs.get_classification(表或視圖,回歸只/判斷值,"分類元素")
只可分析兩個變量之間的關係值,輸出結果如下: