🔬 假设检验 — t检验与p值计算

用统计方法科学地判断差异是否真实

📖 假设检验基本框架

假设检验是统计推断的核心方法,用于基于样本数据对总体参数做出决策。它让我们能科学地回答"这个差异是真实的还是偶然的?"

假设检验五步法: Step 1: 建立假设 ├── H₀(零假设): 无差异/无效果 (如: μ = 50) └── H₁(备择假设): 有差异/有效果 (如: μ ≠ 50) Step 2: 选择显著性水平 α (通常 0.05) Step 3: 计算检验统计量 (t值/z值/χ²值等) Step 4: 计算p值 (在H₀为真时,观测到当前或更极端结果的概率) Step 5: 做出决策 ├── p < α → 拒绝H₀ (结果统计显著) └── p ≥ α → 不拒绝H₀ (证据不足)
概念含义类比
零假设 H₀默认立场:无效应"无罪推定"
备择假设 H₁想要证明的立场"有罪指控"
α (显著性水平)犯I类错误的上限"冤枉好人的容忍度"
β (II类错误率)漏检真实效应"放过坏人的概率"
1-β (统计功效)正确检出真实效应"抓住坏人的概率"
p值H₀为真时的极端程度"证据有多不利"

⚖️ 两类错误

H₀为真(无罪)H₀为假(有罪)
拒绝H₀❌ I类错误 (α)
冤枉好人
✅ 正确 (1-β)
抓住坏人
不拒绝H₀✅ 正确
放过好人
❌ II类错误 (β)
放过坏人
2024年学术界重大讨论:p值<0.05不应作为"显著性"的唯一标准。ASA声明强调p值是连续度量,不是二元开关。建议同时报告效应量和置信区间!

🔢 t检验详解

t检验用于小样本情况下比较均值,当总体方差未知时用样本方差代替,统计量服从t分布。

1. 单样本t检验

检验样本均值是否与已知值有显著差异:

t = (X̄ − μ₀) / (s / √n)    自由度 df = n − 1
import numpy as np
from scipy import stats

np.random.seed(42)

# 螺栓标称长度50mm,抽检20个
sample = np.random.normal(50.3, 0.8, 20)
t_stat, p_value = stats.ttest_1samp(sample, 50)

print(f"样本均值: {sample.mean():.4f}")
print(f"样本标准差: {sample.std(ddof=1):.4f}")
print(f"t统计量: {t_stat:.4f}")
print(f"p值: {p_value:.6f}")
print(f"α=0.05: {'拒绝H₀' if p_value < 0.05 else '不拒绝H₀'}")
# 输出: t=0.9489, p=0.3546 → 不拒绝H₀

2. 手动计算t检验

# 手动实现单样本t检验
n = len(sample)
sample_mean = sample.mean()
pop_mean = 50
sample_std = sample.std(ddof=1)

t_manual = (sample_mean - pop_mean) / (sample_std / np.sqrt(n))
print(f"手动t统计量: {t_manual:.4f}")  # 0.9489
print(f"scipy t统计量: {t_stat:.4f}")   # 0.9489
print(f"一致: ✅")

3. 独立双样本t检验

比较两组独立样本的均值是否有显著差异:

t = (X̄₁ − X̄₂) / √(sₚ²/n₁ + sₚ²/n₂)    其中 sₚ² = [(n₁-1)s₁²+(n₂-1)s₂²] / (n₁+n₂-2)
# 两组学生成绩比较
group_a = np.random.normal(85, 10, 30)  # A班
group_b = np.random.normal(90, 10, 30)  # B班

t_stat2, p_value2 = stats.ttest_ind(group_a, group_b)
print(f"A班均值: {group_a.mean():.2f}")
print(f"B班均值: {group_b.mean():.2f}")
print(f"t统计量: {t_stat2:.4f}, p值: {p_value2:.6f}")
# 输出: t=-3.2567, p=0.0019 → 拒绝H₀ (显著差异)

📊 卡方检验

卡方检验用于分类数据的拟合优度和独立性检验:

χ² = Σ (Oᵢ − Eᵢ)² / Eᵢ    自由度 df = (r-1)(c-1)
from scipy import stats

# 骰子是否公平?观测值 vs 期望值
observed = np.array([45, 30, 15, 10])
expected = np.array([25, 25, 25, 25])

chi2, p_chi2, dof, _ = stats.chi2_contingency(np.array([observed, expected]))
print(f"卡方统计量: {chi2:.4f}")  # 15.0974
print(f"p值: {p_chi2:.6f}")       # 0.001735
print(f"自由度: {dof}")            # 3
# p < 0.05 → 骰子不公平

📈 效应量与置信区间

仅看p值不够!效应量告诉我们差异有多大,置信区间告诉我们估计的精度。

Cohen's d(效应量)

d = (X̄₁ − X̄₂) / sₚ    |d|: 0.2小 / 0.5中 / 0.8大
# 计算Cohen's d
def cohens_d(group1, group2):
    n1, n2 = len(group1), len(group2)
    s_pooled = np.sqrt(((n1-1)*group1.var(ddof=1) + (n2-1)*group2.var(ddof=1)) / (n1+n2-2))
    return (group1.mean() - group2.mean()) / s_pooled

d = cohens_d(group_a, group_b)
print(f"Cohen's d: {d:.4f}")  # 效应量

# 95%置信区间
ci = stats.t.interval(0.95, df=len(sample)-1, 
                       loc=sample.mean(), 
                       scale=stats.sem(sample))
print(f"均值95%CI: [{ci[0]:.4f}, {ci[1]:.4f}]")

📐 2024-2025 假设检验新进展

🔄 常见检验方法选择指南

场景数据类型推荐检验Python函数
单样本均值连续单样本t检验stats.ttest_1samp()
两组均值比较连续,独立独立t检验stats.ttest_ind()
配对样本比较连续,配对配对t检验stats.ttest_rel()
三组及以上均值连续ANOVAstats.f_oneway()
分类变量关联分类卡方检验stats.chi2_contingency()
非参数两组有序/偏态Mann-Whitney Ustats.mannwhitneyu()
非参数配对有序/偏态Wilcoxonstats.wilcoxon()
正态性检验连续Shapiro-Wilkstats.shapiro()
🏆 成就解锁:t检验/p值计算正确
手动计算t统计量与scipy完全一致(0.9489),单样本t检验、双样本t检验、卡方检验均验证通过!
Python验证通过 — 单样本t检验: t=0.9489, p=0.3546(不拒绝H₀);双样本t检验: t=-3.2567, p=0.0019(拒绝H₀,显著差异);卡方检验: χ²=15.0974, p=0.001735(骰子不公平);手动t值与scipy一致。
思考题:
1. p值=0.049和p值=0.051,结论真的有本质区别吗?
2. 为什么"不拒绝H₀"不等于"接受H₀"?
3. 样本量增大时,即使微小差异也会变"显著",如何解读?
4. 什么时候应该用非参数检验而非t检验?

📝 课后练习

  1. 实现配对样本t检验(如同一组学生前后测)
  2. 用Bootstrap方法估计均值的置信区间,与t分布CI对比
  3. 实现多重比较校正(Bonferroni和BH-FDR)
  4. 模拟I类和II类错误率,验证α和β的关系
  5. 计算不同样本量下的统计功效曲线(Power Analysis)
📚 参考资料:
• The ASA Statement on Statistical Significance and P-Values (2016, 2021)
• Statistical Inference (Casella & Berger, 2002)
• scipy.stats 官方文档 (scipy.org)
• Nature 2024: "Retire Statistical Significance" Follow-up