11.2 推断性统计(入门)¶

推断性统计用于根据样本对总体做判断与估计。核心问题包括:

  • 假设检验:样本证据是否足以反对原假设 H0?
  • 置信区间:总体参数大概率落在哪个范围?
  • 回归分析:变量之间的关系如何,能否用一个变量预测另一个变量?

本节用贴近人文社科的例子,配合 numpy / scipy / statsmodels 等工具,完成从概念到代码的入门。

学习目标¶

  • 理解假设检验的基本流程(提出假设 → 选择检验 → 计算统计量与 p 值 → 结论与解释)。
  • 会计算均值的95% 置信区间并用自然语言解释。
  • 会用线性回归建立简易模型,并读懂关键输出(系数/显著性/R²)。
  • 能避开常见误区:p 值解读、效应量、样本量影响等。

11.2.1 语法要点(常用就这些)¶

A. 假设检验(以单样本 t 检验为例)¶

  • 原假设 H0:总体均值等于某个值(如 μ=85)。
  • 备择假设 H1:总体均值不等于该值(双侧检验)。
  • 使用:scipy.stats.ttest_1samp(sample, popmean) → 返回 t 统计量 与 p 值。

B. 置信区间(以均值为例)¶

  • 公式:样本均值 ± 临界值 × 标准误差
  • 小样本/未知方差 → 用 t 分布:stats.t.ppf((1+置信度)/2, df=n-1)

C. 回归分析(线性回归 OLS)¶

  • 模型:Y = β0 + β1 X + ε
  • statsmodels.api.OLS(y, X) (注意需要给 X 添加常数项)。
  • 读懂输出:系数估计、标准误、t/p 值、R²。

🔎 补充:效应量(如 Cohen's d)能反映差异的实际大小,不只看显著性;样本量越大,微小差异也可能显著。

11.2.2 示范(按教材示例逐一跑通)¶

示例 1:单样本 t 检验(均值是否为 85)¶

In [1]:
import numpy as np
from math import sqrt

# 样本:某班成绩
scores = [85, 87, 90, 83, 88]

# 优先尝试 scipy,如不可用则提供简化实现
try:
    from scipy import stats
    t_stat, p_value = stats.ttest_1samp(scores, 85)
except Exception as e:
    # 手动实现(双侧)
    x = np.array(scores, dtype=float)
    n = x.size
    mean = x.mean()
    std = x.std(ddof=1)
    t_stat = (mean - 85) / (std / sqrt(n))
    # 使用正态近似的 p 值(近似),仅作兜底;更推荐安装 scipy 获取精确 t 分布 p 值
    # 这里不实现精确 t 分布 CDF(超出入门范围)
    from math import erf
    # 正态近似双侧 p 值
    p_value = 2*(1-0.5*(1+erf(abs(t_stat)/sqrt(2))))

print("t统计量:", round(t_stat, 2))
print("p值:", round(p_value, 4))
t统计量: 1.32
p值: 0.256

示例 2:95% 置信区间(均值)¶

In [2]:
import numpy as np

x = np.array([85, 87, 90, 83, 88], dtype=float)
mean = np.mean(x)
std = np.std(x, ddof=1)
n = len(x)

confidence = 0.95

try:
    from scipy import stats
    t_critical = stats.t.ppf((1 + confidence) / 2, df=n-1)
except Exception as e:
    # 兜底:用正态近似的 1.96(小样本时会有偏差,建议安装 scipy)
    t_critical = 1.96

margin_error = t_critical * (std / np.sqrt(n))
ci_lower = mean - margin_error
ci_upper = mean + margin_error

print("样本均值:", round(mean, 2))
print("95%置信区间:", (round(ci_lower, 2), round(ci_upper, 2)))
样本均值: 86.6
95%置信区间: (83.25, 89.95)

示例 3:最小线性回归(OLS)¶

In [3]:
import numpy as np

# 构造数据(例:学习小时数 X → 成绩 Y)
X_raw = np.array([1, 2, 3, 4, 5], dtype=float)
Y = np.array([2.5, 3.1, 4.2, 4.8, 5.5], dtype=float)

try:
    import statsmodels.api as sm
    X = sm.add_constant(X_raw)  # 添加常数项
    model = sm.OLS(Y, X).fit()
    print(model.summary())
except Exception as e:
    # 若未安装 statsmodels,则用最小二乘的简要实现与结果展示
    X = np.column_stack([np.ones_like(X_raw), X_raw])  # [1, X]
    beta_hat, *_ = np.linalg.lstsq(X, Y, rcond=None)   # [beta0, beta1]
    y_hat = X @ beta_hat
    resid = Y - y_hat
    rss = float((resid**2).sum())
    tss = float(((Y - Y.mean())**2).sum())
    r2 = 1 - rss/tss
    print("(简版 OLS 输出 - 建议安装 statsmodels 获取完整报告)")
    print(f"截距 β0 ≈ {beta_hat[0]:.4f},  斜率 β1 ≈ {beta_hat[1]:.4f}")
    print(f"R² ≈ {r2:.4f}")
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\pandas\core\computation\expressions.py:21: UserWarning: Pandas requires version '2.8.4' or newer of 'numexpr' (version '2.8.3' currently installed).
  from pandas.core.computation.check import NUMEXPR_INSTALLED
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\pandas\core\arrays\masked.py:60: UserWarning: Pandas requires version '1.3.6' or newer of 'bottleneck' (version '1.3.5' currently installed).
  from pandas.core import (
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.990
Model:                            OLS   Adj. R-squared:                  0.987
Method:                 Least Squares   F-statistic:                     301.5
Date:                Tue, 12 Aug 2025   Prob (F-statistic):           0.000416
Time:                        10:17:53   Log-Likelihood:                 4.0044
No. Observations:                   5   AIC:                            -4.009
Df Residuals:                       3   BIC:                            -4.790
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          1.7100      0.147     11.626      0.001       1.242       2.178
x1             0.7700      0.044     17.363      0.000       0.629       0.911
==============================================================================
Omnibus:                          nan   Durbin-Watson:                   2.908
Prob(Omnibus):                    nan   Jarque-Bera (JB):                0.219
Skew:                           0.351   Prob(JB):                        0.896
Kurtosis:                       2.254   Cond. No.                         8.37
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\statsmodels\stats\stattools.py:74: ValueWarning: omni_normtest is not valid with less than 8 observations; 5 samples were given.
  warn("omni_normtest is not valid with less than 8 observations; %i "

11.2.3 举例(贴近人文社科)¶

例 1:两独立样本 t 检验(比如:A/B 两种教学法的期末成绩)¶

  • H0:两组均值相等;H1:不相等(双侧)。
In [4]:
A = np.array([82, 85, 88, 90, 91], dtype=float)   # 教学法A
B = np.array([78, 80, 84, 86, 87], dtype=float)   # 教学法B

try:
    from scipy import stats
    t2, p2 = stats.ttest_ind(A, B, equal_var=False)  # Welch 修正(推荐)
except Exception as e:
    # 兜底:用正态近似计算,结果仅供参考
    n1, n2 = len(A), len(B)
    mean1, mean2 = A.mean(), B.mean()
    s1, s2 = A.std(ddof=1), B.std(ddof=1)
    se = np.sqrt(s1**2/n1 + s2**2/n2)
    t2 = (mean1 - mean2)/se
    # 正态近似的双侧 p 值
    from math import erf, sqrt
    p2 = 2*(1-0.5*(1+erf(abs(t2)/sqrt(2))))

print("t统计量:", round(t2,2), "  p值:", round(p2,4))
print("A组均值:", round(A.mean(),2), " B组均值:", round(B.mean(),2))
t统计量: 1.75   p值: 0.1178
A组均值: 87.2  B组均值: 83.0

例 2:效应量(Cohen's d)¶

  • 与显著性不同,效应量描述差异的实际大小。
  • 独立样本 Cohen's d(简式)≈ (均值差) / 合并标准差。
In [5]:
def cohens_d_independent(a, b):
    a = np.asarray(a, dtype=float); b = np.asarray(b, dtype=float)
    n1, n2 = len(a), len(b)
    s1, s2 = a.std(ddof=1), b.std(ddof=1)
    # 合并标准差(无偏估计)
    sp = np.sqrt(((n1-1)*s1**2 + (n2-1)*s2**2) / (n1+n2-2))
    return (a.mean() - b.mean()) / sp

d = cohens_d_independent(A, B)
print("Cohen's d:", round(d, 3))
Cohen's d: 1.109

例 3:回归的自然语言解读(示意)¶

  • 斜率 β1 ≈ 每增加 1 个单位的 X,Y 平均增加 β1 个单位。
  • 截距 β0 ≈ 当 X=0 时,Y 的基线水平。
  • R² 反映模型对数据的拟合程度(0–1 之间)。

11.2.4 注意事项(常见坑)¶

  1. p 值不是效应大小:p 很小也可能是因为样本很大;建议同时报告效应量与置信区间。
  2. 多重比较:做了很多检验,出现“偶然显著”的概率更高;需考虑 校正(如 Bonferroni)。
  3. 前提假设:t 检验假定近似正态、方差齐性(Welch t 检验对齐性要求更弱)。
  4. 回归的外推风险:训练数据范围之外的预测常不可靠;注意 异常值 对回归的影响。
  5. 可复现:记录数据处理、统计方法、阈值与软件版本;报告中说明样本来源与限制。

11.2.5 练习(建议先做再看答案)¶

练习 1:单样本 t 检验
样本:[82, 85, 87, 90, 88, 86],检验总体均值是否为 85(双侧)。输出 t 与 p,并给出自然语言结论。

练习 2:95% 置信区间
用练习 1 的样本,计算均值的 95% 置信区间,并解释该区间的含义。

练习 3:两独立样本 t 检验 + 效应量
A 组:[80, 83, 85, 88, 90];B 组:[76, 78, 80, 82, 85]。
进行 Welch t 检验,并计算 Cohen's d。

练习 4:简单回归
自拟 X=[1,2,3,4,5,6] 与 Y(可以设置一个线性关系 + 少量噪声),拟合 OLS,打印系数与 R²。

In [6]:
# === 在此动手做题(你可以多建几个单元格)===

import numpy as np
import pandas as pd

# 练习 1:单样本 t 检验(均值=85)
sample1 = np.array([82, 85, 87, 90, 88, 86], dtype=float)
# TODO: 计算 t 与 p,并写出自然语言结论


# 练习 2:95% 置信区间(均值)
# TODO: 计算 95% CI,并用一句话解释


# 练习 3:两独立样本 t 检验 + 效应量
A = np.array([80, 83, 85, 88, 90], dtype=float)
B = np.array([76, 78, 80, 82, 85], dtype=float)
# TODO: Welch t 检验(equal_var=False),并计算 Cohen's d


# 练习 4:简单回归
# TODO: 自拟 X 与 Y,使用 statsmodels(或 numpy 兜底)拟合,输出系数与 R²

11.2.6 参考答案(对照自检)¶

说明:为了适配不同环境,答案中尽量使用 scipy,若不可用则降级为近似或简版实现。

In [7]:
# 练习 1 参考答案
import numpy as np
sample1 = np.array([82, 85, 87, 90, 88, 86], dtype=float)

try:
    from scipy import stats
    t1, p1 = stats.ttest_1samp(sample1, 85)
except Exception as e:
    from math import sqrt, erf
    n = len(sample1)
    mean = sample1.mean()
    std = sample1.std(ddof=1)
    t1 = (mean - 85) / (std/np.sqrt(n))
    # 正态近似 p 值
    p1 = 2*(1-0.5*(1+erf(abs(t1)/np.sqrt(2))))

print("t统计量:", round(t1,2), " p值:", round(p1,4))
if p1 < 0.05:
    print("结论:在 5% 显著性水平下,拒绝 H0(总体均值=85)的假设。")
else:
    print("结论:在 5% 显著性水平下,无法拒绝 H0(总体均值=85)。")
t统计量: 1.2  p值: 0.2856
结论:在 5% 显著性水平下,无法拒绝 H0(总体均值=85)。
In [8]:
# 练习 2 参考答案(95% CI)
import numpy as np
sample1 = np.array([82, 85, 87, 90, 88, 86], dtype=float)

mean = sample1.mean()
std = sample1.std(ddof=1)
n = len(sample1)

try:
    from scipy import stats
    t_crit = stats.t.ppf(0.975, df=n-1)
except Exception as e:
    t_crit = 1.96  # 兜底近似

me = t_crit * (std/np.sqrt(n))
ci = (mean - me, mean + me)

print("均值:", round(mean,2), " 95%CI:", (round(ci[0],2), round(ci[1],2)))
print("解释:若重复抽样多次,约 95% 的此类区间会覆盖真实总体均值;并非“参数有 95% 概率在区间内”。")
均值: 86.33  95%CI: (83.47, 89.2)
解释:若重复抽样多次,约 95% 的此类区间会覆盖真实总体均值;并非“参数有 95% 概率在区间内”。
In [9]:
# 练习 3 参考答案:Welch t 检验 + Cohen's d
import numpy as np

A = np.array([80, 83, 85, 88, 90], dtype=float)
B = np.array([76, 78, 80, 82, 85], dtype=float)

try:
    from scipy import stats
    t2, p2 = stats.ttest_ind(A, B, equal_var=False)
except Exception as e:
    # 兜底:正态近似
    n1, n2 = len(A), len(B)
    mean1, mean2 = A.mean(), B.mean()
    s1, s2 = A.std(ddof=1), B.std(ddof=1)
    se = np.sqrt(s1**2/n1 + s2**2/n2)
    t2 = (mean1 - mean2)/se
    from math import erf, sqrt
    p2 = 2*(1-0.5*(1+erf(abs(t2)/sqrt(2))))

def cohens_d_independent(a, b):
    a = np.asarray(a, dtype=float); b = np.asarray(b, dtype=float)
    n1, n2 = len(a), len(b)
    s1, s2 = a.std(ddof=1), b.std(ddof=1)
    sp = np.sqrt(((n1-1)*s1**2 + (n2-1)*s2**2) / (n1+n2-2))
    return (a.mean() - b.mean()) / sp

d = cohens_d_independent(A, B)

print("Welch t:", round(t2,2), " p值:", round(p2,4))
print("Cohen's d:", round(d,3))
Welch t: 2.12  p值: 0.0677
Cohen's d: 1.339
In [10]:
# 练习 4 参考答案:简单回归
import numpy as np

X_raw = np.array([1,2,3,4,5,6], dtype=float)
Y = 1.5 + 0.8*X_raw + np.array([0.2, -0.1, 0.0, 0.1, -0.2, 0.3])  # 线性关系 + 少量噪声

try:
    import statsmodels.api as sm
    X = sm.add_constant(X_raw)
    model = sm.OLS(Y, X).fit()
    print(model.summary())
except Exception as e:
    X = np.column_stack([np.ones_like(X_raw), X_raw])
    beta_hat, *_ = np.linalg.lstsq(X, Y, rcond=None)
    y_hat = X @ beta_hat
    resid = Y - y_hat
    rss = float((resid**2).sum())
    tss = float(((Y - Y.mean())**2).sum())
    r2 = 1 - rss/tss
    print("(简版 OLS 输出)")
    print(f"截距 β0 ≈ {beta_hat[0]:.4f}, 斜率 β1 ≈ {beta_hat[1]:.4f}, R² ≈ {r2:.4f}")
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.985
Model:                            OLS   Adj. R-squared:                  0.981
Method:                 Least Squares   F-statistic:                     263.5
Date:                Tue, 12 Aug 2025   Prob (F-statistic):           8.43e-05
Time:                        10:18:12   Log-Likelihood:                 2.1127
No. Observations:                   6   AIC:                           -0.2254
Df Residuals:                       4   BIC:                           -0.6418
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          1.5200      0.194      7.835      0.001       0.981       2.059
x1             0.8086      0.050     16.231      0.000       0.670       0.947
==============================================================================
Omnibus:                          nan   Durbin-Watson:                   2.583
Prob(Omnibus):                    nan   Jarque-Bera (JB):                0.416
Skew:                          -0.127   Prob(JB):                        0.812
Kurtosis:                       1.736   Cond. No.                         9.36
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
C:\Users\Zhouq\AppData\Roaming\Python\Python39\site-packages\statsmodels\stats\stattools.py:74: ValueWarning: omni_normtest is not valid with less than 8 observations; 6 samples were given.
  warn("omni_normtest is not valid with less than 8 observations; %i "

11.2 小结¶

  • 假设检验:别只看 p 值,配合效应量与置信区间更全面。
  • 置信区间:表达估计的不确定性,比“显著/不显著”更有信息量。
  • 回归分析:解释系数含义与方向,关注外推与异常值影响。
  • 报告结果时,建议写:统计方法、样本量、统计量与 p 值、效应量、置信区间、限制说明。

下一步可学:配对 t 检验、比例的置信区间、Logistic 回归与方差分析(ANOVA)。