03 线性回归 (Linear Regression)
核心问题:如何量化变量之间的线性关系?
线性回归是统计学习中最基础、最重要的监督学习方法
- 拥有 200+ 年历史,但至今仍活跃于学术与工业界前沿
- 可解释性强:每个系数都有明确的经济含义
- 理论完备:拥有高斯-马尔可夫定理等坚实理论基础
- 计算高效:闭合解析解,无需迭代优化
本章目标:从 \(Y = \beta_0 + \beta_1 X + \epsilon\) 出发,构建完整的多元回归分析框架
线性回归在金融领域的四大核心应用
| 资产定价 |
CAPM / Fama-French 三因子 |
\(R_i - R_f = \alpha + \beta(R_m - R_f)\) |
| 宏观经济预测 |
泰勒规则 |
利率 ~ GDP增速 + 通胀率 |
| 公司财务分析 |
Altman Z-Score |
违约概率 ~ 财务指标组合 |
| 房地产估值 |
特征价格模型 |
房价 ~ 面积 + 区位 + 学区 |
核心本质:线性回归不仅是统计工具,更是理解经济金融运行规律的思维框架
线性模型的数学框架
\[ \large{Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \cdots + \beta_p X_p + \epsilon} \]
各符号的经济含义:
- \(Y\):响应变量(如营业收入、股票收益率)
- \(X_1, \ldots, X_p\):预测变量(如研发投入、宏观指标)
- \(\beta_0\):截距项,所有 \(X = 0\) 时 \(Y\) 的基准值
- \(\beta_j\):回归系数,\(X_j\) 每增加 1 单位时 \(Y\) 的平均变化量
- \(\epsilon\):误差项,捕捉所有未纳入模型的随机因素
简单线性回归:一元模型
\[ \large{Y = \beta_0 + \beta_1 X + \epsilon} \]
OLS 解析解:斜率与截距的闭合公式
由一阶条件推导得到:
截距估计:
\[ \large{\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x}} \]
斜率估计:
\[ \large{\hat{\beta}_1 = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sum_{i=1}^{n}(x_i - \bar{x})^2}} \]
关键性质:
- 回归线一定经过样本中心点 \((\bar{x}, \bar{y})\)
- 斜率 = 协方差 / 方差,与相关系数直接相关
实证:A 股上市公司销售费用对营收的影响
import numpy as np # 导入numpy用于数值计算
import pandas as pd # 导入pandas用于数据框操作
import matplotlib.pyplot as plt # 导入matplotlib用于绑图
import statsmodels.api as sm # 导入statsmodels用于OLS回归
import os # 导入os模块用于跨平台路径处理
# 配置中文字体与负号显示
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC', 'SimHei', 'Arial Unicode MS'] # 中文字体优先级
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题
# 自动选择本地数据路径(Windows或Linux)
local_data_path = r'C:\qiufei\data\stock\financial_statement.h5' # Windows默认路径
if not os.path.exists(local_data_path): # 若不存在则切换Linux路径
local_data_path = '/home/ubuntu/r2_data_mount/qiufei/data/stock/financial_statement.h5'
df_financial_raw = pd.read_hdf(local_data_path) # 读取A股上市公司财务报表数据
df_2023_annual = df_financial_raw[df_financial_raw['quarter'] == '2023q4'].copy() # 筛选2023年年报
target_columns = ['revenue', 'selling_expense'] # 目标字段:营业收入和销售费用
df_analysis = df_2023_annual[target_columns].dropna() # 剔除缺失值
# 过滤极端小值以保证对数变换有效
df_analysis = df_analysis[(df_analysis['revenue'] > 1e7) & (df_analysis['selling_expense'] > 1e6)]
df_analysis['log_revenue'] = np.log10(df_analysis['revenue']) # 对营业收入取常用对数
df_analysis['log_selling_expense'] = np.log10(df_analysis['selling_expense']) # 对销售费用取常用对数
双对数 OLS 回归:弹性系数的经济含义
Code
# 如果样本量过大,抽样以保持图表清晰
if len(df_analysis) > 250: # 样本量超过可视化阈值
df_plot = df_analysis.sample(250, random_state=42) # 固定种子抽样
else:
df_plot = df_analysis # 全量使用
x_selling = df_plot['log_selling_expense'] # 自变量:对数销售费用
y_revenue = df_plot['log_revenue'] # 因变量:对数营业收入
X_with_const = sm.add_constant(x_selling) # 添加截距列
ols_result = sm.OLS(y_revenue, X_with_const).fit() # OLS拟合
fig, ax = plt.subplots(figsize=(10, 6)) # 创建画布
ax.scatter(x_selling, y_revenue, alpha=0.5, s=50, color='#34495e', edgecolors='white') # 散点图
x_line = np.linspace(x_selling.min(), x_selling.max(), 100) # 拟合线X坐标
y_line = ols_result.predict(sm.add_constant(x_line)) # 预测值
ax.plot(x_line, y_line, color='#e74c3c', linewidth=3, # 红色拟合线
label=f'OLS: β₁={ols_result.params.iloc[1]:.3f}, R²={ols_result.rsquared:.3f}')
ax.set_xlabel('Log₁₀(销售费用)', fontsize=12) # X轴标签
ax.set_ylabel('Log₁₀(营业收入)', fontsize=12) # Y轴标签
ax.set_title('A股制造企业:销售费用→营业收入 (双对数OLS)', fontsize=14, fontweight='bold')
ax.legend(fontsize=11) # 图例
ax.grid(True, linestyle='--', alpha=0.5) # 网格
plt.tight_layout() # 调整布局
plt.show() # 显示
经济解读:双对数模型下,\(\hat{\beta}_1\) 直接代表弹性——销售费用每增加 1%,营收平均增加 \(\hat{\beta}_1\)%
系数估计的准确性:标准误与抽样分布
OLS 估计量的方差与标准误:
\[ \large{\text{SE}(\hat{\beta}_1) = \sqrt{\frac{\sigma^2}{\sum_{i=1}^{n}(x_i - \bar{x})^2}}} \]
\[ \large{\text{SE}(\hat{\beta}_0) = \sqrt{\sigma^2\left[\frac{1}{n} + \frac{\bar{x}^2}{\sum(x_i - \bar{x})^2}\right]}} \]
其中 \(\sigma^2\) 用残差标准误 (RSE) 估计:
\[ \large{\hat{\sigma}^2 = \frac{\text{RSS}}{n - 2}} \]
为什么除以 \(n-2\)? 因为估计了 2 个参数 (\(\beta_0, \beta_1\)),消耗了 2 个自由度(贝塞尔校正)
假设检验与置信区间
t 检验:检验 \(\beta_1\) 是否显著不为零
\[ \large{t = \frac{\hat{\beta}_1}{\text{SE}(\hat{\beta}_1)} \sim t_{n-2}} \]
- \(H_0: \beta_1 = 0\)(X 与 Y 无线性关系)
- \(H_a: \beta_1 \neq 0\)(X 与 Y 存在线性关系)
- 当 \(|t|\) 足够大(p值 < 0.05),拒绝 \(H_0\)
95% 置信区间:
\[ \large{\hat{\beta}_1 \pm t_{0.025,\, n-2} \cdot \text{SE}(\hat{\beta}_1)} \]
置信区间包含了”如果我们重复无数次抽样,95% 的区间会覆盖真实参数”的含义
模型拟合优度:\(R^2\) 与 RSE
\(R^2\) 衡量预测变量解释了响应变量多大比例的变异:
\[ \large{R^2 = 1 - \frac{\text{RSS}}{\text{TSS}} = 1 - \frac{\sum(y_i - \hat{y}_i)^2}{\sum(y_i - \bar{y})^2}} \]
| \(R^2 = 1\) |
模型完美拟合 |
\([0, 1]\) |
| \(R^2 = 0\) |
模型无解释力(等同于均值预测) |
|
| RSE |
观测值围绕拟合线的平均偏离程度 |
\([0, +\infty)\) |
重要提醒:\(R^2\) 随变量增加只增不减 → 需要调整 \(R^2\) 惩罚模型复杂度
从一元到多元:多元线性回归
当有 \(p\) 个预测变量时:
\[ \large{Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \cdots + \beta_p X_p + \epsilon} \]
矩阵形式更简洁:
\[ \large{\mathbf{Y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\epsilon}} \]
其中:
- \(\mathbf{Y}\):\(n \times 1\) 响应向量
- \(\mathbf{X}\):\(n \times (p+1)\) 设计矩阵(含截距列)
- \(\boldsymbol{\beta}\):\((p+1) \times 1\) 系数向量
- \(\boldsymbol{\epsilon}\):\(n \times 1\) 误差向量
正规方程:OLS 的矩阵解析解
最小化 RSS 的矩阵推导:
\[ \text{RSS} = (\mathbf{Y} - \mathbf{X}\boldsymbol{\beta})^T(\mathbf{Y} - \mathbf{X}\boldsymbol{\beta}) \]
对 \(\boldsymbol{\beta}\) 求梯度并令其为零:
\[ \nabla_{\boldsymbol{\beta}} \text{RSS} = -2\mathbf{X}^T\mathbf{Y} + 2\mathbf{X}^T\mathbf{X}\boldsymbol{\beta} = \mathbf{0} \]
得到正规方程 (Normal Equations):
\[ \large{\mathbf{X}^T\mathbf{X}\hat{\boldsymbol{\beta}} = \mathbf{X}^T\mathbf{Y}} \]
OLS 的矩阵闭合解:
\[ \large{\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}} \]
前提:\(\mathbf{X}^T\mathbf{X}\) 必须可逆 → 否则存在完全多重共线性
多元回归实证:三种费用对营收的综合贡献
import numpy as np # 导入numpy用于数值运算
import pandas as pd # 导入pandas用于数据框操作
import matplotlib.pyplot as plt # 导入matplotlib用于绑图
from sklearn.linear_model import LinearRegression # 导入线性回归
from sklearn.preprocessing import StandardScaler # 导入标准化工具
np.random.seed(42) # 设置随机种子
n_firms = 250 # 模拟250家企业
# 模拟三种费用(万元)
rd_expense = np.random.uniform(100, 1000, n_firms) # 研发费用
sales_expense = np.random.uniform(80, 800, n_firms) # 销售费用
admin_expense = np.random.uniform(50, 500, n_firms) # 管理费用
# DGP: Revenue = 200 + 1.5*RD + 2.2*Sales + 0.8*Admin + ε
revenue_observed = (200 + 1.5 * rd_expense + 2.2 * sales_expense + # 真实线性关系
0.8 * admin_expense + np.random.normal(0, 150, n_firms)) # 加入噪声
df_multi = pd.DataFrame({ # 构建数据框
'研发费用': rd_expense, '销售费用': sales_expense, # 三种费用列
'管理费用': admin_expense, '营业收入': revenue_observed # 响应变量
})
feature_names = ['研发费用', '销售费用', '管理费用'] # 特征名列表
X_raw = df_multi[feature_names] # 提取特征矩阵
y_target = df_multi['营业收入'] # 提取目标向量
scaler = StandardScaler() # 实例化标准化器
X_scaled = scaler.fit_transform(X_raw) # 标准化(均值0标准差1)
model_multi = LinearRegression().fit(X_scaled, y_target) # 拟合多元回归模型
print(f'截距: {model_multi.intercept_:.2f}') # 输出截距
for name, coef in zip(feature_names, model_multi.coef_): # 遍历输出标准化系数
print(f'{name} 标准化系数: {coef:.2f}') # 标准化后系数可直接比较重要性
print(f'R² = {model_multi.score(X_scaled, y_target):.4f}') # 输出拟合优度
截距: 2212.06
研发费用 标准化系数: 399.39
销售费用 标准化系数: 476.76
管理费用 标准化系数: 98.41
R² = 0.9460
标准化系数直接反映各变量的相对重要性(消除了量纲差异)
三维可视化:回归超平面
Code
from mpl_toolkits.mplot3d import Axes3D # 导入3D绑图引擎
fig = plt.figure(figsize=(12, 8)) # 创建画布
ax = fig.add_subplot(111, projection='3d') # 添加3D子图
# 绘制散点
scatter = ax.scatter(X_scaled[:, 0], X_scaled[:, 1], y_target, # 研发(X)、销售(Y)、营收(Z)
c=y_target, cmap='viridis', s=40, alpha=0.6) # 颜色映射营收高低
# 构建网格用于绘制回归平面
x_grid = np.linspace(X_scaled[:, 0].min(), X_scaled[:, 0].max(), 20) # 研发维度网格
y_grid = np.linspace(X_scaled[:, 1].min(), X_scaled[:, 1].max(), 20) # 销售维度网格
X_mesh, Y_mesh = np.meshgrid(x_grid, y_grid) # 二维网格矩阵
Z_mesh = (model_multi.intercept_ + model_multi.coef_[0] * X_mesh + # 预测超平面
model_multi.coef_[1] * Y_mesh + model_multi.coef_[2] * 0) # 管理费用=0(均值)
ax.plot_surface(X_mesh, Y_mesh, Z_mesh, alpha=0.2, color='red') # 半透明红色回归平面
ax.set_xlabel('研发费用(标准化)', fontsize=11) # X轴
ax.set_ylabel('销售费用(标准化)', fontsize=11) # Y轴
ax.set_zlabel('营业收入(万元)', fontsize=11) # Z轴
ax.set_title('多元回归超平面', fontsize=14, fontweight='bold') # 标题
plt.tight_layout() # 调整布局
plt.show() # 显示
F 检验:整体模型的联合显著性
检验所有预测变量是否都与 \(Y\) 无关:
- \(H_0: \beta_1 = \beta_2 = \cdots = \beta_p = 0\)
- \(H_a:\) 至少有一个 \(\beta_j \neq 0\)
\[ \large{F = \frac{(\text{TSS} - \text{RSS}) / p}{\text{RSS} / (n - p - 1)} \sim F_{p,\, n-p-1}} \]
| 分子 |
模型解释的平均变异(每个自由度) |
| 分母 |
残差的平均变异(每个自由度) |
| F 值大 |
模型整体解释力显著 |
| p值 < 0.05 |
拒绝 \(H_0\),至少一个变量有用 |
变量选择与多重共线性诊断
单变量显著性:对每个 \(\beta_j\) 做 t 检验
多重共线性:预测变量间高度相关时的危害:
- 系数估计不稳定,方向可能反转
- 标准误膨胀,本应显著的变量变得不显著
- 解释力被”瓜分”
方差膨胀因子 (VIF) 诊断:
\[ \large{\text{VIF}(\hat{\beta}_j) = \frac{1}{1 - R_j^2}} \]
| 1 ~ 5 |
可接受 |
| 5 ~ 10 |
中度共线性,需关注 |
| > 10 |
严重共线性,必须处理 |
处理方法:剔除冗余变量、PCA 降维、岭回归 / Lasso
定性预测变量:虚拟变量编码
对于分类变量(如企业性质),引入虚拟变量 (Dummy Variable):
\[ D_i = \begin{cases} 1 & \text{国有企业} \\ 0 & \text{民营企业} \end{cases} \]
模型:\(Y_i = \beta_0 + \beta_1 D_i + \epsilon_i\)
系数的经济含义:
- \(\beta_0\):民营企业的平均值(基准组)
- \(\beta_0 + \beta_1\):国有企业的平均值
- \(\beta_1\):两组之间的平均差异
多分类变量(如行业)需要 \(k-1\) 个虚拟变量编码 \(k\) 个类别
线性模型的扩展:交互效应
可加性假设的放松:变量之间可能存在协同效应
\[ \large{Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \beta_3(X_1 \times X_2) + \epsilon} \]
交互项 \(\beta_3\) 的含义:
- \(\beta_3 > 0\):两个变量有正向协同效应
- \(\beta_3 < 0\):两个变量有负向协同效应
- \(\beta_3 = 0\):无交互,退化为标准可加模型
金融案例:研发投入在低竞争行业中的产出比是否高于高竞争行业?
\[ \text{Revenue} = \beta_0 + \beta_1 X_{RD} + \beta_2 X_{HHI} + \beta_3(X_{RD} \times X_{HHI}) + \epsilon \]
非线性关系:多项式回归
线性假设的放松:真实关系可能呈 U 型或倒 U 型
\[ \large{Y = \beta_0 + \beta_1 X + \beta_2 X^2 + \epsilon} \]
经典案例——熊彼特假设:公司规模与创新效率的倒 U 型关系
- \(\beta_2 < 0\):存在最优规模,超过后边际效益转负
- 最优规模点:\(X^* = -\frac{\beta_1}{2\beta_2}\)
from sklearn.preprocessing import PolynomialFeatures # 导入多项式特征生成器
from sklearn.pipeline import Pipeline # 导入管道工具
np.random.seed(42) # 固定随机种子
n_obs = 200 # 200个观测
x_budget = np.random.uniform(0, 300, n_obs) # 广告预算
# 二次关系:边际收益递减
y_sales = 5 + 0.06 * x_budget - 0.0001 * x_budget**2 + np.random.normal(0, 2, n_obs)
模型复杂度对比:欠拟合 vs 适度拟合 vs 过拟合
Code
from sklearn.metrics import r2_score # 导入R²评分
fig, axes = plt.subplots(1, 3, figsize=(18, 5)) # 1行3列
degrees = [1, 2, 10] # 三种阶数
colors = ['steelblue', 'darkgreen', 'crimson'] # 对应颜色
titles = ['线性(欠拟合)', '二次(适度)', '10次(过拟合)'] # 标题
for i, (deg, clr, ttl) in enumerate(zip(degrees, colors, titles)): # 遍历
pipe = Pipeline([('poly', PolynomialFeatures(deg)), ('lr', LinearRegression())]) # 管道
pipe.fit(x_budget.reshape(-1, 1), y_sales) # 拟合
x_grid = np.linspace(0, 300, 300).reshape(-1, 1) # 网格
y_pred = pipe.predict(x_grid) # 预测
r2 = r2_score(y_sales, pipe.predict(x_budget.reshape(-1, 1))) # R²
axes[i].scatter(x_budget, y_sales, alpha=0.4, color='gray', s=40) # 散点
axes[i].plot(x_grid, y_pred, color=clr, linewidth=2.5) # 拟合线
axes[i].set_title(f'{ttl}\nR²={r2:.4f}', fontsize=13, fontweight='bold') # 标题
axes[i].set_xlabel('广告预算(万元)', fontsize=11) # X轴
axes[i].set_ylabel('销售量(千件)', fontsize=11) # Y轴
axes[i].grid(True, alpha=0.3) # 网格
plt.tight_layout() # 调整
plt.show() # 显示
第2章的偏差-方差权衡在此完美体现:复杂度太低偏差大,太高方差大
调整 \(R^2\):惩罚模型复杂度
\(R^2\) 随变量增加只增不减 → 不适合比较不同复杂度的模型
\[ \large{\text{Adjusted } R^2 = 1 - \frac{\text{RSS}/(n - p - 1)}{\text{TSS}/(n - 1)}} \]
对比:
| \(R^2\) |
只增不减 |
固定变量数的模型评估 |
| 调整 \(R^2\) |
增加无用变量时会下降 |
不同变量数的模型比较 |
| AIC / BIC |
信息准则,惩罚更严 |
模型选择 |
经验法则:优先参考调整 \(R^2\),辅以 AIC/BIC