本章探讨了多元回归及其在房价分析中的应用,核心概念包括多元回归模型、偏效应和虚拟变量。多元回归模型通过同时考虑多个自变量来预测因变量,βᵢ表示在控制其他变量不变的情况下,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 高度共线, 应删除
解决:
- 删除高度共线的变量
- 岭回归 (L2 正则化, 下章)
- 主成分分析 (PCA, 高级)
5.2 调整 R²
R² 永远增加, 即使加噪声。调整 R² 惩罚了"无意义特征":
| 特征数 | R² | 调整 R² |
|---|---|---|
| 0 | 0.5 | 0.5 |
| 加 1 个有用特征 | 0.6 | 0.59 |
| 加 1 个噪声特征 | 0.501 | 0.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 < 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) |
| Lasso | L1 正则化 + 特征选择 (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. 习题
-
模拟数据, 真实关系 y = 5 + 2x₁ - 1.5x₂ + 0.8x₁x₂ + ε:
- 拟合多元回归 (含交互项)
- 系数跟真实差多少?
- 调整 R²?
-
VIF 计算: 生成 3 个变量, 其中 x3 = 0.95 × x1 + 噪声:
- 计算 VIF
- 删除 x3 后重新拟合, 系数跟删除前有什么变化?
👉 查看参考答案
-
提示: 用
np.random.normal(0, 1, 100)生成 x₁, x₂, 拟合模型含交互项, 系数应接近 [5, 2, -1.5, 0.8]。 -
VIF 计算会显示 x3 跟 x1 高度共线 (VIF > 10), 删除后 x1 系数更稳定, 但 R² 略降 (因为 x3 含部分信息)。
12. 下一章
- 卡方检验: 拟合优度与列联表: 类别变量的统计
- 方差分析 ANOVA: 多组均值比较
- 机器学习入门 → 线性回归: 多元回归的 ML 实现
📚 本章来源: 改编自 Triola《基础统计学》第 14 版 第 10 章 10-4、10-5 节, 加入 ML 全套等价工具。
章末小测验
检验你对《多元回归与虚拟变量》的掌握程度。
关于多元回归模型中的偏效应,以下说法正确的是:
关于虚拟变量编码,以下说法正确的是:
关于多重共线性,以下说法正确的是:
关于调整 R², 以下说法正确的是:
关于模型选择,以下说法正确的是: