Stata连享会 主页 || 视频 || 推文 || 知乎 || Bilibili 站
温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验。
New!
lianxh
命令发布了:
随时搜索推文、Stata 资源。安装:
. ssc install lianxh
详情参见帮助文件 (有惊喜):
. help lianxh
连享会新命令:cnssc
,ihelp
,rdbalance
,gitee
,installpkg
⛳ Stata 系列推文:
作者: 涂冰倩 (浙江大学)
邮箱: 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
目录
数据中有些问题不是关于某一特定变量取值不合理,有时需要联合其他变量才能发现其不合理的地方。多变量的清理建立在单变量清理完成的基础上,通过多变量的联合分布、交叉验证检验变量之间的逻辑一致性等。
此时可以用两个分类变量的交互表来查看两个变量的所有取值组合,用分类变量的取值检验和另一个分类变量的取值是否矛盾。这里以是否处于结婚状态和是否结过婚两个变量的交叉验证为例 (此例无数据)。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),这显然不符合实际。
当存在连续变量时,交互表检验 (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
综合运用 sum
、count
、assert
等命令进行检验,这里以计算村庄劳动力人口占村庄人口比例为例。按照实际来说,村庄劳动人口不应超过村总人口数。
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
利用变量和变量之间的交叉验证需要我们的逻辑、生活常识甚至一些专业知识,比如说男性生过孩子,父母的年龄比孩子的年龄还大,这些情况就不太合理。再比如在用土地面积和耕作面积进行交叉验证时,应该考虑到复种情况的存在等等。
这部分主要是通过基本变量和命令的组合生成一些综合变量。
举例一:检查户主人数
在对农户数据进行分析时,常用户主特征变量表征家庭特征,但可能存在录入错误,所以需要对户主信息进行查验,确保每户只有一个户主。
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
观测值组间计算指按行分组,针对某一个变量进行计算。按观测值对某一变量进行计算,通常需要加入 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
命令后使用 sum
和 total
函数都能计算每个组别的加总值,并且计算时将缺失值自动视为 0
。在使用 sum
函数时,采用 egen
命令和 gen
命令操作不同,前者是对组内所有观察值进行加总,而后者是进行累加。
总结:利用命令 egen
除了能生成上面提到的 mean
、sd
等,还能生成中位数 median
、众数 mode
、百分位数 pctile
、个数 count
、偏度 skew
、峰度 kurt
等,进一步了解 egen
支持的函数列表可以在 Stata 中输入 help egen
。其他求和方式可以参考推文「Stata数据处理:各种求和方式一览」,以及关于分组计算还可以参见推文「Stata:runby - 一切皆可分组计算!」。
观测值组内计算指对列操作,针对同一观测值对不同的变量进行计算。按列进行计算一般不加 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 . |
+----------------------------------+
数据清理过程应保存在 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 文件
两个注意:注意每次变动数据前要进行备份,注意更改变量请在新生成的变量基础上进行修改,要让你处理数据的每一步都有迹可循。
两个查看:每执行完一次 Stata 命令,一要看结果窗口是否报错,二要打开数据库看数据是否发生改变、如何改变。实践出真知,如果对某条命令的操作不确定,可以看下执行该条命令之后数据会发生什么样的改变。
数据清理是一个重复性的工作,有时你认为已经清理干净了,结果跑出来可能会教你重新做人,老老实实又跑到数据清理环节对错误进行修正。对数据保持一颗敬畏之心,你怎样对待数据,数据就会怎样反馈给你结果。「CFPS中国家庭追踪调查官方网站」上有大量的技术报告和数据清理说明,可以进行数据清理的学习。
由于笔者所在专业为农业经济学,现阶段接触到的更多是农户微观调查数据,所以数据清理方法不一定适用于所有专业,一些经验也是一家之谈,若有不正确之处,感谢您的批评指出。
Note:产生如下推文列表的 Stata 命令为:
lianxh CHFS CFPS 数据清洗 离群值 对数 缺失值 补漏 暂元 gen 字符变量 reshape 合并 乱码
安装最新版lianxh
命令:
ssc install lianxh, replace
免费公开课
最新课程-直播课
专题 | 嘉宾 | 直播/回看视频 |
---|---|---|
⭐ 最新专题 | 文本分析、机器学习、效率专题、生存分析等 | |
研究设计 | 连玉君 | 我的特斯拉-实证研究设计,-幻灯片- |
面板模型 | 连玉君 | 动态面板模型,-幻灯片- |
面板模型 | 连玉君 | 直击面板数据模型 [免费公开课,2小时] |
⛳ 课程主页
⛳ 课程主页
关于我们
课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法
等
连享会小程序:扫一扫,看推文,看视频……
扫码加入连享会微信群,提问交流更方便
✏ 连享会-常见问题解答:
✨ https://gitee.com/lianxh/Course/wikis
New!
lianxh
命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh