尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

卡方检验完全指南:从期望频数到Python实践与常见误区

卡方检验完全指南:从期望频数到Python实践与常见误区 做数据分析这些年我接过最多的问题其实是那种“看起来很简单”的需求产品经理甩过来一张展区货架各个SKU的销售数量表问“这个月绿色的明显比公式预期的少是不是应该调整进货比例”运营发来一组AB实验的点击人数问“新按钮是不是真的比旧按钮好”。这类问题背后的数据全是计数、全是类别不能直接套t检验或者方差分析得用一个专门处理类别数据的工具——这就是统计基础系列第9篇的主角卡方检验。前面几篇我们把描述统计、概率分布、抽样分布、参数估计和假设检验的基本框架聊完了这篇专门把“类别数据之间的比较”这个场景单独拿出来讲透。内容适配的业务场景很广电商比价决策、AB测试转化率分析、问卷调研的交叉分析、工厂质检的合格率对比凡是遇到“男/女”“点击/不点击”“红/黄/蓝”这类分类型数据想判断组间有没有显著差异卡方检验几乎都是第一个该用的工具。这篇博文适合刚入门统计学、或者已经在用Python做数据分析但只会调包不会解释结果的朋友我会把公式逻辑、三种典型场景、实操中特别容易踩的坑以及一份可以直接复制的完整代码全部放进来。1. 卡方检验到底在检验什么类别数据之间有没有“意外”卡方检验这个名字听起来有点吓人但它的核心思想非常直白比较“实际观察到的频数”和“在某种假设下应该出现的期望频数”之间差了多少。如果两个数差得很远就说明实际数据和假设不相符如果差不多就说明数据支持这个假设。1.1 为什么有了t检验和方差分析还不够t检验和方差分析比较的是均值要求数据至少是连续型的比如身高、收入、页面停留时长。但实际业务里大量数据是类别型的存下来就是“人数”“数量”“次数”。举个例子1000个人里点击了按钮的是380人另外620人没点。这时候我们能算出一个点击率38%但没法对这个38%做传统的t检验因为它本质上是两个计数点击380、未点击620。再比如性别和手机偏好的关系性别只有男/女手机偏好是A/B/C三个品牌每个交叉格子里都是一个计数。这种数据没有“均值”的概念却非常需要一个统计方法来判断“性别是否和手机偏好有关系”。卡方检验解决的就是这类问题它把每个格子里的观察频数和“两变量独立时应该出现的期望频数”作对比差距汇总起来就是卡方统计量。很多人做分析喜欢一上来就画个堆积柱状图看两眼就说“女性明显更喜欢A品牌”。问题在于样本量不同、基础比例不同视觉上的差异可能是抽样波动造成的。卡方检验给的是一个可以量化的判断标准这个差异在统计学上到底是不是显著。1.2 期望频数卡方检验的“基准尺子”理解卡方检验必须理解期望频数。还是用性别和手机偏好的例子假设调查了300人其中男性180人、女性120人三种手机品牌A、B、C分别被150人、90人、60人选择。如果性别和手机偏好完全独立那么“男性选A品牌”的人数应该怎么算很简单用行合计乘以列合计再除以总人数180 × 150 ÷ 300 90。意思是在“两个变量独立”的假设下180个男性里应该恰好按照150/300的比例去选A也就是90人。同理可以算出每个格子里的期望频数。然后我们从实际表里取出对应格子的观察频数例如实际数据里男性选A的是112期望却是90这里就多出了22人。把每一个格子里的这种偏差收集起来就能得出一个总量。问题是怎么把这么多格子的偏差“公平地”汇总成一个数值这就引出了卡方统计量。1.3 三种卡方检验场景一张表说清楚卡方检验里最常用的其实是三个场景公式底层相通但业务提问方式不同。很多人只知道调chi2_contingency却不知道自己在做什么检验这会直接导致结果解读错误。检验类型业务问题数据形态拟合优度检验一组类别数据的比例是否符合某个理论分布一个变量的频数分布独立性检验两个类别变量之间是否有关联行是变量A列是变量B构成列联表同质性检验不同群体的类别分布是否一致行是群体列是类别形式上也是列联表独立性检验和同质性检验的计算过程几乎一样都用列联表但抽样设计不同。独立性检验是随机抽取一批人同时记录每个人的性别和手机偏好同质性检验是分别从几个群体里抽样再比较这几个群体各自的构成比例。业务场景不同解读结论的句式也要跟着变。我在第3部分会把这个差异展开讲。2. 卡方统计量的构造逻辑公式、自由度和那个又偏又长的分布很多教程直接甩出公式χ² Σ (O - E)² / E然后就让读者跑代码。但如果不理解为什么公式长这样就很容易出现“算出来卡方值很大但不知道大多少算大”的困惑。这里我拆开揉碎讲一遍。2.1 公式拆解为什么是Σ(O-E)²/E还要除以E先看O-E也就是观察频数和期望频数的差这个好理解。那为什么要平方很简单如果不平方正负偏差会相互抵消。男性选A可能偏多了22女性选A就偏少了对应的人数全部加起来可能变成0这显然不能反映“存在偏差”的事实。平方之后再求和偏差就不会因为方向被抵消。再看为什么要除以E。想象两个格子一个格子期望频数是1000和观察频数相差20另一个格子期望频数是10和观察频数相差20。同样是差20第一个格子只是2%的偏离第二个格子已经是200%的偏离显然第二个格子的偏差更异常。所以不能只加总平方差要用偏差平方除以期望频数做一个“标准化”。这一步和方差分析里用组内均方来衡量差异是一个道理本质是给每个格子的偏差公平的权重。所以χ²统计量描述的就是在整个列联表下所有格子的观察频数与期望频数之间“相对偏差”的总和。这个值越大说明数据与“独立”的假设越不合拍。2.2 自由度是怎么算出来的为什么是(r-1)(c-1)自由度的概念容易把人绕晕但理解它有一个简单办法在行合计和列合计全部固定的情况下列联表里有多少个格子是可以“自由填充”的。拿2×2的表格举例行合计和列合计确定之后比如左上角那个格子的观察频数是112那么右上角就只能是180-11268左下角是150-11238右下角更是直接被总数锁死不需要再填。也就是说4个格子里真正可以自由变化的只有1个。这就是df (2-1) × (2-1) 1的直观含义。自由度决定了卡方分布的形状。自由度越小分布越偏也就是统计量集中在偏小的位置但右尾拉得很长自由度变大分布逐渐接近正态分布。卡方检验的原理就是在“独立”这个原假设成立时χ²统计量应该服从相应自由度的卡方分布。如果统计量落在分布的右尾深处就说明观察到的偏差大到几乎不可能是抽样波动造成的。2.3 卡方统计量、p值和完整检验流程完整的卡方检验流程用最规范的说法是这样四步建立原假设H₀和备择假设H₁。独立性检验就是H₀“两变量独立”H₁“两变量不独立”。按公式计算期望频数和χ²统计量。根据自由度查卡方分布表或让SciPy直接给出p值。拿p值和事先定好的显著性水平α通常0.05比较p α就拒绝原假设认为差异显著。实际操作中p值比卡方值更常用因为卡方值对应的p值依赖于自由度不同自由度的卡方值不能直接跨表比较。比如同样是卡方值6自由度1时p≈0.014属显著自由度5时p≈0.306并不显著。这就是为什么看检验结果一定要落到p值上而不是只看统计量大小。3. 三种核心场景的完整案例同一套原理三种问法这一部分我用三个案例把卡方检验跑一遍每个案例都给代码、给结果、给解读可以直接对照自己的数据用。3.1 拟合优度检验实际销量是否符合预期比例某食品公司给同一种零食出了四种口味包装根据历史数据制定了下季度的生产计划预期销量占比是原味30%、番茄味20%、海苔味20%、烧烤味30%。月底实际卖出去1000袋各口味销量是320、180、230、270。这时候要判断的是实际销量和预期比例有没有显著差异。from scipy import stats import numpy as np observed np.array([320, 180, 230, 270]) # 实际销量 # 预期比例换算成和observed同总和的期望频数 total observed.sum() expected np.array([0.3, 0.2, 0.2, 0.3]) * total chi2, p stats.chisquare(f_obsobserved, f_expexpected) print(卡方统计量:, chi2) print(p值:, p)跑出来结果大致是卡方值6.33p值约0.097。在0.05的显著性水平下不能拒绝原假设。也就是说虽然番茄味看起来比20%少了一点但这批数据的偏差还在正常抽样波动范围内不该立刻调整生产比例。这里有个细节值得说一下chisquare只适用于单向频数表的拟合优度检验f_exp传入的期望频数总和必须和observed一致。很多人直接传比例[0.3, 0.2, 0.2, 0.3]部分版本下会得到错误结果。稳妥的做法是先乘总数。3.2 独立性检验性别与产品偏好是否有关回到性别和手机偏好的例子。假设我们收集了300个人的数据形成这样一个2×3列联表品牌A品牌B品牌C合计男性1124424180女性384636120合计1509060300table np.array([[112, 44, 24], [38, 46, 36]]) chi2, p, dof, expected stats.chi2_contingency(table) print(卡方统计量:, chi2) print(p值:, p) print(自由度:, dof) print(期望频数矩阵:\n, expected)这次结果会非常显著p值远小于0.05说明性别和手机品牌偏好确实存在关联。再看期望频数矩阵男性选A的期望是90实际112女性选C的期望是24实际36说明男性相对更偏好A品牌女性相对更偏好C品牌。处理显著结果时一定要回到期望频数和实际频数的对比中去解释“差异具体在哪里”否则只说一句“有关系”对业务毫无帮助。3.3 同质性检验不同渠道的用户年龄结构是否一致现在有三个渠道自然搜索、付费广告、社交媒体各抽了300、350、320个用户统计他们的年龄分布18-25、26-35、36-45、46以上。想判断的是这三个渠道来的用户年龄段构成是否一致。这种情况下抽样方式不是先抽1000个人再记录渠道而是分别从三个渠道的已知用户池里抽样本观察指标是同一个类别变量的分布是否相似。计算上仍然用chi2_contingency但解读结论时的措辞应该是“不同渠道的用户年龄构成是否一致”而不是“渠道和年龄是否独立”。同样一张表独立性检验侧重“两个变量的关联”同质性检验侧重“不同总体的分布对比”。这也是很多教材不讲、但实际工作中经常混用的地方。我个人的建议是业务沟通时不用太纠结这两个词的区分但心里要清楚自己的采样方式否则写分析报告的时候容易把自己的研究设计写混乱。4. 实操中掉进去过四次坑都在这里卡方检验看起来简单实际业务里一到小样本、稀疏表、多组比较就各种翻车。下面这四个坑我全都亲手踩过写出来给大家排雷。4.1 期望频数太小检验结果靠不住卡方统计量近似服从卡方分布是有前提的核心前提是格子里的期望频数不能太小。统计教材通行的经验法则是期望频数小于5的格子数不能超过总格子数的20%且所有期望频数必须大于1。如果期望频数太小计算出来的卡方值会偏大p值会偏小结论很容易“假显著”。比如细胞培养实验里对照组有0个突变实验组有3个突变这样的2×2表期望频数极小直接跑卡方检验会得到一个看似很震撼的p值实际上并不能说明问题。这种情况我建议用Fisher精确检验它不依赖卡方分布的近似条件直接在超几何分布下计算精确概率。SciPy需要额外安装scipy.stats.fisher_exact用法很简单from scipy.stats import fisher_exact table [[0, 10], [3, 7]] odds_ratio, p fisher_exact(table, alternativetwo-sided) print(比值比:, odds_ratio) print(p值:, p)处理策略其实有三个一是增加样本量现实中增加不了就只能合并类别二是把期望频数过小的类别合并成“其他”三是直接用Fisher精确检验。千万不要看见p值小就直接写结论。4.2 2×2表的连续性校正科学和踩坑的分界线在2×2列联表里SciPy的chi2_contingency有一个默认参数correctionTrue默认做Yates连续性校正。校正的目的是让离散的计数数据更好地拟合连续的卡方分布但也让检验变得更保守即p值更大更不容易拒绝原假设。问题就在于很多教程为了简化忽略了correction参数而默认结果和手动计算的结果对不上容易让人怀疑自己算错了。我一般掌握的标准是总样本量大于40且所有期望频数都大于5时不校正和校正的结果差异通常很小用默认就行样本量在20到40之间、期望频数又接近下限时要同时看两个结果如果结论不一致改用Fisher精确检验更稳妥。# 对比校正和不校正的差异 table np.array([[380, 620], [465, 535]]) chi2_corrected, p_corrected, dof, _ stats.chi2_contingency(table, correctionTrue) chi2_uncorrected, p_uncorrected, dof, _ stats.chi2_contingency(table, correctionFalse) print(校正后卡方值:, chi2_corrected, p值:, p_corrected) print(未校正卡方值:, chi2_uncorrected, p值:, p_uncorrected)这种并排输出在业务报告里尤其重要。很多时候一个结果的p值刚好卡在0.048和0.052之间会不会拒绝原假设完全取决于你做没做校正。写报告的时候必须说明自己用了哪种方式否则别人复现你的分析时会觉得很突兀。4.3 p值显著并不等于效应大记得算Cramérs V卡方检验的p值受样本量影响极大。样本量一大非常微小的差异也能变得“统计显著”但这种显著可能根本没有任何业务意义。比如用户量达到100万时两个版本按钮的点击率相差0.3个百分点卡方检验一定会给一个p值小于0.001但产品经理看到这个0.3%的差距大概率不会上线新版本。这时候要补充一个效应量指标。对列联表数据最常用的是Cramérs V系数它把卡方值折算到0到1之间消除样本量的影响def cramers_v(chi2, n, r, c): return np.sqrt(chi2 / (n * min(r-1, c-1))) v cramers_v(chi2, n300, r2, c3) print(Cramérs V:, v)通常的经验参考是V≈0.1算小效应V≈0.3算中等V≈0.5以上才算强关联。同样是p值小于0.001样本量100时V可能有0.4样本量100万时V可能只有0.02。只看p值不看效应量是统计显著性和实际显著性混淆的根源。4.4 整体显著之后还要两两比较当列联表超过2×2比如三个渠道比较或四种包装比较卡方检验显著只说明“至少有一组和其他组不同”并不能告诉你具体是哪两组不同。这在术语上叫“整体检验”事后需要做两两比较。如果直接对每两对组合分别跑卡方检验会引入多重比较问题。三组之间比较三次每次犯一类错误的概率是0.05三次比较下来总体犯错的概率就不止0.05了。常用的办法是Bonferroni校正把显著性水平除以比较次数。三组两两比较需要做3次那么每次比较用0.05/3≈0.0167作为新的显著性水平。也可以用statsmodels里的多重比较方法自动处理但我提醒一句这类事后检验对业务解释力很强报告里最好附上“经校正后显著”和“校正后不再显著”两组结果结论会更严谨。5. 一个完整的Python实战A/B按钮点击率对比到这里我们把原理和坑都过了一遍下面用一个完整的AB实验案例把从数据到结论的全流程串起来。5.1 数据与业务背景某知识付费产品改了课程详情页的购买按钮文案从“立即购买”改为“查看详情立即报名”。产品团队想验证新文案是否真的提升了点击率。实验运行一周后得到数据版本点击未点击合计旧版本A3806201000新版本B4655351000表面看点击率从38%涨到了46.5%似乎新文案效果明显。但能不能排除抽样波动的影响得通过卡方检验来验证。5.2 三步完成卡方检验第一步判断数据类型和检验方法。这里两个变量都是类别型版本A/B、结果点击/未点击是2×2列联表的独立性检验场景。第二步调用SciPy计算卡方统计量、p值、期望频数矩阵。import numpy as np from scipy import stats table np.array([[380, 620], [465, 535]]) chi2, p, dof, expected stats.chi2_contingency(table, correctionFalse) print(卡方统计量:, round(chi2, 4)) print(p值:, format(p, .6g)) print(自由度:, dof) print(期望频数矩阵:\n, expected) n table.sum() r, c table.shape v np.sqrt(chi2 / (n * min(r - 1, c - 1))) print(Cramérs V:, round(v, 4))手动算一下期望频数更容易理解结果。总人数2000总点击845点击率42.25%。如果版本和点击相互独立那么A版本1000人里的期望点击数就是422.5B版本同样。实际数据里A版点击380比期望少了42.5B版点击465比期望多了42.5。把四个格子的偏差代入公式χ² ≈ 14.80自由度1p值约0.00012。第三步计算效应量Cramérs V结果是约0.086。5.3 结论怎么写才严谨这里的统计结论非常明确p值远小于0.05拒绝原假设新文案的点击率和旧文案有显著差异。效应量约0.086属于小效应说明这个差异虽然统计学上显著但幅度并不算大。我写报告时通常会这样表述在95%置信水平下新版本B的点击率显著高于旧版本Aχ²14.80df1p0.05点击率从38%提升到46.5%绝对提升8.5个百分点但Cramérs V仅为0.086属于小效应建议结合后续转化率、购买率等指标进一步评估业务价值。这比直接写一句“新文案有效”严谨得多也经得起业务方追问。我把这个表述格式分享给过很多同事他们反馈“领导终于不再追问你是怎么算的了”。6. 我这些年做统计检验的心得做数据分析时间久了慢慢发现卡方检验这类基础工具反而最容易出问题因为看起来太简单默认参数一跑就出结果很少有人停下来确认前提条件。我个人的经验是第一看数据结构第二看样本量第三看效应量。拿到任何一组计数数据先确认每一行每一列的真实含义是独立抽样还是分层抽样是比率还是绝对频数。有些Excel表格里存的是百分比直接拿去做卡方检验得出来的结果完全没有意义。卡方检验只能处理原始计数百分比必须先还原成频数才能进入计算。还有一件事值得反复强调不要迷信p值。p值小于0.05只是给了你一个“这组数据与假设不一致”的统计学信号它既不能证明业务效果也不能替代领域判断。我在实际工作中见过太多因为p值显著就立刻上线砍掉某个功能版本的决定后来回头复盘发现当时样本量过大0.3%的差异被放大成了“统计显著”但商业收益几乎可以忽略不计。卡方检验真正厉害的地方不在于它有多复杂而在于它把“类别之间的偏差”量化成了一个可比较、可检验的标准。理解了期望频数理解了自由度和p值你就掌握了几乎所有列联表分析的底层逻辑。下一篇我会继续沿着统计基础这条线往下走把回归分析和相关分析放在一起对比讲解到时候你会发现很多回归里的概念其实和卡方检验背后用的是同一套推断思路。刚才那个A/B实验例子如果你想练手可以自己把数据改成三组版本跑一遍整体卡方检验再做两两比较再从效应量的角度解释结果。这套流程跑顺了日常大部分类别数据对比问题你都能稳稳拿下来。
返回列表