温馨提示:若页面不能正常显示数学公式和代码,请阅读原文获得更好的阅读体验。

整理人: 李增杰 (南开大学)
邮箱: Lazaroo@163.com
Source: Andrew Heiss, 2021, Exploring R² and regression variance with Euler/Venn diagramsLink

  • 类别:回归分析
  • Title: R2 分解与 FWL定理:用韦恩图看懂回归表
  • Keywords: Frisch–Waugh–Lovell 定理, R平方, R-sqaure, R2分解, 系数含义, 回归系数

1. 基本说明:为什么 R2 容易被误读?

初学计量的人,拿到回归表时,第一眼会瞄向 R2R2:如果 R2=0.02R2=0.02,就觉得模型解释力太弱,结果“不够好”;如果 R2=0.85R2=0.85,会觉得拟合的不错。

这个习惯可以理解,因为 R2R2 比标准误、似然值、AIC、BIC 更容易读懂。它似乎直接回答了一个问题:这个模型到底解释了多少?

但问题在于:越容易读懂的指标,越容易被过度解释。R2R2 的确有用。它告诉我们,在当前样本和当前模型设定下,解释变量合起来解释了 YY 总波动的多少。但它不能直接告诉我们:

  • 某个核心解释变量是否具有因果含义;
  • 控制变量是否放得合理;
  • 哪个解释变量更重要;
  • 回归系数能不能解释为“控制其他变量不变”;
  • IV / 2SLS 中负的 R2R2 是否意味着模型失败。

这些问题都和 R2R2 有关,但又不能只靠 R2R2 回答。

本文用 R 模拟数据和韦恩图来解释 R2R2。在此基础上,我们进一步引入 FWL 定理和 R2 分解。直观地说:

  • R2R2 回答的是:模型整体解释了多少 YY 的波动;
  • FWL 定理回答的是:在控制其他变量以后,某个核心变量的系数来自哪里;
  • R2 分解回答的是:完整模型的解释力如何分摊到不同解释变量上。

这三个问题看似接近,实际上对应回归表中的三个不同层次。

2. 环境搭建

本文主要使用 R 语言完成模拟、回归和绘图。需要安装和加载如下包:

# 安装相关包
install.packages("tidyverse")   # 数据整理与绘图
install.packages("broom")       # 整理模型输出
install.packages("gapminder")   # Gapminder 示例数据
install.packages("faux")        # 生成相关模拟数据
install.packages("eulerr")      # 绘制欧拉图和韦恩图
install.packages("patchwork")   # 拼接图形
install.packages("latex2exp")   # 在图形中加入 LaTeX 公式

# 加载相关包
library(tidyverse)
library(broom)
library(gapminder)
library(faux)
library(eulerr)
library(patchwork)
library(grid)
library(latex2exp)

这些包的作用并不复杂。tidyverse 用于数据整理和基本绘图;broom 可以把模型结果整理为数据框;gapminder 提供一个直观的现实数据例子;faux 用来生成具有指定相关结构的模拟数据;eulerr 用来绘制变量之间重叠关系的欧拉图。

3. 一个简单例子:人均 GDP 与预期寿命

回归中的 R2R2 可以理解为:模型解释了被解释变量总波动的多少。

设一元线性回归模型为:

Yi=α+βXi+uiYi​=α+βXi​+ui​

在含截距项的 OLS 回归中,有如下平方和分解:

SST=SSR+SSESST=SSR+SSE

其中:

SST=∑i(Yi−Yˉ)2SST=i∑​(Yi​−Yˉ)2

SSR=∑i(Y^i−Yˉ)2SSR=i∑​(Y^i​−Yˉ)2

SSE=∑i(Yi−Y^i)2SSE=i∑​(Yi​−Y^i​)2

于是:

R2=SSRSST=1−SSESSTR2=SSTSSR​=1−SSTSSE​

这就是 R2R2 最常见的定义。它衡量模型解释掉的 YY 的波动占 YY 总波动的比例。

下面用 gapminder 数据做一个简单例子。我们只使用 2007 年数据,用各国人均 GDP 解释预期寿命。

# 只保留 2007 年数据
gapminder_2007 <- gapminder %>%
  filter(year == 2007)

# 使用人均 GDP 解释预期寿命
super_naive_model <- lm(
  lifeExp ~ gdpPercap,
  data = gapminder_2007
)

# 查看回归系数
tidy(super_naive_model)

# 查看 R2 等模型诊断指标
glance(super_naive_model)

示例输出如下:

## # A tibble: 2 × 5
##   term         estimate std.error statistic   p.value
##   <chr>           <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept) 59.6      1.01           59.0 9.89e-101
## 2 gdpPercap    0.000637 0.0000583      10.9 1.69e-20
## # A tibble: 1 × 12
##   r.squared adj.r.squared sigma statistic  p.value
##       <dbl>         <dbl> <dbl>     <dbl>    <dbl>
## 1     0.461         0.457  8.90      120. 1.69e-20

这里的 R2R2 大约为 0.461。也就是说,在这个简单模型中,人均 GDP 可以解释各国预期寿命差异中约 46.1% 的波动。

这个结果不难理解。富裕国家通常有更好的医疗、营养、公共卫生和教育条件,因此人均 GDP 与预期寿命之间存在较强相关关系。

但我们要小心:R2=0.461R2=0.461 并不等于“人均 GDP 对预期寿命有 46.1% 的因果影响”。它只是说,在当前样本和当前模型下,人均 GDP 与预期寿命之间有较大的共同波动。

因此,R2R2 是拟合优度指标,不是因果识别指标。

4. 重叠圆圈形式的回归

公式可以说明 R2R2 的定义,但不一定帮助我们理解多元回归中的重叠关系。一个更直观的方式,是把变量看成圆圈。

每个圆的面积表示一个变量的波动。两个圆的重叠部分表示两个变量之间的共同波动。对回归而言,YY 和解释变量重叠的那部分,就是模型能够解释的 YY 的波动。

这种图在早期文献中常被称为 ballantine 图,也可以理解为一种欧拉图或韦恩图。它不能替代严格统计推断,但很适合帮助我们理解 R2R2、控制变量和多重共线性。

4.1 模拟一组三变量数据

为了让图形更清楚,我们先生成一组模拟数据。设有三个变量 YY、X1X1 和 X2X2。三个变量都服从正态分布,并且彼此之间存在一定相关性。

设定如下:

Y: mean=10, sd=2Y: mean=10, sd=2

X1: mean=9, sd=1.7X1: mean=9, sd=1.7

X2: mean=9, sd=1.3X2: mean=9, sd=1.3

相关系数设为:

corr(Y,X1)=0.5corr(Y,X1)=0.5

corr(Y,X2)=0.3corr(Y,X2)=0.3

corr(X1,X2)=0.4corr(X1,X2)=0.4

使用 faux 包生成数据:

set.seed(1234)

df <- rnorm_multi(
  n = 100,
  mu = c(10, 9, 9),
  sd = c(2, 1.7, 1.3),
  r = c(0.5, 0.3, 0.4),
  varnames = c("Y", "X1", "X2"),
  empirical = FALSE
)

# 查看前几行数据
head(df)

# 查看相关系数
cor(df)

示例输出如下:

##       Y    X1    X2
## 1  8.22  6.73  8.38
## 2 10.17  9.36 10.12
## 3 12.03 10.32  9.86
## 4  5.41  5.80  8.38
## 5 10.10 10.08 10.09
## 6 11.13  9.11  9.94
##        Y    X1    X2
## Y  1.000 0.453 0.373
## X1 0.453 1.000 0.468
## X2 0.373 0.468 1.000

后面的图都基于这组三变量数据。

为了用面积表示变量波动,我们写一个函数来计算变量的离均差平方和:

# 计算变量的离均差平方和
ss <- function(x) {
  sum((x - mean(x))^2)
}

这里使用平方和而不是方差,是因为 aov() 输出中直接包含平方和,后面计算更方便。

温馨提示:若页面不能正常显示数学公式和代码,请阅读原文获得更好的阅读体验。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐