scipy.stats卡方独立性检验报期望频数为零、p值异常解决
报错原因
- 零期望频数报错:卡方独立性检验的适用前提是列联表所有单元格的期望频数不小于1,且期望频数小于5的单元格占比不超过20%。第9列检验时报
(1,0)位置期望频数为0,说明该列与covid19列交叉分组后存在空单元格(即两个分类的交叉组合没有任何观测样本),不满足卡方检验的适用条件。 - p值大于1的异常:生成列联表时设置了
margins=True,该参数会在列联表末尾追加行、列的合计值。如果把带合计值的列联表直接传入chi2_contingency,函数会将合计行、合计列识别为独立的分类水平,导致自由度计算完全错误,最终输出超出[0,1]合理范围的无效p值。 - 原有代码的额外隐患:硬编码切片
0:5截取列联表数值,默认每列都有5个分类水平,一旦某列的分类数不等于5,就会截取到错误的观测矩阵,直接导致计算结果失真。
修正方案
直接使用不带边际合计的原始列联表传入检验函数,不要硬编码切片,同时增加期望频数校验逻辑,对不满足卡方检验前提的场景做兼容处理,修正后的代码如下:
import pandas as pd import numpy as np from scipy.stats import chi2_contingency, fisher_exact def independence_test(col_1, col_2="covid19", merge_low_freq=False): # 生成无合计值的原始列联表,禁止加margins=True ct = pd.crosstab(df[col_2], df[col_1]) obs = ct.values # 初次计算获取期望频数 chi2, p, dof, expected = chi2_contingency(obs, correction=False) min_expected = expected.min() if min_expected < 1: if merge_low_freq: # 方案1:合并低频分类,将列方向频数<5的分类合并为「其他」组,可根据业务规则调整阈值 col_totals = ct.sum(axis=0) low_cats = col_totals[col_totals < 5].index df_copy = df.copy() df_copy[col_1] = df_copy[col_1].replace({cat: "其他" for cat in low_cats}) ct_new = pd.crosstab(df_copy[col_2], df_copy[col_1]) chi2, p, dof, expected = chi2_contingency(ct_new.values, correction=False) return ("chi2_test", chi2, p, dof) else: # 方案2:不满足卡方前提时,切换为费希尔精确检验(适合小样本/空单元格场景) odds_ratio, p_fisher = fisher_exact(obs) return ("fisher_exact", odds_ratio, p_fisher) return ("chi2_test", chi2, p, dof)
注意事项
- 传入统计检验函数的列联表必须是纯交叉计数的原始矩阵,不要加入边际合计、占比等人工计算的衍生行列。
- 不要硬编码分类数做切片,不同字段的分类水平数量可能存在差异,直接取列联表的
values属性即可拿到正确的观测矩阵。 - 若列联表维度超过2x2、样本量较大但存在少量低期望频数单元格,可开启
chi2_contingency的correction=True参数做连续性修正,结果会更稳健。
内容的提问来源于stack exchange,提问作者berkaiii
相关产品推荐
相关产品推荐

