Stata数据处理:一文读懂微观数据库清理(下)

发布时间:2021-07-19 阅读 1409

Stata连享会   主页 || 视频 || 推文 || 知乎 || Bilibili 站

温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验。

New! lianxh 命令发布了:
随时搜索推文、Stata 资源。安装:
. ssc install lianxh
详情参见帮助文件 (有惊喜):
. help lianxh
连享会新命令:cnssc, ihelp, rdbalance, gitee, installpkg

课程详情 https://gitee.com/lianxh/Course

课程主页 https://gitee.com/lianxh/Course

⛳ Stata 系列推文:

PDF下载 - 推文合集

作者: 涂冰倩 (浙江大学)
邮箱: tubingqian@zju.edu.cn

编者按:在上一篇推文中对数据清理的前半部分环节进行讲解,这篇推文将继续进行后半部分讲解。

课程主页https://gitee.com/arlionn/dataclean

b 站视频回放https://www.bilibili.com/video/BV14y4y1T7nR (在 b 站 搜索「连享会」即可直达)

⛳ 完整代码获取方式:
. cnssc install lxhget, replace
. lxhget data_cleaning.do, replace


目录


4. 数据清理

4.2 多变量清理

数据中有些问题不是关于某一特定变量取值不合理,有时需要联合其他变量才能发现其不合理的地方。多变量的清理建立在单变量清理完成的基础上,通过多变量的联合分布、交叉验证检验变量之间的逻辑一致性等。

4.2.1 用分类变量检查分类变量

此时可以用两个分类变量的交互表来查看两个变量的所有取值组合,用分类变量的取值检验和另一个分类变量的取值是否矛盾。这里以是否处于结婚状态和是否结过婚两个变量的交叉验证为例 (此例无数据)。marriedornot 表示是否结过婚 (0-1 变量),marriednow 表示当下是否处于婚姻状态(0-1 变量)。

. tabulate marriednow marriedornot,missing

           |  marriedornot
marriednow |      0      1 |   Total
-----------+---------------+--------
         0 |      5      1 |       6 
         1 |      1      3 |       4 
-----------+---------------+--------
     Total |      6      4 |      10 

. count if marriednow==1 & marriedornot==0
  1
. list id marriednow marriedornot if marriednow==1 & marriedornot==0

     +--------------------------+
     | id   marrie~w   marrie~t |
     |--------------------------|
  5. |  5          1          0 |
     +--------------------------+

可以看到,在当下处于结婚状态的受访者里 (marriednow=1),存在从来没有结过婚的人 (marriedornot=0),这显然不符合实际。

4.2.2 用连续变量检查分类变量

当存在连续变量时,交互表检验 (tabulate) 不再适用。当用连续变量检查分类变量时,可以为每一类别生成一个连续变量的描述统计结果。这里以是否外出务工 (a3) 和外出务工时长 (a4) 两个变量的交叉验证为例。

. *先进行单变量清理 (检查是否存在异常值等等,清理步骤详见第 4.1 小节),生成新变量
. use hh_data_new.dta,clear
. des a3 a4            
. sum a3
. gen outornot=a3
. label var outornot "是否外出务工"
. sum a4
. gen outtime=a4
. label var outtime "外出务工时长"
 
. *用连续变量检查分类变量
. bysort outornot: sum outtime //根据 outornot 不同取值对 outtime 进行描述统计

------------------------------------------------------
-> outornot = 0

 Variable |    Obs       Mean    Std. Dev.  Min    Max
----------+-------------------------------------------
  outtime |      1          8           .     8      8

------------------------------------------------------
-> outornot = 1

 Variable |    Obs       Mean    Std. Dev.  Min    Max
----------+-------------------------------------------
  outtime |     13   9.076923    2.564551     4     11


. sort pid year
. list pid year relation age outornot outtime outincome ///
>     if outornot==0 & !missing(outtime)>0  /*找出这些不符合逻辑的观察值 
>                                   (未外出务工的受访者不应该有外出务工时长)*/

     +-------------------------------------------------------------+
     | pid   year   relation   age   outornot   outtime   outinc~e |
     |-------------------------------------------------------------|
  9. |   5   2001          4    45          0         8       3000 |
     +-------------------------------------------------------------+

. /*联合其他变量对异常值进行更改:发现第 9 个观察值存在外出务工时间和收入,
>   故将此受访者的 outornot 更改为1*/
. replace outornot=1 if pid==5 & year==2001
. list pid year relation age outornot outtime outincome ///
>      if outornot==0 & !missing(outtime)>0 //查看更改是否正确

. /*另外,在有外出务工 (outornot==1)的类别里,
>   是否存在被访者外出务工时长 (outtime)却为缺失值呢?*/
. list pid year relation age outornot outtime outincome ///
>      if outornot==1 & outtime==.

     +-------------------------------------------------------------+
     | pid   year   relation   age   outornot   outtime   outinc~e |
     |-------------------------------------------------------------|
  7. |   4   2001          4    60          1         .          . |
 19. |  10   2001          1     8          1         .          . |
     +-------------------------------------------------------------+

. /*发现第 19 个观察值外出务工时长 (outtime) 为缺失值 
>   且不存在外出务工收入 (outincome),同时其年龄 (age) 为 8 岁,
>   不太可能外出务工,故把 outornot 更改为 0,并把 outtime 改为 0。
>   第 7 个观察值外出务工时长 (outtime)为缺失值且不存在外出务工收入 (outincome)
>   但没有其他辅助信息,是否外出务工仍不能确定,
>   可以选择将该观察值设为缺失值或者不进行处理*/
. 
. replace outornot=0  if pid==10 & year==2001
. list pid year relation age outornot outtime outincome ///
>      if outornot==1 & outtime==. //检查更改结果
. save "hh_data_new", replace 

4.2.3 用连续变量检查连续变量

综合运用 sumcountassert 等命令进行检验,这里以计算村庄劳动力人口占村庄人口比例为例。按照实际来说,村庄劳动人口不应超过村总人口数。

 use "village_data",clear
. sum c1 c2
. tab c1 //发现存在异常值99999
. recode c1 (99999=.) ,gen(v_labor)
. sum c1 v_labor //检查更改结果
. label var v_labor "村庄劳动力数量"
. gen v_pop=c2
. label var v_pop "村庄总人口数"
. count if v_labor>v_pop & !missing(v_labor) //村劳动人口不应超过村总人口数
. assert v_labor<=v_pop if v_labor!=. //上一条命令的等价命令,运行结果为空表示为真

. list vid year v_labor v_pop if v_labor>v_pop & !missing(v_labor) 
> //列出不符合实际的数据

     +------------------------------+
     | vid   year   v_labor   v_pop |
     |------------------------------|
  7. |   2   2021      3105    3000 |
     +------------------------------+

. replace v_pop=v_labor if vid==2 & year==2021 
//将劳动力人口数据更改为人口数据,或者设置为缺失值
. list vid year v_labor v_pop if v_labor>v_pop & !missing(v_labor) 
//检查更改结果
. save "village_data_new", replace

利用变量和变量之间的交叉验证需要我们的逻辑、生活常识甚至一些专业知识,比如说男性生过孩子,父母的年龄比孩子的年龄还大,这些情况就不太合理。再比如在用土地面积和耕作面积进行交叉验证时,应该考虑到复种情况的存在等等。

5. 综合变量生成

这部分主要是通过基本变量和命令的组合生成一些综合变量。

举例一:检查户主人数

在对农户数据进行分析时,常用户主特征变量表征家庭特征,但可能存在录入错误,所以需要对户主信息进行查验,确保每户只有一个户主。

 use hh_data_new.dta,clear
. gen head=cond(relation==1, 1, 0) 
//如果该条观察值是户主,head 变量取值为 1,否则取值为 0

. /*这里cond是条件函数,如果符合第一个逗号前的判断条件,
>   即返回第二个逗号前的值,否则返回最后一个值。*/
 
. bysort fid year:egen headnum=sum(head) //计算每年每户的户主数量
. tab headnum, m //取值为1表明一户有一个户主

    headnum |      Freq.     Percent        Cum.
------------+-----------------------------------
          0 |          6       30.00       30.00
          1 |          7       35.00       65.00
          2 |          7       35.00      100.00
------------+-----------------------------------
      Total |         20      100.00


. /*对于没有户主家庭,指派成年男性为户主,
>   还可以是指派成年女性或者被访问的第一人为户主等等*/
. gen relation2=relation
. replace relation2=1 if headnum==0 & agegroup==2 & gender==1
 
. /*对于有多位户主的家庭,指派年龄最小的成年男性为户主*/
. bysort fid year:egen min_male_age=min(age) if (gender==1) & (agegroup==2) 
//识别出一个家庭最小成年男性的年龄
. replace relation2=. if headnum==2 & relation==1 
//将存在多个户主的家庭的关系变量设置为缺失值
. replace relation2=1 if (headnum==2) & (age==min_male_age) & !missing(age) 
//当成年男性受访者年龄为家庭最小时,被指派为户主
. list fid pid year age relation2 relation headnum if headnum==0|headnum==2 
//对修改后的关系变量relation2进行查看

     +--------------------------------------------------------+
     | fid   pid   year   age   relati~2   relation   headnum |
     |--------------------------------------------------------|
  5. |   1     2   2021     .          .          1         2 |
  6. |   1     4   2021    80          5          5         2 |
  7. |   1     1   2021    50          .          1         2 |
  8. |   1     3   2021    25          1          2         2 |
  9. |   2     6   2001    41          2          2         0 |
     |--------------------------------------------------------|
 10. |   2     7   2001    20          3          3         0 |
 11. |   2     5   2001    45          1          4         0 |
 15. |   3     9   2001    30          .          1         2 |
 16. |   3     8   2001    32          1          5         2 |
 17. |   3    10   2001     8          .          1         2 |
     |--------------------------------------------------------|
 18. |   3     9   2021    50          2          2         0 |
 19. |   3     8   2021    52          1          3         0 |
 20. |   3    10   2021    28          4          4         0 |
     +--------------------------------------------------------+

. /*可以看到,headnum=2和headnum=0的家庭里,只存在一个观察值的relation2变量取值为1,
>   即经过修改后这些家庭只存在一个户主*/

举例二:计算家庭 18 岁以上人口数和抚养系数比

. /*抚养系数比指的是人口中非劳动年龄人口数与劳动年龄人口数之比,
>   这里定义非劳动年龄人口指 14 岁及以下和 65 岁及以上人口。*/

. bysort fid year:gen hnum1=_N  //计算每组中观察值个数,包括缺失值
. bysort fid year:egen hnum2=count(pid) //计算每组中的非缺失值观察值个数
. gen child=cond(age<=14,1,0) //如果受访者在14岁及以下,取值为1,否则取值为0
. gen old=cond(age>=65,1,0)
. gen adult=cond(age>14 & age<65,1,0)
. gen adult18=cond(age>=18,1,0)

. foreach i in child old adult adult18 {
  2.   bysort fid year:egen n_`i'=sum(`i')
  3. }  //分组计算每个家庭每年的 child 等人数
  
. gen adult18_ratio=n_adult18/hnum2 //生成家庭18岁以上人数比例
. gen dependency_ratio=(n_child+n_old)/n_adult //生成抚养系数比

. sum adult18_ratio dependency_ratio //检查生成的变量

    Variable |        Obs        Mean    Std. Dev.       Min        Max
-------------+---------------------------------------------------------
adult18_ra~o |         20          .9    .1420403   .6666667          1
dependency~o |         20         .55    .4261208          0          1

5.1 观测值组间计算-根据观测值分组

观测值组间计算指按行分组,针对某一个变量进行计算。按观测值对某一变量进行计算,通常需要加入 by (sort) var 选项,即根据 var 分组,再进行计算。

举例:根据性别查看不同的收入分布情况,并生成不同的统计值

*观测值组间计算-根据观测值分组
bysort gender: sum outincome //按性别查看收入变量
bysort gender:egen income_mean=mean(outincome) //按性别生成收入均值
//等价于egen income_mean=mean(outincome),by(gender) 
bysort gender:egen income_max=max(outincome) //按性别生成收入最大值
bysort gender:egen income_min=min(outincome) //按性别生成收入最小值
bysort gender:egen income_sd=sd(outincome)   //按性别生成收入标准差
bysort gender:egen income_sum=sum(outincome) //按性别生成加总值
bysort gender:egen income_total=sum(outincome) //按性别生成加总值
bysort gender:gen income_sum2=sum(outincome) 
sum outincome
gen income_standard=outincome-r(mean)/r(sd) //将income标准化
save "hh_data_reg", replace //清理数据完成,保存为新数据供分析所用

命令 egen 通过函数创建新变量,在 egen 命令前使用 bysort 选项,表明按照该变量进行排序并分组操作。在 egen 命令后使用 sumtotal 函数都能计算每个组别的加总值,并且计算时将缺失值自动视为 0。在使用 sum 函数时,采用 egen 命令和 gen命令操作不同,前者是对组内所有观察值进行加总,而后者是进行累加。

总结:利用命令 egen 除了能生成上面提到的 meansd 等,还能生成中位数 median、众数 mode、百分位数 pctile、个数 count、偏度 skew、峰度 kurt 等,进一步了解 egen 支持的函数列表可以在 Stata 中输入 help egen。其他求和方式可以参考推文「Stata数据处理:各种求和方式一览」,以及关于分组计算还可以参见推文「Stata:runby - 一切皆可分组计算!」

5.2 观测值组内计算-变量分组

观测值组内计算指对列操作,针对同一观测值对不同的变量进行计算。按列进行计算一般不加 by (sort) var 选项,常用的选项包括生成行均值 rowmean、行方差 rowsd、行最大值 rowmax、行最小值 rowmin、行中位数 rowmedian、行加总 rowtotal。就这些计算命令而言,对列操作和对行操作在本质上没有区别,对行操作可以通过将数据进行长宽转换后进行列操作实现,反之亦然。另外,还有一些函数命令:

  • rownomiss:计算一组变量中非缺失值的数量;
  • anycount:查看变量列表中元素的个数;
  • anymatch:变量列表中若否存在某个元素返回1,否则返回0 ;
  • anyvalue:指定变量若存在某个元素则返回该元素值,否则返回缺失值;
  • diff:查看变量是否相等;
  • group:根据变量进行分组。
. egen rowtotal=rowtotal(age a5) //仅作为举例,不考虑变量实际含义
. gen plus=age+a5
. list age a5 rowtotal plus in 1/10

     +--------------------------------+
     | age      a5   rowtotal    plus |
     |--------------------------------|
  1. |   .    3000       3000       . |
  2. |  61   30000      30061   30061 |
  3. |  60       .         60       . |
  4. |   .    4800       4800       . |
  5. |  30    4000       4030    4030 |
     |--------------------------------|
  6. |  20    4000       4020    4020 |
  7. |  28       .         28       . |
  8. |   8       .          8       . |
  9. |  50   20000      20050   20050 |
 10. |  40   42000      42040   42040 |
     +--------------------------------+

/*直接对变量相加生成新的变量时,若为存在至少一个缺失值,
> 那么整个加总值都将为缺失值,而 rowtotal 将缺失值视 0 进行加总*/

. egen female1=anymatch(gender), value(0) 
//性别变量若取值为 0 则返回 1,否则返回 0 
. egen female2=anyvalue(gender), value(0) 
//性别变量若取值为 0 则返回 0,否则生成缺失值 
. list pid gender female1 female2 in 9/14, sepby (gender)

     +----------------------------------+
     | pid   gender   female1   female2 |
     |----------------------------------|
  9. |   9       女         1         0 |
 10. |   7       女         1         0 |
 11. |   4       女         1         0 |
     |----------------------------------|
 12. |   3       男         0         . |
 13. |   8       男         0         . |
 14. |   3       男         0         . |
     +----------------------------------+

6. 数据清理管理

数据清理过程应保存在 Stata 的 dofile 文件中,我们可以命名不同的 dofile 文件来执行不同的清理过程,也可以直接采用一个 dofile 来执行所有的清理命令。这里将以上所有的清理命令保存在一个叫 “datacleaning” 的 dofile 里,只需在 Stata 界面中输入 do datacleaning.do 就可以执行以上所有命令操作。

因为在一开始就将所有清理过程保存在了一个叫 “datacleaning” 的 log 文件里,所以即使中途没有及时保存,我们也能在 log 文件中找到我们所有的操作命令和执行结果。在数据清理的一开始,最好建立一个文档对所有数据文件以及大概的清理步骤进行记录,确保清理过程有迹可循。

总结一下,数据清理的整体思路如下:

*step 1:设置工作路径与生成 log 文件
cd E:\stata\连享会 //设置工作路径 (可以自行进行修改)
capture log close //关闭以前的 log 文件,加 capture 不会报错
log using datacleaning, replace //新建一个名为 datacleaning 的 log 文件

*step 2:读取数据+熟悉数据

*step 3:整理和提取变量
//保留数据通过保留列 (变量) 和保留行 (观测值) 两种方式实现 keep  drop
//主要清洗可能使用到的数据

*step 4:数据的合并与转换
//一般来说,先 append 再 merge 比较合适
  **纵向添加数据 append
  //append 需变量名及变量格式对应
  **横向合并数据 merge
  //至少保证其中一方数据能被识别变量唯一识别
  **长宽数据转换 reshape
  **数据堆叠 gather spread stack

*step 5:检查数据
  **检查重复数据 (标识变量 + 数据)
  **检查变量基本情况

*step 6:数据清理
  **单变量清理
  //在生成新变量的基础上进行修改,更改后要查验
    ***分类变量的清理
    ***连续变量的清理
    ***缺失值查验和处理
    ***极端值查验和处理
  **多变量清理
    ***用分类变量检查分类变量
    ***用连续变量检查分类变量
    ***用连续变量检查连续变量

*step 7:综合变量生成
  **观测值组间计算-根据观测值分组
  **观测值组内计算-变量分组

*step 8:筛选变量 + 另存为新数据 + 关闭 log 文件
keep var        //保存分析需用到的变量
order var1 var2 //给变量排序
label data "数据清理20210615" //给数据添加标签
save data_reg, replace       //保存清洗过的数据,为分析所用
log close                    //关闭 log 文件

7. 注意事项

两个注意:注意每次变动数据前要进行备份,注意更改变量请在新生成的变量基础上进行修改,要让你处理数据的每一步都有迹可循。

两个查看:每执行完一次 Stata 命令,一要看结果窗口是否报错,二要打开数据库看数据是否发生改变、如何改变。实践出真知,如果对某条命令的操作不确定,可以看下执行该条命令之后数据会发生什么样的改变。

数据清理是一个重复性的工作,有时你认为已经清理干净了,结果跑出来可能会教你重新做人,老老实实又跑到数据清理环节对错误进行修正。对数据保持一颗敬畏之心,你怎样对待数据,数据就会怎样反馈给你结果。「CFPS中国家庭追踪调查官方网站」上有大量的技术报告和数据清理说明,可以进行数据清理的学习。

由于笔者所在专业为农业经济学,现阶段接触到的更多是农户微观调查数据,所以数据清理方法不一定适用于所有专业,一些经验也是一家之谈,若有不正确之处,感谢您的批评指出。

8. 参考资料

  • 唐丽娜. 社会调查数据管理:基于Stata14管理CGSS数据[M]. 人民邮电出版社, 2016.
  • 迈克尔·N·米歇尔. Stata环境下的数据管理实务手册[M]. 中国人民大学出版社, 2016.
  • CFPS-14 中国家庭追踪调查2010年农村家庭收入的调整办法. -PDF-

9. 相关推文

Note:产生如下推文列表的 Stata 命令为:
lianxh CHFS CFPS 数据清洗 离群值 对数 缺失值 补漏 暂元 gen 字符变量 reshape 合并 乱码
安装最新版 lianxh 命令:
ssc install lianxh, replace

相关课程

免费公开课

最新课程-直播课

专题 嘉宾 直播/回看视频
最新专题 文本分析、机器学习、效率专题、生存分析等
研究设计 连玉君 我的特斯拉-实证研究设计-幻灯片-
面板模型 连玉君 动态面板模型-幻灯片-
面板模型 连玉君 直击面板数据模型 [免费公开课,2小时]
  • Note: 部分课程的资料,PPT 等可以前往 连享会-直播课 主页查看,下载。

课程主页

课程主页

关于我们

  • Stata连享会 由中山大学连玉君老师团队创办,定期分享实证分析经验。
  • 连享会-主页知乎专栏,400+ 推文,实证分析不再抓狂。直播间 有很多视频课程,可以随时观看。
  • 公众号关键词搜索/回复 功能已经上线。大家可以在公众号左下角点击键盘图标,输入简要关键词,以便快速呈现历史推文,获取工具软件和数据下载。常见关键词:课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法

连享会小程序:扫一扫,看推文,看视频……

扫码加入连享会微信群,提问交流更方便

✏ 连享会-常见问题解答:
https://gitee.com/lianxh/Course/wikis

New! lianxh 命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh