5  FWL、虚拟变量与固定效应:理解条件比较

本讲要点
  • FWL 定理的直觉:残差化之后的比较;
  • 控制变量、遗漏变量、坏控制和过度控制;
  • 虚拟变量、分类变量、基准组和虚拟变量陷阱;
  • 个体固定效应、时间固定效应和双向固定效应;
  • 高维固定效应与交互固定效应的基本思想;
  • DID 中的组别、时间和处理交乘项;
  • 如何理解带固定效应和交乘项模型中的系数。
  • 本讲用到的 skills:core/05-regression-interpreter
  • 可运行伴生文件:examples/ch05/ch05_main.do(Stata)、examples/ch05/ch05_python.ipynb(Python)。

本讲的主线只有一句话:回归系数从来不是「影响」,而是「在控制了别的东西之后,拿谁跟谁比」。把这句话讲透,就能从最基本的一元回归,一步步走到面板数据的固定效应模型,再走到政策评估里最常用的双重差分 (DID)。串起这条路的,是一条叫 FWL 的定理——它告诉我们,多元回归的每个系数,本质上都是一次「先把别的变量剔除干净,再拿残差去比」的操作。理解了残差化,虚拟变量、固定效应和 DID 就都是同一件事的不同说法。

5.1 案例情境:三家公司的一张图

先看一个上课时常用的例子。横轴是公司规模 (\(Size=\ln(\text{总资产})\)),纵轴是总资产回报率 (ROA)。图 图 5.1 上半部分有三家公司:左上角那家起步时规模小、ROA 相对较高;右下角那家起步时规模就大、ROA 相对较低。这种公司之间的差异,与所处行业、所有权属性有关,基本不随时间变化。

我们想问的是:一家公司规模变大以后,它的 ROA 会怎么变。可要是无视三家公司之间的差异,把所有点混在一起回归,得到的斜率是负的——「规模越大,ROA 越低」。图的下半部分给出组内去心 (de-mean,把每家公司的均值减掉) 之后的结果:公司之间的差异被减掉,三家公司各自内部的正向关系显露出来,斜率翻正。

图 5.1: 辛普森悖论:混合回归得到负斜率,组内去心之后每家公司内部的关系为正

差别不在数据,而在拿谁跟谁比:混合回归回答的是「大公司是不是比小公司 ROA 低」,用的是公司之间的差异;组内去心回答的是「同一家公司变大以后 ROA 怎么变」,只用每家公司内部的变化。两个问题都可以研究,但它们不是同一个问题;把前者的答案当成后者的答案,就是遗漏变量偏误。

本讲要做的,就是把「换一次控制、就换一次比较对象」这件事,从这张图出发,一路讲到固定效应和 DID。

5.2 从常数项到虚拟变量:残差化的第一课

5.2.1 常数项:回归里最容易被忽略的那一项

从最简单的一元回归讲起:

\[ Y_i = \alpha + \beta X_i + u_i \tag{5.1}\]

这里的常数项 \(\alpha\) 看着不起眼,却藏着理解后面一切的钥匙。把 式 5.1 两边取均值:

\[ \bar{Y} = \alpha + \beta\bar{X} + \bar{u} \]

两式相减,常数项 \(\alpha\) 就被消掉了:

\[ Y_i - \bar{Y} = (X_i - \bar{X})\beta + (u_i - \bar{u}) \tag{5.2}\]

这说明一件事:带常数项的回归,等价于先把 \(Y\)\(X\) 都减去各自的均值,再做一次不带常数项的回归。常数项在这里扮演的角色,就是「把样本均值这一层信息吸走」。换句话说,\(\beta\) 度量的从来不是 \(X\)\(Y\) 的绝对水平关系,而是两者相对各自均值的偏离之间的关系。

常数项还有几件不显眼却要紧的活儿:它会随变量的平移而调整,并保证常规 OLS 回归中的 \(R^2\in[0,1]\)。在 DID 和 RDD 里,常数项本身还可能带明确的经济含义。这里先记住最核心的一点:回归系数度量的是「去掉某个基准之后」的比较

5.2.2 FWL 定理:先剔除,再回归

把上面「减均值」的操作一般化,就是 Frisch-Waugh-Lovell(FWL) 定理。考虑一个多元回归:

\[ Y = \alpha + X_1\beta_1 + X_2\beta_2 + u \tag{5.3}\]

我们只关心 \(X_1\) 的系数 \(\beta_1\)。FWL 定理说,要得到 \(\hat\beta_1\),不必把 \(X_1\)\(X_2\) 一起放进回归,可以分三步走:

  1. \(X_2\) 回归 \(Y\),取残差 \(\tilde{Y}\)(把 \(X_2\)\(Y\) 的影响剔除干净);
  2. \(X_2\) 回归 \(X_1\),取残差 \(\tilde{X}_1\)(把 \(X_2\)\(X_1\) 的影响剔除干净);
  3. \(\tilde{X}_1\) 回归 \(\tilde{Y}\),得到的系数恰好等于 式 5.3 里的 \(\hat\beta_1\)

用一句话概括:「先把其他变量剔除、再回归」与「一起回归」等价。上一小节的减均值,只是 FWL 的一个特例——那里的 \(X_2\) 就是常数项 (一列 1),「剔除 \(X_2\)」正好就是「减去均值」。

FWL 给我们一个极其有用的视角:多元回归里 \(X_1\) 的系数,度量的是「\(X_1\) 中剔除掉其他变量之后剩下的那部分」与「\(Y\) 中剔除掉其他变量之后剩下的那部分」之间的关系。系数是「控制其他变量之后的比较」,这句在上一讲就出现过的话,到这里有了精确的数学表述:所谓「控制」,就是「残差化」;所谓「比较」,就是拿两个残差去回归。

为什么值得专门讲 FWL

FWL 不只是一个理论性质。Stata 里处理高维固定效应的 reghdfeivreg2lasso2 里的 partial() 选项,以及画部分回归图的 avplot 等命令,都可以从残差化的角度理解。理解了「剔除—回归」,后面的固定效应就是它的直接应用。

双重/去偏机器学习 (DML/DDML) 也保留了这一骨架:分别用控制变量预测结果和处理变量,取两边残差后再估计核心关系。不过,DML 还要处理高维、非线性和机器学习过拟合的问题,通常需要样本分割与交叉拟合;它不能替代识别策略本身。Chernozhukov et al. (2018) 给出了系统讨论,进阶读者可继续阅读双重机器学习:DDML

图 5.2 用韦恩图把这个过程画了出来。这是一张帮助理解残差化的示意图,不是严格的集合或方差分解。\(Y\)\(X_1\)\(X_2\) 三个圆的重叠部分,代表彼此能够解释的方差。把 \(Y\) 圆内部按字母拆开:

\[ Y=A+B+C+D \]

其中,\(A\)\(Y\) 里谁也没有解释的部分,\(B\) 只被 \(X_1\) 解释,\(D\) 只被 \(X_2\) 解释,\(C\)\(X_1\)\(X_2\) 共同解释。所谓「把 \(X_2\) partial out」,直观上就是拿一把剪刀,把 \(X_2\) 那个圆整个剪掉:\(Y\) 里凡是和 \(X_2\) 重叠的部分 (也就是 \(C\)\(D\)) 都被剪走,只剩下:

\[ \widetilde{Y}=A+B \]

同样地,\(X_1\) 剪掉与 \(X_2\) 重叠的部分后得到残差 \(\widetilde{X}_1\)。再用 \(\widetilde{Y}\)\(\widetilde{X}_1\) 回归,两者真正共享的只有区域 \(B\)——也就是「\(X_1\) 里扣掉了 \(X_2\)\(Y\) 里也扣掉了 \(X_2\)」之后,两边还共同拥有的那块方差。\(\beta_1\) 就是从区域 \(B\) 里估出来的。

图 5.2: FWL 定理的方差韦恩图:\(\beta_1\) 只用到区域 \(B\)——把 \(X_2\)\(X_1\)\(Y\) 中同时剪掉之后,两者还共同解释的那部分方差

这个定理看似只是一个计算技巧,实际上是一整套方法的理论基础。例如,在 Stata 中,xtreg 命令的 fe 选项、reghdfe 命令的 absorb() 选项、ivreg2lasso2 命令的 partial() 选项,都是 FWL 的直接应用——它们先把固定效应或一部分控制变量 partial out,再对核心变量做回归。

此处要特别注意:要得到干净的 \(\beta_1\),必须把控制变量从核心解释变量和结果变量两边都剔除,而不是只从结果变量一边剔除。 只处理 \(Y\) 而不处理 \(X_1\),得到的并不是原多元回归中 \(\beta_1\) 的 FWL 表达。

5.2.3 遗漏变量、坏控制与过度控制

FWL 也让「遗漏变量偏误」一眼可见。设真实模型是 式 5.3,但我们偷懒,只放 \(X_1\)

\[ \text{只估 }\; Y = X_1\theta_1 + \underbrace{\varepsilon}_{X_2\beta_2+u} \]

此时 \(X_2\) 被塞进了误差项。只要 \(X_1\)\(X_2\) 相关 (\(Corr(X_1,X_2)\neq 0\)),\(\hat\theta_1\) 就不等于 \(\beta_1\)——它把 \(X_2\) 的一部分贡献也算到了 \(X_1\) 头上。辛普森悖论就是这么来的:真实模型里每家公司有自己的截距 (个体效应),漏掉它,规模的系数就被公司间差异污染。

但控制变量并非越多越好。多放一个变量,能不能让系数更干净,取决于这个变量站在因果链条的哪个位置

  • 好的控制变量:它同时影响 \(X_1\)\(Y\),是二者共同的混淆源。放进去,能挡住一条虚假的关联通道。
  • 坏控制 (bad control):它本身是被 \(X_1\) 影响的结果 (处理之后才发生的中间变量)。把它当控制变量放进去,等于把一部分真实效应也一起「控制」掉了,系数反而偏了。
  • 过度控制 (over-control):控制了处理效应必经的中介,或控制了与 \(X_1\) 高度共线、几乎不含独立信息的变量,识别变异被抽干,系数变得不稳、方差飙升。

判断的标准不是「显著与否」「\(R^2\) 高不高」,而是这个变量是原因还是结果。识别策略层面的深入讨论超出本讲范围,这里只需记住一句:控制变量要控在混淆上,不要控在结果或中介上。

有关控制变量的讨论,参见如下推文:

5.2.4 虚拟变量、基准组与虚拟变量陷阱

把 FWL 里的 \(X_2\) 换成一个分类变量,就得到虚拟变量 (dummy variable)。设一份数据里既有男性 (\(M=1\)) 也有女性 (\(M=0\)),模型写成:

\[ y_i = \alpha + {\color{blue}{\gamma}}\,M_i + \beta x_i + u_i \tag{5.4}\]

\(\gamma\) 的含义一目了然——它是男性组相对女性组的截距差

\[ E[y_i\mid M=0]=\alpha+\beta x_i,\qquad E[y_i\mid M=1]=(\alpha+\gamma)+\beta x_i \]

图 5.3 里,两组是两条平行线 (斜率同为 \(\beta\)),\(\gamma\) 就是两条线的垂直间距。这时「女性组」被当作基准组 (base group),它的截距是 \(\alpha\),其余各组的虚拟变量系数都是「相对基准组」的差。

图 5.3: 虚拟变量 = 变截距:两组共用斜率 \(\beta\),虚拟变量系数 \(\gamma\) 是两条平行线的垂直间距

这里藏着一个初学者最常踩的坑——虚拟变量陷阱 (dummy variable trap)。一个分类变量若有 \(k\) 个类别,把 \(k\) 个虚拟变量连同常数项一起放进回归,就会因为「\(k\) 个虚拟变量之和恒等于常数项那一列 1」而完全共线,模型无法估计。解决办法只有二选一:留一个基准组 (放 \(k-1\) 个虚拟变量 + 常数项),或去掉常数项 (放满 \(k\) 个虚拟变量,此时每个系数是该组的组内截距,而非相对差)。二者的斜率估计完全相同,区别只在截距怎么解读。

在 Stata 里,这些都由因子变量 (factor variable) 自动打理,不必手工造虚拟变量:

符号 含义 实例
i. 声明为分类变量 (自动留基准组、生成虚拟变量) reg y x i.id i.year
c. 声明为连续变量 c.age
ib#. 指定第 # 类为基准组 (默认取最小值那类) ib2.race
# 两变量的交乘项 i.D#c.x
## 两变量各自及其交乘项 x##z

例如 reg wage i.race c.hours i.race#c.hours,一行就把「不同种族的工资水平差」和「不同种族里工时回报的差异」都估了出来——这正是把虚拟变量和交乘项组合起来用。理解了「虚拟变量 = 变截距」,就为下一节的固定效应做好了全部准备:固定效应,无非是把「个体」也当成一个分类变量,给每个个体一条自己的截距。

5.3 固定效应模型:从虚拟变量到组内去心

5.3.1 三类变量、三个下标

面板数据每个观测有两个下标:个体 \(i\)、时间 \(t\)。影响 \(y_{it}\) 的变量可以按「随谁变」分成三类:

  • 不随时间变的 \(x_i\):公司文化、个人能力、地区禀赋、所有权属性;
  • 不随个体变的 \(x_t\):宏观冲击、货币政策、经济政策不确定性;
  • 既随时间又随个体变的 \(x_{it}\):规模、杠杆、投资、研发。

麻烦在于,\(x_i\)\(x_t\) 里有大量成员观测不到,又偏偏和我们关心的 \(x_{it}\) 相关。以杠杆率为例,假设真实模型里所有权属性 \(SOE_i\) 影响杠杆,但我们观测不到它:

\[ \text{真实:}\; Lev_{it}=\alpha+SOE_i+Size_{it}\beta+\varepsilon_{it},\qquad \text{只估:}\; Lev_{it}=\alpha+Size_{it}\theta+u_{it} \]

只要国企普遍规模更大、杠杆更高 (\(Corr(SOE_i,Size_{it})\neq 0\)),\(\hat\theta\) 就把所有权的贡献混了进来。这就是上一节遗漏变量偏误的面板版本。

5.3.2 组内去心:不可观测的因素也能控制

固定效应模型的办法直接而漂亮:把所有不随时间变化的因素——无论能不能观测——统统打包进一个个体截距 \(\alpha_i\),再用 FWL 的减均值把它剔除掉:

\[ \begin{aligned} y_{it} &= \alpha_i + x_{it}\beta + \varepsilon_{it} \\ \bar{y}_i &= \alpha_i + \bar{x}_i\beta + \bar\varepsilon_i \\ y_{it}-\bar{y}_i &= (x_{it}-\bar{x}_i)\beta + ({\color{red}{\alpha_i-\alpha_i}}) + (\varepsilon_{it}-\bar\varepsilon_i) \end{aligned} \tag{5.5}\]

第三行里 \(\alpha_i-\alpha_i=0\),这一步就是「不用观测也能控制」的全部秘密:我们从没测量过 \(SOE_i\)、公司文化或管理层风格,但只要它们不随时间变,组内去心就把它们连同 \(\alpha_i\) 一起减掉了。对去心后的数据做 OLS,得到的 \(\hat\beta\),和直接在模型里放 \(N\) 个个体虚拟变量完全等价——这又是 FWL 定理的一次应用。「给每个个体一条截距」= 放 \(N\) 个虚拟变量 = 组内去心,三种说法是同一件事。这也正是上一节「虚拟变量 = 变截距」在面板上的延伸。

Stata 里至少有四种等价写法,前三种是现成命令,第四种手动去心,用来确认自己真懂了组内变换:

xtset id year
xtreg   y x, fe vce(cluster id)              // [1] 组内估计量
areg    y x, absorb(id) vce(cluster id)      // [2]
reghdfe y x, absorb(id) vce(cluster id)      // [3]

bysort id: egen y_bar = mean(y)              // [4] 手动去心
bysort id: egen x_bar = mean(x)
gen c_y = y - y_bar
gen c_x = x - x_bar
reg c_y c_x, noconstant vce(cluster id)      //     斜率同上

四种方法的 \(\hat\beta\) 完全相同。差别只在于:它用的是每家公司内部的变化,而不是公司之间的差异。

5.3.3 个体、时间与双向固定效应

只放个体截距 \(\alpha_i\),是个体固定效应:控制掉一切不随时间变的个体差异。再加一个年份截距 \(\lambda_t\),就是实证中用得最多的双向固定效应 (TWFE)

\[ Y_{it}=x_{it}'\beta+\alpha_i+\lambda_t+\varepsilon_{it} \tag{5.6}\]

写成虚拟变量的形式,它的含义更清楚:

\[ Y_{it}=x_{it}'\beta+\sum_{i=1}^N\alpha_i A_i+\sum_{t=1}^T\lambda_t B_t+\varepsilon_{it} \]

\(\alpha_i\) 吸收「不随时间变的个体差异」,\(\lambda_t\) 吸收「当年对所有个体一致的宏观冲击」。也可以只放 \(\lambda_t\) (时间固定效应),控制共同的时间趋势而不管个体差异。放哪几组、怎么放,取决于你想控制什么。

在完整的平衡面板中,可以把 式 5.5 的思路再推进一步:对每个变量同时减去个体均值和年份均值,再加回总体均值。以 \(y\) 为例:

\[ \widetilde{y}_{it}=y_{it}-\bar{y}_i-\bar{y}_t+\bar{y} \tag{5.7}\]

其中,\(\bar{y}_i\) 是个体 \(i\) 的时间均值,\(\bar{y}_t\) 是年份 \(t\) 的截面均值,\(\bar{y}\) 是总体均值。

\(x_1,x_2,\ldots\) 做相同变换后,用 \(\widetilde{y}_{it}\)\(\widetilde{\mathbf{x}}_{it}\) 做不含常数项的 OLS,得到的斜率与 式 5.6 中的双向固定效应估计一致。这个公式适合帮助理解和核对一个小型、完整的平衡面板。实证数据常常是不平衡面板,或者还要吸收交互固定效应;这时不应把逐次减均值当成通用算法,而应直接使用固定效应估计命令。

实证分析中,下面两种写法在斜率上等价。第一种显式放入年份虚拟变量;第二种把个体和年份固定效应交给 reghdfe 吸收:

xtset id year
xtreg y x1 x2 i.year, fe vce(cluster id)       // 显式加入年份效应
reghdfe y x1 x2, absorb(id year) ///
    vce(cluster id)                             // 吸收两组固定效应

下面的手工代码只用于与命令结果核对。它要求每个个体在每一年都有观测;真实项目中不要据此替代 reghdfe

xtset id year
* 仅用于完整平衡面板;`center` 为 SSC 外部命令。
bysort id: center y x1 x2, inplace
bysort year: center y x1 x2, inplace
reg y x1 x2, noconstant vce(cluster id)
常见问题:为什么固定效应放不进性别和种族

初学者常犯的一个错,是想在个体固定效应模型里加入性别、种族、出生地这类变量,结果 Stata 报告它们被 (omitted) 了。原因就在 式 5.5:个体效应 \(\alpha_i\) 不随时间变,而性别、种族在样本期内也不随时间变,二者完全共线——组内去心把 \(\alpha_i\) 减掉的同时,也把所有时不变变量一起减成了 0,Stata 只能删掉它们。

一句话:固定效应能「控制」个体的一切时不变特征,却无法「估计」这些特征各自的系数。 若研究问题恰恰是「种族对工资的影响」这类时不变变量的效应,就不能用固定效应,需要改用组间估计、加控制变量的混合回归,或 Hausman–Taylor(xthtaylor)、xtseqreg 等专门方法。

按此逻辑,大家也不难理解,在 TWFE 模型中也无法加入只随时间变化的宏观变量,如 M2 增速 (\(M2_t\))、经济政策不确定性 (\(EPU_t\)) 等。

5.4 高维固定效应:更多维度与更窄的比较

在教育经济、移民和公司金融等领域,经常需要使用高维固定效应模型。其中尤以国际贸易领域的「引力模型」影响最大:研究者常在模型中同时放入三维甚至四维固定效应及其交互项。1 若允许经济变量的影响具有异质性,还可以把固定效应与解释变量交乘。

下面以三维固定效应模型为例说明。更一般的情形在原理上与此相似;多维变换的矩阵推导可参见 Balazsi, Matyas and Wansbeek (2018)。2

5.4.1 三维加性固定效应

假设我们研究全国 31 个省份在一段时间内的行业产出。数据有三个维度:省份 (\(i=1,\ldots,N\))、行业 (\(j=1,\ldots,G\)) 和年份 (\(t=1,\ldots,T\))。一个三维加性固定效应模型可写为:

\[ y_{ijt}=\mathbf{x}_{ijt}'\boldsymbol{\beta}+\alpha_i+\gamma_j+\lambda_t+\varepsilon_{ijt} \tag{5.8}\]

沿袭个体固定效应和双向固定效应的思路,在完整平衡面板中,可以用下面的变换同时去除三组固定效应:

\[ \begin{aligned} \widetilde{y}_{ijt} &=y_{ijt}-\bar{y}_{i\cdot\cdot} -\bar{y}_{\cdot j\cdot}-\bar{y}_{\cdot\cdot t}\\ &\quad+2\bar{y}_{\cdot\cdot\cdot}. \end{aligned} \tag{5.9}\]

这里的点号表示对相应维度取均值。例如,\(\bar{y}_{i\cdot\cdot}\) 是省份 \(i\) 跨行业和年份的均值,\(\bar{y}_{\cdot\cdot\cdot}\) 是全样本均值。对解释变量作同样变换后回归残差,斜率与直接放入三组虚拟变量相同。需要说明的是,多维去均值的写法并不唯一;上式是完整平衡面板下最直观的教学表达。3

经过组内变换后的系数与包含多组虚拟变量的 OLS 回归在斜率上相同,但标准误仍须按误差相关结构处理。实际估计交给 reghdfe 更稳妥。4

reghdfe y x1 x2, absorb(province industry year) ///
    vce(cluster province)

若只是想把 式 5.9 的操作逐步看一遍,可以在完整平衡面板中使用 center。这段代码只用于教学核对;不平衡面板、嵌套固定效应和交互固定效应都应直接交给 reghdfe

* `center` 为 SSC 外部命令;下列三次去心会原地替换变量。
bysort province: center y x1 x2, inplace
bysort industry: center y x1 x2, inplace
bysort year: center y x1 x2, inplace
reg y x1 x2, noconstant vce(cluster province)

5.4.2 交互固定效应

国际贸易领域的引力模型通常会包含交互固定效应。5 例如,Egger and Pfaffermayr (2003) 使用双边固定效应来反映贸易双方的交互影响。6 以贸易数据为例,exporter#importer 吸收的是每一对贸易伙伴长期不变的双边差异,例如距离、共同语言和历史联系。最简单的设定为:

\[ y_{ijt}=\mathbf{x}_{ijt}'\boldsymbol{\beta}+\gamma_{ij}+\varepsilon_{ijt} \tag{5.10}\]

其中,\(\gamma_{ij}\) 是双边固定效应。若只需观察它的去均值含义,可以写成 \(\widetilde{y}_{ijt}=y_{ijt}-\bar{y}_{ij\cdot}\);但估计时仍应直接吸收该交互项:

reghdfe y x1 x2, absorb(exporter#importer) ///
    vce(cluster exporter importer)

实证分析中应用更为广泛的是在 式 5.10 中进一步加入时间固定效应:

\[ y_{ijt}=\mathbf{x}_{ijt}'\boldsymbol{\beta}+\gamma_{ij}+\lambda_t+\varepsilon_{ijt} \tag{5.11}\]

reghdfe y x1 x2, absorb(exporter#importer year) ///
    vce(cluster exporter importer)

Matyas (2017) 归纳了更多设定方式。7 例如,有时只需要控制「行业 × 年份」效应;有时则要同时控制「省份 × 年份」与「行业 × 年份」效应:

\[ \begin{aligned} y_{ijt}&=\boldsymbol{\beta}'\mathbf{x}_{ijt} +\alpha_{jt}+\varepsilon_{ijt},\\ y_{ijt}&=\boldsymbol{\beta}'\mathbf{x}_{ijt} +\alpha_{it}+\delta_{jt}+\varepsilon_{ijt}. \end{aligned} \tag{5.12}\]

如果把各个两两交互效应都考虑在内,得到的完整设定是:

\[ y_{ijt}=\boldsymbol{\beta}'\mathbf{x}_{ijt}+\gamma_{ij}+\alpha_{it}+\delta_{jt}+\varepsilon_{ijt} \tag{5.13}\]

reghdfe y x1 x2, ///
    absorb(exporter#importer exporter#year importer#year) ///
    vce(cluster exporter importer)

在完整平衡面板中,这一设定对应的去均值公式也可以明确写出:

\[ \begin{aligned} \widetilde{y}_{ijt} &=y_{ijt}-\bar{y}_{ij\cdot} -\bar{y}_{\cdot jt}-\bar{y}_{i\cdot t}\\ &\quad+\bar{y}_{\cdot\cdot t} +\bar{y}_{\cdot j\cdot}+\bar{y}_{i\cdot\cdot} -\bar{y}_{\cdot\cdot\cdot}. \end{aligned} \tag{5.14}\]

这个公式的价值在于帮助我们看清每一组固定效应吸收了什么;实际数据很少整齐到适合手工处理,因此实务上仍应使用 reghdfe

这类设定并不意味着「固定效应越多越好」。每增加一组固定效应,得到的比较对象就更窄;如果核心解释变量只在「行业 × 年份」层面变化,又吸收了 industry#year,它会被完全吸收并显示为 omitted。这表示模型中没有可用于估计该变量系数的剩余变异,而不是软件故障。

5.4.3 高维固定效应的基本思想

固定效应的组数和维度一旦上去,直接放入虚拟变量在计算上就不再现实。两个典型场景是:国际贸易的引力模型需要同时控制出口国 × 年份、进口国 × 年份和国家对;劳动经济学的雇主—雇员匹配数据则可能同时包含百万级的工人效应和数十万个企业效应。把这些效应逐一写成 reg y x i.id i.firm i.year 这样的虚拟变量,内存和计算时间都可能难以承受。

reghdfe 的基本做法是交替去心:轮流对每一组固定效应做组内变换,反复迭代直到收敛。本质上仍是 FWL 定理的反复应用;对使用者来说,只需要把要吸收的固定效应写进 absorb()

reghdfe y x1 x2, ///
    absorb(exporter#year importer#year exporter#importer) ///
    vce(cluster exporter importer)

使用时,应同时记住下面三个判断:

  1. absorb()vce(cluster) 是两项不同的决定。 前者由需要控制哪些未观测因素决定;后者由误差可能在哪个层面相关决定。二者可以相同,也经常不同。
  2. 不要把手工逐次去均值当成通用算法。 对完整平衡面板,上面的公式可以用于教学核对;对不平衡面板和多组交互效应,应让 reghdfe 的迭代算法完成残差化。
  3. 先检查核心变量的变异层级。 固定效应吸收之后,核心解释变量还必须保留足够的组内变异;否则,即使程序能运行,研究问题也已经无法由该模型回答。

关于三维与交互固定效应的矩阵推导,可参见 Balazsi, Matyas and Wansbeek (2018)。8 引力模型中常见的设定整理见 Matyas (2017)。9

5.5 双向固定效应与 DID

5.5.1 每加一组固定效应,就换一次比较对象

把前面几种设定排在一起,会看到一条清晰的递进——每往模型里多放一组固定效应,被控制的混淆更多,系数回答的问题也随之收窄:

表 5.1: 固定效应设定与比较对象的递进
设定 absorb() 被吸收的混淆 系数回答的问题
混合回归 POLS 公司间差异与公司内变化混在一起
个体 FE id 不随时间变的公司特征 同一家公司变化时,\(y\) 怎么变
双向 FE(TWFE) id year +当年对所有公司一致的宏观冲击 相对同年其他公司,该公司的变化带来什么

表 5.1 是本讲最要紧的一张表:固定效应设定,本质上是研究者在向读者声明「我在拿谁和谁比」。这也回答了一个关键问题——既然固定效应会吸收掉同层级的变量,为什么 DID 的处理变量能在双向固定效应下活下来?因为 DID 的处理变量比 \(\alpha_i\)\(\lambda_t\) 都「高一个层级」,它在个体 × 时间维度上变动,恰好落在双向固定效应吸不掉的那层变异里。

5.5.2 2×2 DID:看清「两次差分」

DID 是政策评估里最干净的一个设定。最基本的情形是「两组两期」:处理组在政策后受处理,对照组始终不受处理。用 \(Treat_i=1\) 标记处理组,\(Post_t=1\) 标记政策后,基础回归是:

\[ y_{it}=\alpha+\theta_1 Treat_i+\theta_2 Post_t+{\color{blue}{\gamma}}\,(Treat_i\times Post_t)+\varepsilon_{it} \tag{5.15}\]

真正关心的是交乘项 \(\gamma\)。把四个「组别—时期」的均值写出来 (\(C_0,C_1\) 是对照组政策前后,\(Y_0,Y_1\) 是处理组政策前后):

表 5.2: 2×2 DID 的四格均值
政策前 政策后 前后变化
对照组 \(C_0\) \(C_1\) \(C_1-C_0\)
处理组 \(Y_0\) \(Y_1\) \(Y_1-Y_0\)
组间差异 \(Y_0-C_0\) \(Y_1-C_1\) \((Y_1-C_1)-(Y_0-C_0)\)

三个系数各有身份,一个都不能混:

  • \(\theta_1=Y_0-C_0\):政策前两组的水平差 (两组本来就不一样,不是政策效应);
  • \(\theta_2=C_1-C_0\):对照组的时间变化 (对照组没受政策,用它刻画共同的时间趋势);
  • 交乘项 \(\gamma\)

\[ \gamma=(Y_1-C_1)-(Y_0-C_0)=(Y_1-Y_0)-(C_1-C_0) \tag{5.16}\]

两种写法等价:左边是「政策后组间差」减「政策前组间差」,右边是「处理组前后变化」减「对照组前后变化」。DID 的精髓,是用对照组的同期变化,替处理组扣掉它本来也会经历的共同趋势:处理组若没有政策,本会从 \(Y_0\) 沿对照组的趋势走到 \(Y_0+(C_1-C_0)\);实际到了 \(Y_1\),二者之差正是政策效应。这里的三个虚拟变量——组别 \(Treat_i\)、时间 \(Post_t\)、以及二者的交乘 \(Treat_i\times Post_t\)——就是大纲说的「DID 中的组别、时间和处理交乘项」。

图 5.4: 2×2 DID 的四个均值与处理组反事实:对照组斜率给出共同趋势,处理组偏离这条趋势的部分就是政策效应

一个经典例子是 Card and Krueger (1994):1992 年新泽西上调最低工资、相邻的宾夕法尼亚没有,\(Treat_i\) 是「是否新泽西」、\(Post_t\) 是「是否调薪后」,交乘项系数就是最低工资对就业的 DID 估计。处理组、对照组、政策前后、交乘项含义全都一目了然,没有一处要猜「在跟谁比」。

5.5.3 从两期到多期:事件研究与平行趋势

真实的政策评估几乎总是多期面板:政策前有若干期、政策后也有若干期。把 式 5.15 沿「相对政策时间」展开,就得到多期 DID(事件研究) 设定。设政策在 \(t^*\) 期实施,令 \(k=t-t^*\) 为相对时间:

\[ Y_{it}=\alpha_i+\lambda_t+\sum_{k\neq -1}\delta_k\big(Treat_i\times\mathbf{1}\{t-t^*=k\}\big)+\varepsilon_{it} \tag{5.17}\]

以政策前一期 \(k=-1\) 作基准 (系数归一化为 0)。两组系数含义要分开看:

  • 事前系数 \(\delta_k\,(k<-1)\):政策来临之前处理组相对对照组的差异。若平行趋势成立,这些系数应当不显著——政策还没来,两组不该有系统性差异。
  • 事后系数 \(\delta_k\,(k\ge 0)\):政策实施后第 \(k\) 期的处理效应,连起来就是政策效应的动态路径。
图 5.5: 标准的多期 DID:事前若干期用于判断平行趋势,事后若干期用于估计动态效应
常见问题:对照组怎么选、平行趋势怎么看

DID 能不能站得住,全押在平行趋势假设上:如果没有政策,处理组和对照组的结果变量本会沿同一条趋势走。两件事最容易出问题——

  • 对照组的选择:对照组要能代表处理组「假如没有政策会怎样」。若处理组和对照组本就走在不同的趋势上 (比如处理组是高增长地区、对照组是停滞地区),DID 会把趋势差误当成政策效应。
  • 平行趋势的检验:主要靠事件研究图看事前系数是否接近零、是否平坦。要强调的是,事前系数不显著只是一个直观的、不严谨的检验——它是必要条件而非充分条件,通不过基本可以否掉平行趋势,通过了也不能算证明。

至于分批推进的政策 (不同个体在不同年份进入处理) 会给双向固定效应带来的麻烦——TWFE 的系数会变成一堆 2×2 比较的加权平均、甚至混入负权重——以及 CSDID 等现代 DID 估计量如何修正,属于高级班内容,本讲不展开。有兴趣的读者可以参考 双重差分:从 TWFE 到 CSDID

5.6 如何理解带固定效应和交乘项的系数

走到这里,可以把一条主线收拢成一句话:无论是一元回归的斜率、虚拟变量的截距差、固定效应模型里的 \(\beta\),还是 DID 的交乘项,它们全都是「在控制了别的东西之后,拿谁跟谁比」。区别只在于「控制了什么」「比较对象是谁」:

  • 一元回归的 \(\beta\):相对样本均值,\(X\) 高一单位对应 \(Y\) 的差;
  • 虚拟变量系数:相对基准组的截距差;
  • 个体固定效应里的 \(\beta\):同一个体内部,随时间变化时 \(Y\) 怎么变;
  • 双向固定效应里的 \(\beta\):相对同年其他个体,该个体的变化带来什么;
  • DID 交乘项:处理组前后变化,再扣掉对照组同期变化。

读懂一个复杂回归式的系数,可以用一张三列表把它拆开——每个变量 (含交乘项) 占一行,逐列写清「变量含义 / 比较对象 / 系数解释」。比如面对 reg y i.treat##i.post 里的 1.treat#1.post,三列分别是:「处理组在政策后的额外项」/「处理组前后变化 vs 对照组前后变化」/「双重差分的政策效应」。养成这个习惯,再复杂的固定效应加交乘项模型,也不会看着系数发懵。下一节的 AI 协作,就把这张三列表交给 AI 来帮你打草稿,你负责核对。

5.7 代码实操

本节用四段可复现的 Stata 代码,把前面的直觉落到命令上。完整脚本见伴生文件 examples/ch05/ch05_main.do(**# 大纲与本讲各节标题一一对应,可在 VS Code 中按节导航运行);真实财务数据 xtcs.dta(中国上市公司资本结构面板) 随本讲提供。代码为静态展示、输出为本机运行后冻结,不在渲染时执行。

其一,辛普森悖论与组内去心:真实斜率 \(\beta=0.5\),但三家公司的个体效应与初始规模负相关。

*----------------- 模拟:辛普森悖论
clear
set seed 20260706
set obs 3
gen id    = _n
gen a_i   = 12 - 3*id            // 个体效应 9,6,3,与规模负相关
gen size0 = 2  + 3*id            // 初创规模 5,8,11
expand 100
gen x = size0 + runiform(0, 4)          // Size
gen y = a_i + 0.5*x + rnormal(0, 0.8)   // DGP: beta = 0.5

reg  y x                 // 混合回归:斜率为负
areg y x, absorb(id)     // 个体固定效应:斜率回到 0.5 附近
xtset id
xtreg y x, be            // 对照:组间关系,就是那条负斜率的来源
   混合回归      reg y x            :  x = -0.305   被公司间差异污染,符号翻负
   个体固定效应  areg y x, absorb(id):  x =  0.562   只用公司内部变异,还原真值 0.5
   组间关系      xtreg y x, be      :  x = -0.500   公司之间的关系,正是负斜率来源

其二,固定效应命令族的等价与时不变变量被 omit

webuse nlswork, clear
xtset idcode year
gen byte black = race == 2                      // 种族虚拟变量(时不变)

xtreg ln_wage tenure hours, fe                  // 个体 FE
xtreg ln_wage tenure hours i.year, fe           // 双向 FE(加时间效应)
reghdfe ln_wage tenure hours, absorb(idcode year) ///
    vce(cluster idcode)                          // 等价的双向 FE

xtreg ln_wage black tenure hours, fe            // 试图加入时不变变量 black
       black |      0.000  (omitted)      ← 时不变变量与个体效应共线,被自动剔除

其三,2×2 DID 还原政策效应:真实政策效应设为 \(+3\),看交乘项能否还原、三个系数是否各归其位。

*----------------- 模拟:2×2 DID
clear
set seed 20260708
set obs 200
gen id    = _n
gen treat = id > 100                    // 后 100 个为处理组
expand 2
bysort id: gen post = _n - 1            // 0=政策前, 1=政策后
* DGP: 基线 10 + 组间差 2 + 时间趋势 4 + 政策效应 3
gen y = 10 + 2*treat + 4*post + 3*(treat*post) + rnormal(0,1)

table post treat, stat(mean y) nformat(%6.2f)   // 四格均值
reg y i.treat##i.post, vce(robust)              // 交乘项应还原 3
   四格均值      政策前   政策后
   对照组        10.06    13.82
   处理组        12.02    19.03

   1.treat        1.959    组间水平差 θ1(处理组本就高约 2)
   1.post         3.761    对照组时间趋势 θ2(≈4)
   1.treat#1.post 3.251    双重差分 γ ← 政策效应(真值 3)

其四,真实财务数据里「换比较对象、换答案」:用中国上市公司资本结构面板 xtcs.dta,看规模 size 对资产负债率 tl 的系数,如何随固定效应一步步递进而变化。

use "$D/xtcs.dta", clear                                     // code=公司, year=年份
xtset code year
reg     tl size ndts tang tobin npr                          // 混合回归 POLS
xtreg   tl size ndts tang tobin npr, fe cluster(code)        // 个体 FE
xtreg   tl size ndts tang tobin npr i.year, fe cluster(code) // 双向 FE
   size 的系数:  混合 POLS  0.041  →  个体 FE  0.120  →  双向 FE  0.134

三个数字不是「越估越准」,而是在回答三个不同的问题:POLS 的 0.041 混着「大公司 vs 小公司」的横截面差异;个体 FE 的 0.120 只问「同一家公司规模变大以后,负债率怎么变」;再加年份效应控制掉共同的宏观波动,双向 FE 给到 0.134。系数一路走高,正是本讲那张比较对象递进表在真实数据上的印证。

5.8 AI 协作

契约边界

本节逐条覆盖归属本讲的四项「AI 协作训练」,不删项、不缩范围。每项给一份可复制的提示词、标注对应的 core skill,并说明怎么人工核对。这些任务,网页版 AI 助手大多都能完成。

带固定效应和交乘项的回归式,是最容易「看着系数发懵」的地方。让 AI 陪你做的,不是替你下结论,而是把系数逐个翻译成「拿谁跟谁比」,再由你回到数据核对。四项训练共用同一个 skill:core/05-regression-interpreter

5.8.1 训练 1:让 AI 解释含 FWL、虚拟变量和交乘项的回归式

任务:把一条回归命令 (或方程) 交给 AI,让它逐项说清每个系数的含义、比较对象和量纲。

提示词

这是一条 Stata 回归命令 (或回归方程):reg wage i.race c.hours i.race#c.hours。请面向计量基础较弱的读者,逐项解释:(1) 每个系数 (含常数项、虚拟变量、连续变量、交乘项) 度量的是什么;(2) 它的比较对象是谁、基准组是谁;(3) 单位怎么读。特别说明交乘项的存在如何改变主变量系数的解释。只依据这条式子本身,不要编造数据里没有的信息;不确定处标出来让我核对。

对应 skillcore/05-regression-interpreter人工核对:重点查 AI 有没有说错基准组、有没有把交乘项存在时的主效应仍当成「总效应」来读。

5.8.2 训练 2:让 AI 检查 DID 型回归中每个变量和交乘项的含义

任务:把一条 DID 回归交给 AI,让它对照「组别 / 时间 / 处理交乘项」逐一核对每个变量的角色。

提示词

下面是一条双重差分 (DID) 回归:reg y i.treat##i.post, vce(robust)(treat 为处理组虚拟变量,post 为政策后虚拟变量)。请逐项说明:1.treat1.post1.treat#1.post 各自度量什么,对应 2×2 四格均值里的哪个差;哪个才是政策效应;并用「处理组前后变化减对照组前后变化」的话把交乘项复述一遍。指出这条回归识别政策效应所依赖的关键假设。只依据这条式子,不要脑补。

对应 skillcore/05-regression-interpreter人工核对:确认 AI 没把 1.treat(组间水平差) 或 1.post(时间趋势) 误当成政策效应;核对它说的关键假设是不是平行趋势。

5.8.3 训练 3:让 AI 检查固定效应模型中的系数是否可以被识别

任务:把模型设定和变量的「随谁变」信息交给 AI,让它判断哪些系数在给定固定效应下还能识别、哪些会被吸收。

提示词

我要估计一个固定效应模型:reghdfe y x1 x2 gender region, absorb(id year)。已知:x1x2 随个体和时间都变;gender 不随时间变;region 在样本期内不随时间变。请判断:加入个体固定效应 (id) 后,哪些变量的系数还能被识别、哪些会因为与个体效应共线而被 omitted,并解释原因。如果我确实想估计 gender 的效应,有哪些替代做法?只做可识别性判断,不要替我下实证结论。

对应 skillcore/05-regression-interpreter人工核对:AI 的判断要回到「是否随时间变」这一条硬标准去验;对它给的替代方法 (组间估计、Hausman–Taylor 等) 只当线索,适用与否自己判断。

5.8.4 训练 4:让 AI 把复杂回归式改写成「变量含义 + 比较对象 + 系数解释」三列表

任务:把一条带多个虚拟变量、固定效应和交乘项的回归式交给 AI,让它输出一张三列表。

提示词

请把下面这条回归式改写成一张三列表格,每个变量 (含交乘项、常数项) 占一行,三列分别是「变量含义」「比较对象 / 基准组」「系数解释 (一句话)」:

reghdfe y treat_post x1 x2, absorb(id year),其中 treat_post = treat × post

表格要让一个没读过这篇分析的人,也能照着读懂每个系数在跟谁比。不确定的地方留空并标注,让我补。

对应 skillcore/05-regression-interpreter人工核对:三列表是脚手架,每一行的「比较对象」要你对照模型的固定效应结构逐条确认,AI 填错基准组是最常见的错。

5.9 Python 附录

同样的 FWL、固定效应与 DID,用 Python 也能一行行复现。载体是伴生文件 examples/ch05/ch05_python.ipynb,内容与本讲代码实操一一对应:用 linearmodels(PanelOLS) 或 pyfixest(feols) 估计个体、时间与双向固定效应,用 statsmodels 跑 2×2 DID 的交乘项回归;每段配「任务说明 + 提示词」,代码由 AI 生成、输出本机运行后冻结。

想让 AI 帮你把某段 Stata 翻成等价 Python,可以这样提示:

提示词

下面这段 Stata 代码用双向固定效应估计 yx 的关系:reghdfe y x, absorb(id year) cluster(id)。请用 Python 的 linearmodels.PanelOLS(或 pyfixest) 写出等价实现,包含设定面板索引、加入个体与时间固定效应、按 id 聚类稳健标准误;并逐行加中文注释说明它和 Stata 命令的对应关系。只写等价实现,不要改变模型设定。

5.10 小结与练习

本讲用一条主线把三样东西串了起来:FWL 定理说「系数 = 剔除其他变量之后的残差回归」,虚拟变量说「分类变量 = 变截距」,固定效应说「给每个个体一条截距 = 组内去心」,而 DID 不过是「组别 × 时间 × 处理」三个虚拟变量组合出的两次差分。它们看似是四个话题,底层却是同一件事——每一次控制,都是在换一次比较对象

一句话带走:回归系数从来不回答「影响有多大」,它只回答「在控制了别的东西之后,拿谁跟谁比」;读懂了比较对象,就读懂了系数。

5.10.1 练习

练习 1(概念·FWL 与残差化)

有人说「多元回归里 \(x_1\) 的系数,就是把 \(x_1\) 单独拿出来对 \(y\) 回归得到的斜率」。请用 FWL 定理指出这句话错在哪,并说明正确的说法是什么。

错在漏掉了「剔除其他变量」这一步。单独用 \(x_1\) 回归 \(y\),得到的斜率会被 \(x_1\) 与其他变量的相关性污染 (遗漏变量偏误)。FWL 的正确说法是:\(x_1\) 的多元回归系数,等于「\(x_1\) 剔除掉其他变量后的残差 \(\tilde{x}_1\)」对「\(y\) 剔除掉其他变量后的残差 \(\tilde{y}\)」回归的斜率——是残差对残差,不是原始变量对原始变量。

练习 2(概念·固定效应可识别性)

一位研究者用个体固定效应模型 xtreg wage tenure female, fe 估计工作年限和性别对工资的影响,发现 female 的系数是 (omitted)。请解释原因,并给出若他确实想估计性别效应可以怎么办。

性别在样本期内不随时间变化,与不随时间变化的个体效应 \(\alpha_i\) 完全共线,组内去心把 \(\alpha_i\) 减掉的同时也把 female 减成了 0,所以被 omitted。固定效应能控制个体的一切时不变特征,却无法估计这些特征各自的系数。若要估性别效应,可改用加控制变量的混合回归 / 组间估计,或 Hausman–Taylor(xthtaylor)、xtseqreg 等能保留时不变变量的方法。

练习 3(概念·DID 系数身份)

在 2×2 DID 回归 reg y i.treat##i.post 中,1.treat1.post1.treat#1.post 三个系数分别是什么含义?哪一个才是政策效应?为什么不能直接用「处理组政策后均值减对照组政策后均值」来估政策效应?

1.treat = Y0−C0 是政策前两组的水平差 (两组本来就不同);1.post = C1−C0 是对照组的时间趋势;1.treat#1.post = (Y1−Y0)−(C1−C0) 是双重差分,才是政策效应。直接用「处理组政策后减对照组政策后」(即 \(Y_1-C_1\)) 会混入政策前就存在的组间水平差 \(Y_0-C_0\);DID 的关键正是用对照组的同期变化,替处理组扣掉它本来也会经历的共同趋势。

练习 4(交给 AI 并审计)

任取一条你研究领域里带固定效应和交乘项的回归式,用本讲「训练 4」的提示词让 AI 生成一张三列表;然后回到模型设定,找出 AI 至少一处填错或含糊的地方 (最常见的是基准组),说明你是怎么发现的。

本题无标准答案,重点在「审计」。合格的回答应能指出 AI 的具体错处 (如把交乘项存在时的主效应当成总效应、写错基准组、把被固定效应吸收的变量仍列为可识别),并说明核对依据 (模型的固定效应结构、因子变量的基准组设定、变量是否随时间变化)。能稳定发现并纠正这类偏差,正是本讲要练的能力。

5.11 延伸阅读


  1. 引力模型的经典讨论可参见 Silva and Tenreyro (2006);系统综述见 Head and Mayer (2014)。↩︎

  2. Balazsi, Matyas and Wansbeek (2018) 给出了多维固定效应变换的矩阵讨论,并强调变换写法不止一种。↩︎

  3. Balazsi, Matyas and Wansbeek (2018) 给出了多维固定效应变换的矩阵讨论,并强调变换写法不止一种。↩︎

  4. reghdfe 通过迭代吸收多组固定效应;命令的说明、算法边界和版本信息应以 官方帮助页 为准。它处理的是固定效应残差化,不替研究者决定应当吸收什么或怎样聚类。↩︎

  5. 引力模型的经典讨论可参见 Silva and Tenreyro (2006);系统综述见 Head and Mayer (2014)。↩︎

  6. Egger and Pfaffermayr (2003) 讨论了欧洲贸易一体化中的引力模型设定。↩︎

  7. Matyas (ed., 2017) 系统整理了引力模型及其多种固定效应设定。↩︎

  8. Balazsi, Matyas and Wansbeek (2018) 给出了多维固定效应变换的矩阵讨论,并强调变换写法不止一种。↩︎

  9. 引力模型的经典讨论可参见 Silva and Tenreyro (2006);系统综述见 Head and Mayer (2014)。↩︎