正态性检验不止p值:QQ图与PP图图形诊断实战
2026/9/17 11:15:28 网站建设 项目流程

正态性检验这四个字,在很多人的工作流里就是一个按钮:点一下,弹出 p 值,大于 0.05 就继续走正态那条路,小于 0.05 就转头去找非参数方法。我刚开始做数据分析那两年也是这么干的,直到有一次拿着三百多个样本的工业测量数据跑出来 p=0.03,被同事一句"你看过 QQ 图吗"问住——把正态性检验里的QQ图画出来一看,点几乎贴着直线,只有两个疑似录入错误的极端值把尾巴拽了出去。删掉那两个点,p 值回到 0.4。如果当时我只看数值检验的结论,就会把一套本来合适的正态模型白白换掉。

QQ图(Quantile-Quantile Plot)和PP图(Probability-Probability Plot)是图形化正态性诊断里的两把主力工具,它们和 Shapiro-Wilk 这类数值检验不是替代关系,而是互补关系:数值检验告诉你"偏离是否超出了随机波动",图形告诉你"偏离长什么样、发生在哪里、值不值得处理"。这篇文章会从这两张图的构造原理讲起,把位置参数的选择、参数估计带来的偏差、敏感区差异、常见伪形这些坑一个个拆开,再给出 Python 和 R 两套可以立刻跑的代码。适合已经会调shapiro.test但看到 p 值不知道下一步该干什么的人,也适合需要把正态性假设写进报告、被评审追问细节的人。

1. 正态性检验里,为什么图形诊断比 p 值更值得先看

1.1 正态假设到底出现在哪些环节

很多人把正态性检验当成一道"准入门槛",其实它出现在远比想象中多的位置,而且每一处对偏离的容忍度都不一样。t 检验和方差分析关心的是残差的正态性,不是原始因变量的正态性,这是被误解最多的一条;线性回归的推断同样依赖残差;而像正态判别分析、某些过程能力指数(Cp/Cpk)的计算,依赖的是原始变量的正态性。混淆这两者,会让检验对象直接选错。

再看用途:如果只是做点估计,样本均值的抽样分布依中心极限定理在大样本下会趋近正态,原始数据偏一点问题不大;但如果要做预测区间或者容忍区间,尾部的分位数会被直接使用,此时尾部的形状误差会被原样放大到区间宽度上;如果是做质量控制的控制图,±3σ 之外的判异规则完全建立在对尾部的假设上。

这就是为什么图形诊断必须放在前面。数值检验只吐出一个标量,你无法知道这个标量是被中部贡献的,还是被某个孤立点贡献的。而 QQ 图会直接把贡献者的位置指给你看。我在实际项目里的习惯是:先画图建立"偏离的形态假设",再用数值检验去验证这个假设,而不是反着来。

1.2 数值检验的三条软肋

第一条软肋是样本量的双重绑架。样本很小(比如 n<20)时,Shapiro-Wilk 的功效低得可怜,明明数据来自指数分布也可能判你"正态";样本很大(比如 n>5000)时,功效又高得离谱,0.5% 的形状偏差都能被揪出来判显著,而这个量级的偏差对后续分析基本没有实质影响。同一个 p 值阈值,在小样本和大样本下的含义完全不同。

第二条是备择假设的模糊性。检验的原假设是"服从正态",备择假设是"不服从正态"——这是个包罗万象的补集,包括偏态、厚尾、双峰、截断、离散化等等。检验显著了,但你不知道往哪个方向修。想换对数变换?那是赌偏态。想换秩检验?那是放弃参数解释。图形能把这个方向指出来。

第三条是对局部异常的不设防。一个量级离谱的录入错误,可能让 p 值直接归零,但整批数据主体其实非常正态。数值检验不会告诉你"问题出在第 287 号观测"。而 QQ 图上那个孤零零飘在右上角的点,一眼就能看见。

1.3 QQ图与PP图在一次诊断中的分工

这两张图的坐标轴系统不一样,因而"视野焦点"也不一样。QQ 图横轴是理论分位数、纵轴是样本分位数,横轴在两端被拉伸得很开,所以两端的一个点会被放到很远的位置,尾部的任何异常都会被放大。PP 图横轴是理论累积概率、纵轴是经验累积概率,两个轴都被压在 [0,1] 区间里,两端的数据会被挤成密密麻麻的一小簇,中部的形态变化反而更清楚

一个很实用的经验:如果你怀疑数据有厚尾、离群点、或者需要检查尾部(比如做极值相关的工作、算容忍区间),QQ 图是首选;如果你怀疑分布在腰部有系统性变形(比如混合了两个相近的总体,导致 CDF 中部出现台阶),PP 图更容易看出来。

提示:两张图不要二选一。我的固定流程是同时画,先看 QQ 图定尾部,再看 PP 图定腰部,两边结论不一致时,说明偏离形态比较复杂,需要回到直方图或者核密度估计去确认。

理解了分工,接下来就该把这两张图的坐标到底怎么算出来的讲透。很多人画了几年图,却说不清纵轴上的第 5 个点为什么对应那个数值,一旦要和别人对齐结果就抓瞎。

2. QQ图的构造原理:把分位数放到同一把尺子上

2.1 从排序后的样本到理论分位数

QQ 图的纵轴是样本分位数,算法很直白:把 n 个观测值从小到大排序,得到次序统计量 x₍₁₎ ≤ x₍₂ ≤ … ≤ xₙ₎,第 i 个点的纵坐标就是 x₍ᵢ₎(标准化之后就是 z₍ᵢ₎)。横轴是该点"应该"处在的位置——如果数据真的服从指定分布,第 i 个次序统计量对应的理论分位数应该是多少。

问题的关键在于:x₍ᵢ 对应的累积概率究竟是多少?直觉上有人会说 i/n,但这是错的。举个极端例子,n=10 时最大的那个点,它的累积概率不可能是 1,因为你只抽了 10 个样本,样本最大值在真实分布里几乎肯定不是上界。用 i/n 会让最大点被放在横轴的无穷远处,图直接废掉。

正确的思路是:把次序统计量经过概率积分变换后得到 U₍ᵢ₎ = F(X₍ᵢ₎),这是均匀分布 U(0,1) 的次序统计量。它的期望是 E[U₍ᵢ₎] = i/(n+1),中位数近似是 (i-1/3)/(n+1/3)。于是常见的做法是用

pᵢ = (i - a) / (n + 1 - 2a)

作为概率的估计值,再取理论分位数 Q(pᵢ) = μ + σ·Φ⁻¹(p) 作为横坐标。a 就是所谓的位置参数,取值在 0 到 0.5 之间,本质是在"用期望做估计"和"用中位数做估计"之间找一个平衡点。

2.2 位置参数怎么选:Blom、Hazen 与 Weibull

位置参数看着是个无关紧要的细节,但它直接决定了两端点的落位,样本量小的时候肉眼可辨。下面是几个最常见的取值,我把它们的公式和特征列在一起:

名称参数 a概率公式特点与常见场景
Weibull0i/(n+1)最保守,把端点推得最远,小样本下尾部容易显得"偏厚"
Blom3/8 = 0.375(i-0.375)/(n+0.25)正态分布下近似无偏,理论和实践都推荐
Hazen1/2(i-0.5)/n最简单直观,大样本下与 Blom 差别可忽略
Filliben中位数近似端点用 1-0.5^(1/n) 修正对端点单独处理,部分软件默认

拿一组 n=30 的数据算一下就有感觉了。用 Blom:第一个点的概率是 (1-0.375)/(30+1-0.75) = 0.625/30.25 ≈ 0.02066,查标准正态分位数约等于 -2.040;用 Weibull:第一个点的概率是 1/31 ≈ 0.03226,对应分位数约 -1.849。两者差了将近 0.2 个标准差。这意味着如果你的数据和别人的图对不上,第一件要检查的事就是位置参数是不是同一个。

R 的ppoints()函数默认规则是:n ≤ 10 时用 a = 3/8,n > 10 时用 a = 1/2,而qqnorm()内部调用的就是它。SciPy 的probplot在 n > 10 时用的是 a = 0.375 的 Blom 式变体,n ≤ 10 时走 Filliben 的中位数修正。所以严格来说,R 和 Python 在中等样本量下画的 QQ 图点位置是有细微差异的,肉眼看不出来,但做严密对比时要知道这件事。

注意:位置参数只影响横轴的落位,不影响你图的整体形状判断。但如果样本量在 10 到 50 之间,又想和别人复现完全一致的图,把位置参数写死成 Blom 是最省事的选择。

2.3 一条直线背后的参数含义

QQ 图上的参考线是 y = μ + σx 这条直线(数据标准化后就退化成 45 度线)。这条线不是随便画的,它同时承载了位置参数和尺度参数的信息:截距刻画均值,斜率刻画标准差。当点整体平行于直线但偏离时,说明数据的位置或尺度与假设不符;当点弯曲成弧形时,说明形状(偏度、峰度)不符。

这里有一个常被忽略的实操细节。R 里qqline()画的不是 45 度线,而是连接第一和第三四分位点确定的直线,也就是用四分位数去稳健地估计 μ 和 σ。为什么不用普通最小二乘拟合整条线?因为 LS 会被尾部的离群点带偏,而四分位数只依赖中间 50% 的数据,稳健得多。如果你已经做过标准化,那 45 度线就是对的;如果没标准化,务必用qqline()或者自己按四分位数算线,不要直接abline(0, 1),那是画错的。

再补一个判读上的经验:参考线附近的点不代表"拟合良好",它只代表"在直线模型下残差小"。真正需要关注的是偏离的方向和集中位置。只有一两个点偏离,多半是异常值;一小段连续的坡度变化,说明某个区间的分布形状不对;两端对称地向外张开,是峰度问题。

3. PP图的构造原理:概率轴上的逐点对照

3.1 经验分布函数与理论 CDF 的对齐

PP 图的思路和 QQ 图正好相反。它把横轴设为理论累积分布函数值 F(x₍ᵢ),纵轴设为经验累积分布函数值 Fₙ(x₍₎)。经验 CDF 的定义是

Fₙ(t) = (落在 t 左侧的样本数) / n

对第 i 个次序统计量来说,这个值就是 i/n,或者更稳妥地用位置参数写成与 QQ 图一致的 pᵢ = (i-a)/(n+1-2a)。纵轴就这么固定下来了,n 个点在纵轴上等间距排开,间距约等于 1/(n+1)。

横轴则取决于你假设的分布。以正态为例,先把数据用样本均值和样本标准差标准化,得到 z₍ᵢ₎,横坐标就是 Φ(z₍ᵢ)——注意用的是cdf而不是ppf。这是 QQ 图和 PP 图在实现层面最直观的差别:QQ 图要分位数函数,PP 图要累积分布函数

对于有闭式 CDF 的分布(正态、指数、Weibull、Gamma 等),两者都好办;但对于一些参数估计后没有简单闭式分位数的分布,PP 图在实现上反而更省事。反过来说,某些分布的分位数函数好算而 CDF 难算,那就优先 QQ 图。

3.2 两张图的数学关系:一个是逆函数,一个是分布函数

如果从纯数学角度描述,给定同一组数据、同一个假设分布:QQ 图绘的是 (Q(pᵢ), x₍ᵢ) 这一组点对,PP 图绘的是 (F(x₍ᵢ₎), pᵢ) 这一组点对。把 QQ 图的横轴做一次 F 变换,得到 (pᵢ, xᵢ₎);把纵轴做一次同样变换,得到 (pᵢ, pᵢ),全线落在一个点上——这就是为什么不能简单地通过对 QQ 图做坐标变换得到 PP 图,因为变换后的信息分布结构完全不同。

更实用的理解方式是看点密度。PP 图的纵轴是均匀分布的,n 个点在整个 [0,1] 区间上等间距分布;但横轴是 CDF 值,数据在中间密集、两端稀疏。所以 PP 图上,靠近 0 和 1 的两端会被一堆点挤成一条窄带,而中间区域点很稀。QQ 图恰好相反:横轴是分位数,两端被拉得很开,中间点很密。

这个密度差异直接决定了判读策略。QQ 图两端稀疏意味着单个点的偏离很显眼,适合抓离群点;PP 图两端拥挤意味着单个点的偏离被淹没,但中间的稀疏使得腰部的一点走向异常很容易被看出

3.3 敏感区差异:QQ 看尾巴,PP 看腰身

把结论说清楚:如果数据分布是标准正态,但混入了一个均值相同、标准差更大的正态分量(比如测量中混入了约 20% 的粗差),形成的分布是厚尾的。在 QQ 图上,两端的点会明显外扩,一眼可见;在 PP 图上,因为尾部的点被挤在接近 0 和 1 的地方,外扩的绝对量很小,看起来只是稍微弯一点,很容易漏判。

反过来,如果数据是两个均值相差 2 个标准差、方差相同的正态混合,QQ 图的中间部分会呈现一个明显的 S 形,但两端反而是直的;PP 图则因为 CDF 中部斜率最大,混合造成的台阶在中部被放大成一条清晰的 S 形凹陷,比 QQ 图更醒目。

有一个更严谨的联系值得记住:Kolmogorov-Smirnov 统计量就是 PP 图上点到对角线的最大垂直距离。因为 D = sup|Fₙ(x) − F(x)|,而 PP 图的纵坐标减横坐标恰好是 Fₙ − F。这就解释了为什么 K-S 检验对中部敏感——CDF 中部的斜率最大,同样的数据偏差在中部转换成的 CDF 差异最大。而 Anderson-Darling 统计量在 K-S 的基础上加了 1/[F(1−F)] 这个权重,把权重往两端推,所以它对尾部更敏感。

理解了这层关系,你会发现图形和数值检验其实是在描述同一件事,只是取了不同的"切片"。

4. 动手实现:Python 与 R 两套可复现代码

4.1 Python 手写 QQ 图与 PP 图

生产环境里我更推荐自己写,因为这样位置参数、参数估计方式、参考线全都可控。下面这段代码从头到尾只用 NumPy、SciPy 和 Matplotlib:

import numpy as np import scipy.stats as st import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["DejaVu Sans"] rng = np.random.default_rng(20240501) # 故意造一份右偏数据:Gamma(shape=2, scale=1),样本量 300 x = rng.gamma(shape=2.0, scale=1.0, size=300) def make_points(x, a=0.375): xs = np.sort(np.asarray(x, dtype=float)) n = xs.size # 位置参数:Blom p = (np.arange(1, n + 1) - a) / (n + 1 - 2 * a) mu, sd = xs.mean(), xs.std(ddof=1) z = (xs - mu) / sd # 标准化的样本分位数 q_theory = st.norm.ppf(p) # QQ 图横轴:理论分位数 f_theory = st.norm.cdf(z) # PP 图横轴:理论 CDF return z, q_theory, p, f_theory, mu, sd z, q_theory, p, f_theory, mu, sd = make_points(x) print(f"mu={mu:.4f} sd={sd:.4f} n={z.size}") fig, ax = plt.subplots(1, 2, figsize=(11.5, 4.8)) # QQ 图 ax[0].plot(q_theory, z, "o", ms=4, alpha=0.7, color="steelblue") ax[0].plot([-3, 3], [-3, 3], "r-", lw=1.2) ax[0].set_xlim(-3, 3); ax[0].set_ylim(-3, 3) ax[0].set_xlabel("theoretical quantiles N(0,1)") ax[0].set_ylabel("sorted sample (standardized)") ax[0].set_title("QQ plot") # PP 图 ax[1].plot(f_theory, p, "o", ms=4, alpha=0.7, color="darkorange") ax[1].plot([0, 1], [0, 1], "r-", lw=1.2) ax[1].set_xlim(0, 1); ax[1].set_ylim(0, 1) ax[1].set_xlabel("theoretical CDF") ax[1].set_ylabel("empirical CDF (plotting positions)") ax[1].set_title("PP plot") plt.tight_layout() plt.show()

跑完你会发现 Gamma 数据在 QQ 图上是一条向上弓起的弧线:左端低于 45 度线,右端明显翘到线上方。这就是典型的右偏形态。而 PP 图上,点整体落在对角线的下方,靠近右端时贴得更紧——这是因为 PP 图把尾部的差距压缩了。

如果不想手写,SciPy 和 statsmodels 都有现成的封装:

from scipy import stats import matplotlib.pyplot as plt res = stats.probplot(x, dist="norm", plot=plt) plt.title("scipy.stats.probplot") # statsmodels 版本,支持 QQ 与 PP 两种 import statsmodels.api as sm pp = sm.ProbPlot(x, dist=stats.norm) fig = pp.qqplot(line="45") fig = pp.ppplot(line="45")

statsmodels.ProbPlotfit=True默认会用样本均值和标准差估计参数,与上面手写版本口径一致,可以直接对照验证。

4.2 加一层模拟置信包络

只画参考线的最大问题是没有"随机波动的尺度感"。样本量 300 的时候,点偏离直线 0.1 个标准差完全正常;样本量 20 的时候,偏离 0.5 个标准差都可能只是运气。解决办法是模拟:从标准正态里反复抽样、标准化、排序,取每个位置参数点上的 2.5% 和 97.5% 分位数作为包络带。

def normal_envelope(n, a=0.375, B=3000, seed=7): rng = np.random.default_rng(seed) p = (np.arange(1, n + 1) - a) / (n + 1 - 2 * a) zs = np.sort(rng.standard_normal((B, n)), axis=1) lo, hi = np.percentile(zs, [2.5, 97.5], axis=0) return st.norm.ppf(p), lo, hi q_t, lo, hi = normal_envelope(z.size) fig, ax = plt.subplots(figsize=(6.2, 5.2)) ax.fill_between(q_t, lo, hi, color="grey", alpha=0.25, label="95% envelope") ax.plot(q_t, z, "o", ms=4, alpha=0.75, color="steelblue") ax.plot([-3.6, 3.6], [-3.6, 3.6], "r-", lw=1.2) ax.set_xlim(-3.6, 3.6); ax.set_ylim(-3.6, 3.6) ax.legend() plt.show()

有了包络带,判读就从"像不像直线"升级为"有多少点跑出了带子、跑出去多远"。这比数值检验的 p 值直观得多,也更容易向非技术同事解释。

4.3 R 版本:qqnorm 与自定义 PP 图

R 基础绘图里qqnorm+qqline是经典组合,但缺一个现成的 PP 图函数,自己写十几行就够了:

set.seed(20240501) x <- rgamma(300, shape = 2, scale = 1) z <- (x - mean(x)) / sd(x) par(mfrow = c(1, 2)) # QQ 图 qqnorm(z, pch = 19, cex = 0.6, col = "steelblue", main = "Normal QQ plot", xlim = c(-3, 3), ylim = c(-3, 3)) abline(0, 1, col = "red", lwd = 2) # 已标准化,可直接用 45 度线 qqline(z, col = "darkgreen", lwd = 2, lty = 2) # 四分位稳健线 # PP 图 n <- length(z) p_emp <- ppoints(n) # 与 qqnorm 一致的位置参数 f_theo <- pnorm(sort(z)) # 理论 CDF 在排序样本处的取值 plot(f_theo, p_emp, pch = 19, cex = 0.6, col = "darkorange", xlim = c(0, 1), ylim = c(0, 1), xlab = "Theoretical CDF", ylab = "Empirical CDF", main = "Normal PP plot") abline(0, 1, col = "red", lwd = 2) par(mfrow = c(1, 1))

qqlineabline(0,1)同时画出来是刻意为之:数据已经标准化了,两条线理论上应该重合;如果肉眼看到明显分离,说明样本的分布形状让四分位斜率偏离了整体斜率,这本身就是一条线索。实测下来,Gamma 数据在这张图上两条线会分开一小截,右侧尾部越厚,分开得越明显。

5. 图形读法:典型形态、成因与处置

5.1 尾部形态:厚尾、薄尾与单侧翘起

判断尾部形态只看两端。两端点同时向外侧偏离直线,中间贴合,是厚尾(峰度大于 3)。成因通常是数据里混入了少量极端观测,或者底层分布本身就是 t 分布、拉普拉斯分布这类重尾分布。处置方式取决于业务:如果是录入错误就清掉,如果极端值是真实的高价值样本,就该考虑稳健统计量或者换用对尾部不敏感的模型。

两端点同时向内侧收敛,中间贴合,是薄尾。最常见的原因是数据被截断或者被加了上下限(比如仪器在量程边界会溢出成固定值),也可能数据本身就来自均匀分布附近的形状。薄尾对均值的推断影响不大,但会让 t 检验的置信区间偏保守,实际覆盖率高于名义水平。

只有一端翘起的是单侧偏离。右端翘、左端正常,常见于存在上限截断的数据;左端翘、右端正常,常见于存在下限截断,比如响应时间被计时器最小分辨率截断。这种形态在业务上往往有明确的物理原因,比厚尾薄尾更容易解释。

5.2 偏态与多峰的辨识

把直线想象成一根绷紧的弦,如果点组成的弧线向上弓起(左端沉在弦下,右端翘到弦上),是右偏,也叫正偏。对数正态、指数、Gamma、以及收入、耗时、金额这类不能为负的变量基本都是这个形态。如果弧线向下弓起(左端翘在线 上,右端沉在弦下),是左偏

右偏数据的最直接处置是取对数再看一次 QQ 图。如果取完对数后点贴合直线,说明数据的对数近似正态,这往往比硬做 Box-Cox 更有业务解释力(比如对数后的值就是增长率)。要注意对数变换在 0 附近会出问题,遇到零值需要先加一个常数。

多峰在 QQ 图上不太好认,通常表现为分段不同的斜率,像几段直线拼接在一起,而不是平滑的弧线。在 PP 图上,多峰会在中部的 CDF 曲线里形成一段局部的平台或陡坡。如果 QQ 图上看到明显的折点,别急着归因于分布形状,先查一下数据是不是混合了两个批次、两个设备或者两个时间段的样本。我遇到过最典型的一次,是某台设备在凌晨自动校准,前后两批数据的均值差了 1.5 个标准差,QQ 图折点非常清晰。

5.3 阶梯、断层与离群点

阶梯状的水平段出现在 QQ 图上,通常只有一个原因:大量重复值。离散数据、评分数据、只保留两位小数的测量记录都会这样。比如一份 1 到 5 分的满意度调查,排序后你会看到一条水平线从 -1.5 一直延伸到 +0.8,因为中间上千个样本的值都是 3。这种数据本来就不该用正态去拟合,用 QQ 图判断"是正态"或"不是正态"都没有意义,直接考虑有序回归或者二项类模型。

PP 图上重复值的表现是垂直的一列点,同一横坐标上叠着好几个点。因为纵轴是固定的等间距位置,横轴(理论 CDF)会因为相同的样本值而重合。这个特征比 QQ 图的水平阶梯更容易辨认。

离群点在 QQ 图上永远是右上方或左下方那几个孤立点。判断方法很简单:把最极端的一个点去掉重画,如果整体形态没变,那就是一个孤立异常;如果形态明显改善,说明这个点背后可能有系统性问题,需要往回追数据来源。记住一点,QQ 图不能告诉你某个点是错的,它只能告诉你这个点与其他点在正态假设下不兼容。

5.4 形态、成因与处置速查表

QQ 图形态分布特征常见成因建议处置
整体贴合直线近似正态直接使用参数方法
两端外扩,中间贴线厚尾极值混入、t 分布类检查极值来源,考虑稳健方法
两端内收,中间贴线薄尾截断、量程限制检查数据边界,区间宽放
弧线向上弓起右偏对数正态、指数类尝试对数变换后复查
弧线向下弓起左偏负偏分布、天花板效应尝试幂变换或镜像对数
明显折点、分段斜率混合分布多批次、多设备分组后分别检验
水平阶梯离散化严重评分、计数数据放弃正态假设,换模型
个别点远离存在离群点录入错误或真实极端值溯源后决定保留或剔除

这张表在实际沟通里非常好用,尤其是给不熟悉统计的同事解释"为什么我说这个 p 值没用"。

6. 实操踩坑与常见问题排查

6.1 参数估计带来的自我美化效应

这是个在一线特别容易被忽略的坑。当你用样本均值和标准差去构造理论分位数时,你实际上已经让假设分布"尽力贴合"这批数据了,因此 QQ 图上的点会比真实情况更接近直线。用专业一点的说法,参数估计引入了两个约束,图形自由度减少了 2。在小样本下这种美化效果相当明显,n=15 的时候,即使数据来自一个轻微偏态分布,用估计参数画出来的 QQ 图也可能看起来挺直。

规避方法有两个:一是用 4.2 节的模拟包络带,把参数估计的影响模拟进去;二是在样本量足够时,用一半数据估参数、另一半画图。后一种做法更严格,但会损失一半样本的信息,实际项目中我一般只在需要出具正式结论时才用。

注意:很多人报告里的 QQ 图是"用估计参数 + 45 度线"的组合,这种图永远看起来不错。看别人的图时,先确认他的参数是估计的还是预设的。

6.2 样本量与检验功效的拉扯

n<20 时不要指望 QQ 图能判断什么。这个样本量下,任何单点的随机波动都会让图看起来歪七扭八,判读的可靠性极低。我一般的处理是:n<20 就别做正式的正态性判断,直接选稳健方法,或者把结论的置信度明确标低。

n 在 30 到 200 之间是 QQ 图最好用的区间,点足够密能看到形状,单点的随机扰动又不至于淹没信号。n>2000 之后,图形上的细节差异会被放得很大,此时应该把判读重点从"是否偏离"转到"偏离的效应量有多大"——比如尾部两个点偏离直线 0.3 个标准差,这个量级对后续的 t 检验基本没有影响,可以忽略。

还有一个具体经验:不要因为 n=5000 时 Shapiro-Wilk 报出 p=0.001 就惊慌失措,去看一眼 QQ 图,如果点几乎都在包络带内,那这个显著性纯粹是样本量堆出来的。

6.3 重复值与离散数据的伪形

前面在 5.3 提过,这里补充一个排查技巧。当你在 QQ 图上看到阶梯,第一件事是算一下唯一值比例len(np.unique(x)) / len(x)。如果这个比例低于 0.5,说明重复值已经严重到影响图形判读,此时正态性检验的结果基本没有参考价值。评分数据、计数数据、以分为单位记录的金额、以及被量化到固定档位的传感器读数,都属于这一类。

另一个容易混淆的现象是"边界堆积"。如果数据在某个值上有一个巨大的峰(比如 0 值占比 40%),QQ 图会出现一个异常长的水平段加上一个陡峭的上升段。这种情况常见于用户行为数据,也常见于检测限以下的测量结果被记为固定值。处置办法是先做零膨胀建模的两阶段拆分,而不是对着 QQ 图琢磨怎么变换。

6.4 和 Shapiro-Wilk、K-S、AD 怎么配合用

图形和数值检验的配合有固定套路:图形定形态,数值定显著性,最后回到图形看效应量。下面是常见检验方法的适用条件对照:

方法适用样本量敏感区域使用要点
Shapiro-Wilk3 ~ 5000整体,尾部较好小样本功效最高,首选
Anderson-Darling不限尾部参数估计后需修正临界值
K-S (Lilliefors 修正)大样本中部与 PP 图对应,需修正临界值
D'Agostino K²n > 100偏度 + 峰度只抓三、四阶矩
Jarque-Bera大样本偏度 + 峰度对厚尾敏感
Cramér-von Mises不限整体对中部和尾部均衡

实操中我基本是固定的两步:先shapiro出 p 值(n 超过 5000 就换 AD 或 D'Agostino),同时画 QQ 图和 PP 图。p 值显著但图形贴合,判"实质正态";p 值不显著但图形明显弯曲,也要警惕,很可能是样本量不够导致检验没抓到。两者冲突时,永远以图形加上业务判断为准,因为检验只是给出了一个概率,而图形给出了偏离的具体模样。

提示:做 K-S 检验时,如果参数是从数据里估出来的,必须用 Lilliefors 修正后的临界值,否则 p 值会严重偏大,把非正态判成正态。这是文献里被反复强调、但代码里最容易漏掉的一步。

6.5 一个反直觉的提醒:别把正态性检验当准入门槛

这点可能和很多教材的说法不一致。大量模拟研究表明,先做正态性检验、再根据结果选择 t 检验还是秩检验的两阶段流程,会破坏最终检验的第一类错误控制——因为秩检验的选择本身是数据驱动的。而且 t 检验对正态偏离的稳健性比想象中好,n>30 且没有极端离群时,即使数据轻中度偏态,t 检验的实际水平也接近名义水平。

所以我在实际工作中的定位是:正态性诊断是用来理解数据形状判断尾部风险的,不是用来当开关的。真正决定方法选择的,是样本量、偏离的具体形态,以及结论对尾部有多敏感。QQ 图和 PP 图在这里的价值,恰恰是让你看清楚"偏离到底是什么样的",从而做出有依据的判断,而不是被一个 p 值牵着走。

7. 一套可以固定下来的诊断流程

7.1 从画图到结论的六步走

第一步,确定检验对象。是做残差的正态性还是原始变量,这一条决定了后面所有步骤。

第二步,算基本描述。样本量、唯一值比例、偏度、峰度、最小值最大值。唯一值比例低于 0.5 的话,直接跳到第 5.3 节的结论,别浪费时间去画图。

第三步,同时画 QQ 图和 PP 图,QQ 图叠 95% 模拟包络带,位置参数统一用 Blom 并记录下来。

第四步,跑一个数值检验,n ≤ 5000 用 Shapiro-Wilk,超过就用 D'Agostino K² 或者 Anderson-Darling。

第五步,对照第 5.4 节的速查表,把图形形态翻译成具体成因假设,然后回到数据验证这个假设(查批次、查设备、查录入)。

第六步,给出结论时同时报告三件事:偏离的方向、偏离的幅度(用标准化尺度衡量,别只写"显著")、以及对后续分析的实际影响。

7.2 报告和交接文档里怎么写

我在项目文档里通常写这样一段话的变体:"对变量 X(n=312)的正态性做图形与数值双重诊断。QQ 图显示右偏,右端三个观测偏离参考线约 1.2 个标准差,落在 95% 模拟包络带之外;对应对数变换后 QQ 图贴合(最大偏离 0.3 个标准差)。Shapiro-Wilk 检验原始尺度 p<0.001,对数尺度 p=0.21。后续分析采用对数尺度。"

这种写法有三个好处:一是把偏离量化和定位,别人可以复核;二是给出了变换的效果对比,不是一句"做了变换";三是结论直接对接下一步动作,评审不会追问"所以你最后用了什么"。

千万不要只写"经检验数据服从正态分布(p>0.05)"。这句话既没告诉你用什么方法检验的,也没告诉你样本量多大,更没说明如果换一种检验会不会翻盘。真正有价值的诊断描述一定是图形加数值加处置,三位一体。

最后分享一个我自己踩过的坑:有次为了赶进度,用一份 n=18 的小样本数据画了 QQ 图,看到点大致贴着线,就直接按正态做了区间估计。后来加了 40 个样本重跑,QQ 图右端明显翘起,之前那个区间估计的右边界被严重低估。从那以后,只要样本量低于 30,我都会在结论里明确标注"图形判读可靠性有限,建议以稳健方法为准"。这个习惯看起来啰嗦,但省下来的返工时间远比标注的那几行字值钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询