ML 学习站
跳到正文

多元回归与虚拟变量

多元线性回归、虚拟变量、逻辑回归铺垫。

40 分钟2 / 43,109
加载中...

本章探讨了多元回归及其在房价分析中的应用,核心概念包括多元回归模型、偏效应和虚拟变量。多元回归模型通过同时考虑多个自变量来预测因变量,βᵢ表示在控制其他变量不变的情况下,xᵢ对y的偏效应。虚拟变量用于编码分类特征,如城市,通过设置基准类并为其他类别创建虚拟变量来避免虚拟变量陷阱。读者将学会如何构建多元回归模型,解释模型系数,并处理分类变量。此外,本章还介绍了交互作用、多重共线性、调整R²和F检验等高级主题。完成学习后,读者能够运用多元回归分析复杂数据集,评估模型性能,并进行特征选择和模型优化。

多元回归与虚拟变量

本章问题: 房价由面积、房间数、房龄、楼层、位置共同决定。一个一个简单回归显然不够。多元回归 = 同时考虑多个变量, 跟 ML 的"多特征"完全等价。

1. 多元回归模型

  • β₀: 截距 (所有 x = 0 时的 y)
  • βᵢ: 第 i 个变量的偏效应 (其他变量不变时, xᵢ 增加 1 单位, y 变多少)
  • ε: 残差 ~ N(0, σ²)
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression

# 模拟多元数据: 3 个特征
X, y = make_regression(
    n_samples=200, n_features=3,
    noise=10, random_state=42,
    coef=True  # 保留真实系数
)
print(f"真实系数: {X.shape}")  # 真实 [真 β1, β2, β3]
model = LinearRegression().fit(X, y)
print(f"拟合系数: {model.coef_.round(2)}")
print(f"拟合截距: {model.intercept_:.2f}")
# 拟合系数应该接近真实

2. 多元回归的"偏效应"含义

关键: β₁ 不是"y vs x₁ 的简单斜率", 而是"控制 x₂, x₃ 不变时, x₁ 的影响"。

# 演示: 简单回归 vs 多元回归系数差异
import pandas as pd
import numpy as np
np.random.seed(42)
n = 200
x1 = np.random.normal(0, 1, n)       # 真实"感兴趣"变量
x2 = 0.8 * x1 + np.random.normal(0, 0.3, n)  # x2 跟 x1 强相关
y = 1 + 0.5 * x1 + 0.3 * x2 + np.random.normal(0, 0.5, n)

# 简单回归 y ~ x1 (忽略 x2)
m1 = LinearRegression().fit(x1.reshape(-1, 1), y)
print(f"简单回归: β1 = {m1.coef_[0]:.3f} (真实 0.5)")

# 多元回归 y ~ x1 + x2
m2 = LinearRegression().fit(np.column_stack([x1, x2]), y)
print(f"多元回归: β1 = {m2.coef_[0]:.3f}, β2 = {m2.coef_[1]:.3f}")
# β1 接近 0.5, β2 接近 0.3

⚠️ 忽略重要变量 → 偏倚。如果 x2 是混杂, 简单回归的 β1 会被错误估计 (这里因为 x1 和 x2 都跟 y 正相关, β1 会被"放大")。

3. 虚拟变量 (Dummy Variable): 编码分类特征

"城市" 是一类变量: 北京/上海/广州/深圳。不能直接算 1.5 城市!

3.1 编码方法

  • 选一个"基准类" (如北京), 其他每个类 1 个虚拟变量
  • 基准类不编码 (避免虚拟变量陷阱: 完全多重共线性)
城市:  北京  上海  广州  深圳
编码:  0    1    0    0   → 上海
       0    0    1    0   → 广州
       0    0    0    1   → 深圳
       0    0    0    0   → 北京 (基准)
import pandas as pd
df = pd.DataFrame({
    "city": ["北京", "上海", "广州", "深圳"] * 50,
    "size": np.random.uniform(50, 200, 200),
})
# 独热编码
df_encoded = pd.get_dummies(df, columns=["city"], drop_first=True)
# drop_first=True 避免虚拟变量陷阱
print(df_encoded.head())

3.2 系数的解释

price = 100 + 0.5 × size + 20 × city_上海 + 15 × city_广州 + 25 × city_深圳
  • 截距 100: 北京房子的基础价
  • β_size = 0.5: 面积每 +1 m², 房价 +0.5 万
  • β_上海 = 20: 上海房子比北京贵 20 万 (其他条件相同时)
  • β_广州 = 15: 广州比北京贵 15 万
  • β_深圳 = 25: 深圳最贵
# 拟合
from sklearn.linear_model import LinearRegression
df["price"] = 100 + 0.5 * df["size"] + np.random.normal(0, 5, 200)
df["city"] = pd.Categorical(df["city"])
df_encoded = pd.get_dummies(df, columns=["city"], drop_first=True)
X = df_encoded.drop("price", axis=1)
y = df_encoded["price"]
model = LinearRegression().fit(X, y)
print("系数:", dict(zip(X.columns, model.coef_.round(2))))
print("截距:", model.intercept_.round(2))
# 应该接近 [0.5, 20, 15, 25] 跟 100

4. 交互作用: 变量不是独立的

真实情况: 楼层对房价的影响因城市而异 (一线城市黄金楼层贵, 三线不区分)。

模型:

price = β₀ + β₁ × size + β₂ × city_上海 + β₃ × (size × city_上海) + ε
  • β₃: 上海的"size 效应"相对北京多多少
df_encoded["size_x_上海"] = df_encoded["size"] * df_encoded["city_上海"]
model = LinearRegression().fit(df_encoded[["size", "city_上海", "size_x_上海"]], y)
print("size:", model.coef_[0], "  上海:", model.coef_[1], "  交互:", model.coef_[2])
# size + 交互 = 上海的 size 效应

5. 多元回归诊断

5.1 多重共线性 (Multicollinearity)

问题: 几个自变量高度相关, 系数估计不稳定 (跟真实差很多, 但 R² 仍高)

检测:

  • VIF (Variance Inflation Factor): VIF > 10 严重共线性
from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd

def calc_vif(X):
    return pd.DataFrame({
        "feature": X.columns,
        "VIF": [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
    })

# 例
np.random.seed(42)
X = pd.DataFrame({
    "x1": np.random.normal(0, 1, 100),
    "x2": np.random.normal(0, 1, 100),
    "x3": lambda: None,  # 跟 x1 强相关
})
X["x3"] = X["x1"] * 0.9 + np.random.normal(0, 0.1, 100)

print(calc_vif(X))
# x3 的 VIF > 10, 跟 x1 高度共线, 应删除

解决:

  1. 删除高度共线的变量
  2. 岭回归 (L2 正则化, 下章)
  3. 主成分分析 (PCA, 高级)

5.2 调整 R²

R² 永远增加, 即使加噪声。调整 R² 惩罚了"无意义特征":

特征数调整 R²
00.50.5
加 1 个有用特征0.60.59
加 1 个噪声特征0.5010.49
# sklearn 不直接给, 算一下
def adj_r2(model, X, y):
    n = X.shape[0]
    k = X.shape[1]
    r2 = model.score(X, y)
    return 1 - (1 - r2) * (n - 1) / (n - k - 1)

5.3 F 检验: 整体显著

H₀: β₁ = β₂ = ... = β_k = 0 (所有自变量都没用) H₁: 至少一个 βᵢ ≠ 0

import statsmodels.api as sm

# statsmodels 给出完整诊断
X_sm = sm.add_constant(X)  # 加截距
model = sm.OLS(y, X_sm).fit()
print(model.summary())
# 输出含: R², 调整 R², F 检验, 每个系数的 t 检验

6. 模型选择: 哪些特征该留?

6.1 前进法 / 后退法 / 逐步法

import statsmodels.api as sm
import pandas as pd

def stepwise_selection(X, y, direction="both"):
    """逐步回归"""
    selected = []
    remaining = list(X.columns)
    while remaining:
        best_p = 1
        best_feat = None
        for feat in remaining:
            current = selected + [feat]
            X_curr = sm.add_constant(X[current])
            p = sm.OLS(y, X_curr).fit().pvalues[feat]
            if p < best_p:
                best_p = p
                best_feat = feat
        if best_p &lt; 0.05:
            selected.append(best_feat)
            remaining.remove(best_feat)
        else:
            break
    return selected

6.2 信息准则: AIC / BIC

越低越好, 惩罚"复杂模型"。

# statsmodels 自动给
print(f"AIC = {model.aic}")
print(f"BIC = {model.bic}")

7. 实战: 房价多因素分析

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split

# 模拟真实感房价数据
np.random.seed(42)
n = 500
df = pd.DataFrame({
    "size": np.random.uniform(50, 200, n),         # m²
    "rooms": np.random.randint(1, 6, n),            # 房间数
    "age": np.random.uniform(0, 50, n),             # 房龄
    "floor": np.random.randint(1, 30, n),           # 楼层
    "city": np.random.choice(["北京", "上海", "广州", "深圳"], n),
    "distance_subway": np.random.uniform(0.1, 5, n),  # 距地铁 km
})
df_encoded = pd.get_dummies(df, columns=["city"], drop_first=True).astype(float)

# 真实关系 + 噪声
df["price"] = (
    50
    + 0.5 * df["size"]
    + 5 * df["rooms"]
    - 0.3 * df["age"]
    + 0.2 * df["floor"]
    - 2 * df["distance_subway"]
    + (df["city"] == "上海").astype(int) * 30
    + (df["city"] == "广州").astype(int) * 15
    + (df["city"] == "深圳").astype(int) * 25
    + np.random.normal(0, 5, n)
)

# 1. 普通线性回归
X = df_encoded.drop(columns=["size", "rooms", "age", "floor", "distance_subway"]).join(
    df[["size", "rooms", "age", "floor", "distance_subway"]]
)
y = df["price"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

lr = LinearRegression().fit(X_train, y_train)
y_pred = lr.predict(X_test)
print(f"线性回归 R² = {r2_score(y_test, y_pred):.3f}")
print(f"调整 R² = {1 - (1-r2_score(y_test, y_pred))*(len(y_test)-1)/(len(y_test)-X.shape[1]-1):.3f}")

# 2. 岭回归 (L2 正则化, 防过拟合)
ridge = Ridge(alpha=1.0).fit(X_train, y_train)
y_pred_r = ridge.predict(X_test)
print(f"岭回归 R² = {r2_score(y_test, y_pred_r):.3f}")

# 3. Lasso (L1, 自动特征选择)
lasso = Lasso(alpha=0.1).fit(X_train, y_train)
y_pred_l = lasso.predict(X_test)
print(f"Lasso R² = {r2_score(y_test, y_pred_l):.3f}")
print(f"Lasso 选中的特征数: {(lasso.coef_ != 0).sum()} / {X.shape[1]}")

8. 多元回归的 ML 等价

经典统计ML 概念
多元线性回归sklearn LinearRegression
调整 R²测试集 R²
AIC / BIC赤池信息量, 模型选择
岭回归L2 正则化 (sklearn Ridge)
LassoL1 正则化 + 特征选择 (sklearn Lasso)
逐步回归前向/后向特征选择 (SequentialFeatureSelector)
F 检验模型整体显著性
系数 t 检验特征重要性 (permutation importance)
虚拟变量OneHotEncoder, Embedding
交互项特征交叉 (Feature Crossing)

9. Python 实战: 完整多元回归报告

import statsmodels.api as sm
import pandas as pd

# statsmodels 给出学术论文级别报告
X_sm = sm.add_constant(X)
model = sm.OLS(y, X_sm).fit()

print(model.summary())
# 输出:
# - R², 调整 R²
# - F 统计量 (整体显著)
# - 每个系数的 β, SE, t, p, [0.025, 0.975]
# - AIC, BIC
# - 残差诊断
# - 多重共线性警告

10. 小结

你学到了关键点
多元回归同时考虑 k 个变量, βᵢ 是"偏效应"
虚拟变量分类特征用 0/1 编码, drop_first 避免陷阱
交互项β₃ × (x₁ × x₂) 表示"x₁ 效应因 x₂ 而异"
多重共线性VIF > 10 严重, 用岭回归 / PCA / 删变量
调整 R²惩罚复杂模型, 比 R² 可靠
F 检验整体显著性 (vs 0 模型)
AIC/BIC模型选择, 越低越好
跟 ML 等价多元回归 = sklearn LinearRegression 起点

11. 习题

  1. 模拟数据, 真实关系 y = 5 + 2x₁ - 1.5x₂ + 0.8x₁x₂ + ε:

    • 拟合多元回归 (含交互项)
    • 系数跟真实差多少?
    • 调整 R²?
  2. VIF 计算: 生成 3 个变量, 其中 x3 = 0.95 × x1 + 噪声:

    • 计算 VIF
    • 删除 x3 后重新拟合, 系数跟删除前有什么变化?
👉 查看参考答案
  1. 提示: 用 np.random.normal(0, 1, 100) 生成 x₁, x₂, 拟合模型含交互项, 系数应接近 [5, 2, -1.5, 0.8]。

  2. VIF 计算会显示 x3 跟 x1 高度共线 (VIF > 10), 删除后 x1 系数更稳定, 但 R² 略降 (因为 x3 含部分信息)。

12. 下一章


📚 本章来源: 改编自 Triola《基础统计学》第 14 版 第 10 章 10-4、10-5 节, 加入 ML 全套等价工具。

章末小测验

检验你对《多元回归与虚拟变量》的掌握程度。

1

关于多元回归模型中的偏效应,以下说法正确的是:

2

关于虚拟变量编码,以下说法正确的是:

3

关于多重共线性,以下说法正确的是:

4

关于调整 R², 以下说法正确的是:

5

关于模型选择,以下说法正确的是:

讨论区(0)

加载评论中...