本文先从统计基础的卡方分布、卡方检验说起,之后再到卡方分箱的理解就比较容易,最后是利用Python如何实现卡方分箱。
1.卡方分布
- 定义: 设随机变量 X 1 X_1 X1, X 2 X_2 X2, X 3 X_3 X3,…, X n X_n Xn相互独立,且 X i X_i Xi(i=1,2,3,…,n)服从标准正态分布N(0,1),则他们的平方和 ∑ X i 2 \sum X_i^2 ∑Xi2服从自由度为n的 χ 2 \chi^2 χ2分布。
-
χ
2
\chi^2
χ2分布示意图
- 如上图,df自由度越小,分布就越向左倾斜,随着自由度逐渐增大, χ 2 \chi^2 χ2分布趋近于正态分布。
2.卡方统计量
χ 2 \chi^2 χ2统计量:
χ 2 = ∑ ( f 0 − f e ) 2 f e \chi^2=\sum\frac{(f_0-f_e)^2}{f_e} χ2=∑fe(f0−fe)2
f 0 f_0 f0表示观测值频数, f e f_e fe表示期望值频数
- χ 2 \chi^2 χ2统计量描述了观察值与期望值的接近程度,两者越接近,即 ( f 0 − f e ) (f_0-f_e) (f0−fe)的绝对值越小,计算的 χ 2 \chi^2 χ2值就越小;反之,则 χ 2 \chi^2 χ2越大。
3.卡方检验
-
χ 2 \chi^2 χ2检验是对分类数据的频数进行分析的统计方法;用于分析分类变量与分类变量之间的关系(相关程度)
-
通过对 χ 2 \chi^2 χ2的计算结果与 χ 2 \chi^2 χ2分布中的临界值进行比较,做出是否拒绝原假设的统计决策。
-
χ 2 \chi^2 χ2检验分为拟合优度检验和独立性检验
3.1 拟合优度检验
- 定义:拟合优度检验是对一个分类变量的检验,即根据总体的分布情况,计算出分类变量中各分类的期望频数,与分布的观测频数进行对比,判断期望频数与观察频数是否有显著差异。
- 举例:泰坦尼克号例子中,我们关注存活情况与性别是否有关系。
船上共有2208人,其中男性1738人,女性470人;幸存者718人,其中男性374人女性344人,以 α \alpha α=0.1的显著性水平检验存活状况是否与性别相关。
解:
1、提出假设:
H 0 H_0 H0:观察频数与期望频数一致
H 1 H_1 H1:观察频数与期望频数不一致
2、根据观察频数计算期望频数:
总体存活率为718/2208=0.325,如果是否活下与性别无关,那么1738名男性中应该存活的人数为17380.325=565人,在470位女性中应存活4700.325=153人。故565和153就是期望频数,实际存活数就是观察频数。
3、计算卡方统计量:
4、根据自由度和显著性水平,查找卡方分布表临界值,并与上一步 χ 2 \chi^2 χ2值进行比较,做出接受或拒接原假设的决策。
自由度df=R-1=1,R为分类变量类型的个数。 显著性水平为0.1, 查表得 χ 0.1 2 ( 1 ) \chi^2_{0.1}(1) χ0.12(1)=2.706;
所以 χ 2 \chi^2 χ2远大于 χ 0.1 2 ( 1 ) \chi^2_{0.1}(1) χ0.12(1),拒绝 H 0 H_0 H0,接受 H 1 H_1 H1,说明存活情况与性别有关。
3.2 列联分析:独立性检验
-
定义:独立性检验对两个分类变量的检验,分析过程通过列联表(contingency table)方式呈现,实际就转换为分析列联表中行变量与列变量是否相互独立(或有关联)。
-
举例:一种原料来自三个不同地区,原料质量又被分成三个不同等级,从这批原料中随机抽取500个检验,如下表;那么检验地区和等级直接是否存在依赖关系。
解:
1、提出假设:
H 0 H_0 H0:地区和原料等级之间是独立的
H 1 H_1 H1:地区和原料等级之间是不独立的(存在依赖关系)2、根据观察频数计算期望频数:
- 以列联表表第一行第一列(甲,一级)为例,甲地区的合计为140,用140/500作为甲地区的原料比例估计值;一级原料的合计为162,用162/500作为一级原料的比例的估计值。如果地区与原料等级独立,那么第一行第一列单元格的期望值比例为:
(140/500)x(162/500)= 0.09072,故相应的频数为 0.09072 x 500 = 45.36。
- 以列联表表第一行第一列(甲,一级)为例,甲地区的合计为140,用140/500作为甲地区的原料比例估计值;一级原料的合计为162,用162/500作为一级原料的比例的估计值。如果地区与原料等级独立,那么第一行第一列单元格的期望值比例为:
-
推广,可采用以下方式计算任一单元格的期望频数:
f e f_e fe = R T n \frac{RT}{n} nRT * C T n \frac{CT}{n} nCT * n n n = C T ∗ R T n \frac{CT *RT}{n} nCT∗RT
RT为给定单元格所在行的合计,CT为给定单元格所在列的统计,n为观测值的个数,即样本量。
3. 计算卡方统计量
4、根据自由度和显著性水平,查找卡方分布表临界值,并与上一步 χ 2 \chi^2 χ2值进行比较,做出接受或拒接原假设的决策。
自由度df=(R-1)(C-1)=4,显著性水平取0.05,查看可知 χ 0.05 2 ( 4 ) \chi^2_{0.05}(4) χ0.052(4)=9.488;
所以 χ 2 \chi^2 χ2远大于 χ 0.05 2 ( 4 ) \chi^2_{0.05}(4) χ0.052(4),拒绝 H 0 H_0 H0,接受 H 1 H_1 H1,说明地区和原料等级存在依赖关系,即原料等级受地区的影响。
4. 卡方分箱原理及流程
卡方分箱是基于卡方检验中第二种列联独立性检验原理进行分箱。
以下是截取卡方分箱论文中分箱的算法:
-
上诉算法理解分为初始化和自底向上的合并过程
-
初始化:首先根据连续变量的值的大小排序,进行初始的离散处理
-
合并:
箱子合并过程分为两个步骤,连续重复进行:
1. 计算每个相邻箱子的 χ 2 \chi^2 χ2值
2. 对低卡方值的相邻箱子进行合并
(根据卡方检验原理可知卡方值越低,表明两个类别越独立,相互影响的程度越小;或者另一种理解是两箱分布相似,可以进行合并。)合并停止条件:
- 直到所有相邻箱子的
χ
2
\chi^2
χ2值大于等于设置的
χ
2
\chi^2
χ2阈值
(根据自由度和显著性水平选取合适的 χ 2 \chi^2 χ2阈值;自由度则是根据数据能够确定的为(R-1)x(C-1),因为都是计算相邻两箱的,故R=2;C也可根据数据情况确定。显著性水平推荐选择0.1,0.05,0.01。) - 或者,箱子数量达到预先设置的数量
(可以设置最小分箱数和最大分箱数,推荐最大分箱数在10-15间)
- 直到所有相邻箱子的
χ
2
\chi^2
χ2值大于等于设置的
χ
2
\chi^2
χ2阈值
-
-
公式的理解
与卡方检验中列联独立性检验的 χ 2 \chi^2 χ2值的计算是相同的
A i j A_{ij} Aij就是 f 0 f_0 f0, E i j E_{ij} Eij就是 f e f_e fe
k就是列的分类变量类型个数
m=2(只计算行相邻两箱)
具体也可看下面示例:
ps:注意每两箱计算时列的合计数是不同的,只使用两箱的列合计数,而不是全部的列合计数,行合计数不受影响。样本数量也只使用两箱的样本数量。
5.利用Python实现卡方分箱
def chimerge(data,col,target,n=20,alpha=0.05,max_groups=8):
'''
data: 输入的pandas DataFrame数据集
col: 需要分箱得连续型变量名
target: Y值
n:初始化分箱的个数,一般选取比较大的值(使用的是等距分箱)
alpha:卡方分布的显著性水平
max_groups: 最大分箱个数
'''
# 先进行初略的等距分箱
df = data[[col,target]]
df['cut'],bins = pd.cut(df[col],bins=n,retbins=True,right=False) # right=False,使得区间为左闭右开如[0,10),与下面的cutoff相一致
freq_tab = pd.crosstab(df['cut'],df[target],dropna=False) # 注意要添加dropna=False,否则全为0的组就不显示
freq = freq_tab.values # 转换为array数组
cutoff = bins[:-1] # 分组区间是左闭右开的,如cutoffs = [1,2,3],则表示区间 [1,2) , [2,3) ,[3,3+)。
# 以下代码确保每一箱中都有target正负样本
# 这样做的好处一是后续如果计算WOE时有这个需要,二是也能保证计算的期望频数fe不为0。
for i in range(n):
# 如果第一箱没有包含正样本或负样本,则向下一组合并
# 但即使原来第一箱和第二进行和合并,还是不能保证新的第一箱都包含正负样本,故使用continue跳出本次循环,开始下一次循环
if 0 in freq[0]:
freq[0] = freq[0] + freq[1]
freq = np.delete(freq,1,0)
cutoff = np.delete(cutoff,1,0)
continue
# 经过上面代码确保第一箱都包含正负样本,则判断之后的每箱,是否都包含正负样本,如果不包含,则向前一箱合并
for i in range(1,len(freq)):
if 0 in freq[i]:
freq[i-1] = freq[i] + freq[i-1]
freq = np.delete(freq,i,0)
cutoff = np.delete(cutoff,i,0)
break
else:
break
# 计算相邻箱的卡方值
threshold = scipy.stats.chi2.isf(alpha,freq.shape[-1]) # 卡方阈值根据显著性水平和自由度设置
while len(freq) > max_groups: # 先根据设定的最大分箱数,合并最小的卡方值
chi_vs=[]
for i in range(len(freq)-1):
chi_v = scipy.stats.chi2_contingency(freq[i:i+2])[0]
chi_vs.append(chi_v)
i = chi_vs.index(min(chi_vs))
freq[i] = freq[i] + freq[i+1]
freq = np.delete(freq,i+1,0)
cutoff = np.delete(cutoff,i+1,0)
# 按照预先设定的分箱数合并完毕后,如果发现最小卡方值还有低于卡方阈值的再接着合并
while True:
for i in range(len(freq)-1):
chi_vs=[]
chi_v = scipy.stats.chi2_contingency(freq[i:i+2])[0]
chi_vs.append(chi_v)
if min(chi_vs) < threshold:
i = chi_vs.index(min(chi_vs))
freq[i] = freq[i] + freq[i+1]
freq = np.delete(freq,i+1,0)
cutoff = np.delete(cutoff,i+1,0)
continue
else:
break
return cutoff,freq
# 将变量的值转换为相应的组
def value_to_group(x,cutoff):
'''
x 是需要划分组的标量值
cutoff 是根据上述chimerge函数返回的箱边值
'''
cutoff.sort()
# 小于最小值的异常值也都放入第一组
if x < cutoff[0]:
return 'group-1'
for i in range(1,len(cutoff)):
if cutoff[i-1] <= x < cutoff[i]:
return 'group-{}'.format(i)
# 最后一组也包含非常大的异常值
if x >= cutoff[len(cutoff)-1]:
return 'group-{}'.format(len(cutoff))
以上,如有错误,还请大家指教。
【参考资料】
1.贾俊平统计学第七版
2.卡方分箱论文
3.菜菜的sklearn课堂
4.Python评分卡建模—卡方分箱(2)之代码实现
5.python中break,continue,pass,else的用法和区别详解