伍德里奇先生的问题:PSM 分析中的配对——小蝌蚪找妈妈
作者:刘涛 (中山大学岭南学院本科生)
写在前面: 相信《小蝌蚪找妈妈》是不少人的童年回忆。在数据处理中,我们常常需要处理像“小蝌蚪”要找到"妈妈"的数据,并将它们成功匹配。
本文将借助伍德里奇教授在
statalist
论坛的提问和后续讨论来讲讲利用stata数据处理**中"小蝌蚪"找到"妈妈"的故事。
1. 问题描述
我们手头有如下面板数据(精简版):
+-----------------------------+
| id year nb1 nb2 x |
|-----------------------------|
| 1 2000 2 3 100 |
| 1 2001 2 3 110 |
| 1 2002 2 3 120 |
|-----------------------------|
| 2 2000 1 4 200 |
| 2 2001 1 4 210 |
| 2 2002 1 4 220 |
|-----------------------------|
| 3 2000 4 . 300 |
| 3 2001 4 . 310 |
| 3 2002 4 . 320 |
|-----------------------------|
| 4 2000 3 2 400 |
| 4 2001 3 2 410 |
| 4 2002 3 2 420 |
+-----------------------------+
该样本源于一个社区调查,共有四户居民,其中id
为编号,year
为调查年份,nb1
为其邻居一neighbor1
的id
,nb2
为其邻居二neighbor2
的编号。以第一个样本为例,其nb1=2
,nb2=3
,说明第二户和第三户居民是其邻居。x
值是调查值,可以理解为收入。
现在,我们需要做的工作就是,将样本对应的邻居的 x 值匹配到数据中来,即让样本找到其邻居的数据,来构造一个协变量。最终想要的结果如下:
+---------------------------------------------+
| id year nb1 nb2 x x_nb1 x_nb2 |
|---------------------------------------------|
| 1 2000 2 3 100 200 300 |
| 1 2001 2 3 110 210 310 |
| 1 2002 2 3 120 220 320 |
|---------------------------------------------|
| 2 2000 1 4 200 100 400 |
| 2 2001 1 4 210 110 410 |
| 2 2002 1 4 220 120 420 |
|---------------------------------------------|
| 3 2000 4 . 300 400 . |
| 3 2001 4 . 310 410 . |
| 3 2002 4 . 320 420 . |
|---------------------------------------------|
| 4 2000 3 2 400 300 200 |
| 4 2001 3 2 410 310 210 |
| 4 2002 3 2 420 320 220 |
+---------------------------------------------+
接下来,我们将基于论坛中Sebastian、Robert 提出的解决方法,来介绍如何巧妙地使用Stata 中的 Merge
和 Rangestat
的命令来解决上述问题。
这些解决思路在 PSM (Propensity Score Matching,倾向得分匹配分析) 中非常有用。例如,对于文献中普遍使用的 PSM-DID
方法中,最为棘手的问题就是如何在完成 PSM 分析后,把 实验组(Treat group) 和 对照组(Control group) 的样本对应起来,以便做后续的 DID (倍分法, 双重差分) 分析。
2. 解决方法
先讲原始数据存储起来:
*-----------balance data---------------
clear
input ///
id year nb1 nb2 x
1 2000 2 3 100
1 2001 2 3 110
1 2002 2 3 120
2 2000 1 4 200
2 2001 1 4 210
2 2002 1 4 220
3 2000 4 . 300
3 2001 4 . 310
3 2002 4 . 320
4 2000 3 2 400
4 2001 3 2 410
4 2002 3 2 420
end
save "Peer_Woold_balance", replace
*-------------------------------------
2.1 方法1:公式求解
根据 *Sebastian 的方法,我们可以将原始的数据按id
,year
排序,之后通过 nb1
的值构造出对应公式,找出数组中的下标,从而代入完成匹配。代码如下:
use "Peer_Woold_balance", clear
sort id year //排序
by id: gen n = _n //按id生成序列,即1,2,3分别代表不同的年份
local N = n[_N] //生成暂元,记录固定的N=3
gen x_nb1 = x[(nb1 - 1) * `N' + n] //匹配nb1的数据
gen x_nb2 = x[(nb2 - 1) * `N' + n] //匹配nb2的数据
drop n
该方法的关键之处就是构造出了 (nb1-1)N + n
这个公式来计算下标。因为排序过后,x
是一个按 id
和 year
排序的序列,只要代入下标,就能得到对应的值。如,当 nb1=3
时,我们就要取 x 中的第 7 位到第 9 位,(nb1-1)*3
得到 6,再分别加序列 1,2,3
即得到我们想要的结果。
2.2 方法2:使用 Merge 命令进行匹配
通常我们在匹配数据时,我们可能会想到 Excel 中万能的 VLOOKUP
函数:
VLOOKUP(要查找的值,查找范围,返回值的位置,精确匹配)
其实,stata
中的 Merge 命令也能实现该功能。
- Merge 命令常用来进行数据集的合并,即从外部数据文件中,增加变量,它主要有四种方式来进行合并:
-
1:1 合并
merge 1:1 varlist using filename [, options]
-
m:1 合并
merge m:1 varlist using filename [, options]
-
1:m 合并
merge 1:m varlist using filename [, options]
-
m:m 合并
merge m:m varlist using filename [, options]
其中,varlist
则是用来唯一标示每一行观察值的一个或一组的变量。
Robert 认为,可以将 nb1、nb2
对应的数据单独导出为一个数据文件,再通过 Merge命令,通过 id
和 year
来识别每一行观察值,从而将 nb1
和 nb2
的数值对应横向合并到原有数据集中。
* 保存 nb1 的数据集
use "Peer_Woold_balance", clear
keep id year x
rename (id x) (nb1 x_nb1)
save "nb1.dta", replace
* 保存 nb2 的数据集
use "Peer_Woold_balance", clear
keep id year x
rename (id x) (nb2 x_nb2)
save "nb2.dta", replace
* 回到原始的数据集 Peer_Woold_balance.dta,进行横向合并
use "Peer_Woold_balance", clear
merge m:1 nb1 year using "nb1.dta", keep(master match) nogen //nogen是设定不生成_Merge的指示变量。
merge m:1 nb2 year using "nb2.dta", keep(master match) nogen
isid id year, sort //按id、year排序
list, sepby(id)
这是通过 Merge 的实现方法,更为具体的功能可以 help merge
深入了解。
2.3 方法3:使用 rangestat 命令
除了 Merge 之外,Robert 也提出了借助 Rangestat 来解决数据匹配问题的方法。
根据 stata 帮助文件的定义:rangestat 命令主要用于针对特定的观察值进行相关的统计工作,格式如下:
-
rangestat (stat) varname, interval(keyvar low high)[options]
-
(stat)
: 常常是你所要进行的统计工作,如求最小值min
,均值mean
等等 -
varname
: 即是所进行统计的变量 -
interval()
: 则是选定数据的范围
-
如下例子,则可以对 rep78
筛选后的数据,统计price
、mpg
变量的最小值、均值等:
sysuse auto, clear
rangestat (min) price mpg (mean) price mpg, interval(rep78 0 0)
由于我们的数据是面板数据,其中要通过 id
、year
两个变量来确定样本,
而 rangestat 筛选条件只有一个,因此,我们可以通过创建一个新变量来包含上述 id
、year
的信息。
在这里,类似于创建我们的学号,如 15353333,15代表入学年份,35代表院系,3333代表学生顺序,创建了
12000、12001 等变量。
gen double bigid= id*10000+year
gen double idyear=nb1*10000+year
在将创建完新的变量之后,我们需要将 idyear
和 bigid
进行匹配,
将那些 bigid
的值等于当前 idyear
的观察值找出来,
interval(bigid idyear idyear)
命令可以完美实现这个功能。
rangestat (min) x_nb1=x,interval(bigid idyear idyear)
//以第一个样本为例,idyear=22000,rangestat通过这个语句,就能在bigid这一变量中找到bigid=22000的样本,之后对其进行统计。
replace idyear = nb2 * 10000 + year
replace idyear = 0 if mi(idyear)
rangestat (min) nb_2=x, interval(bigid idyear idyear)
list, sepby(id)
3. 哪种方法好?
表面上看起来第一种方法似乎最为简洁,但第二种方法的思路最为清晰。对于任何一种方法,我们的主要评价依据是该方法是否有通用性。以此来判断,第二种方法的通用性是最强的。这是因为,第一种方法只适用于平行面板数据,而在实际的匹配过程中,我们的数据往往是非平行面板。大家可以考虑一下非平行面板的情形:
+-----------------------------+
| id year nb1 nb2 x |
|-----------------------------|
| 1 2000 2 3 100 |
| 1 2001 2 3 110 |
|-----------------------------|
| 2 2000 1 4 200 |
| 2 2001 1 4 210 |
| 2 2002 1 4 220 |
|-----------------------------|
| 3 2000 4 . 300 |
| 3 2001 4 . 310 |
| 3 2002 4 . 320 |
|-----------------------------|
| 4 2000 3 2 400 |
| 4 2001 3 2 410 |
| 4 2002 3 2 420 |
| 4 2003 . . 430 |
+-----------------------------+
此时,第一种方法得到的结果是错乱的(当然,可能需要重新构造公式):
+---------------------------------------------+
| id year nb1 nb2 x x_nb1 x_nb2 |
|---------------------------------------------|
| 1 2000 2 3 100 220 400 |
| 1 2001 2 3 110 300 410 |
|---------------------------------------------|
| 2 2000 1 4 200 100 . |
| 2 2001 1 4 210 110 . |
| 2 2002 1 4 220 200 . |
|---------------------------------------------|
| 3 2000 4 . 300 . . |
| 3 2001 4 . 310 . . |
| 3 2002 4 . 320 . . |
|---------------------------------------------|
| 4 2000 3 2 400 400 220 |
| 4 2001 3 2 410 410 300 |
| 4 2002 3 2 420 420 310 |
| 4 2003 . . 430 . . |
+---------------------------------------------+
相对而言,第二种方法仍然可以得到正确结果:
*-----------unbalance data---------------
clear
input ///
id year nb1 nb2 x
1 2000 2 3 100
1 2001 2 3 110
2 2000 1 4 200
2 2001 1 4 210
2 2002 1 4 220
3 2000 4 . 300
3 2001 4 . 310
3 2002 4 . 320
4 2000 3 2 400
4 2001 3 2 410
4 2002 3 2 420
4 2003 . . 430
end
*---------------------------------------
save "Peer_Woold_unbalance", replace
list, sepby(id) noobs
*-----------
*-2.2 方法2:使用 Merge 命令进行匹配
* 保存nb1的数据集
use "Peer_Woold_unbalance", clear
keep id year x
rename (id x) (nb1 x_nb1)
save "nb1.dta", replace
* 保存nb2的数据集
use "Peer_Woold_unbalance", clear
keep id year x
rename (id x) (nb2 x_nb2)
save "nb2.dta", replace
* 回到原始的数据集main.dta,进行横向合并
use "Peer_Woold_unbalance", clear
merge m:1 nb1 year using "nb1.dta", keep(master match) nogen //nogen是设定不生成_Merge的指示变量。
merge m:1 nb2 year using "nb2.dta", keep(master match) nogen
isid id year, sort //按id、year排序
list, sepby(id)
结果如下:
+---------------------------------------------+
| id year nb1 nb2 x x_nb1 x_nb2 |
|---------------------------------------------|
| 1 2000 2 3 100 200 300 |
| 1 2001 2 3 110 210 310 |
|---------------------------------------------|
| 2 2000 1 4 200 100 400 |
| 2 2001 1 4 210 110 410 |
| 2 2002 1 4 220 . 420 |
|---------------------------------------------|
| 3 2000 4 . 300 400 . |
| 3 2001 4 . 310 410 . |
| 3 2002 4 . 320 420 . |
|---------------------------------------------|
| 4 2000 3 2 400 300 200 |
| 4 2001 3 2 410 310 210 |
| 4 2002 3 2 420 320 220 |
| 4 2003 . . 430 . . |
+---------------------------------------------+
第三种方法留待各位读者自行测试。
4. 总结
通过介绍伍德里奇教授在 Statalist
的提问,想必大家对如何利用 merge
和 rangestat
命令帮助"小蝌蚪"数据们找到了"妈妈"有了一定的了解。
当然,在数据处理中,问题是复杂的,解决问题的方法是多样的,大家若有新的想法或是问题,也希望大家与 Stata 连享会进行交流,共同进步。
参考资料
-
Stata帮助手册
A.1 附: 伍德里奇老兄的提问原文
(Jeff Wooldridge,01 May 2016, 10:00)Hi All:
I have a problem where I have a (balanced) panel data set, and each cross-sectional unit in the data set comes with two neighbors, contained in the variables nb1 and nb2 (although some neighbor values are missing). I'd like to put the values of some of the neighbors' covariates on the proper line so I can include them in regression analysis. I know how to do this without a panel structure, and I found a recent helpful entry by Nick Cox that confirms what I thought. But I'm stuck for the panel data case.
The identities of the neighbors do not change over time.
The first block below gives a simple example of the structure, and the second block gives what I would like to have. I'd really appreciate any hints. Thanks, Jeff.
Code:
id year nb1 nb2 x
1 2000 2 3 100
1 2001 2 3 110
1 2002 2 3 120
2 2000 1 4 200
2 2001 1 4 210
2 2002 1 4 220
3 2000 4 . 300
3 2001 4 . 310
3 2002 4 . 320
4 2000 3 2 400
4 2001 3 2 410
4 2002 3 2 420
id year nb1 nb2 x x_nb1 x_nb2
1 2000 2 3 100 200 300
1 2001 2 3 110 210 310
1 2002 2 3 120 220 320
2 2000 1 4 200 100 400
2 2001 1 4 210 110 410
2 2002 1 4 220 120 420
3 2000 4 . 300 400 .
3 2001 4 . 310 410 .
3 2002 4 . 320 420 .
4 2000 3 2 400 300 200
4 2001 3 2 410 310 210
4 2002 3 2 420 320 220
关于我们
- 【Stata 连享会(公众号:StataChina)】由中山大学连玉君老师团队创办,旨在定期与大家分享 Stata 应用的各种经验和技巧。
- 公众号推文同步发布于 【简书-Stata连享会】 和 【知乎-连玉君Stata专栏】。可以在简书和知乎中搜索关键词
Stata
或Stata连享会
后关注我们。 - 推文中的相关数据和程序,以及 Markdown 格式原文 可以在 【Stata连享会-码云】 中获取。【Stata连享会-码云】 中还放置了诸多 Stata 资源和程序。如 Stata命令导航 || stata-fundamentals || Propensity-score-matching-in-stata || Stata-Training 等。
联系我们
-
欢迎赐稿: 欢迎将您的文章或笔记投稿至
Stata连享会(公众号: StataChina)
,我们会保留您的署名;录用稿件达五篇
以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。 - 意见和资料: 欢迎您的宝贵意见,您也可以来信索取推文中提及的程序和数据。
- 招募英才: 欢迎加入我们的团队,一起学习 Stata。合作编辑或撰写稿件五篇以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。
- 联系邮件: StataChina@163.com
连玉君Stata现场班报名中
欢迎加入Stata连享会(公众号: StataChina)