Skip to content

8.3 固定效应模型(Fixed Effects Model)

差分的力量:消除不可观测异质性的终极武器

难度重要性


本节目标

  • 深入理解固定效应(FE)模型的数学原理
  • 掌握三种 FE 估计方法(组内变换、LSDV、First Difference)
  • 区分单向 FE 和双向 FE(Two-Way FE)
  • 使用 linearmodels.PanelOLS 实现 FE 回归
  • 理解 FE 的识别假设和因果解释
  • 处理 FE 的常见陷阱(时不变变量、坏控制)
  • 完整案例:工资决定因素(Mincer 方程)

固定效应模型的核心思想

问题的起源:遗漏变量偏差

回顾上一节的例子:研究教育对工资的影响

真实模型

问题

  • (能力)不可观测,无法直接测量
  • 相关(聪明人受更多教育)
  • 如果忽略 会有偏差

传统解决方案

  1. 找到"完美"的代理变量(proxy)测量能力 → 几乎不可能
  2. 通过随机实验消除相关性 → 成本高、不现实

固定效应的解决方案利用面板数据的时间维度,通过差分消除


FE 的核心直觉:差分消除固定效应

假设我们观测同一个人两年

第 1 年

第 2 年

差分(第2年 - 第1年)

神奇之处 被消除了!

直觉

  • (能力)在两年内没变(固定的)
  • 差分后,我们只利用个体内部的时间变化
  • 工资的变化 = 教育的变化 × 回报率

这就是固定效应的本质!


固定效应模型的数学表达

通用 FE 模型

符号定义

  • :个体索引
  • :时间索引
  • :因变量
  • :第 个自变量
  • 个体固定效应(不随时间变化,可以与 相关)
  • :随机误差项(idiosyncratic error)

关键假设

  1. 严格外生性(Strict Exogeneity):

  2. 固定效应不随时间变化

  3. 允许 相关:


固定效应的三种估计方法

方法 1:组内变换(Within Transformation)⭐ 推荐

核心思想:对每个变量做去均值(demeaning)

步骤 1:计算个体均值

注意(因为 不随时间变化)

步骤 2:原方程减去均值方程

原方程

均值方程(两边对时间取均值):

差分(去均值)

其中 去均值后的变量

关键结果 被消除了!

步骤 3:OLS 估计去均值后的方程

这就是固定效应估计量(Within Estimator)


Python 实现:手动组内变换

python
import numpy as np
import pandas as pd
import statsmodels.api as sm

# 模拟数据
np.random.seed(42)
data = []
for i in range(1, 101):  # 100 个个体
    alpha_i = np.random.normal(0, 1)  # 个体固定效应
    for t in range(2015, 2020):  # 5 年
        x = 10 + t - 2015 + np.random.normal(0, 2)
        y = 5 + 2 * x + alpha_i + np.random.normal(0, 1)
        data.append({'id': i, 'year': t, 'y': y, 'x': x})

df = pd.DataFrame(data)

print("=" * 70)
print("原始数据")
print("=" * 70)
print(df.head(10))

# 手动计算组内变换
# 步骤 1:计算个体均值
df['y_mean'] = df.groupby('id')['y'].transform('mean')
df['x_mean'] = df.groupby('id')['x'].transform('mean')

# 步骤 2:去均值
df['y_within'] = df['y'] - df['y_mean']
df['x_within'] = df['x'] - df['x_mean']

print("\n" + "=" * 70)
print("去均值后的数据(前 10 行)")
print("=" * 70)
print(df[['id', 'year', 'y', 'y_within', 'x', 'x_within']].head(10))

# 步骤 3:OLS 回归去均值后的数据(无截距!)
model_within = sm.OLS(df['y_within'], df['x_within']).fit()

print("\n" + "=" * 70)
print("组内变换 FE 回归结果(手动实现)")
print("=" * 70)
print(f"系数: {model_within.params['x_within']:.4f}")
print(f"标准误: {model_within.bse['x_within']:.4f}")
print(f"真实参数: 2.0000")

关键观察

  • 去均值后,每个个体的均值变为 0
  • 只剩下组内变异(within variation)
  • 回归时不需要截距(去均值后截距为 0)

方法 2:最小二乘虚拟变量(LSDV)

核心思想:为每个个体添加一个虚拟变量

其中 如果 ,否则为 0

解释

  • 个虚拟变量(第 1 个个体作为参照组)
  • :个体 相对于个体 1 的固定效应差异
  • (如果 )或 (参照组)

优点

  • 可以估计出每个个体的固定效应
  • 与组内变换等价(系数 完全相同)

缺点

  • 如果 很大,需要估计很多参数(
  • 计算慢(但结果与组内变换一致)

Python 实现

python
import pandas as pd
import statsmodels.api as sm

# 创建虚拟变量(N-1 个)
dummies = pd.get_dummies(df['id'], prefix='id', drop_first=True)

# 合并数据
X_lsdv = pd.concat([df[['x']], dummies], axis=1)
X_lsdv = sm.add_constant(X_lsdv)

# OLS 回归
model_lsdv = sm.OLS(df['y'], X_lsdv).fit()

print("=" * 70)
print("LSDV 回归结果")
print("=" * 70)
print(f"x 的系数: {model_lsdv.params['x']:.4f}")
print(f"估计的固定效应数量: {len(dummies.columns)}")

注意

  • LSDV 和组内变换的 系数完全相同
  • 但标准误可能略有不同(自由度调整)

方法 3:一阶差分(First Difference)

核心思想:对相邻时期做差分

简写为:

与组内变换的区别

  • 组内变换(减去所有时期的均值)
  • 一阶差分(减去上一期的值)

何时等价?

  • 如果 (只有两期),两种方法完全等价
  • 如果 ,通常组内变换更有效率

何时优先使用一阶差分?

  • 如果误差项存在随机游走(random walk)
  • 如果自变量存在严重的序列相关

Python 实现

python
# 一阶差分
df_sorted = df.sort_values(['id', 'year'])
df_sorted['y_diff'] = df_sorted.groupby('id')['y'].diff()
df_sorted['x_diff'] = df_sorted.groupby('id')['x'].diff()

# 去掉第一期(没有差分)
df_fd = df_sorted.dropna(subset=['y_diff', 'x_diff'])

# OLS 回归(无截距)
model_fd = sm.OLS(df_fd['y_diff'], df_fd['x_diff']).fit()

print("=" * 70)
print("一阶差分 FE 回归结果")
print("=" * 70)
print(f"系数: {model_fd.params['x_diff']:.4f}")

️ linearmodels.PanelOLS:专业工具

基本语法

python
from linearmodels.panel import PanelOLS

# 设置面板索引
df_panel = df.set_index(['id', 'year'])

# 固定效应回归
model = PanelOLS(
    dependent=df_panel['y'],
    exog=df_panel[['x1', 'x2']],
    entity_effects=True,      # 个体固定效应
    time_effects=False        # 时间固定效应(可选)
).fit(
    cov_type='clustered',     # 标准误类型
    cluster_entity=True       # 在个体层面聚类
)

print(model)

参数详解

1. entity_effects:个体固定效应

python
# 开启个体固定效应(推荐)
entity_effects=True

等价于对每个变量做组内变换

2. time_effects:时间固定效应

python
# 开启时间固定效应
time_effects=True

控制所有个体共同的时间趋势(宏观冲击、政策变化等)

3. cov_type:标准误类型

选项含义何时使用
'unadjusted'经典 OLS 标准误仅用于教学
'robust'异方差稳健标准误有异方差时
'clustered'聚类标准误面板数据的标准选择 ⭐
'kernel'Newey-West 标准误时间序列相关严重时

推荐

python
cov_type='clustered',
cluster_entity=True  # 在个体层面聚类

完整示例:工资决定因素

python
import numpy as np
import pandas as pd
from linearmodels.panel import PanelOLS
import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
sns.set_style("whitegrid")

# 模拟数据:工资面板
np.random.seed(123)

N = 500  # 500 名工人
T = 7    # 7 年(2015-2021)

data = []
for i in range(N):
    # 个体固定效应(能力、家庭背景等)
    ability = np.random.normal(0, 0.3)

    # 初始教育水平
    education_0 = np.random.choice([10, 12, 14, 16])

    for t in range(T):
        year = 2015 + t

        # 教育(可能增加,如读夜校)
        education = education_0 + (0.1 * t if np.random.rand() < 0.1 else 0)

        # 工作经验
        experience = t

        # 是否工会成员(随时间可能变化)
        union = 1 if np.random.rand() < 0.3 else 0

        # 对数工资
        # 真实参数:education=0.08, experience=0.05, union=0.10
        log_wage = (1.5 + 0.08 * education + 0.05 * experience +
                    0.10 * union + ability + np.random.normal(0, 0.1))

        data.append({
            'id': i,
            'year': year,
            'log_wage': log_wage,
            'education': education,
            'experience': experience,
            'union': union,
            'ability': ability  # 实际不可观测
        })

df = pd.DataFrame(data)

print("=" * 70)
print("数据摘要")
print("=" * 70)
print(df[['log_wage', 'education', 'experience', 'union']].describe())
print(f"\n样本量: {len(df)}")
print(f"个体数: {df['id'].nunique()}")
print(f"时间期数: {df['year'].nunique()}")

# 设置面板索引
df_panel = df.set_index(['id', 'year'])

# 方法 1:混合 OLS(有偏)
import statsmodels.api as sm
X_pooled = sm.add_constant(df[['education', 'experience', 'union']])
model_pooled = sm.OLS(df['log_wage'], X_pooled).fit()

print("\n" + "=" * 70)
print("方法 1:混合 OLS(遗漏能力 → 有偏)")
print("=" * 70)
print(model_pooled.summary().tables[1])

# 方法 2:固定效应(无偏)
model_fe = PanelOLS(
    df_panel['log_wage'],
    df_panel[['education', 'experience', 'union']],
    entity_effects=True
).fit(cov_type='clustered', cluster_entity=True)

print("\n" + "=" * 70)
print("方法 2:固定效应(控制能力 → 无偏)")
print("=" * 70)
print(model_fe)

# 对比估计结果
print("\n" + "=" * 70)
print("估计对比")
print("=" * 70)
results_table = pd.DataFrame({
    '真实参数': [0.08, 0.05, 0.10],
    '混合 OLS': [model_pooled.params['education'],
               model_pooled.params['experience'],
               model_pooled.params['union']],
    '固定效应': [model_fe.params['education'],
               model_fe.params['experience'],
               model_fe.params['union']]
}, index=['education', 'experience', 'union'])

print(results_table.round(4))

# 计算偏差
print("\n偏差(估计值 - 真实值):")
print((results_table[['混合 OLS', '固定效应']] - results_table['真实参数'].values[:, None]).round(4))

输出解读

  • 混合 OLS:高估教育系数(因为遗漏了能力)
  • 固定效应:接近真实值(差分消除了能力)

单向 FE vs 双向 FE(Two-Way FE)

单向固定效应(One-Way FE)

模型

控制

  • 个体固定效应 (个体异质性)

Python 实现

python
model_oneway = PanelOLS(y, X, entity_effects=True).fit()

双向固定效应(Two-Way FE)

模型

控制

  • 个体固定效应 (个体异质性)
  • 时间固定效应 (宏观趋势)

Python 实现

python
model_twoway = PanelOLS(y, X,
                        entity_effects=True,
                        time_effects=True).fit()

何时使用双向 FE?

场景 1:存在宏观时间趋势

  • 例如:经济周期、通货膨胀、技术进步
  • 这些因素影响所有个体,但与你的自变量无关

场景 2:DID 研究

  • 双向 FE 是 DID 的标准做法
  • 控制共同的时间趋势

场景 3:避免虚假相关

  • 如果 都有上升趋势,可能是因为共同的时间因素
  • 时间 FE 消除这种虚假相关

Python 对比:单向 vs 双向 FE

python
import numpy as np
import pandas as pd
from linearmodels.panel import PanelOLS

# 模拟数据:加入时间趋势
np.random.seed(42)
data = []
for i in range(1, 101):
    alpha_i = np.random.normal(0, 1)
    for t in range(2010, 2020):
        # 时间趋势(影响所有个体)
        lambda_t = 0.1 * (t - 2010)

        x = 10 + np.random.normal(0, 2)
        y = 5 + 2 * x + alpha_i + lambda_t + np.random.normal(0, 1)

        data.append({'id': i, 'year': t, 'y': y, 'x': x})

df = pd.DataFrame(data)
df_panel = df.set_index(['id', 'year'])

# 单向 FE(只控制个体)
model_oneway = PanelOLS(df_panel['y'], df_panel[['x']],
                        entity_effects=True).fit()

# 双向 FE(控制个体 + 时间)
model_twoway = PanelOLS(df_panel['y'], df_panel[['x']],
                        entity_effects=True,
                        time_effects=True).fit()

print("=" * 70)
print("单向 FE vs 双向 FE")
print("=" * 70)
print(f"真实参数:  2.0000")
print(f"单向 FE:   {model_oneway.params['x']:.4f}")
print(f"双向 FE:   {model_twoway.params['x']:.4f}")

结论

  • 如果存在时间趋势且未控制,单向 FE 可能有偏
  • 双向 FE 同时消除个体和时间效应,更稳健

️ 固定效应的局限性

局限 1:无法估计时不变变量

问题:FE 差分消除了所有不随时间变化的变量

例子

  • 性别(gender)
  • 种族(race)
  • 出生地(birthplace)
  • 行业(industry,如果不换工作)

为什么?

  • 差分后:(恒为 0)
  • 无法估计其系数

解决方案

  1. 使用随机效应(RE)模型(如果 RE 假设成立)
  2. 研究时变变量与时不变变量的交互效应
  3. 使用 Mundlak 方法(在 RE 中加入时变变量的均值)

局限 2:只利用组内变异

问题:FE 丢弃了组间变异(between variation)

后果

  • 如果自变量在组内变异很小,FE 估计效率低
  • 例如:教育水平在短期内很少变化

示例

python
# 计算变异比例
total_var = df['education'].var()
within_var = df.groupby('id')['education'].apply(lambda x: (x - x.mean()).var()).mean()
between_var = df.groupby('id')['education'].mean().var()

print(f"总变异:   {total_var:.2f}")
print(f"组内变异: {within_var:.2f} ({within_var/total_var*100:.1f}%)")
print(f"组间变异: {between_var:.2f} ({between_var/total_var*100:.1f}%)")

如果组内变异 < 10%

  • FE 估计的标准误会很大(不精确)
  • 考虑使用 RE(如果 RE 假设成立)

局限 3:严格外生性假设

假设

含义

  • 误差项与所有时期的自变量都不相关
  • 不仅包括当期 ,还包括过去和未来的

违反的场景

  • 反馈效应(Feedback Effect): 影响
  • 同期内生性(Simultaneity): 互相影响
  • 测量误差(Measurement Error): 被错误测量

解决方案

  • 使用工具变量(IV-FE)
  • 使用动态面板模型(Arellano-Bond)

局限 4:坏控制问题(Bad Control)

问题:加入被处理影响的变量作为控制变量

例子:研究教育对工资的影响

python
#  错误:职业是教育的结果(中介变量)
model = PanelOLS(log_wage, pd.concat([education, occupation]), entity_effects=True).fit()

为什么错误?

  • 教育 → 职业 → 工资(因果链)
  • 控制职业后,阻断了教育的部分效应
  • 估计的是教育的直接效应,而非总效应

决策规则

  • 只控制混杂因素(confounders):同时影响
  • 不控制中介变量(mediators):被 影响,再影响
  • 不控制结果变量(colliders):被 共同影响

完整案例:Mincer 工资方程

背景

Mincer 方程(1974)是劳动经济学最经典的模型:

解释

  • :教育回报率(每多一年教育,工资增加
  • :经验的非线性效应(先增后减)

Python 完整实现

python
import numpy as np
import pandas as pd
from linearmodels.panel import PanelOLS
from statsmodels.iolib.summary2 import summary_col
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']

# 模拟真实的 Mincer 数据
np.random.seed(2024)

N = 1000  # 1000 名工人
T = 10    # 10 年

data = []
for i in range(N):
    # 个体固定效应(能力)
    ability = np.random.normal(0, 0.4)

    # 初始特征
    education_0 = np.random.choice([10, 12, 14, 16], p=[0.2, 0.3, 0.3, 0.2])
    experience_0 = np.random.randint(0, 10)

    for t in range(T):
        year = 2010 + t

        # 教育(可能增加)
        education = education_0 + (0.5 if (t > 3 and np.random.rand() < 0.05) else 0)

        # 经验
        experience = experience_0 + t

        # 对数工资(Mincer 方程)
        log_wage = (1.8 + 0.08 * education + 0.05 * experience -
                    0.001 * experience**2 + ability + np.random.normal(0, 0.15))

        data.append({
            'id': i,
            'year': year,
            'log_wage': log_wage,
            'education': education,
            'experience': experience,
            'ability': ability
        })

df = pd.DataFrame(data)
df['experience_sq'] = df['experience'] ** 2

print("=" * 70)
print("Mincer 工资方程:面板数据分析")
print("=" * 70)
print(f"样本量: {len(df):,}")
print(f"个体数: {df['id'].nunique()}")
print(f"时间跨度: {df['year'].min()} - {df['year'].max()}")

# 设置面板索引
df_panel = df.set_index(['id', 'year'])

# 模型 1:混合 OLS
import statsmodels.api as sm
X1 = sm.add_constant(df[['education', 'experience', 'experience_sq']])
model1 = sm.OLS(df['log_wage'], X1).fit()

# 模型 2:固定效应(单向)
model2 = PanelOLS(df_panel['log_wage'],
                  df_panel[['education', 'experience', 'experience_sq']],
                  entity_effects=True).fit(cov_type='clustered',
                                           cluster_entity=True)

# 模型 3:固定效应(双向)
model3 = PanelOLS(df_panel['log_wage'],
                  df_panel[['education', 'experience', 'experience_sq']],
                  entity_effects=True,
                  time_effects=True).fit(cov_type='clustered',
                                         cluster_entity=True)

# 对比结果
print("\n" + "=" * 70)
print("回归结果对比")
print("=" * 70)

results = summary_col([model1, model2, model3],
                      stars=True,
                      float_format='%.4f',
                      model_names=['混合 OLS', '单向 FE', '双向 FE'],
                      info_dict={
                          'N': lambda x: f"{int(x.nobs):,}",
                          'R²': lambda x: f"{x.rsquared:.3f}" if hasattr(x, 'rsquared') else 'N/A'
                      })
print(results)

# 计算教育回报率
print("\n" + "=" * 70)
print("教育回报率估计")
print("=" * 70)
print(f"真实参数:   8.0%")
print(f"混合 OLS:   {model1.params['education']*100:.2f}%  (高估)")
print(f"单向 FE:    {model2.params['education']*100:.2f}%")
print(f"双向 FE:    {model3.params['education']*100:.2f}%")

# 可视化:经验-工资曲线
experience_range = np.linspace(0, 30, 100)
wage_curve = (model2.params['experience'] * experience_range +
              model2.params['experience_sq'] * experience_range**2)

plt.figure(figsize=(10, 6))
plt.plot(experience_range, wage_curve * 100, linewidth=3, color='darkblue')
plt.xlabel('工作经验(年)', fontweight='bold', fontsize=12)
plt.ylabel('对数工资变化(%)', fontweight='bold', fontsize=12)
plt.title('经验对工资的边际效应(Mincer 方程)', fontweight='bold', fontsize=14)
plt.grid(alpha=0.3)
plt.axhline(0, color='black', linewidth=0.8, linestyle='--')
plt.tight_layout()
plt.show()

# 计算经验的最优值
optimal_exp = -model2.params['experience'] / (2 * model2.params['experience_sq'])
print(f"\n工资达到峰值的经验年数: {optimal_exp:.1f} 年")

输出解读

  1. 混合 OLS:高估教育回报率(遗漏能力)
  2. 单向 FE:控制个体异质性,接近真实值
  3. 双向 FE:进一步控制时间趋势,最稳健
  4. 经验曲线:先上升后下降(倒 U 型)

本节小结

核心要点

  1. FE 的本质:通过差分消除不可观测的个体异质性

  2. 三种估计方法

    • 组内变换(Within)⭐ 最常用
    • LSDV(虚拟变量):等价于组内变换
    • 一阶差分: 时等价,否则组内变换更优
  3. 单向 vs 双向 FE

    • 单向:控制个体异质性
    • 双向:同时控制个体 + 时间趋势
  4. FE 的优势

    • 控制不可观测异质性
    • 允许 相关
    • 因果识别的利器
  5. FE 的局限

    • 无法估计时不变变量
    • 只利用组内变异(效率损失)
    • 需要严格外生性
  6. 实战工具

    • linearmodels.PanelOLS
    • 聚类标准误(必须!)

下一步

第4节:随机效应模型 中,我们将学习:

  • RE 模型的理论和 GLS 估计
  • FE vs RE 的选择(Hausman 检验)
  • 何时 RE 比 FE 更优

差分的力量,因果推断的基石!

基于 MIT 许可证发布。内容版权归作者所有。