1. 为什么我们还在用两百年前的OLS
1.1 一个被低估的“老家伙”
最小二乘法(Ordinary Least Squares,OLS)线性回归,这个名字听起来像是统计学课本里第一章就会出现的“老古董”。很多人学完就扔,觉得它太简单、太基础,实际工作中恨不得直接上XGBoost、神经网络。但我做了十多年数据建模,踩过无数坑之后发现一个反直觉的事实:大部分业务场景下,OLS能给你80%的答案,而且解释成本几乎为零。
先把这个东西说清楚。OLS干的事情本质上就一件:找一条直线(或者超平面),让所有数据点到这条直线的垂直距离的平方和最小。为什么是平方而不是绝对值?这里面有两个原因。第一,平方函数处处可导,求极值方便,能直接得到解析解。第二,平方会放大较大误差的权重,相当于对“离谱的点”惩罚更重,这符合大多数实际场景的需求。
它解决的核心问题是:当你想知道一个或多个因素对某个结果的影响方向和大小,并且希望这个影响是线性可加的时候,OLS就是最直接的工具。比如你想知道广告投入每增加一万块,销售额平均能涨多少;比如你想知道温度、湿度、风速分别对用电量有多大影响。这些问题不需要深度学习,OLS就能给你一个干净、可解释的答案。
适合谁看?三类人。第一类是做数据分析但没系统学过统计的朋友,想搞明白回归到底怎么回事。第二类是学过但忘了的,需要一份能直接上手操作的参考。第三类是用过OLS但踩过坑的,想看看有没有什么自己没注意到的细节。不管你是哪类,这篇内容都会给你一些能直接抄作业的东西。
1.2 OLS到底在优化什么
很多人用OLS就是调个库,model.fit(X, y)一行代码完事。但如果你不理解它在优化什么,出了问题你根本不知道怎么排查。
形式化地说,OLS假设真实关系是 $y = X\beta + \epsilon$,其中 $\epsilon$ 是误差项。我们要找的 $\hat{\beta}$ 使得残差平方和最小:
$$RSS(\beta) = \sum_{i=1}^{n}(y_i - x_i^T\beta)^2$$
写成矩阵形式就是 $(y - X\beta)^T(y - X\beta)$。对 $\beta$ 求导令其为零,得到正规方程:
$$X^TX\hat{\beta} = X^Ty$$
如果 $X^TX$ 可逆,解析解就是 $\hat{\beta} = (X^TX)^{-1}X^Ty$。
这个公式看起来简单,但里面藏着OLS最核心的假设和最容易出问题的地方。$X^TX$ 可逆意味着什么?意味着你的特征之间不能有完全的线性关系。如果两个特征完全共线,$X^TX$ 就不可逆,你就没法直接求逆。这就是为什么后面要讲多重共线性问题。
注意:实际计算中几乎不会真的去求矩阵逆,而是用QR分解或者SVD分解来解正规方程,数值稳定性好得多。但理解求逆这个过程,对理解OLS的假设和局限至关重要。
2. 五个核心假设,每一个都能让你翻车
2.1 线性假设:世界没你想的那么直
OLS的第一个假设是模型对参数是线性的。注意,是对参数线性,不是对自变量线性。你可以放 $x^2$、$\log(x)$、$x_1 \times x_2$ 进去,只要它们对 $\beta$ 是线性的就行。
但问题在于,很多人默认“线性”就是“直线”,拿到数据直接拟合,结果残差图一看是个明显的U型。这说明什么?说明真实关系可能是二次的,或者需要对 $y$ 做变换。
我一般的做法是:先画 $y$ 和每个 $x$ 的散点图,再加局部平滑曲线。如果平滑曲线明显弯曲,就考虑加多项式项或者对变量做变换。这一步花不了五分钟,但能避免后面大量的返工。
2.2 误差零均值与同方差:残差图是你的第一道防线
误差项 $\epsilon$ 的期望为零,这个假设通常没问题,因为截距项会吸收均值。真正容易出问题的是同方差性——误差的方差在所有 $x$ 水平上应该相同。
如果方差不齐,OLS估计仍然是无偏的,但标准误算错了,导致t检验和F检验失效。你会看到某些变量明明很重要,p值却很大,或者反过来。
怎么判断?画残差 vs 拟合值的散点图。如果残差呈现喇叭形(拟合值越大,残差越分散),那就是异方差。解决办法有两个:一是对 $y$ 做对数变换,二是用稳健标准误(HC系列)。我一般优先用稳健标准误,因为它不改变模型形式,只修正推断。
2.3 无自相关:时间序列数据的隐形杀手
这个假设在截面数据里通常不是问题,但一旦你的数据有时间维度,就必须检查。误差项之间存在相关性,会让标准误严重低估,t值虚高,你以为显著的变量其实不显著。
判断方法:Durbin-Watson统计量。值在2附近说明没有一阶自相关,小于1.5或大于2.5就要警惕。更全面的做法是画残差的自相关图(ACF)。
如果确实存在自相关,最简单的处理是加滞后项,或者用Newey-West标准误。如果是面板数据,那就需要考虑固定效应或聚类标准误了。
2.4 无多重共线性:特征之间不能“串通”
多重共线性是指两个或多个自变量高度相关。完全共线会导致 $X^TX$ 不可逆,高度共线则会让系数估计变得极不稳定——数据稍微变一点,系数可能翻天覆地。
检测方法:方差膨胀因子(VIF)。经验规则是VIF大于10就说明共线性严重,大于5就要留意。计算公式是 $VIF_j = 1/(1-R_j^2)$,其中 $R_j^2$ 是把第 $j$ 个特征对其他所有特征做回归得到的 $R^2$。
处理方式有几种:删除其中一个特征、用PCA降维、加正则化(岭回归)。但要注意,如果共线性不影响预测精度,只是让系数解释变得困难,你可以选择不处理,只要在报告里说明就行。
2.5 正态性假设:其实没那么重要
很多人以为OLS要求误差必须正态分布,其实这个假设只在小样本下对推断有影响。大样本下,根据中心极限定理,系数估计的分布会趋近正态,不管误差本身是什么分布。
所以我的建议是:样本量小于30的时候,看一下QQ图;样本量大的时候,不用太纠结这个。真正需要担心的是前面几个假设,它们对推断的影响更直接。
3. 从零手写OLS:理解每一步在干什么
3.1 数据准备与探索
假设我们有一个模拟数据集,包含三个特征和一个目标变量。先用Python生成一些数据来演示。
import numpy as np import pandas as pd import matplotlib.pyplot as plt np.random.seed(42) n = 200 X = np.random.randn(n, 3) # 真实系数 true_beta = np.array([2.5, -1.2, 0.8]) y = X @ true_beta + 1.5 + np.random.randn(n) * 0.5 df = pd.DataFrame(X, columns=['x1', 'x2', 'x3']) df['y'] = y生成完数据,第一步永远是看。看什么?看分布、看相关性、看异常值。
print(df.describe()) print(df.corr())如果某个特征的分布极度偏斜,考虑做变换。如果特征之间相关性超过0.8,就要警惕共线性。如果目标变量有极端异常值,OLS会被严重拉偏,这时候要么删除异常值(需谨慎),要么用稳健回归。
3.2 手写正规方程求解
理解了原理,手写一遍正规方程,比调一百次库都有用。
# 添加截距项 X_with_intercept = np.column_stack([np.ones(n), X]) # 正规方程求解 beta_hat = np.linalg.inv(X_with_intercept.T @ X_with_intercept) @ X_with_intercept.T @ y print("估计系数:", beta_hat)这里我用的是np.linalg.inv直接求逆,实际项目中应该用np.linalg.lstsq,它内部用SVD分解,数值稳定性更好。
beta_hat_svd, residuals, rank, sv = np.linalg.lstsq(X_with_intercept, y, rcond=None) print("SVD求解:", beta_hat_svd)两个结果应该几乎一样。如果差异很大,说明矩阵条件数很差,存在严重的共线性问题。
3.3 计算标准误与t统计量
系数估计出来了,但光有点估计不够,还需要知道它的不确定性。
# 残差 y_pred = X_with_intercept @ beta_hat_svd residuals = y - y_pred # 残差方差 n, p = X_with_intercept.shape sigma2 = np.sum(residuals**2) / (n - p) # 系数协方差矩阵 cov_beta = sigma2 * np.linalg.inv(X_with_intercept.T @ X_with_intercept) se_beta = np.sqrt(np.diag(cov_beta)) # t统计量 t_stats = beta_hat_svd / se_beta print("标准误:", se_beta) print("t统计量:", t_stats)这里的关键是自由度 $n-p$。$n$ 是样本量,$p$ 是参数个数(包括截距)。自由度越小,估计越不稳定,标准误越大。
3.4 模型整体显著性检验
除了单个系数,还需要看模型整体是否显著。F统计量衡量的是:模型解释的方差是否显著大于随机误差。
# 总平方和 SST = np.sum((y - np.mean(y))**2) # 回归平方和 SSR = np.sum((y_pred - np.mean(y))**2) # 残差平方和 SSE = np.sum(residuals**2) # R方 R2 = SSR / SST # 调整R方 adj_R2 = 1 - (1 - R2) * (n - 1) / (n - p) # F统计量 F_stat = (SSR / (p - 1)) / (SSE / (n - p)) print(f"R2: {R2:.4f}, 调整R2: {adj_R2:.4f}, F: {F_stat:.2f}")R方告诉你模型解释了目标变量多少变异,但R方会随着特征增加而虚高,所以要看调整R方。F统计量对应的p值如果小于0.05,说明模型整体是显著的。
4. 用statsmodels做完整分析:一份可直接复用的模板
4.1 基础回归与结果解读
手写一遍是为了理解,实际工作中当然用库。statsmodels是我最推荐的OLS工具,因为它给出的统计信息最全面。
import statsmodels.api as sm X_sm = sm.add_constant(X) model = sm.OLS(y, X_sm).fit() print(model.summary())summary()输出的表格信息量很大,我逐项说一下怎么看:
| 指标 | 含义 | 关注点 |
|---|---|---|
| coef | 系数估计 | 方向和大小是否符合预期 |
| std err | 标准误 | 越小说明估计越精确 |
| t | t统计量 | 绝对值大于2通常显著 |
| P>|t| | p值 | 小于0.05说明显著 |
| R-squared | 决定系数 | 模型解释了多少变异 |
| Adj. R-squared | 调整R方 | 考虑特征数量后的R方 |
| F-statistic | F统计量 | 模型整体显著性 |
| Prob (F) | F检验p值 | 小于0.05模型整体显著 |
| Durbin-Watson | 自相关检验 | 接近2最好 |
| Jarque-Bera | 正态性检验 | p值小于0.05说明非正态 |
| Condition Number | 条件数 | 大于30警惕共线性 |
4.2 残差诊断四件套
模型拟合完,必须看残差。我一般做四个图:
fig, axes = plt.subplots(2, 2, figsize=(12, 10)) # 残差 vs 拟合值 axes[0, 0].scatter(model.fittedvalues, model.resid, alpha=0.5) axes[0, 0].axhline(y=0, color='r', linestyle='--') axes[0, 0].set_xlabel('Fitted Values') axes[0, 0].set_ylabel('Residuals') axes[0, 0].set_title('Residuals vs Fitted') # QQ图 sm.qqplot(model.resid, line='45', ax=axes[0, 1]) axes[0, 1].set_title('Normal Q-Q') # 尺度-位置图 axes[1, 0].scatter(model.fittedvalues, np.sqrt(np.abs(model.resid)), alpha=0.5) axes[1, 0].set_xlabel('Fitted Values') axes[1, 0].set_ylabel('Sqrt(|Residuals|)') axes[1, 0].set_title('Scale-Location') # 残差 vs 杠杆值 sm.graphics.influence_plot(model, ax=axes[1, 1], criterion="cooks") plt.tight_layout() plt.show()残差 vs 拟合值:理想情况是随机散布在0附近,没有明显模式。如果出现U型或倒U型,说明线性假设不成立。如果呈喇叭形,说明异方差。
QQ图:点应该大致落在45度线上。两端偏离说明厚尾或薄尾,中间偏离说明偏态。
尺度-位置图:和第一个图类似,但用残差绝对值的平方根,更容易看出异方差。
影响点图:识别对模型影响过大的点。Cook距离大于1的点需要特别关注。
4.3 稳健标准误:异方差的简单解药
如果残差图显示异方差,最省事的办法是用稳健标准误。
model_robust = sm.OLS(y, X_sm).fit(cov_type='HC3') print(model_robust.summary())HC3是异方差稳健标准误的一种,在样本量不大时表现较好。还有HC0、HC1、HC2,区别在于对残差平方的调整方式。我一般直接用HC3,省心。
用了稳健标准误之后,系数估计不变,但标准误、t值、p值都会变。如果之前显著的变量变得不显著了,说明之前的显著性是被异方差“撑”起来的,不可信。
5. 那些只有踩过坑才知道的事
5.1 多重共线性的实战处理
VIF大于10怎么办?我的处理顺序是这样的:
第一步,看业务逻辑。如果两个特征业务含义高度重叠,比如“房间面积”和“房间数量”,直接删一个。保留业务解释性更强的那个。
第二步,看相关系数矩阵。找出相关系数最高的那一对,删掉其中一个,重新算VIF。重复这个过程直到所有VIF都小于5。
第三步,如果删了之后模型效果明显下降,考虑用岭回归。岭回归通过加L2惩罚项,让系数估计变得稳定,代价是引入了一点偏差。但在共线性严重时,这点偏差换来的方差降低是值得的。
from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X) ridge = Ridge(alpha=1.0) ridge.fit(X_scaled, y) print("Ridge系数:", ridge.coef_)注意,用岭回归之前一定要标准化,否则惩罚项对不同量纲的特征不公平。
5.2 异常值:删还是不删
异常值对OLS的影响极大,因为平方项会放大它的权重。一个极端异常值可能让整条回归线偏转。
但不要轻易删异常值。先判断它是数据错误还是真实极端情况。如果是录入错误,修正或删除。如果是真实情况,那它可能包含重要信息。
我一般用Cook距离来识别影响点:
influence = model.get_influence() cooks_d = influence.cooks_distance[0] outliers = np.where(cooks_d > 4/n)[0] print("高影响点索引:", outliers)Cook距离大于 $4/n$ 的点值得关注,大于1的点必须检查。处理方式可以是:删除、缩尾处理(Winsorize)、或者用稳健回归(如Huber回归)。
5.3 变量变换的实用技巧
当线性假设不满足时,变量变换是最直接的补救措施。常用的变换和对应用途:
| 变换 | 公式 | 适用场景 |
|---|---|---|
| 对数变换 | $\log(x)$ | 右偏分布,弹性解释 |
| 平方根 | $\sqrt{x}$ | 计数数据,轻度右偏 |
| 倒数 | $1/x$ | 渐近趋于零的关系 |
| Box-Cox | 自动寻找最优幂 | 不确定用什么变换时 |
对数变换是最常用的,而且解释很方便:$\log(y)$ 对 $\log(x)$ 的回归系数就是弹性——$x$ 变化1%,$y$ 平均变化 $\beta$%。
注意:对数变换要求变量为正。如果有零或负值,可以用 $\log(x+1)$ 或者先做平移。但平移会改变系数的解释,需要谨慎。
5.4 样本量的经验法则
OLS需要多少样本?一个粗略的经验法则是每个特征至少10-15个样本。如果特征多,样本少,模型会严重过拟合,R方虚高,但预测能力很差。
更严格的做法是看事件每变量(EPV),在分类问题中常用,回归问题中类似。如果样本量实在不够,考虑降维或者用正则化。
我个人的底线是:样本量至少是特征数的5倍,最好10倍以上。低于这个数,我会非常谨慎地解释结果。
6. 常见问题速查与排查思路
6.1 系数符号和预期相反怎么办
这是最让人头疼的问题之一。明明业务逻辑上 $x$ 对 $y$ 应该是正影响,结果系数是负的。原因通常有三个:
第一,多重共线性。两个高度相关的特征,一个系数为正一个为负,实际上它们共同捕捉了同一个效应,符号变得不可解释。解决办法是删掉一个或者用岭回归。
第二,遗漏变量偏差。有一个重要变量没放进去,导致现有变量的系数吸收了它的影响。解决办法是补上遗漏的变量,但这需要你对业务有深入理解。
第三,因果方向搞反了。你以为 $x$ 影响 $y$,实际上可能是 $y$ 影响 $x$,或者两者互相影响。这时候OLS估计的就不是因果效应,而是相关关系。需要工具变量或者实验设计来解决。
6.2 R方很低但模型显著
R方只有0.1,但F检验显著,系数也显著。这种情况在社会科学和业务数据中非常常见。不要因为R方低就否定模型。
R方低说明目标变量的变异大部分没有被模型解释,但这不代表模型没有价值。如果系数显著且方向符合预期,说明变量之间的平均关系是存在的,只是个体差异很大。
比如预测用户消费,R方0.15可能已经很有用了,因为影响消费的因素太多了,你能捕捉到15%已经不错了。
6.3 预测时新数据有缺失值
OLS本身不能处理缺失值。如果新数据有缺失,要么删除该样本,要么用均值/中位数填充,要么用模型预测填充。
但要注意,填充会引入偏差。如果缺失不是随机的,填充后的预测会有系统误差。最好的办法是在建模阶段就处理好缺失值,或者用能处理缺失值的模型(如XGBoost)。
6.4 模型在训练集表现好但新数据差
这是典型的过拟合。OLS虽然比复杂模型不容易过拟合,但在特征多、样本少的情况下同样会过拟合。
判断方法:交叉验证。把数据分成K份,轮流用K-1份训练,1份验证,看验证误差的波动。
from sklearn.model_selection import cross_val_score from sklearn.linear_model import LinearRegression lr = LinearRegression() scores = cross_val_score(lr, X, y, cv=5, scoring='neg_mean_squared_error') print("交叉验证MSE:", -scores.mean())如果交叉验证误差远大于训练误差,说明过拟合了。解决办法:减少特征、增加样本、用正则化。
6.5 问题排查速查表
| 症状 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 系数符号异常 | 共线性/遗漏变量 | 算VIF/业务分析 | 删特征/补变量/岭回归 |
| p值普遍偏大 | 异方差/自相关 | 残差图/DW检验 | 稳健标准误/Newey-West |
| R方高但预测差 | 过拟合 | 交叉验证 | 减特征/正则化/增样本 |
| 残差有模式 | 非线性 | 残差vs拟合图 | 加多项式/变换变量 |
| QQ图两端偏离 | 厚尾 | QQ图 | 稳健回归/删异常值 |
| VIF大于10 | 共线性 | VIF计算 | 删特征/PCA/岭回归 |
7. 一个完整的实战案例
7.1 场景设定与数据生成
假设我们在分析某类产品的销量影响因素。有三个特征:价格、广告投入、竞品价格。真实关系是:
np.random.seed(123) n = 500 price = np.random.uniform(10, 50, n) ad_spend = np.random.uniform(100, 1000, n) competitor_price = price * 0.8 + np.random.randn(n) * 3 # 与价格相关 # 真实关系:价格负影响,广告正影响,竞品价格正影响 sales = 2000 - 15 * price + 0.8 * ad_spend + 10 * competitor_price + np.random.randn(n) * 50 data = pd.DataFrame({ 'price': price, 'ad_spend': ad_spend, 'competitor_price': competitor_price, 'sales': sales })注意,我故意让竞品价格和价格相关,这样会引入共线性问题,模拟真实场景。
7.2 完整分析流程
import statsmodels.api as sm from statsmodels.stats.outliers_influence import variance_inflation_factor # 第一步:描述性统计 print(data.describe()) # 第二步:相关性分析 print(data.corr()) # 第三步:拟合模型 X = data[['price', 'ad_spend', 'competitor_price']] X = sm.add_constant(X) y = data['sales'] model = sm.OLS(y, X).fit() print(model.summary()) # 第四步:VIF检验 vif_data = pd.DataFrame() vif_data['feature'] = X.columns vif_data['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] print(vif_data)运行后会看到,price和competitor_price的VIF可能很高,因为两者相关。系数可能变得不稳定,甚至符号异常。
7.3 处理共线性后的对比
# 方案一:删除competitor_price X1 = data[['price', 'ad_spend']] X1 = sm.add_constant(X1) model1 = sm.OLS(y, X1).fit() print("删除竞品价格后:") print(model1.summary()) # 方案二:用岭回归 from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(data[['price', 'ad_spend', 'competitor_price']]) ridge = Ridge(alpha=10) ridge.fit(X_scaled, y) print("岭回归系数:", ridge.coef_)对比两个方案的结果,你会发现删除变量后系数变得符合预期,但R方可能略有下降。岭回归则保留了所有变量,系数被压缩但方向正确。
我的选择逻辑是:如果业务上竞品价格确实重要,不能删,那就用岭回归。如果业务上竞品价格和自身价格高度重叠,解释起来很困难,那就删掉,用更简洁的模型。
7.4 最终模型诊断与报告
选定模型后,做完整的残差诊断:
# 残差诊断 fig, axes = plt.subplots(1, 3, figsize=(15, 4)) axes[0].scatter(model1.fittedvalues, model1.resid, alpha=0.5) axes[0].axhline(y=0, color='r', linestyle='--') axes[0].set_title('Residuals vs Fitted') sm.qqplot(model1.resid, line='45', ax=axes[1]) axes[1].set_title('Q-Q Plot') axes[2].hist(model1.resid, bins=30, edgecolor='black') axes[2].set_title('Residual Distribution') plt.tight_layout() plt.show() # Durbin-Watson from statsmodels.stats.stattools import durbin_watson dw = durbin_watson(model1.resid) print(f"Durbin-Watson: {dw:.3f}")如果残差图没有明显模式,QQ图大致在线上,DW接近2,那这个模型就可以用了。报告时写清楚:系数、标准误、p值、R方、调整R方、F统计量,以及你对每个系数业务含义的解释。
8. 一些零散但重要的经验
8.1 标准化什么时候做
如果只是做预测,标准化不影响预测结果。但如果要比较系数大小(哪个特征更重要),必须标准化。标准化后,系数的大小直接反映特征对目标变量的相对影响。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_std = scaler.fit_transform(X) X_std = sm.add_constant(X_std) model_std = sm.OLS(y, X_std).fit() print(model_std.params)注意,标准化后截距项的含义变了,它表示所有特征取均值时目标变量的预测值。
8.2 交互项怎么加
有时候一个特征的影响依赖于另一个特征。比如广告效果可能依赖于价格——价格低的时候广告效果更好。这时候需要加交互项。
data['price_ad'] = data['price'] * data['ad_spend'] X_interact = sm.add_constant(data[['price', 'ad_spend', 'price_ad']]) model_interact = sm.OLS(y, X_interact).fit() print(model_interact.summary())加了交互项后,price的系数表示广告投入为零时价格的影响,ad_spend的系数表示价格为零时广告的影响。解释起来要小心,最好在均值附近解释。
8.3 多项式项:捕捉非线性
如果散点图显示明显的曲线关系,加平方项:
data['price_sq'] = data['price'] ** 2 X_poly = sm.add_constant(data[['price', 'price_sq', 'ad_spend']]) model_poly = sm.OLS(y, X_poly).fit() print(model_poly.summary())加了平方项后,价格对销量的边际影响不再是常数,而是 $ \beta_1 + 2\beta_2 \times price $。可以通过求导找到最优价格点。
8.4 样本外预测的注意事项
OLS的预测能力取决于模型假设是否在新数据上成立。如果新数据的分布和训练数据不同(比如价格范围超出了训练数据的范围),预测会不可靠。
永远不要外推到训练数据范围之外太远。线性关系在局部可能成立,但放到更大范围往往不成立。比如价格从10到50的范围内销量线性下降,但价格到100的时候销量可能已经降到零了,不会变成负数。
8.5 模型比较的准则
如果有多个模型,怎么选?我一般看三个指标:
调整R方:越大越好,但要注意过拟合。AIC/BIC:越小越好,BIC对复杂模型惩罚更重。交叉验证误差:最可靠,但计算量大。
print(f"AIC: {model1.aic:.2f}, BIC: {model1.bic:.2f}")AIC和BIC的绝对值没有意义,只有比较不同模型时才有用。差值大于10通常说明有实质性差异。
9. 写在最后
OLS线性回归看起来简单,但真正用好需要理解它的假设、知道怎么诊断、清楚怎么处理违反假设的情况。我见过太多人拿到数据直接fit,然后看着summary里的p值做决策,结果被共线性、异方差、异常值坑得死死的。
这篇文章里的代码都可以直接跑,数据是模拟的,但流程和真实项目一模一样。你可以把数据换成自己的,按着步骤走一遍。重点不是记住代码,而是理解每一步为什么要做——为什么要看残差图,为什么要算VIF,为什么要用稳健标准误。
最后分享一个我自己的习惯:每次跑完OLS,先不看系数,先看残差图。残差图没问题,再看系数和p值。残差图有问题,系数再漂亮也不可信。这个习惯帮我避免了很多次错误结论。
如果后续想深入,可以看看广义线性模型(GLM)、面板数据模型、工具变量法。但OLS是这一切的基础,基础打牢了,后面的东西学起来会快很多。