← 返回目录

第24章 线性回归

Linear Regression

数据科学的基石:从数据中寻找线性关系

"本质上,所有的模型都是错误的,但有些是有用的。"
"Essentially, all models are wrong, but some are useful."
—— 乔治·博克斯 (George Box) | 英国统计学家 | 1919 ~ 2013

🐍Python工具箱

numpy.linalg.lstsq()

最小二乘求解

sklearn.linear_model.LinearRegression

线性回归模型

statsmodels.api.OLS()

普通最小二乘

matplotlib.pyplot.scatter()

散点图

seaborn.regplot()

回归图

scipy.stats.pearsonr()

相关系数

本章概览

🎯 核心内容

  • • 线性回归基本概念
  • • 最小二乘法
  • • 模型评估与诊断
  • • 多元线性回归
  • • 正则化方法

📐 关键概念

  • • y = β₀ + β₁x + ε
  • • 最小化RSS
  • • R²决定系数
  • • 残差分析
  • • Ridge与Lasso
24.1

线性回归基础

Fundamentals of Linear Regression

线性回归是统计学和机器学习中最基础、最重要的方法之一。 它试图用一条直线(或超平面)来描述自变量和因变量之间的关系, 是理解更复杂模型的起点。

🎯 什么是线性回归?

线性回归是一种监督学习方法,用于建立连续型因变量与一个或多个自变量之间的线性关系模型。
简单线性回归
y = β₀ + β₁x + ε
公式(1):简单线性回归模型
y:因变量(响应变量)
我们要预测的目标
x:自变量(解释变量)
用来预测的特征
β₀:截距(intercept)
x=0时y的期望值
β₁:斜率(slope)
x每增加1,y的变化
ε:误差项(error term)
模型无法解释的随机波动
直观理解
几何意义:
在二维平面上,找一条直线,使其尽可能接近所有数据点
预测任务:
给定新的x值,用这条直线预测对应的y值
核心假设:
x和y之间存在线性关系(即可以用直线近似)
📈 典型应用
• 房价预测(面积→价格)
• 销售预测(广告投入→销量)
• 温度预测(海拔→温度)
• 成绩预测(学习时间→分数)
🎯 为什么重要?
• 简单易懂,易于解释
• 计算高效,实现简单
• 理论基础坚实
• 许多复杂模型的基础

📊 实例:房价预测

让我们通过一个具体例子理解线性回归。
问题描述
任务:根据房屋面积预测价格
数据:10套已售房屋的面积和价格
面积(m²): [50, 60, 70, 80, 90, 100, 110, 120, 130, 140]
价格(万元): [150, 180, 210, 240, 270, 300, 330, 360, 390, 420]
建立模型
模型形式:
价格 = β₀ + β₁ × 面积 + ε
目标:
找到最佳的β₀和β₁,使模型预测尽可能准确
结果(通过最小二乘法):
β₀ ≈ 0(几乎为0)
β₁ ≈ 3(每平米约3万元)
模型:价格 ≈ 3 × 面积
使用模型
预测新房价格:
面积75m² → 价格 ≈ 3 × 75 = 225万元
解释:
• β₁=3表示每增加1平米,价格增加3万元
• β₀≈0表示理论上0平米房子价格为0(合理)
💡 关键观察
这个简单例子展示了线性回归的三个核心步骤:
1. 收集数据:已知的(x, y)配对
2. 拟合模型:找最佳的β₀和β₁
3. 进行预测:用模型预测新的x值

⚖️ 线性回归的假设

线性回归建立在几个关键假设之上,理解这些假设对正确使用模型至关重要。
1
线性关系(Linearity)
假设:y和x之间存在线性关系
意义:可以用直线/平面描述关系
检验:散点图、残差图
2
独立性(Independence)
假设:观测值之间相互独立
意义:一个观测不影响另一个
违反:时间序列、空间数据常违反
3
同方差性(Homoscedasticity)
假设:误差项方差恒定
意义:预测精度不随x变化
检验:残差vs拟合值图
4
正态性(Normality)
假设:误差项服从正态分布
意义:用于统计推断(置信区间、假设检验)
检验:QQ图、直方图
5
无多重共线性(No Multicollinearity)
假设:自变量之间不高度相关(多元回归)
意义:确保系数估计稳定
检验:VIF(方差膨胀因子)
⚠️ 重要提醒
• 假设1-3:对模型拟合至关重要
• 假设4:主要用于统计推断,大样本时可放宽
• 假设5:仅用于多元回归
• 违反假设:可能导致估计偏差、推断错误
• 实践中:完全满足所有假设很少见,关键是识别和处理

📐 几何直观理解

从几何角度理解线性回归,有助于建立直觉。
一维(简单回归)
空间:二维平面(x-y)
模型:一条直线
参数:2个(β₀, β₁)
目标:找最佳拟合直线
二维(两个自变量)
空间:三维空间(x₁-x₂-y)
模型:一个平面
参数:3个(β₀, β₁, β₂)
目标:找最佳拟合平面
高维(多元回归)
空间:p+1维空间(p个自变量+1个因变量)
模型:p维超平面
y = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ + ε
参数:p+1个(β₀, β₁, ..., βₚ)
挑战:无法直接可视化,但数学原理相同
关键概念:残差
残差(Residual):
实际值与预测值的差:eᵢ = yᵢ - ŷᵢ
几何意义:
数据点到拟合线/面的垂直距离
最小二乘:
最小化所有残差平方和(RSS):Σeᵢ²

🎯 24.1 关键要点

基本概念
• y = β₀ + β₁x + ε
• 线性关系建模
• 监督学习方法
• 预测连续值
核心假设
• 线性关系
• 独立性
• 同方差性
• 正态性
• 无多重共线性
几何理解
• 1D: 直线
• 2D: 平面
• pD: 超平面
• 残差=垂直距离
💡 核心洞察
• 简单但强大:线性回归虽然简单,但在许多实际问题中非常有效
• 可解释性:系数β有明确的物理意义,易于理解和解释
• 基石地位:是理解更复杂模型(GLM、神经网络等)的基础
• 假设重要:理解并检验假设是正确使用的关键
• 下一步:学习如何求解参数β(24.2最小二乘法)
✦
24.2

最小二乘法

Ordinary Least Squares (OLS)

最小二乘法是求解线性回归参数的经典方法。 它的核心思想是:找到使残差平方和最小的参数值, 即让预测值与真实值的平方误差尽可能小。

🎯 核心思想:最小化残差平方和

最小二乘法通过最小化残差平方和(RSS)来找到最佳参数。
残差平方和(RSS)
RSS = Σᵢ₌₁ⁿ (yᵢ - ŷᵢ)² = Σᵢ₌₁ⁿ (yᵢ - β₀ - β₁xᵢ)²
公式(2):残差平方和
yᵢ:第i个真实值
实际观测到的值
ŷᵢ:第i个预测值
ŷᵢ = β₀ + β₁xᵢ
eᵢ:第i个残差
eᵢ = yᵢ - ŷᵢ
为什么是"平方"和?
1. 正负抵消问题:
直接求和Σeᵢ,正负残差会相互抵消
2. 惩罚大误差:
平方使大残差得到更重的惩罚(e=2 → 4,e=3 → 9)
3. 数学优美:
二次函数易于求导,有唯一最优解
4. 统计意义:
在正态误差假设下,等价于最大似然估计
✅ 优点
• 有唯一解析解(闭式解)
• 计算高效
• 数学性质好
• 无需迭代
⚠️ 局限
• 对异常值敏感
• 假设误差平方同等重要
• 可能过拟合(高维)

📐 参数求解:数学推导

通过对RSS求偏导数并令其为0,可以得到参数的解析解。
步骤1:写出目标函数
RSS(β₀, β₁) = Σᵢ₌₁ⁿ (yᵢ - β₀ - β₁xᵢ)²
目标:找到β₀*和β₁*使得RSS最小
步骤2:对β₀求偏导并令为0
∂RSS/∂β₀ = -2Σᵢ₌₁ⁿ (yᵢ - β₀ - β₁xᵢ) = 0
化简得
Σyᵢ = nβ₀ + β₁Σxᵢ
两边除以n
β₀ = ȳ - β₁x̄
(ȳ和x̄分别是y和x的均值)
步骤3:对β₁求偏导并令为0
∂RSS/∂β₁ = -2Σᵢ₌₁ⁿ xᵢ(yᵢ - β₀ - β₁xᵢ) = 0
代入β₀ = ȳ - β₁x̄,化简后得
β₁ = Σᵢ₌₁ⁿ (xᵢ - x̄)(yᵢ - ȳ) / Σᵢ₌₁ⁿ (xᵢ - x̄)²
= Cov(x, y) / Var(x)
最终解
斜率
β₁ = Cov(x,y) / Var(x)
截距
β₀ = ȳ - β₁x̄
这就是最小二乘估计量的闭式解!
💡 直观理解
• β₁ = Cov(x,y)/Var(x):斜率取决于x和y的协方差与x方差的比值
• Cov(x,y)大:x和y强正相关,斜率陡
• Var(x)大:x分散,相同协方差下斜率平缓
• β₀ = ȳ - β₁x̄:回归线必过点(x̄, ȳ),即数据的重心

💻 Python实现要点

线性回归的核心计算公式和实现方法。
关键公式
β₁ = Σ(x - x̄)(y - ȳ) / Σ(x - x̄)²
β₀ = ȳ - β₁ · x̄
💡 sklearn实现:LinearRegression().fit(X, y)
📝 完整代码示例(点击展开)
import numpy as np
import matplotlib.pyplot as plt

def simple_linear_regression(x, y):
    """
    简单线性回归:手动计算最小二乘解
    
    Parameters:
    -----------
    x : array-like
        自变量
    y : array-like
        因变量
    
    Returns:
    --------
    beta_0, beta_1 : float
        截距和斜率
    """
    # 转换为numpy数组
    x = np.array(x)
    y = np.array(y)
    
    # 计算均值
    x_mean = np.mean(x)
    y_mean = np.mean(y)
    
    # 计算斜率 β₁ = Cov(x,y) / Var(x)
    numerator = np.sum((x - x_mean) * (y - y_mean))  # 协方差 * (n-1)
    denominator = np.sum((x - x_mean) ** 2)          # 方差 * (n-1)
    beta_1 = numerator / denominator
    
    # 计算截距 β₀ = ȳ - β₁x̄
    beta_0 = y_mean - beta_1 * x_mean
    
    return beta_0, beta_1

# 示例数据:房价预测
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140])
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])

# 拟合模型
beta_0, beta_1 = simple_linear_regression(area, price)
print(f"截距 β₀ = {beta_0:.2f}")
print(f"斜率 β₁ = {beta_1:.2f}")

# 预测
def predict(x, beta_0, beta_1):
    """预测新值"""
    return beta_0 + beta_1 * x

# 预测75平米的房价
price_75 = predict(75, beta_0, beta_1)
print(f"\n75平米房价预测: {price_75:.2f}万元")

# 可视化
plt.figure(figsize=(10, 6))
plt.scatter(area, price, color='blue', s=50, alpha=0.6, label='实际数据')
plt.plot(area, predict(area, beta_0, beta_1), 
         color='red', linewidth=2, label='回归线')
plt.scatter(75, price_75, color='green', s=100, 
           marker='*', label=f'预测点(75, {price_75:.0f})')
plt.xlabel('面积(m²)', fontsize=12)
plt.ylabel('价格(万元)', fontsize=12)
plt.title(f'房价预测: y = {beta_0:.2f} + {beta_1:.2f}x', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
方法2:使用numpy(矩阵版)
import numpy as np

def ols_matrix(X, y):
    """
    最小二乘法:矩阵形式
    
    对于多元线性回归: y = Xβ + ε
    最小二乘解: β̂ = (XᵀX)⁻¹Xᵀy
    
    Parameters:
    -----------
    X : array-like (n, p)
        设计矩阵(包含截距列)
    y : array-like (n,)
        因变量
    
    Returns:
    --------
    beta : array (p,)
        参数向量
    """
    # 添加截距列(全1列)
    X = np.column_stack([np.ones(len(X)), X])
    
    # β̂ = (XᵀX)⁻¹Xᵀy
    beta = np.linalg.inv(X.T @ X) @ X.T @ y
    
    return beta

# 使用示例
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140])
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])

# 拟合(注意X是一维,需要reshape)
beta = ols_matrix(area.reshape(-1, 1), price)
print(f"参数: β₀={beta[0]:.2f}, β₁={beta[1]:.2f}")

# 预测
X_new = np.column_stack([np.ones(1), [[75]]])
y_pred = X_new @ beta
print(f"预测: {y_pred[0]:.2f}万元")
方法3:使用sklearn(生产版)
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# 数据
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140]).reshape(-1, 1)
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])

# 创建模型并拟合
model = LinearRegression()
model.fit(area, price)

# 查看参数
print(f"截距: {model.intercept_:.2f}")
print(f"斜率: {model.coef_[0]:.2f}")

# 预测
price_pred = model.predict([[75]])
print(f"\n预测价格: {price_pred[0]:.2f}万元")

# 模型评估
y_pred_all = model.predict(area)
mse = mean_squared_error(price, y_pred_all)
r2 = r2_score(price, y_pred_all)
print(f"\nMSE: {mse:.2f}")
print(f"R²: {r2:.4f}")

# 完整预测函数
def predict_price(area_sqm):
    """预测指定面积的房价"""
    return model.predict([[area_sqm]])[0]

print(f"\n100平米房价: {predict_price(100):.2f}万元")
方法1:手动
✅ 教学清晰
✅ 理解原理
❌ 仅简单回归
❌ 效率较低
方法2:矩阵
✅ 通用多元
✅ 数学优雅
⚠️ 需矩阵知识
⚠️ 手动处理细节
方法3:sklearn
✅ 生产级
✅ 功能完整
✅ 优化高效
✅ 推荐使用

🎨 几何解释:最小化距离

从几何角度理解最小二乘法,有助于建立直观认识。
二维视角
数据点:
(x₁,y₁), (x₂,y₂), ..., (xₙ,yₙ)
回归线:
ŷ = β₀ + β₁x
残差:
垂直距离 eᵢ = yᵢ - ŷᵢ
目标:
最小化 Σeᵢ²
(所有垂直距离的平方和)
高维视角
向量空间:
y, ŷ 是n维向量
列空间:
ŷ在X的列空间中
投影:
ŷ是y在列空间的正交投影
最短距离:
∥y - ŷ∥²最小
(欧氏距离最短)
为什么是"垂直"距离?
因变量的不确定性:
我们假设x是精确的,而y有误差(ε)
预测y的值:
给定x,我们预测y的值,所以关心y方向的误差
对比:
• 最小二乘:垂直距离(y方向)
• TLS(总体最小二乘):正交距离
• 最小一乘:绝对值和|eᵢ|

📊 最小二乘估计量的性质

最小二乘估计量具有良好的统计性质。
1️⃣
无偏性(Unbiasedness)
E[β̂₀] = β₀, E[β̂₁] = β₁
估计量的期望等于真实参数值
2️⃣
最优性(Efficiency)
Gauss-Markov定理:
在所有线性无偏估计量中,OLS方差最小
BLUE: Best Linear Unbiased Estimator
3️⃣
一致性(Consistency)
β̂ →ᵖ β (as n → ∞)
样本量增加时,估计量收敛到真值
4️⃣
渐近正态性(Asymptotic Normality)
√n(β̂ - β) →ᵈ N(0, σ²(XᵀX)⁻¹)
大样本下,估计量近似服从正态分布
🎯 实践意义
• 无偏性:长期平均来看,估计是准确的
• 最优性:在线性无偏估计量中,OLS精度最高
• 一致性:数据越多,估计越准确
• 正态性:可以进行统计推断(置信区间、假设检验)

🎯 24.2 关键要点

核心思想
• 最小化RSS
• 残差平方和
• 惩罚大误差
• 唯一最优解
解析解
• β₁=Cov/Var
• β₀=ȳ-β₁x̄
• 闭式公式
• 无需迭代
Python实现
• 手动计算
• 矩阵形式
• sklearn工具
• 三种方法
优良性质
• 无偏性
• 最优性
• 一致性
• 渐近正态
💡 核心洞察
• 优雅的数学:最小二乘法有唯一的闭式解,计算高效
• 几何直观:最小化数据点到拟合线的垂直距离平方和
• 统计最优:Gauss-Markov定理保证了OLS的BLUE性质
• 易于实现:从手动计算到sklearn,多种实现方式
• 下一步:学习如何评估模型好坏(24.3模型评估)
✦
24.3

模型评估与诊断

Model Evaluation and Diagnostics

拟合了模型后,我们需要评估模型的好坏。 本节介绍常用的评估指标(R²、MSE等)和残差诊断方法, 帮助我们判断模型是否可靠、是否违反了假设。

📊 拟合优度:R²决定系数

R²(R-squared)是最常用的评估指标,衡量模型解释了多少因变量的变异。
R²定义
R² = 1 - (SS_res / SS_tot)
SS_res = Σ(yᵢ - ŷᵢ)² (残差平方和)
SS_tot = Σ(yᵢ - ȳ)² (总平方和)
等价形式
R² = SS_reg / SS_tot
SS_reg = Σ(ŷᵢ - ȳ)² (回归平方和)
R²的含义
取值范围:0 ≤ R² ≤ 1
R² = 0:模型完全没用,预测等于均值ȳ
R² = 1:完美拟合,所有点在回归线上
R² = 0.8:模型解释了80%的变异
(剩余20%由误差ε解释)
✅ R²的优点
• 直观易懂(百分比)
• 无量纲,便于比较
• 广泛使用,易于交流
• 01之间,容易判断
⚠️ R²的局限
• 添加变量总会增加R²
• 不能判断模型是否合适
• 高R²≠因果关系
• 低R²不一定是坏模型
💡 实践指南
• R² > 0.7:通常认为拟合较好
• 0.5 < R² < 0.7:中等拟合
• R² < 0.5:拟合较差,但可能仍有用
• 重要:不同领域标准不同,社会科学R²=0.3可能已经很好

📏 误差指标:MSE、RMSE、MAE

除了R²,还有多种误差指标从不同角度评估模型。
1
MSE(均方误差)
MSE = (1/n) Σ(yᵢ - ŷᵢ)²
含义:残差平方的平均值
单位:y的单位的平方
特点:惩罚大误差
2
RMSE(均方根误差)
RMSE = √MSE = √[(1/n) Σ(yᵢ - ŷᵢ)²]
含义:MSE的平方根
单位:与y相同
特点:更直观,常用
3
MAE(平均绝对误差)
MAE = (1/n) Σ|yᵢ - ŷᵢ|
含义:残差绝对值的平均
单位:与y相同
特点:对异常值不敏感
4
MAPE(平均绝对百分比误差)
MAPE = (100/n) Σ|yᵢ - ŷᵢ|/|yᵢ|
含义:相对误差的百分比
单位:百分比(%)
特点:易于理解和比较
🎯 如何选择?
• RMSE:最常用,与y同单位,便于解释
• MAE:数据有异常值时使用
• MAPE:需要相对误差时使用(如预测增长率)
• MSE:理论分析和优化目标
• 建议:同时报告多个指标

💻 Python实现:评估指标计算

使用sklearn和numpy计算各种评估指标。
完整评估代码
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import (
    mean_squared_error, 
    mean_absolute_error, 
    r2_score
)
import matplotlib.pyplot as plt

# 示例数据
area = np.array([50, 60, 70, 80, 90, 100, 110, 120, 130, 140]).reshape(-1, 1)
price = np.array([150, 180, 210, 240, 270, 300, 330, 360, 390, 420])

# 拟合模型
model = LinearRegression()
model.fit(area, price)

# 预测
y_pred = model.predict(area)

# 计算评估指标
def evaluate_model(y_true, y_pred):
    """
    计算所有常用评估指标
    
    Parameters:
    -----------
    y_true : array-like
        真实值
    y_pred : array-like
        预测值
    
    Returns:
    --------
    metrics : dict
        评估指标字典
    """
    # R²
    r2 = r2_score(y_true, y_pred)
    
    # MSE
    mse = mean_squared_error(y_true, y_pred)
    
    # RMSE
    rmse = np.sqrt(mse)
    
    # MAE
    mae = mean_absolute_error(y_true, y_pred)
    
    # MAPE
    mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100
    
    # 组织结果
    metrics = {
        'R²': r2,
        'MSE': mse,
        'RMSE': rmse,
        'MAE': mae,
        'MAPE': mape
    }
    
    return metrics

# 评估模型
metrics = evaluate_model(price, y_pred)

# 打印结果
print("="*50)
print("模型评估指标")
print("="*50)
for name, value in metrics.items():
    if name == 'R²':
        print(f"{name:8s}: {value:.4f}")
    elif name == 'MAPE':
        print(f"{name:8s}: {value:.2f}%")
    else:
        print(f"{name:8s}: {value:.2f}")
print("="*50)

# 可视化:实际值 vs 预测值
plt.figure(figsize=(12, 4))

# 子图1:散点图 + 回归线
plt.subplot(1, 3, 1)
plt.scatter(area, price, color='blue', s=50, alpha=0.6, label='实际数据')
plt.plot(area, y_pred, color='red', linewidth=2, label='回归线')
plt.xlabel('面积(m²)')
plt.ylabel('价格(万元)')
plt.title(f'拟合结果 (R²={metrics["R²"]:.3f})')
plt.legend()
plt.grid(True, alpha=0.3)

# 子图2:实际值 vs 预测值
plt.subplot(1, 3, 2)
plt.scatter(price, y_pred, color='green', s=50, alpha=0.6)
plt.plot([price.min(), price.max()], 
         [price.min(), price.max()], 
         'r--', linewidth=2, label='完美预测')
plt.xlabel('实际价格(万元)')
plt.ylabel('预测价格(万元)')
plt.title('实际 vs 预测')
plt.legend()
plt.grid(True, alpha=0.3)

# 子图3:残差图
plt.subplot(1, 3, 3)
residuals = price - y_pred
plt.scatter(y_pred, residuals, color='purple', s=50, alpha=0.6)
plt.axhline(y=0, color='r', linestyle='--', linewidth=2)
plt.xlabel('预测价格(万元)')
plt.ylabel('残差(万元)')
plt.title(f'残差图 (RMSE={metrics["RMSE"]:.2f})')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
左图:拟合结果
• 散点图+回归线
• 显示R²值
• 直观看拟合效果
中图:预测精度
• 实际vs预测散点
• 45°线表示完美
• 偏离程度看误差
右图:残差分析
• 残差vs预测值
• 应随机分布在0附近
• 检验假设

🔍 残差诊断:检验模型假设

残差分析是检验模型假设是否满足的重要手段。
1. 残差 vs 拟合值图
目的:检验线性关系和同方差性
✅ 好的模式:
残差随机分布在0附近,无明显模式
❌ 坏的模式:
• 曲线模式→非线性
• 扇形→异方差
2. QQ图(正态性检验)
目的:检验残差是否服从正态分布
✅ 好的模式:
点基本在45°直线上
❌ 坏的模式:
• S形→偏斜
• 尾部偏离→重尾
3. 残差直方图
目的:直观查看残差分布
✅ 好的模式:
近似钟形(正态)分布
❌ 坏的模式:
• 明显偏斜
• 多峰
4. 尺度-位置图
目的:检验同方差性(另一个角度)
方法:√|残差| vs 拟合值
期望:点应均匀分布,无趋势
📝 完整诊断代码(点击展开)
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

def residual_diagnostics(y_true, y_pred):
    """
    完整的残差诊断图
    
    Parameters:
    -----------
    y_true : array-like
        真实值
    y_pred : array-like
        预测值
    """
    residuals = y_true - y_pred
    
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))
    
    # 1. 残差 vs 拟合值
    ax1 = axes[0, 0]
    ax1.scatter(y_pred, residuals, alpha=0.6)
    ax1.axhline(y=0, color='r', linestyle='--', linewidth=2)
    ax1.set_xlabel('拟合值')
    ax1.set_ylabel('残差')
    ax1.set_title('残差 vs 拟合值')
    ax1.grid(True, alpha=0.3)
    
    # 2. QQ图
    ax2 = axes[0, 1]
    stats.probplot(residuals, dist="norm", plot=ax2)
    ax2.set_title('QQ图(正态性检验)')
    ax2.grid(True, alpha=0.3)
    
    # 3. 残差直方图
    ax3 = axes[1, 0]
    ax3.hist(residuals, bins=20, edgecolor='black', alpha=0.7)
    ax3.axvline(x=0, color='r', linestyle='--', linewidth=2)
    ax3.set_xlabel('残差')
    ax3.set_ylabel('频数')
    ax3.set_title('残差直方图')
    ax3.grid(True, alpha=0.3)
    
    # 4. 尺度-位置图
    ax4 = axes[1, 1]
    ax4.scatter(y_pred, np.sqrt(np.abs(residuals)), alpha=0.6)
    ax4.set_xlabel('拟合值')
    ax4.set_ylabel('√|残差|')
    ax4.set_title('尺度-位置图')
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 统计检验
    print("="*50)
    print("残差诊断统计量")
    print("="*50)
    
    # Shapiro-Wilk正态性检验
    stat, p_value = stats.shapiro(residuals)
    print(f"Shapiro-Wilk检验:")
    print(f"  统计量: {stat:.4f}")
    print(f"  p值: {p_value:.4f}")
    if p_value > 0.05:
        print("  结论: 残差可能服从正态分布 ✓")
    else:
        print("  结论: 残差可能不服从正态分布 ✗")
    
    print("\n" + "="*50)

# 使用示例
# residual_diagnostics(price, y_pred)

🎯 调整R²:解决变量增加问题

普通R²的一个问题是:添加任何变量都会使R²增加,即使该变量无用。调整R²解决了这个问题。
调整R²公式
R̄² = 1 - [(1 - R²)(n - 1) / (n - p - 1)]
n:样本量
p:自变量个数
简单回归:p=1
R² vs R̄²
R²:
• 添加变量→总是增加
• 不惩罚复杂度
• 可能过拟合
R̄²:
• 添加变量→可能减少
• 惩罚无用变量
• 防止过拟合
何时使用?
简单回归:
R²和R̄²差别很小,用R²即可
多元回归:
必须用R̄²,尤其是比较不同变量数的模型
💡 关键性质
• R̄² ≤ R²:调整后的值不会更大
• R̄²可能为负:模型极差时可能<0
• 变量选择:用R̄²选择模型,不用R²
• Python:model.score()返回R²,需要手动计算R̄²

⚠️ 常见问题与解决方案

模型诊断中常见的问题及其解决方法。
问题1:非线性关系
症状:残差图显示明显曲线模式
解决方案:
• 添加多项式项(x²、x³)
• 对y或x取对数
• 使用非线性模型
# 多项式回归
from sklearn.preprocessing
import PolynomialFeatures
poly = PolynomialFeatures(2)
X_poly = poly.fit_transform(X)
问题2:异方差性
症状:残差图呈扇形,方差不恒定
解决方案:
• 对y取对数/平方根
• 加权最小二乘(WLS)
• 稳健标准误
# 对数变换
y_log = np.log(y)
model.fit(X, y_log)
# 预测时需还原
y_pred = np.exp(model.predict(X))
问题3:异常值
症状:个别点残差极大
解决方案:
• 检查数据是否有误
• 移除异常值(谨慎)
• 使用稳健回归
# 稳健回归
from sklearn.linear_model
import HuberRegressor
robust = HuberRegressor()
robust.fit(X, y)
问题4:非正态残差
症状:QQ图明显偏离直线
影响:
• 参数估计仍无偏
• 统计推断可能失效
• 大样本影响较小
解决方案:
• 检查异常值
• 变换y(对数等)
• Bootstrap置信区间

🎯 24.3 关键要点

拟合优度
• R²决定系数
• 0到1之间
• 解释变异比例
• 调整R²更好
误差指标
• MSE、RMSE
• MAE、MAPE
• 不同侧重
• 综合评估
残差诊断
• 4种诊断图
• 检验假设
• 发现问题
• 指导改进
常见问题
• 非线性
• 异方差
• 异常值
• 非正态
💡 核心洞察
• 多维评估:不能只看R²,需要结合多个指标和残差诊断
• 假设检验:残差分析是检验模型假设的关键步骤
• 发现问题:诊断图能揭示模型的不足和数据的问题
• 指导改进:根据诊断结果选择合适的解决方案
• 下一步:学习多元线性回归(24.4多个自变量)
✦
24.4

多元线性回归

Multiple Linear Regression

现实中,因变量往往受多个因素影响。 多元线性回归将简单回归推广到多个自变量的情况, 是实际应用中最常用的回归形式。

🎯 多元回归模型

多元线性回归用多个自变量预测一个因变量。
模型形式
y = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ + ε
公式(3):多元线性回归模型
y:因变量(响应变量)
要预测的目标
x₁, x₂, ..., xₚ:自变量
p个预测特征
β₀:截距(intercept)
所有x=0时y的期望值
β₁, β₂, ..., βₚ:偏回归系数
控制其他变量时的边际效应
矩阵形式
y = Xβ + ε
y:n×1向量
[y₁, y₂, ..., yₙ]ᵀ
X:n×(p+1)矩阵
设计矩阵(含截距列)
β:(p+1)×1向量
[β₀, β₁, ..., βₚ]ᵀ
设计矩阵X:
第一列全为1(对应β₀),其余列为各自变量的观测值
✅ 优势
• 同时考虑多个因素
• 控制混淆变量
• 提高预测精度
• 更符合现实
⚠️ 挑战
• 多重共线性
• 维度诅咒(p大)
• 解释复杂
• 需要更多数据

📊 实例:房价预测(多因素)

现实中房价受多个因素影响,让我们建立更实际的模型。
问题设定
任务:根据多个特征预测房价
特征:
• x₁: 面积(m²)
• x₂: 房龄(年)
• x₃: 卧室数量
• x₄: 距离市中心(km)
模型:
价格 = β₀ + β₁×面积 + β₂×房龄 + β₃×卧室数 + β₄×距离 + ε
系数解释
β₁ = 2.5:
控制其他变量不变时,面积每增加1m²,价格增加2.5万元
β₂ = -0.5:
控制其他变量不变时,房龄每增加1年,价格降低0.5万元
β₃ = 15:
控制其他变量不变时,多1个卧室,价格增加15万元
β₄ = -3:
控制其他变量不变时,距市中心每远1km,价格降低3万元
关键概念:偏回归系数
偏效应(Partial Effect):
βⱼ衡量xⱼ对y的边际效应,控制了所有其他变量的影响
与简单回归的区别:
• 简单回归:总效应(包含混淆)
• 多元回归:偏效应(剔除混淆)
• 系数可能符号都不同!

📐 最小二乘法:矩阵形式

多元回归的最小二乘解有优雅的矩阵形式。
正规方程
β̂ = (XᵀX)⁻¹Xᵀy
公式(4):最小二乘估计量的矩阵形式
推导要点
1. 最小化RSS = (y - Xβ)ᵀ(y - Xβ)
2. 对β求导:∂RSS/∂β = -2Xᵀy + 2XᵀXβ
3. 令导数为0:XᵀXβ = Xᵀy
4. 两边左乘(XᵀX)⁻¹:β̂ = (XᵀX)⁻¹Xᵀy
矩阵维度
y:n×1
n个观测值
X:n×(p+1)
n行,p+1列
β:(p+1)×1
p+1个参数
XᵀX:(p+1)×(p+1) 方阵
XᵀX需可逆:要求n ≥ p+1 且X列满秩(无完全多重共线性)
✅ 矩阵形式优点
• 简洁优雅
• 适用任意p
• 计算高效(矩阵运算)
• 便于理论分析
💡 计算注意
• 不要直接求逆XᵀX
• 用QR分解或SVD
• sklearn自动优化
• 大p时考虑正则化

💻 Python实现:多元回归

使用sklearn和numpy实现多元线性回归。
完整示例:房价预测
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error
import matplotlib.pyplot as plt

# 生成模拟数据
np.random.seed(42)
n_samples = 100

# 特征
area = np.random.uniform(50, 150, n_samples)        # 面积 50-150m²
age = np.random.uniform(0, 30, n_samples)           # 房龄 0-30年
bedrooms = np.random.randint(1, 5, n_samples)       # 卧室 1-4个
distance = np.random.uniform(1, 20, n_samples)      # 距离 1-20km

# 真实模型:价格 = 50 + 2.5×面积 - 0.5×房龄 + 15×卧室 - 3×距离 + 噪声
price = (50 + 2.5*area - 0.5*age + 15*bedrooms - 3*distance 
         + np.random.normal(0, 10, n_samples))

# 组织数据
X = np.column_stack([area, age, bedrooms, distance])
feature_names = ['面积(m²)', '房龄(年)', '卧室数', '距离(km)']

# 创建DataFrame便于查看
df = pd.DataFrame(X, columns=feature_names)
df['价格(万元)'] = price

print("数据前5行:")
print(df.head())
print()

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, price, test_size=0.2, random_state=42
)

# 拟合模型
model = LinearRegression()
model.fit(X_train, y_train)

# 查看参数
print("="*60)
print("模型参数")
print("="*60)
print(f"截距 β₀: {model.intercept_:.2f}")
for i, (name, coef) in enumerate(zip(feature_names, model.coef_)):
    print(f"系数 β{i+1} ({name}): {coef:.2f}")
print("="*60)
print()

# 预测
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)

# 评估
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
train_rmse = np.sqrt(mean_squared_error(y_train, y_train_pred))
test_rmse = np.sqrt(mean_squared_error(y_test, y_test_pred))

print("="*60)
print("模型评估")
print("="*60)
print(f"训练集 R²: {train_r2:.4f}")
print(f"测试集 R²: {test_r2:.4f}")
print(f"训练集 RMSE: {train_rmse:.2f}")
print(f"测试集 RMSE: {test_rmse:.2f}")
print("="*60)
print()

# 特征重要性可视化
plt.figure(figsize=(12, 4))

# 子图1:系数柱状图
plt.subplot(1, 3, 1)
colors = ['green' if c > 0 else 'red' for c in model.coef_]
plt.barh(feature_names, model.coef_, color=colors, alpha=0.7)
plt.xlabel('系数值')
plt.title('特征系数')
plt.axvline(x=0, color='black', linestyle='--', linewidth=1)
plt.grid(True, alpha=0.3)

# 子图2:训练集预测
plt.subplot(1, 3, 2)
plt.scatter(y_train, y_train_pred, alpha=0.5, s=30)
plt.plot([y_train.min(), y_train.max()], 
         [y_train.min(), y_train.max()], 
         'r--', linewidth=2, label='完美预测')
plt.xlabel('实际价格(万元)')
plt.ylabel('预测价格(万元)')
plt.title(f'训练集 (R²={train_r2:.3f})')
plt.legend()
plt.grid(True, alpha=0.3)

# 子图3:测试集预测
plt.subplot(1, 3, 3)
plt.scatter(y_test, y_test_pred, alpha=0.5, s=30, color='green')
plt.plot([y_test.min(), y_test.max()], 
         [y_test.min(), y_test.max()], 
         'r--', linewidth=2, label='完美预测')
plt.xlabel('实际价格(万元)')
plt.ylabel('预测价格(万元)')
plt.title(f'测试集 (R²={test_r2:.3f})')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 预测新房价格
new_house = np.array([[100, 5, 3, 10]])  # 100m², 5年, 3卧室, 10km
predicted_price = model.predict(new_house)[0]
print(f"\n新房预测价格: {predicted_price:.2f}万元")
print(f"(100m², 5年房龄, 3卧室, 距市中心10km)")
数据准备
• 生成模拟数据
• 组织特征矩阵X
• 训练/测试划分
• DataFrame展示
模型训练
• LinearRegression
• 拟合训练数据
• 查看系数
• 预测评估
可视化分析
• 系数柱状图
• 训练集拟合
• 测试集泛化
• 预测新样本

⚠️ 多重共线性问题

多重共线性是多元回归中的重要问题,会导致估计不稳定。
什么是多重共线性?
定义:自变量之间存在高度线性相关
完全共线性:
某个xⱼ可以被其他x完全线性表示
例:x₃ = 2x₁ + 3x₂
近似共线性:
某个xⱼ与其他x高度相关
例:身高(cm)和身高(inch)
后果
1. 系数不稳定:
微小数据变化→系数大幅波动
2. 标准误增大:
系数估计精度↓,置信区间↑
3. 统计显著性↓:
t检验可能失败(但整体F检验OK)
4. 解释困难:
难以分离各变量的独立效应
检测方法
1. 相关矩阵:查看特征间相关系数,|r| > 0.8警惕
2. VIF(方差膨胀因子):
VIFⱼ = 1/(1-Rⱼ²),其中Rⱼ²是xⱼ对其他x回归的R²
VIF > 10:严重共线性,VIF > 5:需要关注
解决方案
1. 移除变量:
删除冗余或相关性高的变量
2. 合并变量:
创建复合指标(如主成分)
3. 正则化:
Ridge回归(L2惩罚)
4. 增加数据:
更多样本有助于稳定估计
💡 重要提示
• 预测:多重共线性对预测影响不大(如果特征间关系稳定)
• 解释:严重影响系数解释和统计推断
• 权衡:预测为主→可容忍;解释为主→必须处理

🔍 变量选择方法

实践中常需选择哪些变量应该包含在模型中。
1
前向选择(Forward Selection)
方法:从空模型开始,逐个添加最能改善模型的变量
✅ 计算量小
❌ 一旦加入不能移除
2
后向消除(Backward Elimination)
方法:从全模型开始,逐个移除最不显著的变量
✅ 考虑所有变量的联合效应
❌ 需要n > p
3
逐步回归(Stepwise Regression)
方法:前向+后向的组合,既能加入也能移除
✅ 更灵活
⚠️ 可能陷入局部最优
4
全子集选择(Best Subset)
方法:尝试所有可能的变量组合,选最佳的
✅ 保证找到最优组合
❌ 计算量大(2ᵖ个模型)
🎯 选择标准
• AIC(赤池信息准则):-2log(L) + 2p,越小越好
• BIC(贝叶斯信息准则):-2log(L) + p·log(n),惩罚更重
• 调整R²:考虑模型复杂度的拟合优度
• 交叉验证:最可靠,但计算量大
💡 现代方法
• Lasso回归:L1正则化自动做变量选择
• Elastic Net:L1+L2组合
• 随机森林:特征重要性排序
• 建议:Lasso是首选的自动变量选择方法

🎯 24.4 关键要点

多元模型
• y=β₀+Σβⱼxⱼ+ε
• 矩阵形式y=Xβ+ε
• 偏回归系数
• 控制其他变量
矩阵求解
• β̂=(XᵀX)⁻¹Xᵀy
• 正规方程
• 需要列满秩
• 用QR/SVD
多重共线性
• 特征高度相关
• VIF检测
• 系数不稳定
• Ridge/Lasso
变量选择
• 前向/后向
• AIC/BIC准则
• Lasso自动选择
• 交叉验证
💡 核心洞察
• 多元优势:同时考虑多因素,控制混淆变量,更符合现实
• 偏效应:系数βⱼ是控制其他变量后xⱼ的边际效应
• 矩阵优雅:β̂=(XᵀX)⁻¹Xᵀy统一了简单和多元回归
• 共线性挑战:特征相关导致估计不稳定,需检测和处理
• 下一步:学习正则化方法(24.5 Ridge和Lasso)
✦
24.5

正则化回归

Regularized Regression: Ridge & Lasso

普通最小二乘法在特征相关或维度过高时会出现问题。正则化方法通过添加惩罚项, 约束参数大小,从而提高模型的稳定性和泛化能力。

🎯 为什么需要正则化?

普通最小二乘法(OLS)在某些情况下表现不佳。
问题1:多重共线性
症状:特征高度相关,系数不稳定
后果:微小数据变化导致系数剧烈波动
问题2:过拟合
症状:特征数p接近样本数n,模型过于复杂
后果:训练集表现好,测试集表现差
问题3:p > n问题
症状:特征数超过样本数
后果:(XᵀX)不可逆,OLS无解或有无穷多解
正则化的核心思想
约束参数大小:
在最小化RSS的同时,限制β的大小,防止系数过大
偏差-方差权衡:
• 增加偏差(bias):不完全拟合训练数据
• 减少方差(variance):提高模型稳定性
• 结果:更好的泛化能力

🏔️ Ridge回归(L2正则化)

Ridge回归在RSS基础上添加L2惩罚项(系数平方和)。
Ridge目标函数
minβ RSS + λΣⱼ₌₁ᵖ βⱼ²
= minβ Σᵢ₌₁ⁿ(yᵢ - β₀ - Σⱼ₌₁ᵖβⱼxᵢⱼ)² + λΣⱼ₌₁ᵖβⱼ²
公式(5):Ridge回归目标函数
RSS项:
拟合数据,最小化残差
L2惩罚项:
λΣβⱼ²,约束系数大小
λ(lambda):
正则化参数,控制惩罚强度
注意:
β₀不惩罚(截距项)
解析解
β̂Ridge = (XᵀX + λI)⁻¹Xᵀy
关键观察:
• λ=0:退化为OLS,β̂=(XᵀX)⁻¹Xᵀy
• λ> 0:XᵀX+λI必定可逆(即使XᵀX不可逆)
• λ→∞:β̂→0(系数被压缩到0附近)
✅ Ridge优点
• 解决多重共线性
• p> n时仍有解
• 提高稳定性
• 有解析解,计算快
• 所有系数保留
⚠️ Ridge局限
• 不做变量选择
• 系数仅收缩,不为0
• 特征多时解释困难
• 需要选择λ
💡 直观理解
• 几何视角:在β的L2球约束下最小化RSS
• 贝叶斯视角:等价于β的高斯先验(MAP估计)
• 效果:将大系数"收缩"向0,但不会精确为0
• 适用:多重共线性、p接近n、所有特征都有用

🎯 Lasso回归(L1正则化)

Lasso回归使用L1惩罚项(系数绝对值和),能够自动做变量选择。
Lasso目标函数
minβ RSS + λΣⱼ₌₁ᵖ |βⱼ|
= minβ Σᵢ₌₁ⁿ(yᵢ - β₀ - Σⱼ₌₁ᵖβⱼxᵢⱼ)² + λΣⱼ₌₁ᵖ|βⱼ|
公式(6):Lasso回归目标函数
RSS项:
拟合数据(同Ridge)
L1惩罚项:
λΣ|βⱼ|,系数绝对值和
Lasso的独特性质
1. 稀疏解(Sparsity):
许多系数精确为0,自动做变量选择
2. 无解析解:
需要用优化算法求解(如坐标下降、LARS)
3. 为什么L1产生稀疏解?
• 几何原因:L1约束域有"尖角"
• 等高线在尖角处相切→某些βⱼ=0
• L2约束域光滑→βⱼ接近0但不为0
✅ Lasso优点
• 自动变量选择
• 稀疏解,易解释
• 处理高维数据
• 特征筛选
⚠️ Lasso局限
• 强相关特征:只选一个
• p> n:最多选n个特征
• 分组特征不稳定
• 计算比Ridge慢
💡 适用场景
• 特征多:p很大,需要筛选
• 稀疏真相:只有少数特征真正有用
• 可解释性:需要简洁的模型
• 特征工程:自动特征选择

⚖️ Ridge vs Lasso:全面对比

Ridge和Lasso各有优劣,选择取决于具体问题。
特性Ridge(L2)Lasso(L1)
惩罚项λΣβⱼ²(平方和)λΣ|βⱼ|(绝对值和)
解析解✅ 有:(XᵀX+λI)⁻¹Xᵀy❌ 无,需优化算法
系数收缩收缩但不为0可以精确为0
变量选择❌ 不做✅ 自动做
稀疏性密集解稀疏解
多重共线性✅ 处理好⚠️ 只选一个
p> n问题✅ 可解✅ 可解(最多选n个)
计算速度快(有解析解)较慢(迭代优化)
可解释性中等(保留所有特征)高(稀疏,特征少)
几何约束L2球(光滑)L1菱形(有尖角)
贝叶斯先验高斯分布拉普拉斯分布
🎯 如何选择?
• 特征少、都有用:Ridge
• 特征多、需筛选:Lasso
• 多重共线性、分组特征:Ridge或Elastic Net
• 不确定:Elastic Net(L1+L2组合)
• 实践建议:都试试,交叉验证选最佳

💻 Python实现要点

Ridge和Lasso的核心API调用。
快速使用
# Ridge回归
from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)
model.fit(X_train, y_train)
# Lasso回归
from sklearn.linear_model import Lasso
model = Lasso(alpha=0.1)
model.fit(X_train, y_train)
💡 关键:使用前必须标准化特征!alpha即λ
📝 完整对比代码(点击展开)
import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score, mean_squared_error
import matplotlib.pyplot as plt

# 生成高维数据(多重共线性)
np.random.seed(42)
n_samples = 100
n_features = 50

# 创建相关特征
X = np.random.randn(n_samples, n_features)
# 添加多重共线性
X[:, 1] = X[:, 0] + np.random.randn(n_samples) * 0.1
X[:, 2] = X[:, 0] - X[:, 1] + np.random.randn(n_samples) * 0.1

# 只有前10个特征真正有用(稀疏真相)
true_coef = np.zeros(n_features)
true_coef[:10] = np.random.randn(10) * 2

# 生成响应变量
y = X @ true_coef + np.random.randn(n_samples) * 0.5

# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 标准化(重要!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 拟合不同模型
models = {
    'OLS': LinearRegression(),
    'Ridge': Ridge(alpha=1.0),
    'Lasso': Lasso(alpha=0.1),
    'Elastic Net': ElasticNet(alpha=0.1, l1_ratio=0.5)
}

results = {}
for name, model in models.items():
    # 训练
    model.fit(X_train_scaled, y_train)
    
    # 预测
    y_train_pred = model.predict(X_train_scaled)
    y_test_pred = model.predict(X_test_scaled)
    
    # 评估
    train_r2 = r2_score(y_train, y_train_pred)
    test_r2 = r2_score(y_test, y_test_pred)
    train_rmse = np.sqrt(mean_squared_error(y_train, y_train_pred))
    test_rmse = np.sqrt(mean_squared_error(y_test, y_test_pred))
    
    # 非零系数数量
    n_nonzero = np.sum(np.abs(model.coef_) > 1e-5)
    
    results[name] = {
        'train_r2': train_r2,
        'test_r2': test_r2,
        'train_rmse': train_rmse,
        'test_rmse': test_rmse,
        'n_nonzero': n_nonzero,
        'coef': model.coef_
    }

# 打印结果
print("="*70)
print("模型对比")
print("="*70)
print(f"{'模型':<15} {'训练R²':<10} {'测试R²':<10} {'训练RMSE':<12} {'测试RMSE':<12} {'非零系数'}")
print("-"*70)
for name, res in results.items():
    print(f"{name:<15} {res['train_r2']:<10.4f} {res['test_r2']:<10.4f} "
          f"{res['train_rmse']:<12.4f} {res['test_rmse']:<12.4f} {res['n_nonzero']}")
print("="*70)

# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 1. 系数对比
ax1 = axes[0, 0]
for name in ['OLS', 'Ridge', 'Lasso']:
    ax1.plot(results[name]['coef'], 'o-', label=name, alpha=0.7, markersize=4)
ax1.axhline(y=0, color='k', linestyle='--', linewidth=1)
ax1.set_xlabel('特征索引')
ax1.set_ylabel('系数值')
ax1.set_title('系数对比')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 2. 非零系数数量
ax2 = axes[0, 1]
nonzero_counts = [results[name]['n_nonzero'] for name in models.keys()]
colors = ['blue', 'green', 'red', 'purple']
ax2.bar(models.keys(), nonzero_counts, color=colors, alpha=0.7)
ax2.set_ylabel('非零系数数量')
ax2.set_title('模型复杂度')
ax2.grid(True, alpha=0.3, axis='y')

# 3. R²对比
ax3 = axes[1, 0]
x_pos = np.arange(len(models))
train_r2s = [results[name]['train_r2'] for name in models.keys()]
test_r2s = [results[name]['test_r2'] for name in models.keys()]
width = 0.35
ax3.bar(x_pos - width/2, train_r2s, width, label='训练集', alpha=0.7)
ax3.bar(x_pos + width/2, test_r2s, width, label='测试集', alpha=0.7)
ax3.set_xlabel('模型')
ax3.set_ylabel('R²')
ax3.set_title('拟合优度对比')
ax3.set_xticks(x_pos)
ax3.set_xticklabels(models.keys())
ax3.legend()
ax3.grid(True, alpha=0.3, axis='y')

# 4. 真实系数 vs Lasso估计
ax4 = axes[1, 1]
ax4.scatter(true_coef, results['Lasso']['coef'], alpha=0.6, s=30)
lim = max(abs(true_coef).max(), abs(results['Lasso']['coef']).max()) * 1.1
ax4.plot([-lim, lim], [-lim, lim], 'r--', linewidth=2, label='完美估计')
ax4.set_xlabel('真实系数')
ax4.set_ylabel('Lasso估计')
ax4.set_title('Lasso的变量选择能力')
ax4.legend()
ax4.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 交叉验证选择最佳α
from sklearn.linear_model import RidgeCV, LassoCV

# Ridge最佳α
alphas_ridge = np.logspace(-3, 3, 50)
ridge_cv = RidgeCV(alphas=alphas_ridge, cv=5)
ridge_cv.fit(X_train_scaled, y_train)
print(f"\nRidge最佳α: {ridge_cv.alpha_:.4f}")

# Lasso最佳α
lasso_cv = LassoCV(alphas=None, cv=5, max_iter=10000, random_state=42)
lasso_cv.fit(X_train_scaled, y_train)
print(f"Lasso最佳α: {lasso_cv.alpha_:.4f}")
print(f"Lasso选择的特征数: {np.sum(np.abs(lasso_cv.coef_) > 1e-5)}")
数据准备
• 高维数据(50特征)
• 多重共线性
• 稀疏真相(10个有用)
• 标准化很重要
模型对比
• OLS、Ridge、Lasso
• Elastic Net
• R²、RMSE评估
• 非零系数统计
可视化分析
• 系数对比
• 模型复杂度
• 泛化能力
• 变量选择效果

🎛️ 超参数选择:如何确定λ?

λ(alpha)是正则化的关键参数,需要仔细选择。
λ的作用
λ = 0:
无正则化,等于OLS
可能过拟合
λ适中:
平衡拟合与正则
最佳泛化
λ → ∞:
系数→0
可能欠拟合
交叉验证(最推荐)
方法:
尝试不同λ值,用交叉验证评估,选择CV误差最小的λ
# sklearn实现 from sklearn.linear_model import RidgeCV, LassoCV # Ridge CV ridge_cv = RidgeCV(alphas=[0.1, 1, 10, 100], cv=5) ridge_cv.fit(X, y) best_alpha = ridge_cv.alpha_ # Lasso CV(自动选择α范围) lasso_cv = LassoCV(cv=5) lasso_cv.fit(X, y) best_alpha = lasso_cv.alpha_
其他方法
信息准则:
AIC、BIC(快但不够准确)
验证集:
单独留出(样本少时不推荐)
💡 实践建议
• 搜索范围:对数尺度,如[1e-3, 1e-2, ..., 1e3]
• 粗搜索:先大范围找大概区域
• 细搜索:在最佳区域附近密集搜索
• 标准化:α的合适范围依赖特征尺度,务必先标准化
• sklearn默认:RidgeCV和LassoCV自动搞定

🎯 24.5 关键要点

正则化动机
• 多重共线性
• 过拟合
• p>n问题
• 偏差-方差权衡
Ridge(L2)
• 惩罚λΣβⱼ²
• 系数收缩不为0
• 保留所有特征
• 处理共线性
Lasso(L1)
• 惩罚λΣ|βⱼ|
• 系数可为0
• 自动变量选择
• 稀疏解
λ选择
• 交叉验证
• RidgeCV/LassoCV
• 对数尺度搜索
• 标准化重要
💡 核心洞察
• 权衡:正则化增加偏差但减少方差,提高泛化能力
• Ridge:特征都有用、多重共线性严重时首选
• Lasso:需要特征选择、稀疏解时首选
• Elastic Net:结合L1和L2优点,不确定时的保险选择
• 标准化:正则化对特征尺度敏感,务必先标准化
• 实践:都试试,用交叉验证选最佳
🎓 第24章完成!
从基础到高级,我们系统学习了线性回归:
• 24.1:基本概念和假设
• 24.2:最小二乘法求解
• 24.3:模型评估和诊断
• 24.4:多元回归和共线性
• 24.5:正则化方法(Ridge/Lasso)
恭喜完成数据科学最重要的基石之一!
✦
24.6

多项式回归与特征工程

Polynomial Regression & Feature Engineering

现实中许多关系是非线性的。 通过特征工程——创建新特征(如多项式项、交互项), 我们可以用线性模型拟合非线性关系,大幅提升模型表现。

🌊 超越线性:处理非线性关系

线性回归不意味着只能拟合直线,关键在于对参数β是线性的。
线性 vs 非线性
线性模型(对β):
y = β₀ + β₁x + β₂x² + β₃x³ ✅
虽然对x是非线性(曲线),但对β是线性
非线性模型(对β):
y = β₀exp(β₁x) ❌
对β是非线性,需要非线性优化
关键洞察:
通过特征变换(x→x²、log(x)等),可以用线性模型拟合非线性关系
多项式回归
y = β₀ + β₁x + β₂x² + β₃x³
拟合曲线关系
对数变换
y = β₀ + β₁log(x)
指数衰减/增长
交互项
y = β₀ + β₁x₁ + β₂x₂ + β₃x₁x₂
特征间相互作用

📈 多项式回归

多项式回归通过添加高次项(x²、x³等)来拟合曲线关系。
多项式模型
y = β₀ + β₁x + β₂x² + ... + βₐxᵈ + ε
d: 多项式阶数(degree)
d=1:直线
y = β₀ + β₁x
d=2:抛物线
y = β₀ + β₁x + β₂x²
d=3:三次曲线
y = β₀ + β₁x + β₂x² + β₃x³
核心API
# 创建多项式特征
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=3)
X_poly = poly.fit_transform(X)
# 拟合
model = LinearRegression()
model.fit(X_poly, y)
📝 完整示例代码(点击展开)
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# 生成数据
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = 2 + 3*X.ravel() - 0.5*X.ravel()**2 + np.random.randn(100)*2

# 创建多项式特征(2次)
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X)

# 拟合
model = LinearRegression()
model.fit(X_poly, y)

# 预测
y_pred = model.predict(X_poly)
print(f"R² = {model.score(X_poly, y):.4f}")
✅ 优点
• 拟合非线性关系
• 仍是线性模型(对β)
• 易于实现
• 可解释性好
⚠️ 注意事项
• d太大→过拟合
• 边界处波动大
• 需要交叉验证选d
• 高次项共线性严重
💡 实践建议
• 起点:从d=2或d=3开始
• 选择:用交叉验证或AIC/BIC选择d
• 标准化:高次项数值范围大,务必标准化
• 正则化:高次多项式易过拟合,考虑Ridge/Lasso
• 替代:d很大时考虑样条、GAM等方法

🔗 交互项:特征间的协同作用

交互项捕捉特征间的相互作用:一个特征的效应取决于另一个特征的值。
交互项模型
无交互项:
y = β₀ + β₁x₁ + β₂x₂

x₁和x₂独立作用,效应相加
有交互项:
y = β₀ + β₁x₁ + β₂x₂ + β₃(x₁×x₂)

x₁的效应随x₂变化
解释:
∂y/∂x₁ = β₁ + β₃x₂
x₁对y的边际效应不是常数,取决于x₂的值
实例:广告效应
问题:电视广告和网络广告对销量的影响
无交互模型:
销量 = β₀ + β₁×TV + β₂×网络
假设两种广告独立作用
有交互模型:
销量 = β₀ + β₁×TV + β₂×网络 + β₃×(TV×网络)
考虑协同效应
如果β₃> 0:
两种广告同时投放效果更好(协同)
如果β₃< 0:
一种广告会削弱另一种(竞争)
Python实现
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures

# 生成数据(有交互效应)
np.random.seed(42)
n = 100
x1 = np.random.uniform(0, 10, n)
x2 = np.random.uniform(0, 10, n)
# 真实模型有交互项
y = 5 + 2*x1 + 3*x2 + 0.5*x1*x2 + np.random.randn(n)*2

# 准备数据
X = np.column_stack([x1, x2])

# 模型1:无交互项
model_no_interact = LinearRegression()
model_no_interact.fit(X, y)
y_pred_no = model_no_interact.predict(X)

# 模型2:有交互项
# 方法1:手动创建
X_interact = np.column_stack([x1, x2, x1*x2])
model_interact = LinearRegression()
model_interact.fit(X_interact, y)
y_pred_yes = model_interact.predict(X_interact)

# 方法2:使用PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False, 
                          interaction_only=True)
X_poly = poly.fit_transform(X)
print(f"特征名称: {poly.get_feature_names_out(['x1', 'x2'])}")

# 评估
from sklearn.metrics import r2_score
r2_no = r2_score(y, y_pred_no)
r2_yes = r2_score(y, y_pred_yes)

print("="*60)
print("模型对比")
print("="*60)
print(f"无交互项模型 R²: {r2_no:.4f}")
print(f"有交互项模型 R²: {r2_yes:.4f}")
print(f"\n系数(有交互):")
print(f"  β₀ (截距): {model_interact.intercept_:.2f}")
print(f"  β₁ (x₁): {model_interact.coef_[0]:.2f}")
print(f"  β₂ (x₂): {model_interact.coef_[1]:.2f}")
print(f"  β₃ (x₁×x₂): {model_interact.coef_[2]:.2f}")
print("="*60)
🎯 何时使用交互项?
• 理论预期:领域知识提示特征间有协同
• 探索性分析:分组后斜率明显不同
• 模型改进:添加后R²显著提升
• 注意:交互项会快速增加特征数(p个特征→p(p-1)/2个交互项)

🔄 特征变换

通过数学变换改变特征的分布或关系形式。
1. 对数变换
log(x):
• 压缩右偏分布
• 处理指数增长
• y = β₀ + β₁log(x)
log(y):
• 处理异方差
• 百分比解释
• log(y) = β₀ + β₁x
2. 平方根/Box-Cox变换
√x:温和的压缩变换
Box-Cox:自动找最佳变换参数λ
from sklearn.preprocessing import PowerTransformer pt = PowerTransformer(method='box-cox') X_transformed = pt.fit_transform(X)
3. 标准化/归一化
StandardScaler:
z = (x-μ)/σ
均值0,标准差1
MinMaxScaler:
x' = (x-min)/(max-min)
缩放到[0,1]
4. 分箱(Binning)
将连续变量转为类别变量
例:年龄→[0-18, 18-35, 35-60, 60+]
pd.cut(age, bins=[0, 18, 35, 60, 100], labels=['儿童', '青年', '中年', '老年'])
💡 选择指南
• 偏态分布:log或Box-Cox
• 异方差:log(y)或√y
• 正则化:标准化(必须)
• 非线性:多项式或样条
• 类别化:分箱+独热编码

🎨 综合案例:房价预测特征工程

展示完整的特征工程流程。
完整代码
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import r2_score, mean_squared_error

# 生成模拟房价数据
np.random.seed(42)
n = 200

# 原始特征
area = np.random.uniform(50, 200, n)        # 面积
age = np.random.uniform(0, 50, n)           # 房龄
rooms = np.random.randint(1, 6, n)          # 房间数
distance = np.random.uniform(1, 30, n)      # 距市中心距离

# 真实关系包含:
# 1. 面积的平方效应(递减)
# 2. 面积×房间数交互
# 3. 距离的对数效应
price = (100 + 
         3*area - 0.005*area**2 +        # 面积非线性
         10*rooms +                       # 房间数
         0.05*area*rooms -                # 交互项
         5*np.log(distance+1) -           # 距离对数
         0.5*age +                        # 房龄
         np.random.randn(n)*10)           # 噪声

# 创建DataFrame
df = pd.DataFrame({
    '面积': area,
    '房龄': age,
    '房间数': rooms,
    '距离': distance,
    '价格': price
})

print("数据前5行:")
print(df.head())
print(f"\n数据形状: {df.shape}")

# 特征工程
def feature_engineering(df, fit=True, scaler=None, poly=None):
    """完整的特征工程流程"""
    X = df[['面积', '房龄', '房间数', '距离']].copy()
    
    # 1. 对数变换(距离)
    X['距离_log'] = np.log(X['距离'] + 1)
    
    # 2. 多项式特征(面积二次项)
    X['面积平方'] = X['面积'] ** 2
    
    # 3. 交互项(面积×房间数)
    X['面积×房间数'] = X['面积'] * X['房间数']
    
    # 移除原始距离(用对数版本替代)
    X = X.drop('距离', axis=1)
    
    # 4. 标准化
    if fit:
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
    else:
        X_scaled = scaler.transform(X)
    
    return X_scaled, scaler

# 划分数据
train_df, test_df = train_test_split(df, test_size=0.2, random_state=42)
y_train = train_df['价格'].values
y_test = test_df['价格'].values

# 应用特征工程
X_train, scaler = feature_engineering(train_df, fit=True)
X_test, _ = feature_engineering(test_df, fit=False, scaler=scaler)

# 对比模型
print("\n" + "="*70)
print("模型对比")
print("="*70)

# 基线:只用原始特征
X_train_base = train_df[['面积', '房龄', '房间数', '距离']].values
X_test_base = test_df[['面积', '房龄', '房间数', '距离']].values
scaler_base = StandardScaler()
X_train_base = scaler_base.fit_transform(X_train_base)
X_test_base = scaler_base.transform(X_test_base)

model_base = LinearRegression()
model_base.fit(X_train_base, y_train)
y_pred_base = model_base.predict(X_test_base)
r2_base = r2_score(y_test, y_pred_base)
rmse_base = np.sqrt(mean_squared_error(y_test, y_pred_base))

print(f"基线模型(原始特征):")
print(f"  测试集 R² = {r2_base:.4f}")
print(f"  测试集 RMSE = {rmse_base:.2f}")

# 特征工程模型
model_fe = LinearRegression()
model_fe.fit(X_train, y_train)
y_pred_fe = model_fe.predict(X_test)
r2_fe = r2_score(y_test, y_pred_fe)
rmse_fe = np.sqrt(mean_squared_error(y_test, y_pred_fe))

print(f"\n特征工程模型:")
print(f"  测试集 R² = {r2_fe:.4f}")
print(f"  测试集 RMSE = {rmse_fe:.2f}")
print(f"\n改进:")
print(f"  R²提升: {(r2_fe - r2_base):.4f}")
print(f"  RMSE降低: {(rmse_base - rmse_fe):.2f}")

# 特征重要性
feature_names = ['面积', '房龄', '房间数', '距离_log', '面积平方', '面积×房间数']
importances = np.abs(model_fe.coef_)
indices = np.argsort(importances)[::-1]

print(f"\n特征重要性(按系数绝对值):")
for i in indices:
    print(f"  {feature_names[i]:<15}: {importances[i]:.3f}")

print("="*70)

🎯 24.6 关键要点

超越线性
• 对β线性即可
• 对x可非线性
• 特征变换
• 仍用OLS求解
多项式回归
• x、x²、x³...
• 拟合曲线
• 交叉验证选d
• 配合正则化
交互项
• x₁×x₂
• 协同效应
• 边际效应变化
• 理论驱动
特征变换
• log、√、Box-Cox
• 标准化
• 分箱
• 改善分布
💡 核心洞察
• 线性模型≠直线:通过特征工程可拟合复杂非线性关系
• 多项式:简单强大,但需防止过拟合(交叉验证+正则化)
• 交互项:捕捉协同效应,数量O(p²)需谨慎
• 变换:log处理偏态和异方差,标准化配合正则化
• 流程:理解数据→领域知识→尝试变换→交叉验证→正则化
• 权衡:复杂度vs可解释性vs泛化能力
🎓 第24章真正完成!
从基础到高级,从理论到实践,我们全面学习了线性回归:
• 24.1:基本概念和假设
• 24.2:最小二乘法求解
• 24.3:模型评估和诊断
• 24.4:多元回归和共线性
• 24.5:正则化方法(Ridge/Lasso)
• 24.6:多项式回归与特征工程
这是数据科学最完整的线性回归教程!🎉
✦
24.7

统计推断与预测区间

Statistical Inference & Prediction Intervals

除了点估计,我们还需要量化不确定性: 系数是否显著?置信区间有多宽?预测有多可靠? 统计推断为这些问题提供了严格的数学框架。

📊 统计推断框架

线性回归的统计推断基于正态性假设。
回顾:模型假设
完整模型:
y = Xβ + ε, 其中 ε ~ N(0, σ²I)
假设1:线性关系
E[y|X] = Xβ
假设2:独立性
εᵢ相互独立
假设3:同方差
Var(εᵢ) = σ²(常数)
假设4:正态性
εᵢ ~ N(0, σ²)
参数估计量的分布
OLS估计量:
β̂ = (XᵀX)⁻¹Xᵀy
分布(在正态性假设下):
β̂ ~ N(β, σ²(XᵀX)⁻¹)
即:β̂是无偏的,方差为σ²(XᵀX)⁻¹
标准误:
SE(β̂ⱼ) = σ̂ · √[(XᵀX)⁻¹]ⱼⱼ
其中σ̂² = RSS/(n-p-1)(残差方差的估计)

🔬 假设检验

假设检验用于判断系数是否显著(是否显著不为0)。
t检验(单个系数)
假设:
H₀: βⱼ = 0(系数为0,特征无效)
H₁: βⱼ ≠ 0(系数不为0,特征有效)
t统计量
t = β̂ⱼ / SE(β̂ⱼ)
在H₀下,t ~ t(n-p-1)(t分布,自由度n-p-1)
决策规则:
|t| > t临界值 → 拒绝H₀
或 p值 < α → 拒绝H₀
解释:
拒绝H₀ → βⱼ显著不为0
特征xⱼ对y有显著影响
F检验(整体显著性)
假设:
H₀: β₁ = β₂ = ... = βₚ = 0(所有系数为0)
H₁: 至少一个βⱼ ≠ 0(至少有一个特征有效)
F统计量
F = (TSS - RSS) / p
RSS / (n - p - 1)
= (R² / p) / [(1 - R²) / (n - p - 1)]
在H₀下,F ~ F(p, n-p-1)
解释:
• F很大 → 模型整体显著
• F≈1 → 模型无效(不比均值模型好)
• 通常F检验显著但个别t检验不显著(多重共线性)
💡 p值的含义
• 定义:在H₀为真的条件下,观察到当前或更极端结果的概率
• p < 0.05:通常认为显著(但不是绝对标准)
• p < 0.01:高度显著
• p > 0.05:不显著(但不代表βⱼ=0,可能是样本量不足)
• 警告:p值不是"效应重要性",大样本下微小效应也可能显著

📏 置信区间

置信区间给出参数的合理取值范围。
系数的置信区间
95%置信区间
β̂ⱼ ± t₀.₀₂₅(n-p-1) × SE(β̂ⱼ)
或写作:[β̂ⱼ - t × SE, β̂ⱼ + t × SE]
含义:
有95%把握真实βⱼ在此区间内
与假设检验的关系:
若0不在95%CI内 ⟺ p<0.05
置信区间的解释
✅ 正确解释:
如果重复抽样构造置信区间,95%的区间会包含真实参数
❌ 错误解释:
真实参数有95%概率在此区间内(参数是固定的,不是随机变量)
实用解释:
• 区间窄 → 估计精确
• 区间宽 → 估计不确定(需要更多数据或降低噪声)
• 区间不包含0 → 参数显著
🎯 影响CI宽度的因素
• 样本量n↑ → SE↓ → CI更窄(更精确)
• 噪声σ²↑ → SE↑ → CI更宽(更不确定)
• X变异↑ → SE↓ → CI更窄(信息更多)
• 多重共线性↑ → SE↑ → CI更宽(难以分离效应)

🎯 预测区间

预测新样本时,不仅要考虑参数的不确定性,还要考虑随机误差。
点预测 vs 区间预测
点预测:
ŷ₀ = x₀ᵀβ̂
给出单个数值
区间预测:
[ŷ₀ - margin, ŷ₀ + margin]
给出合理范围
预测区间公式
95%预测区间
ŷ₀ ± t₀.₀₂₅(n-p-1) × SE_pred
SE_pred = σ̂ × √[1 + x₀ᵀ(XᵀX)⁻¹x₀]
注意"1+":这是关键!
两种不确定性
1. 参数估计的不确定性:
σ̂ × √[x₀ᵀ(XᵀX)⁻¹x₀]
• 来源:β̂是估计值,不是真值
• 可以通过增加样本量减小
2. 固有随机误差:
σ̂ × √1 = σ̂
• 来源:即使知道真实β,y仍有随机性
• 无法通过增加样本量消除
总不确定性(两者叠加):
√(σ̂² + σ̂²x₀ᵀ(XᵀX)⁻¹x₀) = σ̂√(1 + x₀ᵀ(XᵀX)⁻¹x₀)
置信区间 vs 预测区间
特性置信区间(CI)预测区间(PI)
估计对象E[y|x₀] 的均值新样本y₀的值
不确定性仅参数估计参数+随机误差
宽度较窄较宽(PI ≥ CI)
n→∞时→ 0(消失)→ σ(不为0)
应用估计均值预测个体
💡 实践建议
• 报告预测:同时给出点估计和95% PI
• 内插vs外推:x₀在训练数据范围内(内插)→可靠;范围外(外推)→不可靠
• PI宽度:反映预测的不确定性,不是模型的好坏
• 多个预测:各预测区间相互独立(都有5%概率失败)

💻 Python实现:完整统计推断

使用statsmodels实现完整的统计推断。
完整代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression

# 生成数据
np.random.seed(42)
n = 100
X = np.random.uniform(0, 10, n)
true_beta0, true_beta1 = 5, 2
y = true_beta0 + true_beta1 * X + np.random.randn(n) * 3

# 方法1:使用statsmodels(推荐,提供完整统计信息)
X_with_const = sm.add_constant(X)  # 添加截距列
model_sm = sm.OLS(y, X_with_const)
results = model_sm.fit()

print("="*70)
print("statsmodels完整输出")
print("="*70)
print(results.summary())
print("\n" + "="*70)

# 提取关键统计量
print("\n关键统计量:")
print("-"*70)
print(f"系数估计:")
print(f"  β₀ (截距): {results.params[0]:.4f}")
print(f"  β₁ (斜率): {results.params[1]:.4f}")
print(f"\n标准误:")
print(f"  SE(β₀): {results.bse[0]:.4f}")
print(f"  SE(β₁): {results.bse[1]:.4f}")
print(f"\nt统计量和p值:")
print(f"  β₀: t={results.tvalues[0]:.4f}, p={results.pvalues[0]:.4e}")
print(f"  β₁: t={results.tvalues[1]:.4f}, p={results.pvalues[1]:.4e}")
print(f"\n95%置信区间:")
conf_int = results.conf_int(alpha=0.05)
print(f"  β₀: [{conf_int[0][0]:.4f}, {conf_int[0][1]:.4f}]")
print(f"  β₁: [{conf_int[1][0]:.4f}, {conf_int[1][1]:.4f}]")
print(f"\nR²和F统计量:")
print(f"  R² = {results.rsquared:.4f}")
print(f"  调整R² = {results.rsquared_adj:.4f}")
print(f"  F统计量 = {results.fvalue:.4f}, p值 = {results.f_pvalue:.4e}")
print(f"\n残差标准差:")
print(f"  σ̂ = {np.sqrt(results.mse_resid):.4f}")
print("="*70)

# 预测与预测区间
print("\n" + "="*70)
print("预测与预测区间")
print("="*70)

# 新样本点
X_new = np.array([2, 5, 8])
X_new_const = sm.add_constant(X_new)

# 点预测
y_pred = results.predict(X_new_const)

# 置信区间(均值的区间)
pred_ci = results.get_prediction(X_new_const).conf_int(alpha=0.05)

# 预测区间(个体的区间)
pred_pi = results.get_prediction(X_new_const).conf_int(obs=True, alpha=0.05)

print(f"\n{'X':<10} {'预测值':<12} {'95% CI':<30} {'95% PI':<30}")
print("-"*70)
for i, x in enumerate(X_new):
    print(f"{x:<10.1f} {y_pred[i]:<12.2f} "
          f"[{pred_ci[i,0]:.2f}, {pred_ci[i,1]:.2f}]{'':>12} "
          f"[{pred_pi[i,0]:.2f}, {pred_pi[i,1]:.2f}]")
print("="*70)

# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 1. 拟合曲线与置信带
ax1 = axes[0, 0]
X_plot = np.linspace(X.min(), X.max(), 200)
X_plot_const = sm.add_constant(X_plot)
y_plot = results.predict(X_plot_const)
pred_plot = results.get_prediction(X_plot_const)
ci_plot = pred_plot.conf_int(alpha=0.05)
pi_plot = pred_plot.conf_int(obs=True, alpha=0.05)

ax1.scatter(X, y, alpha=0.5, s=30, label='数据')
ax1.plot(X_plot, y_plot, 'r-', linewidth=2, label='拟合线')
ax1.fill_between(X_plot, ci_plot[:, 0], ci_plot[:, 1], 
                  alpha=0.3, color='blue', label='95% 置信带(均值)')
ax1.fill_between(X_plot, pi_plot[:, 0], pi_plot[:, 1], 
                  alpha=0.2, color='green', label='95% 预测带(个体)')
ax1.set_xlabel('X')
ax1.set_ylabel('y')
ax1.set_title('拟合曲线、置信带与预测带')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 2. 系数的置信区间
ax2 = axes[0, 1]
params = results.params
conf_int = results.conf_int(alpha=0.05)
param_names = ['β₀\n(截距)', 'β₁\n(斜率)']
x_pos = np.arange(len(params))

ax2.errorbar(x_pos, params, 
             yerr=[params - conf_int[:, 0], conf_int[:, 1] - params],
             fmt='o', markersize=10, capsize=10, capthick=2, linewidth=2)
ax2.axhline(y=0, color='red', linestyle='--', linewidth=2, label='H₀: β=0')
ax2.set_xticks(x_pos)
ax2.set_xticklabels(param_names)
ax2.set_ylabel('系数值')
ax2.set_title('系数的95%置信区间')
ax2.legend()
ax2.grid(True, alpha=0.3, axis='y')

# 3. t统计量
ax3 = axes[1, 0]
t_values = results.tvalues
colors = ['green' if abs(t) > 2 else 'red' for t in t_values]
ax3.bar(param_names, t_values, color=colors, alpha=0.7)
ax3.axhline(y=2, color='black', linestyle='--', linewidth=1, label='临界值≈2')
ax3.axhline(y=-2, color='black', linestyle='--', linewidth=1)
ax3.axhline(y=0, color='gray', linestyle='-', linewidth=1)
ax3.set_ylabel('t统计量')
ax3.set_title('t统计量(|t|>2→显著)')
ax3.legend()
ax3.grid(True, alpha=0.3, axis='y')

# 4. 残差图
ax4 = axes[1, 1]
residuals = results.resid
fitted = results.fittedvalues
ax4.scatter(fitted, residuals, alpha=0.5, s=30)
ax4.axhline(y=0, color='red', linestyle='--', linewidth=2)
ax4.set_xlabel('拟合值')
ax4.set_ylabel('残差')
ax4.set_title('残差图')
ax4.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 方法2:手动计算(教学用)
print("\n" + "="*70)
print("手动计算(验证)")
print("="*70)

# 使用sklearn拟合
from sklearn.linear_model import LinearRegression
model_sk = LinearRegression()
model_sk.fit(X.reshape(-1, 1), y)

# 预测和残差
y_pred_train = model_sk.predict(X.reshape(-1, 1))
residuals = y - y_pred_train
RSS = np.sum(residuals**2)
TSS = np.sum((y - y.mean())**2)

# 自由度
n = len(y)
p = 1  # 1个自变量
df_resid = n - p - 1

# 残差标准差
sigma_hat = np.sqrt(RSS / df_resid)

# 标准误(简化计算,假设X已中心化)
X_centered = X - X.mean()
var_x = np.sum(X_centered**2)
SE_beta1 = sigma_hat / np.sqrt(var_x)
SE_beta0 = sigma_hat * np.sqrt(1/n + X.mean()**2/var_x)

# t统计量
t_beta0 = model_sk.intercept_ / SE_beta0
t_beta1 = model_sk.coef_[0] / SE_beta1

print(f"\n手动计算结果:")
print(f"  σ̂ = {sigma_hat:.4f}")
print(f"  SE(β₀) = {SE_beta0:.4f}")
print(f"  SE(β₁) = {SE_beta1:.4f}")
print(f"  t(β₀) = {t_beta0:.4f}")
print(f"  t(β₁) = {t_beta1:.4f}")
print(f"\n与statsmodels对比:基本一致✓")
print("="*70)
statsmodels
• 完整统计输出
• t检验、F检验
• CI和PI
• 推荐用于推断
可视化
• 置信带vs预测带
• 系数CI可视化
• t统计量柱状图
• 残差诊断
手动计算
• 理解公式
• 验证结果
• 教学目的
• 自定义需求

🎯 24.7 关键要点

统计框架
• β̂~N(β, σ²(XᵀX)⁻¹)
• 正态性假设
• 标准误SE
• t分布、F分布
假设检验
• t检验(单个β)
• F检验(整体)
• p值解释
• 显著性判断
置信区间
• β̂±t×SE
• 参数不确定性
• n↑→CI窄
• 0不在CI内→显著
预测区间
• PI > CI(更宽)
• 参数+随机误差
• n→∞, PI→σ
• 预测个体值
💡 核心洞察
• 统计推断:从点估计→区间估计→假设检验,量化不确定性
• t检验:判断单个系数是否显著,|t|> 2通常显著
• F检验:判断模型整体是否有效,与R²密切相关
• CI vs PI:CI估计均值(窄),PI预测个体(宽)
• 两种不确定性:参数估计(可减小)+随机误差(固有)
• 实践:用statsmodels获取完整统计信息,报告CI/PI
🎓 统计推断完成!
至此,线性回归的核心内容全部完成:
• 24.1-24.2:模型和求解(基础)
• 24.3:评估和诊断(质量控制)
• 24.4-24.5:多元和正则化(扩展)
• 24.6:特征工程(实践技巧)
• 24.7:统计推断(不确定性量化)
从点估计到区间估计,从拟合到推断,体系完整!