重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
为什么要叫“卡方”?因为原名是“chi-squared”,一半是音译,一半是意译。其中,chi 是希腊字母 的读音,其实读音更像是“开”,而不是“卡”。square表示平方,因此在英语中,卡方分布写作 distribution。
成都创新互联公司是一家以网络技术公司,为中小企业提供网站维护、网站设计、做网站、网站备案、服务器租用、域名注册、软件开发、小程序设计等企业互联网相关业务,是一家有着丰富的互联网运营推广经验的科技公司,有着多年的网站建站经验,致力于帮助中小企业在互联网让打出自已的品牌和口碑,让企业在互联网上打开一个面向全国乃至全球的业务窗口:建站服务电话:028-86922220
在理解卡方检验之前,应当理解卡方分布。卡方分布是一种连续概率分布。
如果一个随机变量 服从标准正态分布,即 ,那么 就服从自由度为1的卡方分布。记作 或者
而如果 都服从标准正态分布,那么它们的平方和服从自由度为 的卡方分布,记作:
或者写作 。
对于非负自变量 的自由度为 的卡方分布的概率密度函数 (简称"pdf"):
(1)为什么 非负?因为根据定义,卡方分布的自变量是一个平方和。
(2)这里的 是一个函数。关于这个函数具体是什么,以及上门的概率密度函数如何推导,这里不展开,只需要知道有这么个函数即可。实在是好奇的,可以 参考这里 。
(3)卡方分布的均值为 ,而标准差为 。
(4)自由度越大,该函数图像越对称。
(5)为什么这里 需要正态分布,我的理解是,如果零假设为真,那么观测值和期望值之间的波动程度,应该是正态分布的,或者说“噪声”应该是正态分布的。
卡方检验有两个用途:
拟合优度检验 chi-squared test goodness of fit
独立性检验 chi-squared test of independence
某新闻说某个篮球明星的原地两连投的单次命中率是0.8,根据历次比赛的数据汇总得到下面的表格:
意思是说,在比赛中,有5次两连投是一次都没中,有82次是在两连投中命中1次。现在,我们来用卡方检验验证新闻说的0.8的命中率是否正确。零假设如下:
:两连投的成功次数符合二项分布,且概率为
(1)先根据零假设计算“期望”的命中次数分布:
由于总的观察次数为 ,于是在 成立的前提下,可以计算每种两连投结果的期望次数:
0次命中:
1次命中:
2次命中:
显然,期望的观察次数和实际的观察次数是有偏差的,那么问题在于这个偏差是否大到具有统计显著性,进而可以否定零假设。
(2)我们来构造卡方检验统计量(chi-squared test statistic):
这个值是把表里每个格子的实际值和期望值进行对比。为什么要用平方?目的在于规避正负号的影响。为什么要除以期望值?目的在于消除数量绝对值的影响。例如你预算3块钱的水,商家加价50元,那么这个波动是你无法忍受的,而你预算20万的车,商家加价50元,则变得可以忍受。也就是说,除以期望值目的在于聚焦于变化率,而不是变化量。
之后,把这些“变化率”加总得到 。而计算自由度有一个公式:
其中 R 表示行数,C 表示列数。对于本例:
从另一个角度解释为什么 :前面的定义是如果是 个符合标准正态分布的 相加,则自由度是 ,但是这里自有两个格子可以自由变化,第三个格子可以用总观察数减出来,例如 。
因此,真正自由的只有2个格子,所以自由度是2。
好了,将格子的数据代入,求出检验统计量:
(3)根据自由度为2的卡方分布,找到检验统计量对应的位置:
不难理解,随着统计量增大,表示预期的分布和实际的分布的差异也就越来越大。
另外,由于通常意义上,p值是越小越能推翻零假设,那么显然我们需要用右侧的面积来表示p值,这里用Python计算来代替查表:
输出:statistic: 17.26, pvalue: 0.0002
由于p值很小(假设我们的显著性水平的0.05),那么我们可以推翻零假设。
进一步的,我们来探索下,该运动员的两连投的成功次数分数是否真的符合二项分布。零假设:
:两连投的成功次数符合二项分布。
既然符合二项分布,那么我们需要先估算一下最合理的 概率,那当然是用总命中数除以总投篮数来计算了:
然后,用该概率值重复之前的计算,也就是先计算出一个期望的表格:
注意,这里的 ,这是因为,我们每从数据估计一个参数,那么我们就损失一个自由度。这里用了一个平均命中的概率,因此自由度只有 。
这时候,在使用 Python 进行计算时,注意调整默认的自由度:
这里的 ddof 就是额外损失的自由度,本意是“delta degree of freedom”
输出:statistic: 0.34, pvalue: 0.56
可以看到p值很大,因此不足以推翻零假设,也就是说该运动员的投篮命中次数可能真的是二项分布。
下面表格表示喝酒频率和与警察发生麻烦的频数。
以第一列为例,表示从不喝酒的人中,4992人不发生麻烦,71人会发生麻烦。
现在的问题是,能否从以下数据推断说喝酒频率和与警察发生麻烦这两个事件相互独立?
我们的零假设应该如何设计?如果要说明两者相互独立,那么上表的分布应该满足乘法公式。也就是说两个独立事件一起发生的概率等于分别发生的概率之积。
于是我们有:
发生麻烦的总人数除以总人数
不喝酒的总人数除以总人数
进一步,根据总人数算出不喝酒而发生麻烦的人数的期望(下标表示零假设):
用类似的算法,计算每一个格子在零假设成立的情况下的值,写在原表数据下的括号里:
仔细观察可以看出,其实每个格子就是对应的:
另外可以看到,零假设下的各个格子的行列之和与原来相同。这不是偶然的,我们用字母代替计算一下就知道了:
于是第一列的两个格子应该是:
对于其他格子、行的总和,都一样,这里不多说了。
好,继续分析。我们直接用上表计算卡方统计量和p值:
这部分计算方法和拟合优度是一样的,就不赘述了。计算发现这个p值非常小,接近0,因此我们可以推翻零假设。也就是说,喝酒的频率和被警察找麻烦的并不是独立的,而是相关的。
关于独立性检验,有一个比卡方检验更精准的检验,叫 fisher's exact test。它通过直接计算否定零假设的概率,也就直接得到了一个准确的p值。有一个经典的女士品茶的统计学故事。有一个女士号称可以区分出一杯茶是先倒入了奶还是先倒入了茶。统计学家 Fisher 为了验证她的说法,做了一个实验。拿了8杯茶,4杯是先茶后奶,4杯是先奶后茶。
实验结果是全部说对了。那么问题是,这是否具有统计显著性呢?比如说一个人猜对了一次硬币,他的预测能力靠谱吗?
我们假设女士的判断是完全随机的,这个是我们的零假设。那么8杯里面抽中4杯全对的概率是:
如果显著性水平是0.01,那么我们不能推翻零假设,即不敢确定这位女士真有这个识别能力。如果显著性水平定在0.05,则我们可以认为她确实有这个识别能力。
如果让实验结果有更大的说服力呢?一个简单的办法就是增加茶的数量,比如我们设定为两种茶各10杯,要求10杯都判断正确,那么p值为多少呢?
这个算起来比较麻烦,这里我写一个 python 脚本来计算:
计算结果:
这个p值就小得很夸张了,基本可以断定零假设不成立了。
那么,回到实验本身,如果女士只选对了三杯,那么在零假设的前提下,这个发生的概率是多少?
这个概率比较大了,原大于通常使用的显著性水平 0.05,因此我们没有办法推翻零假设。为什么要这样 乘 呢?这个是因为一共有这么多种取法。你把所有可能的取法罗列处理,就是16种,然后除以总的取法数,就是随机取到这样结果的概率。
比较 Fisher's exact test 和 chi-squared test,可以 参考这篇文章 。
一般来说,两者都适用的情况下,应该优先选择 Fisher's exact test,因为它是精确值。如果实验观察的数量很小(小于10),应该不使用 chi-squared test。
下面使用一个脚本来计算:
python可以对大数据进行卡方检验
需要澄清两点之后才可以比较全面的看这个问题:
1. 百万行级不算大数据量,以目前的互联网应用来看,大数据量的起点是10亿条以上。
2. 处理的具体含义,如果是数据载入和分发,用python是很高效的;如果是求一些常用的统计量和求一些基本算法的结果,python也有现成的高效的库,C实现的和并行化的;如果是纯粹自己写的算法,没有任何其他可借鉴的,什么库也用不上,用纯python写是自讨苦吃。
python的优势不在于运行效率,而在于开发效率和高可维护性。针对特定的问题挑选合适的工具,本身也是一项技术能力。
创建结构元素: clear;close all SE = strel('rectangle',[40 30]); %注意:结构元素必须具有适当的大小,既可以删电流线又可以删除矩形.
def calc_chiSquare(sampleSet, feature, target):
'''
计算某个特征每种属性值的卡方统计量
params:
sampleSet: 样本集
feature: 目标特征
target: 目标Y值 (0或1) Y值为二分类变量
return:
卡方统计量dataframe
feature: 特征名称
act_target_cnt: 实际坏样本数
expected_target_cnt:期望坏样本数
chi_square:卡方统计量
'''
# 计算样本期望频率
target_cnt = sampleSet[target].sum()
sample_cnt = len(sampleSet[target])
expected_ratio = target_cnt * 1.0/sample_cnt
# 对变量按属性值从大到小排序
df = sampleSet[[feature, target]]
col_value = list(set(df[feature]))
# 计算每一个属性值对应的卡方统计量等信息
chi_list = []; target_list = []; expected_target_list = []
for value in col_value:
df_target_cnt = df.loc[df[feature] == value, target].sum()
df_cnt = len(df.loc[df[feature] == value, target])
expected_target_cnt = df_cnt * expected_ratio
chi_square = (df_target_cnt - expected_target_cnt)**2 / expected_target_cnt
chi_list.append(chi_square)
target_list.append(df_target_cnt)
expected_target_list.append(expected_target_cnt)
# 结果输出到dataframe, 对应字段为特征属性值, 卡方统计量, 实际坏样本量, 期望坏样本量
chi_stats = pd.DataFrame({feature:col_value, 'chi_square':chi_list,
'act_target_cnt':target_list, 'expected_target_cnt':expected_target_list})
return chi_stats[[feature, 'act_target_cnt', 'expected_target_cnt', 'chi_square']]
def chiMerge_maxInterval(chi_stats, feature, maxInterval=5):
'''
卡方分箱合并--最大区间限制法
params:
chi_stats: 卡方统计量dataframe
feature: 目标特征
maxInterval:最大分箱数阈值
return:
卡方合并结果dataframe, 特征分割split_list
'''
group_cnt = len(chi_stats)
split_list = [chi_stats[feature].min()]
# 如果变量区间超过最大分箱限制,则根据合并原则进行合并
while(group_cnt maxInterval):
min_index = chi_stats[chi_stats['chi_square']==chi_stats['chi_square'].min()].index.tolist()[0]
# 如果分箱区间在最前,则向下合并
if min_index == 0:
chi_stats = merge_chiSquare(chi_stats, min_index+1, min_index)
# 如果分箱区间在最后,则向上合并
elif min_index == group_cnt-1:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
# 如果分箱区间在中间,则判断与其相邻的最小卡方的区间,然后进行合并
else:
if chi_stats.loc[min_index-1, 'chi_square'] chi_stats.loc[min_index+1, 'chi_square']:
chi_stats = merge_chiSquare(chi_stats, min_index, min_index+1)
else:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
group_cnt = len(chi_stats)
chiMerge_result = chi_stats
split_list.extend(chiMerge_result[feature].tolist())
return chiMerge_result, split_list
def chiMerge_minChiSquare(chi_stats, feature, dfree=4, cf=0.1, maxInterval=5):
'''
卡方分箱合并--卡方阈值法
params:
chi_stats: 卡方统计量dataframe
feature: 目标特征
maxInterval: 最大分箱数阈值, default 5
dfree: 自由度, 最大分箱数-1, default 4
cf: 显著性水平, default 10%
return:
卡方合并结果dataframe, 特征分割split_list
'''
threshold = get_chiSquare_distuibution(dfree, cf)
min_chiSquare = chi_stats['chi_square'].min()
group_cnt = len(chi_stats)
split_list = [chi_stats[feature].min()]
# 如果变量区间的最小卡方值小于阈值,则继续合并直到最小值大于等于阈值
while(min_chiSquare threshold and group_cnt maxInterval):
min_index = chi_stats[chi_stats['chi_square']==chi_stats['chi_square'].min()].index.tolist()[0]
# 如果分箱区间在最前,则向下合并
if min_index == 0:
chi_stats = merge_chiSquare(chi_stats, min_index+1, min_index)
# 如果分箱区间在最后,则向上合并
elif min_index == group_cnt-1:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
# 如果分箱区间在中间,则判断与其相邻的最小卡方的区间,然后进行合并
else:
if chi_stats.loc[min_index-1, 'chi_square'] chi_stats.loc[min_index+1, 'chi_square']:
chi_stats = merge_chiSquare(chi_stats, min_index, min_index+1)
else:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
min_chiSquare = chi_stats['chi_square'].min()
group_cnt = len(chi_stats)
chiMerge_result = chi_stats
split_list.extend(chiMerge_result[feature].tolist())
return chiMerge_result, split_list
def get_chiSquare_distuibution(dfree=4, cf=0.1):
'''
根据自由度和置信度得到卡方分布和阈值
params:
dfree: 自由度, 最大分箱数-1, default 4
cf: 显著性水平, default 10%
return:
卡方阈值
'''
percents = [0.95, 0.90, 0.5, 0.1, 0.05, 0.025, 0.01, 0.005]
df = pd.DataFrame(np.array([chi2.isf(percents, df=i) for i in range(1, 30)]))
df.columns = percents
df.index = df.index+1
# 显示小数点后面数字
pd.set_option('precision', 3)
return df.loc[dfree, cf]
def merge_chiSquare(chi_result, index, mergeIndex, a = 'expected_target_cnt',
b = 'act_target_cnt', c = 'chi_square'):
'''
params:
chi_result: 待合并卡方数据集
index: 合并后的序列号
mergeIndex: 需合并的区间序号
a, b, c: 指定合并字段
return:
分箱合并后的卡方dataframe
'''
chi_result.loc[mergeIndex, a] = chi_result.loc[mergeIndex, a] + chi_result.loc[index, a]
chi_result.loc[mergeIndex, b] = chi_result.loc[mergeIndex, b] + chi_result.loc[index, b]
chi_result.loc[mergeIndex, c] = (chi_result.loc[mergeIndex, b] - chi_result.loc[mergeIndex, a])**2 /chi_result.loc[mergeIndex, a]
chi_result = chi_result.drop([index])
chi_result = chi_result.reset_index(drop=True)
return chi_result
for col in bin_col:
chi_stats = calc_chiSquare(exp_f_data_label_dr, col, 'label')
chiMerge_result, split_list = chiMerge_maxInterval(chi_stats, col, maxInterval=5)
print(col, 'feature maybe split like this:', split_list)