首页 / 资讯中心 / 文章详情

Shapiro-Wilk与Shapiro-Francia正态性检验选型指南:原理、代码与避坑

Shapiro-Wilk与Shapiro-Francia正态性检验选型指南:原理、代码与避坑 ★ FEATURED ARTICLE
简介这份资源聚焦复合正态性检验面向需要做参数假设检验的数据分析人员、统计学习者与科研工作者解决样本正态性判断这一常见前置问题。核心实现 Shapiro-Wilk 检验支持样本量 3≤n≤5000基于 Royston R94 算法并对 platykurtic 样本额外执行 Shapiro-Francia 正态性检验兼顾不同分布形态下的判断需求。压缩包为 zip 格式仅含 1 个 m 文件体积约 3KB属于轻量级脚本工具便于直接嵌入现有 MATLAB 分析流程调用。目前已有 946 人学习下载说明其在统计检验场景中具有一定参考价值。读者可获得一套可直接运行的检验脚本理解两种检验的适用边界与算法实现思路并借助样本量范围设定快速完成批量数据的正态性筛查为后续 t 检验、方差分析等方法的选择提供依据。1. 正态性检验选谁Shapiro-Wilk 和 Shapiro-Francia 到底差在哪做数据预处理时很多人会顺手跑一个shapiro.test()看到 p 值大于 0.05 就松一口气觉得数据“正态了”然后放心去做 t 检验或线性回归。但真正踩过坑的人知道正态性检验不是一道是非题而是一组权衡样本量多大、尾部有多重、有没有并列值都会让 Shapiro-Wilk 和 Shapiro-Francia 给出不一样的结论。这两个检验名字很像都带 Shapiro核心思路也一脉相承——把排序后的样本和正态分布的理论分位数做回归看拟合得有多直。区别在于它们对“直”的度量方式不同导致在小样本、重尾、并列值场景下表现分化。这篇文章面向需要做正态性判断的从业者无论你是做 A/B 实验、金融收益率建模还是传感器数据清洗只要涉及“这组数能不能当正态处理”Shapiro-Wilk 和 Shapiro-Francia 就是绕不开的两个工具。我会把选型理由、手算逻辑、代码复现和踩坑记录一次讲透让你下次看到 p 值时心里有底而不是被一个数字牵着走。2. 两个检验的底层逻辑为什么它们比 K-S 检验更值得用2.1 从“排序后回归”理解检验统计量Shapiro-Wilk 和 Shapiro-Francia 都属于基于次序统计量的正态性检验。把样本从小到大排列成 $x_{(1)} \le x_{(2)} \le \dots \le x_{(n)}$正态分布的理论分位数记作 $m_i \Phi^{-1}((i - 0.375)/(n 0.25))$这是 Blom 近似也是常见实现里的默认做法。如果样本真的来自正态分布那么 $x_{(i)}$ 和 $m_i$ 应该近似落在一条直线上。两个检验都在度量这条直线的“直度”但用的统计量不同。Shapiro-Wilk 的统计量是$$ W \frac{\left(\sum_{i1}^{n} a_i x_{(i)}\right)^2}{\sum_{i1}^{n} (x_i - \bar{x})^2} $$其中 $a_i$ 是一组由样本量和正态分布协方差矩阵推导出来的权重分子是“最优线性组合”的平方分母是总平方和。$W$ 越接近 1越像正态。Shapiro-Francia 则更直接它用平方相关系数$$ W \frac{\left(\sum_{i1}^{n} (m_i - \bar{m})(x_{(i)} - \bar{x})\right)^2}{\sum_{i1}^{n}(m_i - \bar{m})^2 \sum_{i1}^{n}(x_{(i)} - \bar{x})^2} $$也就是排序样本和理论分位数的 Pearson 相关系数的平方。$W$ 同样越接近 1 越正态。区别在于Shapiro-Wilk 的权重 $a_i$ 是经过优化的对尾部更敏感Shapiro-Francia 直接用分位数做相关计算更简单但在小样本下对尾部偏离的敏感度略低。提示很多教材把这两个检验混为一谈实际上它们的零分布不同p 值不能互换使用。2.2 为什么不用 Kolmogorov-Smirnov 或 Anderson-DarlingK-S 检验比较的是经验分布函数和理论分布函数的最大距离它对分布中心敏感但对尾部不敏感。而正态性检验最怕的就是尾部出问题——金融收益率、传感器噪声、用户行为间隔往往都是尾部偏重。Shapiro 系列检验专门针对正态分布设计功效更高。Anderson-Darling 虽然也对尾部敏感但它的临界值依赖分布假设实现起来更麻烦。所以在“只判断正态性”这个任务上Shapiro-Wilk 和 Shapiro-Francia 是更专注的工具。另一个现实原因是R 的shapiro.test()默认就是 Shapiro-WilkPython 的scipy.stats.shapiro也是。Shapiro-Francia 在 R 里需要shapiro.test的变体或nortest包Python 里没有直接内置但可以自己实现。下面我会给出两种语言的复现代码。2.3 样本量边界3 到 5000 的硬约束Shapiro-Wilk 原始版本要求样本量在 3 到 5000 之间。超过 5000 时R 会报错 “sample size must be between 3 and 5000”。这不是随便定的而是因为 $a_i$ 权重表在 n 很大时计算不稳定而且大样本下任何微小偏离都会导致拒绝。Shapiro-Francia 的适用范围更宽理论上可以到几千但同样不建议超过 5000因为大样本下 p 值会变得过于敏感。实际工作中如果样本量超过 5000常见做法是随机抽样到 5000 以内或者改用偏度-峰度检验、Anderson-Darling。但要注意大样本下正态性检验几乎总是拒绝因为真实数据很难完美正态。这时候更应该看效应量比如偏度和峰度的绝对值而不是死磕 p 值。3. 用 Python 和 R 跑通两个检验最小命令与参数说明3.1 Python 实现 Shapiro-Wilk 与手写 Shapiro-FranciaPython 的scipy.stats.shapiro直接给出 W 和 p 值。Shapiro-Francia 需要自己算下面是一个完整可复现的脚本。import numpy as np from scipy import stats # 生成一组模拟数据正态 轻微右偏 np.random.seed(42) normal_data np.random.normal(loc10, scale2, size50) skewed_data np.random.exponential(scale2, size50) 5 def shapiro_francia(x): 计算 Shapiro-Francia 的 W 和 p 值p 值用近似公式 x: 一维数组 返回: (W_prime, p_value) x np.sort(x) n len(x) # Blom 近似理论分位数 m stats.norm.ppf((np.arange(1, n1) - 0.375) / (n 0.25)) m_bar np.mean(m) x_bar np.mean(x) # 分子协方差的平方 numerator (np.sum((m - m_bar) * (x - x_bar))) ** 2 # 分母两个平方和的乘积 denominator np.sum((m - m_bar)**2) * np.sum((x - x_bar)**2) W_prime numerator / denominator # p 值近似基于 Royston 1993 的变换这里用简化版 # 实际使用时建议查表或使用专门包 # 这里仅作演示p 值用正态近似 z (W_prime - 0.98) / 0.02 # 粗略近似不精确 p_value 2 * (1 - stats.norm.cdf(abs(z))) return W_prime, p_value # Shapiro-Wilk W, p stats.shapiro(normal_data) print(f正态数据 Shapiro-Wilk: W{W:.4f}, p{p:.4f}) W2, p2 stats.shapiro(skewed_data) print(f偏态数据 Shapiro-Wilk: W{W2:.4f}, p{p2:.4f}) # Shapiro-Francia Wf, pf shapiro_francia(normal_data) print(f正态数据 Shapiro-Francia: W{Wf:.4f}, p{pf:.4f}) Wf2, pf2 shapiro_francia(skewed_data) print(f偏态数据 Shapiro-Francia: W{Wf2:.4f}, p{pf2:.4f})这段代码的逻辑说明shapiro_francia函数先对数据排序然后计算 Blom 近似分位数m。分子是m和x的协方差平方分母是两者平方和的乘积得到 $W$。p 值部分我用了非常粗略的正态近似实际生产中不要用这个 p 值应该查 Royston 1993 的表格或使用scipy.stats.shapiro的 p 值作为参考。参数方面np.random.seed(42)保证可复现size50是样本量你可以改成 20、100、500 观察变化。运行结果会显示正态数据的 W 接近 0.98p 值大于 0.05偏态数据的 W 明显更低p 值小于 0.05。Shapiro-Francia 的 $W$ 趋势类似但数值略有不同。3.2 R 语言shapiro.test与nortest包R 里 Shapiro-Wilk 是内置的Shapiro-Francia 在nortest包里叫sf.test。# 安装 nortest 包如果还没装 # install.packages(nortest) library(nortest) set.seed(42) normal_data - rnorm(50, mean 10, sd 2) skewed_data - rexp(50, rate 0.5) 5 # Shapiro-Wilk sw_normal - shapiro.test(normal_data) sw_skewed - shapiro.test(skewed_data) print(sw_normal) print(sw_skewed) # Shapiro-Francia sf_normal - sf.test(normal_data) sf_skewed - sf.test(skewed_data) print(sf_normal) print(sf_skewed)shapiro.test返回 W 和 p 值sf.test返回 W 和 p 值。注意sf.test的样本量限制也是 3 到 5000。参数上shapiro.test没有额外参数sf.test也没有。如果你要批量检验多个列可以用apply或purrr::map。注意R 的shapiro.test在样本量小于 3 时会报错大于 5000 也会报错。sf.test同样。3.3 参数怎么设显著性水平、样本量与重复检验显著性水平 $\alpha$ 默认 0.05但在多重检验场景下需要校正。比如你对 20 个特征分别做正态性检验至少有一个假阳性的概率是 $1 - 0.95^{20} \approx 0.64$。这时候应该用 Bonferroni 校正$\alpha/20$或 FDR。样本量方面如果 n 20检验功效很低即使数据不正态也可能不拒绝如果 n 5000几乎总是拒绝。所以我的习惯是n 20 时看 Q-Q 图为主n 在 20 到 5000 之间用 Shapiro-Wilkn 5000 时抽样或看偏度峰度。4. 避坑与排查正态性检验的 5 个血泪教训4.1 现象p 值刚好在 0.05 附近结论反复横跳原因Shapiro-Wilk 的 p 值在 0.05 附近对样本量极其敏感。增加或减少几个样本p 值可能从 0.048 跳到 0.052。这不是 bug而是检验本身的特性。解决不要只看 p 值。同时报告 W 统计量和 Q-Q 图。如果 W 在 0.98 以上即使 p 值略小于 0.05也可以认为近似正态。如果 W 低于 0.95即使 p 值大于 0.05也要警惕。4.2 现象数据有大量并列值Shapiro-Wilk 报错或结果异常原因Shapiro-Wilk 假设连续分布并列值会破坏次序统计量的独立性。比如问卷的 1-5 分李克特量表大量重复值会导致 W 被高估p 值偏大。解决对并列值多的数据改用 Shapiro-Francia 或 Anderson-Darling。或者先做 ties 校正。R 的shapiro.test在并列值多时会给出警告 “ties should not be present”。4.3 现象样本量 5001R 直接报错原因shapiro.test硬编码了 3 到 5000 的限制。解决随机抽样到 5000 以内或者改用nortest::ad.testAnderson-Darling或moments::agostino.test偏度检验。抽样时用set.seed保证可复现。4.4 现象Shapiro-Wilk 和 Shapiro-Francia 结论相反原因两个检验对尾部的敏感度不同。Shapiro-Wilk 对尾部更敏感Shapiro-Francia 对中心更敏感。如果数据尾部有离群值但中心很直Shapiro-Wilk 会拒绝Shapiro-Francia 可能不拒绝。解决结合 Q-Q 图判断。如果 Q-Q 图两端偏离但中间直说明尾部有问题这时候用稳健统计方法或考虑 t 检验的替代方案如 Wilcoxon。4.5 现象对残差做正态性检验p 值很大但模型预测很差原因正态性检验只检查残差分布是否正态不检查线性、同方差、独立性。残差正态但模型欠拟合的情况很常见。解决正态性检验只是回归诊断的一环。还要看残差-拟合值图、Scale-Location 图、Durbin-Watson 检验。不要因为残差正态就认为模型没问题。5. 进阶技巧用模拟评估检验功效与样本量规划5.1 用 Monte Carlo 模拟比较两个检验的功效想知道在特定样本量和偏离程度下哪个检验更容易检出非正态可以跑一个简单的模拟。import numpy as np from scipy import stats def power_comparison(n, n_sim1000, alpha0.05): 比较 Shapiro-Wilk 和 Shapiro-Francia 在指数分布下的功效 n: 样本量 n_sim: 模拟次数 alpha: 显著性水平 sw_reject 0 sf_reject 0 for _ in range(n_sim): # 指数分布明显非正态 data np.random.exponential(scale1.0, sizen) # Shapiro-Wilk _, p_sw stats.shapiro(data) if p_sw alpha: sw_reject 1 # Shapiro-Francia用前面的函数 _, p_sf shapiro_francia(data) if p_sf alpha: sf_reject 1 return sw_reject / n_sim, sf_reject / n_sim for n in [10, 20, 50, 100]: sw_power, sf_power power_comparison(n) print(fn{n}: Shapiro-Wilk 功效{sw_power:.3f}, Shapiro-Francia 功效{sf_power:.3f})这段代码模拟指数分布数据重复 1000 次计算两个检验拒绝原假设的比例。参数n_sim越大越稳定但耗时越长。alpha是显著性水平。运行后你会看到小样本n10时两者功效都低n50 时 Shapiro-Wilk 略高n100 时两者都接近 1。这能帮你判断在给定样本量下检验是否能可靠检出非正态。5.2 样本量规划需要多少数据才能检出偏离反过来如果你希望以 80% 的功效检出某种程度的偏离需要多大样本可以用模拟反推。常见做法是设定效应量比如偏度 0.5然后逐步增加 n直到功效达到 0.8。这个模拟比查表更灵活因为你可以针对自己的数据分布做。5.3 一个实用习惯永远画 Q-Q 图我做了这么多年数据最深的教训就是不要只信 p 值。p 值告诉你“是否偏离”但不告诉你“偏离多大”和“偏离在哪”。Q-Q 图能直接看到尾部、中心、离群点。我的习惯是先画 Q-Q 图再跑 Shapiro-Wilk如果两者矛盾以 Q-Q 图为准。如果 Q-Q 图显示尾部有问题我会考虑用稳健方法或数据变换而不是硬做正态假设。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站