# 假设检验

*假设检验提供了一个严谨的框架，用于判断观测到的效应是真实存在的还是由随机因素造成的。本文涵盖原假设与备择假设、p值、显著性水平、t检验、卡方检验、方差分析以及第一类/第二类错误——这些逻辑同样应用于A/B测试、模型比较和研究中。*

- 统计学不仅仅是对数据进行描述。你经常需要做出决策：新药是否有效？某个算法是否比另一个更快？平均值是否发生了变化？假设检验为你提供了一个基于数据回答这些问题的结构化框架。

- 其思路很简单：假设没有任何变化（"原假设"），然后检验数据是否极端到让这个假设难以令人置信。

- **原假设**（$H_0$）是默认的主张，通常表述为"无效应"或"无差异"。例如："平均配送时间仍为30分钟"或"新模型并不比旧模型更好"。

- **备择假设**（$H_1$ 或 $H_a$）是你认为可能成立的替代情况："平均配送时间发生了变化"或"新模型更好"。

- 你永远无法直接证明 $H_1$。相反，你提出这样一个问题：如果 $H_0$ 成立，观察到如此极端的数据的可能性有多大？如果这种可能性非常小，你就拒绝 $H_0$，转而接受 $H_1$。

- **检验统计量**是一个单一数值，它概括了你的样本结果与 $H_0$ 预测值之间的偏差程度。不同的检验使用不同的公式，但逻辑始终一致：度量观测值与期望值之间的距离。

- **p值**是在假设 $H_0$ 成立的前提下，观察到至少与当前检验统计量一样极端的结果的概率。p值越小，意味着在 $H_0$ 下数据越令人意外。

- **显著性水平**（$\alpha$）是你在看到数据之前设定的阈值。如果 $p \le \alpha$，则拒绝 $H_0$。常用的选择有 $\alpha = 0.05$（5%）和 $\alpha = 0.01$（1%）。

![正态曲线，阴影区域为拒绝域，标出了检验统计量，并高亮了p值区域](../images/hypothesis_test.svg)

- 阴影尾部即拒绝域。如果你的检验统计量落在此区域，说明在 $H_0$ 下数据足够极端，你拒绝 $H_0$。绿色区域显示了某个特定检验统计量对应的p值。

- 以下是逐步流程：
    - **第1步**：提出 $H_0$ 和 $H_1$
    - **第2步**：选择显著性水平 $\alpha$
    - **第3步**：收集数据并计算检验统计量
    - **第4步**：计算p值（或将检验统计量与临界值比较）
    - **第5步**：如果 $p \le \alpha$，拒绝 $H_0$；否则，无法拒绝 $H_0$

- **实例演练**：某工厂声称其螺栓的平均长度为10 cm。你测量了36个螺栓，得到样本均值为10.3 cm。已知总体标准差为0.9 cm。是否有证据表明均值发生了变化？

- $H_0$：$\mu = 10$，$H_1$：$\mu \neq 10$，$\alpha = 0.05$

- 检验统计量（z检验，因为 $\sigma$ 已知且 $n$ 较大）：

$$z = \frac{\bar{x} - \mu_0}{\sigma / \sqrt{n}} = \frac{10.3 - 10}{0.9 / \sqrt{36}} = \frac{0.3}{0.15} = 2.0$$

- 对于 $\alpha = 0.05$ 的双侧检验，临界值为 $\pm 1.96$。我们的 $z = 2.0 > 1.96$，因此拒绝 $H_0$。p值约为0.046，小于0.05。

- 结论：有统计学上的显著证据表明，螺栓的平均长度与10 cm不同。

- **单侧检验**检查效应是否朝着某个特定方向发生（$H_1$：$\mu > 10$ 或 $\mu < 10$）。整个 $\alpha$ 集中于一个尾部，使得在该方向上更容易拒绝 $H_0$，但无法检测到相反方向的效应。

- **双侧检验**检查是否存在任何差异（$H_1$：$\mu \neq 10$）。$\alpha$ 被分配到两个尾部（各 $\alpha/2$）。这种方法更保守，但能捕捉到两个方向上的效应。

- 即使有了良好的流程，错误仍然可能发生。共有两种类型的错误：

![2×2矩阵图，展示第一类错误和第二类错误：实际情况与决策结果](../images/type_errors.svg)

- **第一类错误**（假阳性）：当 $H_0$ 实际为真时，你错误地拒绝了它。其概率为 $\alpha$，你可以通过选择显著性水平来控制。就像没有火灾时火灾报警器却响了。

- **第二类错误**（假阴性）：当 $H_0$ 实际为假时，你未能拒绝它。其概率为 $\beta$。就像发生真实火灾时火灾报警器保持沉默。

- **检验功效**为 $1 - \beta$，即正确拒绝错误 $H_0$ 的概率。功效越高，意味着你检测真实效应的能力越强。功效随以下因素增加：
    - 真实效应量更大（差异越大越容易检测）
    - 样本量更大（更多数据 = 更高精度）
    - 显著性水平 $\alpha$ 更大（但这会增加第一类错误的风险）
    - 变异性更低（噪声更小）

- 第一类错误与第二类错误之间存在权衡关系。降低 $\alpha$（对假阳性更加谨慎）会增加 $\beta$（更多假阴性）。在固定样本量下，你无法同时最小化这两类错误。

- **参数检验**假设数据服从特定的分布（通常是正态分布）。当假设条件成立时，参数检验的功效更高。

- **Z检验**：在 $\sigma$ 已知且 $n$ 较大（$n \ge 30$）时，将样本均值与已知值进行比较。检验统计量：

$$z = \frac{\bar{x} - \mu_0}{\sigma / \sqrt{n}}$$

- **T检验**：类似于z检验，但适用于 $\sigma$ 未知（由样本估计）或 $n$ 较小的情况。使用t分布，其尾部比正态分布更厚。更厚的尾部反映了估计 $\sigma$ 所引入的额外不确定性。

$$t = \frac{\bar{x} - \mu_0}{s / \sqrt{n}}$$

- t分布有一个称为**自由度**（$df = n - 1$）的参数。随着 $df$ 增大，t分布趋近于正态分布。

- t检验有几种变体：
    - **单样本t检验**：样本均值是否与某个特定值不同？
    - **独立双样本t检验**：两个独立组的均值是否不同？
    - **配对t检验**：两个相关测量值的均值是否不同（例如同一批受试者治疗前后的测量值）？

- **方差分析**：检验三个或更多组的均值是否相等。与运行多次t检验（这会膨胀第一类错误率）不同，方差分析通过比较组间方差与组内方差进行一次统一检验。

$$F = \frac{\text{组间方差}}{\text{组内方差}}$$

- 较大的 $F$ 比值意味着各组之间的差异超出了随机变异所能解释的范围。

- **非参数检验**对数据分布的假设较少。它们基于秩次而非原始值进行运算，因此对异常值和非正态性具有稳健性。

- **卡方检验**（$\chi^2$）：检验观测频数与期望频数是否一致。用于分类数据。例如：红、蓝、绿三种颜色汽车的比例是否与制造商声称的比例一致？

$$\chi^2 = \sum \frac{(O_i - E_i)^2}{E_i}$$

- **Mann-Whitney U检验**：独立双样本t检验的非参数替代方法。通过比较秩次来检验一组是否倾向于比另一组有更大的值。

- **Wilcoxon符号秩检验**：配对t检验的非参数替代方法。通过考察差异的大小和方向来比较配对观测值。

- **Kruskal-Wallis检验**：单因素方差分析的非参数替代方法。通过比较所有组的秩次来检验多个组是否来自同一分布。

- **拟合优度检验**检查数据是否服从某个特定的理论分布。卡方拟合优度检验将观测到的区间计数与假设分布下的期望计数进行比较。

- **正态性检验**专门检验数据是否服从正态分布。常用的检验包括Shapiro-Wilk检验（对小样本检验力强）和Kolmogorov-Smirnov检验（将样本经验分布函数与理论分布函数进行比较）。

- 在机器学习中，假设检验出现在比较模型性能时。如果模型A达到92%的准确率，模型B达到91%的准确率，这种差异是真实的还是仅仅是噪声？对交叉验证得分进行配对t检验可以回答这个问题。

## 编程练习（使用CoLab或notebook）

1. 对文中的螺栓工厂示例执行z检验。计算检验统计量、p值并做出决策。
```python
import jax.numpy as jnp

x_bar = 10.3    # 样本均值
mu_0 = 10.0     # 原假设值
sigma = 0.9     # 已知总体标准差
n = 36           # 样本量
alpha = 0.05

# 检验统计量
z = (x_bar - mu_0) / (sigma / jnp.sqrt(n))
print(f"z = {z:.4f}")

# p值（双侧检验）使用正态CDF近似
# 对于 |z| = 2.0，p ≈ 0.0456
from jax.scipy.stats import norm
p_value = 2 * (1 - norm.cdf(jnp.abs(z)))
print(f"p值 = {p_value:.4f}")
print(f"拒绝H₀？{p_value <= alpha}")
```

2. 模拟第一类错误：当 $H_0$ 为真时，我们犯错误的频率有多高？运行10,000次实验，检验拒绝率是否与 $\alpha$ 一致。
```python
import jax
import jax.numpy as jnp

key = jax.random.PRNGKey(0)
mu_0 = 50.0
sigma = 10.0
n = 30
alpha = 0.05
n_experiments = 10_000

rejections = 0
for i in range(n_experiments):
    key, subkey = jax.random.split(key)
    sample = mu_0 + sigma * jax.random.normal(subkey, shape=(n,))
    z = (sample.mean() - mu_0) / (sigma / jnp.sqrt(n))
    p_value = 2 * (1 - __import__("jax").scipy.stats.norm.cdf(jnp.abs(z)))
    if p_value <= alpha:
        rejections += 1

print(f"拒绝率：{rejections/n_experiments:.4f}")
print(f"期望值（α）：  {alpha}")
```

3. 对两组数据分别运行t检验和Mann-Whitney U检验。生成一组均值略高于另一组的数据，观察哪种检验能检测出差异。
```python
import jax
import jax.numpy as jnp

key = jax.random.PRNGKey(99)
k1, k2 = jax.random.split(key)

group_a = jax.random.normal(k1, shape=(25,)) * 5 + 100
group_b = jax.random.normal(k2, shape=(25,)) * 5 + 103  # 均值略高

# 双样本t检验（假设方差相等）
n_a, n_b = len(group_a), len(group_b)
mean_a, mean_b = group_a.mean(), group_b.mean()
pooled_var = ((n_a - 1) * group_a.var() + (n_b - 1) * group_b.var()) / (n_a + n_b - 2)
se = jnp.sqrt(pooled_var * (1/n_a + 1/n_b))
t_stat = (mean_a - mean_b) / se
print(f"t检验统计量：{t_stat:.4f}")

# Mann-Whitney：统计group_a的值小于group_b值的次数
u_stat = jnp.sum(group_a[:, None] < group_b[None, :])
print(f"Mann-Whitney U：  {u_stat}")
print(f"\nA组均值：{mean_a:.2f}，B组均值：{mean_b:.2f}")
```
