Stata连享会 主页 || 视频 || 推文 || 知乎 || Bilibili 站
温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验。
New!
lianxh
命令发布了:
随时搜索推文、Stata 资源。安装:
. ssc install lianxh
详情参见帮助文件 (有惊喜):
. help lianxh
连享会新命令:cnssc
,ihelp
,rdbalance
,gitee
,installpkg
⛳ Stata 系列推文:
作者: 涂冰倩 (浙江大学)
邮箱: tubingqian@zju.edu.cn
编者按:撰写这篇推文的目的是,即便 Stata 小白,也能直接照着这篇文章对数据库进行清理,为之后的数据分析打下基础。需要说明的是,针对一手数据,实际处理中可能更为复杂,我们应根据实际情况采取合理的方法进行操作,文中提到的一些方法也仅作为参考。现有的大型微观调查数据库如 CFPS、CGSS 等,在发布前已经对数据进行过初步清理,则不需要太复杂的清理工作。
课程主页:https://gitee.com/arlionn/dataclean
b 站视频回放:https://www.bilibili.com/video/BV14y4y1T7nR (在 b 站 搜索「连享会」即可直达)
目录
对于原始数据的录入,可以采用 “双录数据” 的清理策略,即把数据录入两次,通过对比这两个数据集来识别出数据录入的错误。两个数据集之间如果出现不一致,说明可能存在数据录入错误,这时只需要对照原始资料就能找出并纠正错误,具体操作步骤可参见《Stata 环境下的数据管理实务手册》一书 3.2 节。
微观调查数据库,尤其是小型调查,在访问员调查过程和数据录入环节都可能存在问题,比如漏答某道问题、填写答案时出现笔误等,所以在进行数据清理前需要对录入数据进行认真核查。当然,一些较为正式的大型数据库如 CFPS 则不太可能出现此类问题。
假设拿到的是 Excel 形式的原始数据,第一步需要对变量进行编号,编号应该清晰易懂不重复;第二步是尽量将数据数字化 (若数值型变量中存在大量文字型回答,也许还需要在 Excel 中进行手工清理);第三步是将数据导入 Stata 中,Stata 主要有以下 4 个数据读入命令:
*读取数据的四种方法
use xx.dta //读取本地数据
sysuse xx, clear //读取系统数据
webuse xx, clear //直接读取网络数据
import excel xx.xlsx, clear //可读取除 dta 格式以外的其他数据 (如 xlsx 数据)
Stata 窗口视图左下角会显示当前的工作路径,在读取数据之前最好先设置工作路径,这样直接使用 use 命令可以调用当前工作路径文件下的数据,输出的结果和图表也默认保存在该工作路径下。
log 文件可以完整保存 Stata 界面的分析过程,以防止没有及时保存或者想回溯之前的清理工作。
*设置工作路径
cd E:\stata\连享会 //设置工作路径 (可以自行进行修改)
capture log close //关闭以前的 log 文件,加 capture 不会报错
log using datacleaning, replace //新建一个名为 datacleaning 的 log 文件
有时候我们打开数据发现是乱码,这里给出一个解决 Stata14 以前版本数据打开后存在乱码的方法。更多详细介绍,请参考推文「Stata15-Unicode:一次性转码解决中文乱码问题」和「Stata 中文乱码顽疾解决方法」。
*和要修改的数据放在同一个工作路径下,可以转换改工作路径下的所有文件
clear
unicode analyze *
unicode encoding set gb18030
unicode translate *, invalid(mark)
这里笔者将构造一份数据集,包括农户数据、村庄数据等,为下文的数据清洗示例所用。
*生成农户数 (仅为举例所用,不具有实际意义)
/*vid 为村庄编号;fid 为家庭编号;pid 为个人编号;
relation 为和受访者的家庭关 (1 为户主);
year 为调查年份;province 为省份;
a1 性别;a2 出生年份;a3 是否外出务工;
a4 外出务工时长;a5 外出务工收入*/
clear
input vid fid pid relation year province a1 a2 a3 a4 a5
1 1 1 1 2001 1 1 1971 1 11 2000
1 1 2 2 2001 1 2 2004 1 10 3000
1 1 3 3 2001 1 1 1996 0 0 0
1 1 4 4 2001 1 2 1941 1 0 0
1 2 5 4 2001 1 1 1956 0 8 3000
1 2 6 2 2001 1 3 1960 1 8 2000
1 2 7 3 2001 1 2 1981 1 11 4000
2 3 8 5 2001 2 1 1969 1 4 2000
2 3 9 1 2001 2 2 1971 1 11 4000
2 3 10 1 2001 2 2 1993 1 0 0
end
recode a4 a5 (0=.)
save "hh_year2001.dta", replace
clear
input vid2021 fid2021 pid2021 relation2021 year province a1 a2 a3 a4 a5
1 1 1 1 2021 1 1 1971 1 11 10000
1 1 2 1 2021 1 2 2004 1 10 4800
1 1 3 2 2021 1 1 1996 0 0 0
1 1 4 5 2021 1 2 1941 0 0 0
1 2 5 1 2021 1 1 1956 1 8 12000
1 2 6 3 2021 1 2 1960 1 8 30000
1 2 7 5 2021 1 2 1981 1 11 42000
2 3 8 3 2021 2 1 1969 1 4 25000
2 3 9 2 2021 2 2 1971 1 11 20000
2 3 10 4 2021 2 2 1993 0 0 0
end
recode a4 a5 (0=.)
save "hh_year2021.dta", replace
*生成村庄数据
/*c1 表示村庄内劳动力数量,c2 表示村庄人口数量*/
clear
input vid year c1 c2
1 2001 1000 1300
2 2001 2100 2300
3 2001 99999 2500
4 2001 4352 4600
5 2001 3210 3210
1 2021 2023 2300
2 2021 3105 3000
3 2021 3391 3912
4 2021 4310 4319
5 2021 3150 5000
1 2001 1000 1300
end
save "village_data", replace
在开始处理数据前,一定要对原始数据进行备份 (在不同地方多做几个备份)。对于大型微观数据库来说,我们的研究只用到其中部分数据,所以只需要集中精力在可能使用的变量上,这就需要我们把相关变量和观测值提取出来 (如果我们获得了某个比较独特的数据库,想要通过探索数据来获得选题,可能就需要对整个数据集进行清理)。本文将以构造的村庄数据和农户数据演示如何进行数据清理。
保留数据通过保留列 (变量) 和保留行 (观测值) 两种方式实现,命令上可以采用 keep
或者 drop
,还可以通过逻辑关系的叠加实现变量的筛选提取。
*保留或删除变量
use hh_year2001.dta, clear
keep vid-year a1-a3 /*保留 vid-year 以及 a1-a3 之间所有变量,
等价于命令 "drop province a4 a5"*/
keep in 2/10 //保留第 2 个到第 10 个之间的所有观测值,等价于命令 "drop in 1"
在根据变量进行样本的删减前,应先对变量本身取值、分布等进行查看,可以使用 sum
、tabulate
、codebook
、inspect
等命令进行查看,若发现异常值则需要剔除或结合其他变量进行修改。
. *变量的查看
. use hh_year2001, clear
. sum a1-a5
Variable | Obs Mean Std. Dev. Min Max
---------+-----------------------------------------
a1 | 10 1.7 .6749486 1 3
a2 | 10 1974.2 19.56641 1941 2004
a3 | 10 .8 .421637 0 1
a4 | 7 9 2.581989 4 11
a5 | 7 2857.143 899.7354 2000 4000
. codebook a1
-------------------------------------------------------
a1 (unlabeled)
-------------------------------------------------------
type: numeric (float)
range: [1,3] units: 1
unique values: 3 missing .: 0/10
tabulation: Freq. Value
4 1
5 2
1 3
可以看到,变量 a1 为数值型,有 4 个样本值取值为 1,5 个样本值取值为 2,1 个样本取值为 3,不存在缺失值。
. inspect a2
a2: Number of Observations
----- ----------------------------------
Total Integers Nonintegers
| # # Negative - - -
| # # Zero - - -
| # # # Positive 10 10 -
| # # # ------ ----------- -----------
| # # # # # Total 10 10 -
| # # # # # Missing -
+---------------------- ------
1941 2004 10
(9 unique values)
可以看到,受访者出生年份 (a2) 分布在 1941 年至 2004 年之间,均为正整数,不存在缺失值
当然我们也可以使用 drop
命令对无关数据进行删除,方法上和使用 keep
命令一样,不过是其的逆向操作。需要注意的是,keep
和 dorp
命令都是不可逆的,删除数据一定要非常谨慎,在不影响分析的前提下,数据应尽量保持完整并提前做好备份。
数据的合并与转换主要涉及到 merge
、append
、reshape
、gather
、spread
和 stack
几个命令。
合并数据有两种方式,一是纵向添加观察值 (append
),二是横向匹配变量 (merge
)。merge
要求更为严格,一般来说,先 append
再 merge
比较合适。
我们常将不同时期、不同地点的数据进行 append
以增加样本量。在 append
前,可以先在 Excel 中对需要 append
的变量做好记录,如图所示,检查变量的定义、统计口径等是否相同。
使用 append
命令时,需要两组对应数据均为数值型或字符型,否则会报错。当然也可以在后面加 force
选项,将数值型数据和字符型数据进行合并,但这样会造成数据的缺失。注意 append
之后一定要打开数据库看下有何变化。
*纵向添加数据
use hh_year2021.dta, clear //打开上面保存的数据
renvars vid2021 fid2021 pid2021 relation2021 \ vid fid pid relation
//修改变量名,以便于合并
append using hh_year2001.dta //合并之后应打开数据窗口检查变量对应情况
save hh_data.dta, replace
在清理数据库时常常需要将不同维度的数据进行匹配,比如将家庭数据和个人数据进行匹配,将地区层面数据和企业数据进行匹配等,需要使用到 merge
命令。在使用 merge
命令时,有以下四点需要注意:
duplicates list/report
等查看重复值的命令查看后进行修改;merge
命令需要匹配的两个文件所有变量名 (除标识变量以外) 不一样,在进行匹配前,可以使用 cf (using file)
来检查主数据和使用数据中是否存在相同变量。当主数据和使用数据使用相同变量名时,仅主数据中的取值将会被保留。若在后面加上 update
选项,那当主数据和使用数据存在相同变量名时,主数据中的缺失值会被更新为使用数据中的非缺失值。若在后面加上 update replace
选项,则正好相反,即保留使用数据,而使用数据中的缺失值将被更新为主数据中的非缺失值;using
文件中保持一样的格式,即同为数值型或字符型数据,当主文件和使用文件中标识变量格式不一致时,添加 force
选项能强行进行合并,但数据格式将被保存为主数据中的数据格式;merge m : m
在数据处理中较少用到,需要多对多匹配时可以考虑使用 join
命令并配合 by()
选项。. *横向合并数据 merge
. use village_data.dta,clear //村庄数据
. duplicates report vid year //发现有两条信息重复
--------------------------------------
copies | observations surplus
----------+---------------------------
1 | 9 0
2 | 2 1
--------------------------------------
. duplicates tag vid year,gen(tag) //标记重复值
. tab tag
tag | Freq. Percent Cum.
--------+-------------------------------
0 | 9 81.82 81.82
1 | 2 18.18 100.00
--------+-------------------------------
Total | 11 100.00
. list vid year c1 c2 if tag!=0 //发现是录入重复,只需要任意删掉其中一条数据
+--------------------------+
| vid year c1 c2 |
|--------------------------|
1. | 1 2001 1000 1300 |
11. | 1 2001 1000 1300 |
+--------------------------+
. duplicates drop vid year, force //删除重复值
. merge 1:m vid year using hh_data /*将主文件 (村数据) 根据 vid 和 year
> 与匹配文件 (农户数据) 进行对应,
> 一个村庄观察值将对应多个家庭观察值*/
Result # of obs.
-------------------------------
not matched 6
from master 6 (_merge==1)
from using 0 (_merge==2)
matched 20 (_merge==3)
-------------------------------
. keep if _merge==3 /*保留成功匹配上的数据,1 表示仅来自主文件,
> 2 表示仅来自子文件,3 代表被匹配上的观察值*/
. drop _merge
. save vill_hh.dta, replace //保存合并后的文件,为之后的清理工作所用
想对合并数据进行进一步学习,请参考以下推文 (也可以直接在 Stata 命令窗口输入如下命令:):
. lianxh 小白 合并
reshape
命令能实现长宽格式的转换,数据由行 (观测值) 与列 (变量) 构成,长宽格式的转换是指行和列之间的转换。具体转换过程如图所示。
*长数据转换为宽数据
reshape wide income, i(pid) j(year)
/*选项 i() 里放要保留的 id 变量,
j() 里放要换成列的变量*/
*宽数据转换为长数据
reshape long income, i(pid) j(year)
/*i() 放标识变量,
j()里的变量在原数据中不存在,
是要转换为的长数据中要生成的新变量*/
进一步学习 reshape
命令,请参考推文「reshape 命令一文读懂 (上)」和「reshape 命令一文读懂 (下)」。
gather
和 spread
可以说是 reshape
命令的一个简单变形,这两个命令的使用相对来说更为简单。如图所示,gather
将宽数据转换为长数据 (顾名思义,把数据聚集起来)。spread
将长数据转换为宽数据 (把数据散开),gather
之后想要变回原数据,即长变宽,可以直接采用 spread
命令。
此外,stack
命令可以实现样本的堆叠。具体用法,如图所示:
*gather 和 spread
gather income consumption, variable(type) value(money)
spread type money //恢复原状
*stack 命令
stack a b c d, into(x1 x2)
/*将 a b c d 四个变量 (四列)
堆叠成两个变量 x1 x2 (两列)*/
想进一步了解 gather
和 spread
这两个命令,请参考推文「Stata: 你还在用 reshape 转换长宽数据吗?那你就 Out 了!」
在进行正式的数据清理工作之前,应先检查标识变量和变量格式 (标签、注释等),以及是否存在重复数据等。
一般来说,在两个环节需要检查是否存在重复数据。一是在 merge
(横向合并) 之前,需要检查识别变量的唯一性。二是在进行正式的数据清理和变量构造之前。常用的命令包括 isid
、unique
、duplicates
等。
. *标识变量的检查,以下方法都可以用来检查是否存在重复值
. use hh_data, clear
//方法一:运行结果为空表明标识变量唯一且不重复
. isid pid year
//方法二:显示非重复值个数
. unique pid
Number of unique values of pid is 10
Number of records is 20
//方法三:显示标识变量的重复次数
. duplicates report pid year
Duplicates in terms of pid year
--------------------------------------
copies | observations surplus
----------+---------------------------
1 | 20 0
--------------------------------------
//方法四:展示重复值
. duplicates list pid year
Duplicates in terms of pid year
(0 observations are duplicates)
//方法五:标记重复值
. duplicates tag pid year, gen(tag1)
Duplicates in terms of pid year
. tab tag1
tag1 | Freq. Percent Cum.
------------+-----------------------------------
0 | 20 100.00 100.00
------------+-----------------------------------
Total | 20 100.00
在正式进行数据清理前,先用 des
命令查看数据整体情况,若存在字符型变量 (Stata 数据窗口显示为红色),可使用 destring
命令将其转换为数值型变量。
. *检查变量基本情况
. des //对所有变量进行描述统计
Contains data from hh_data.dta
obs: 20
vars: 12 17 Jul 2021 10:37
-----------------------------------------------------------
storage display value
variable name type format label variable label
-----------------------------------------------------------
vid float %9.0g
fid float %9.0g
pid float %9.0g
relation float %9.0g
year float %9.0g
province float %9.0g
a1 float %9.0g
a2 float %9.0g
a3 float %9.0g
a4 float %9.0g
a5 float %9.0g
tag1 byte %12.0g
-----------------------------------------------------------
Sorted by:
Note: Dataset has changed since last saved.
. sum a1-a3 //sum 适用于数值型变量统计描述
Variable | Obs Mean Std. Dev. Min Max
---------+------------------------------------
a1 | 20 1.65 .5871429 1 3
a2 | 20 1974.2 19.04455 1941 2004
a3 | 20 .75 .4442617 0 1
. *格式转换
//将 a1 变量转换成字符型变量
. tostring a1,replace
a1 was float now str1
//将字符型变量转换为数值型变量
. destring a1,replace
a1: all characters numeric; replaced as byte
//将省代码转换为字符型变量
. tostring province, replace
province was float now str1
. replace province="湖南" if province=="1"
variable province was str1 now str6
(14 real changes made)
. replace province="山东" if province=="2"
(6 real changes made)
//查看province变量前3个观察值
. list province in 1/3
+----------+
| province |
|----------|
1. | 湖南 |
2. | 湖南 |
3. | 湖南 |
+----------+
//转换之后会自动生成与原文字对应的值标签
. encode province, gen(provid)
//label list可查看转换之后的数字文字对应表
. label list provid
provid:
1 山东
2 湖南
. /*destring 适用于所有字符变量,若原字符变量的取值是非数字字符,
则生成取值为缺失值;
对于 encode 来说,如果原变量的取值为非数字字符,取值从 1 开始,
若原始变量的取值是数字字符,生成取值也为相应数值*/
想进一步了解数据格式间的转换,请参考推文「七条建议:用 Stata 处理文字变量和字符变量」。
对于单个变量的清理校验,因数据的属性而有所不同。就分类变量而言,应检验该变量的取值是否合理,比如性别取值一般来说只有两类,而对于连续变量,则需检验该变量的取值是否在合理的区间内。在清理过程中,应注意两点:
清理目标:检查 a1 (性别) 变量,查找并修改异常值,生成性别变量 (虚拟变量)
检查分类变量最适用的方法是使用命令 tabulate
,该命令可以查看变量所有取值和对应的频数分布。查看值标签可以发现,男为 1 女为 2,在实际分析过程中,常常把性别定义为虚拟变量,这里用 recode
对取值进行更改,注意进行更改后需要和原始变量进行对比,以防产生错误。
. *a1 变量的清理
. use hh_data.dta,clear
. des a1 //检查变量标签
. label var a1 "受访者的性别" //构建标签
. des a1 //检查是否改对变量标签
storage display value
variable name type format label variable label
--------------------------------------------------------------------
//为 a1 变量定义一个值标签,命名为 a1lab
. label define a1lab 1"男" 2"女"
. label values a1 a1lab //将值标签 a1lab 赋给变量 a1
. label list a1lab //查看其值标签
a1lab:
1 男
2 女
. tab a1, missing /*查看变量的所有取值及频数,附加选项 missing
> 可以查看其缺失值数量 (包括系统缺失值)*/
“受访 |
者的性 |
别” | Freq. Percent Cum.
------------+-----------------------------------
男 | 8 40.00 40.00
女 | 11 55.00 95.00
3 | 1 5.00 100.00
------------+-----------------------------------
Total | 20 100.00
/*发现 a1 存在取值为 1 和 2 之外的观察值,这里用 list 列出取值为 3 的观察值查看,
并在生成新变量的基础上对异常值进行修改或设置为缺失值。*/
. list pid a1 a2 a3 a4 a5 if a1==3 //查看这条异常值
+----------------------------------+
| pid a1 a2 a3 a4 a5 |
|----------------------------------|
1. | 6 3 1960 1 8 2000 |
+----------------------------------+
. assert (a1==1 | a1==2) if !missing(a1) /*第二种检查方法,报告非法值,
> 结果为空表示 assert 后所列条件为真*/
1 contradiction in 20 observations
. assert inlist(a1,1,2) /*和上面那条命令等价,inlist 函数表示 a1 变量取值为 1
> 或者 2 则返回,否则为 0*/
1 contradiction in 20 observations
. count if (a1!=1) & (a1!=2) & !missing (a1) /*第三种检查方法,列示出
> 符合 if 条件后的样本值个数,相当于 assert 命令的逆操作*/
1
recode a1 (2=0 "女") (1=1 "男") ( 3=. ), gen (gender) /*根据原始变量生成新变量,
不要直接在原始变量上进行改动*/
label var gender "受访者的性别" //为新生成的变量附加标签
. sum a1 gender //对更改结果进行查看,确保变量生成正确
Variable | Obs Mean Std. Dev. Min Max
---------+--------------------------------------
a1 | 20 1.65 .5871429 1 3
gender | 19 .4210526 .5072573 0 1
清理目标:检查第二个变量 a2 (出生日期),分别生成连续变量、定序变量、虚拟变量
. *生成连续变量
. des a2
storage display value
variable name type format label variable label
-------------------------------------------------------------
a2 float %9.0g
. label var a2 "受访者的出生年份"
. sum a2 if year==2001, detail //对连续变量进行查看,发现存在异常值 2004
受访者的出生年份
-------------------------------------------------------------
Percentiles Smallest
1% 1941 1941
5% 1941 1956
10% 1948.5 1960 Obs 10
25% 1960 1969 Sum of Wgt. 10
50% 1971 Mean 1974.2
Largest Std. Dev. 19.56641
75% 1993 1981
90% 2000 1993 Variance 382.8444
95% 2004 1996 Skewness -.0298544
99% 2004 2004 Kurtosis 2.112319
. sum a2 if year==2021,detail
受访者的出生年份
-------------------------------------------------------------
Percentiles Smallest
1% 1941 1941
5% 1941 1956
10% 1948.5 1960 Obs 10
25% 1960 1969 Sum of Wgt. 10
50% 1971 Mean 1974.2
Largest Std. Dev. 19.56641
75% 1993 1981
90% 2000 1993 Variance 382.8444
95% 2004 1996 Skewness -.0298544
99% 2004 2004 Kurtosis 2.112319
. gen age=year-a2 if a2!=2004 //根据原始变量生成新的年龄变量(调查年份-出生年份)
. sum a2 age //查看新生成的变量是否正确
Variable | Obs Mean Std. Dev. Min Max
----------+-----------------------------------------
a2 | 20 1974.2 19.04455 1941 2004
age | 18 40.11111 19.87872 5 80
. /*比如我们想研究新农保对老年人的影响,需要保留 60 岁以上的人群进行分析*/
. preserve
. keep if age!=. & age>=60
. restore
. /*保留年龄变量取值大于等于 60 的所有观测值,等价于命令"drop if age<60"
> 注意:缺失值会被视为最大值*/
. *生成定序变量
. sum age
Variable | Obs Mean Std. Dev. Min Max
----------+--------------------------------------
age | 18 40.11111 19.87872 5 80
. recode age (5/17=1 "儿童组") (18/59=2 "成年组") (60/80=3 "老年组"), ///
> gen (agegroup) lab(labagegroup)
. /*根据 age 变量生成 agegroup 变量,并将值标签命名为 labagegroup*/
. label list labagegroup //查看值标签
labagegroup:
1 儿童组
2 成年组
3 老年组
. sum age agegroup //检查变量是否生成正确
Variable | Obs Mean Std. Dev. Min Max
----------+-------------------------------------
age | 18 40.11111 19.87872 5 80
agegroup | 18 2.111111 .5829831 1 3
. /*如果要把不同年龄段的人平分成不同的组,
> 可以采用 cohort 命令或者 autocode 命令*/
. sort age
. egen cohort1=cut(age) if inrange(age,18,60), ///
> group(4) //将 18-60 岁的人平分成 4 个组,前面需加上 sort 命令
. tab cohort1
cohort1 | Freq. Percent Cum.
------------+-----------------------------------
0 | 3 23.08 23.08
1 | 3 23.08 46.15
2 | 3 23.08 69.23
3 | 4 30.77 100.00
------------+-----------------------------------
Total | 13 100.00
. replace cohort1=cohort1+1
. tabstat age, by(cohort1) stat(min max mean n) //对不同组受访者的年龄进行描述统计
Summary for variables: age
by categories of: cohort1
cohort1 | min max mean N
---------+-------------------------------
1 | 20 28 24.33333 3
2 | 30 32 30.66667 3
3 | 40 45 42 3
4 | 50 60 53 4
---------+-------------------------------
Total | 20 60 38.69231 13
-----------------------------------------
. save "hh_data_new", replace //清洗完的数据应该被保存在一份新的 data 文件中
其中,group
函数的使用,可以参考推文「Stata: gen 命令中的 group() 函数的潜在风险」。
缺失值在 Stata 中被当做最大值来保存,在计算变量的时候要格外注意这一点。比如在渐进 DID 中,政策发生时间不同,我们根据改革时间 (reformyear) 生成处理变量。缺失改革年份信息的样本,生成的处理变量应该也为缺失值,在定义时若忽略缺失值,会造成变量的错误定义 (当然,有些地方是因为未改革所以才缺失改革年份信息)。
gen treat=(year>=reformyear) //错误的定义
gen treat=(year>=reformyear) if !missing(reformyear) //正确的定义
在对缺失值进行处理前,应该对缺失值的缺失比例做好记录,以便和进行补漏后的数据进行对比。对于缺失值记的基本形态可以通过 misstable
命令进行查看。此外,missings
命令可对样本缺失值进行查看、删除等。
. *对于缺失值的查看
. use "hh_data_new", clear
. misstable pattern //列示缺失值的模式
. misstable sum a4 //查看 a4 变量缺失值的基本统计
Obs<.
+--------------------
| | Unique
Variable | Obs=. Obs>. Obs<. | values Min Max
----------+-----------------------+--------------------
a4 | 6 14 | 4 4 11
-------------------------------------------------------
. missings report a4 //对 a4 变量中缺失值的数量进行查看
Checking missings in a4:
6 observations with missing values
----------
| #
----+-----
a4 | 6
----------
. missings list //列出所有变量中含有缺失值的变量
. missings dropobs a1 a2 a3, force //删除a1 a2 a3 都存在缺失值的样本
. *missings dropvars b1 b2 b3 //删除所有取值都为缺失值的变量
可以看到 a4 变量存在 6 个缺失值。如果我们只想保留不存在缺失值的样本,那么可以使用 missings dropobs
命令,它表示按样本值 (行) 对缺失值进行删除,missings dropvar
命令表示按变量 (列) 对缺失值进行删除。
在决定是否删除存在缺失值的样本时一定要谨慎,不当的删除可能导致估计结果的有偏。一般来说,如果缺失值量不大,可以先不做处理,看下回归结果怎样,再回过头来决定是否对缺失值进行补漏。这里提供弥补缺失值的几个思路,仅供参考。
第一个思路:通过统计值进行推断
这里以计算农业生产收入为例,首先根据销售量和销售价格计算农业销售收入,再根据总产量和销售价格计算农业总收入。在计算农业总收入的时候,发现有总产量值数据对应的销售价格为缺失值,推测这部分产量可能用于农户自家消费,若不计入收入,可能导致收入的低估。这部分缺失的价格,一是可以在公开的农产品价格网上获取,二是可以根据地区平均价格等统计值进行弥补。
这里将展示第二种方法的使用,为了消除极端值的影响,采用去掉极端值的均值数据对原有价格数据进行弥补。这里假设当观察值超过均值 3 倍标准差为异常值,也可以结合实际情况采取其他方法对异常值进行识别,比如小于观察值的 0.25 倍,大于观察值的 10 倍视为异常值,等等。中位数和众数较少受极端值的影响,所以也可以直接采用价格中位数或众数对原有价格数据进行弥补。
/*构造数据:两个县城(cid) 三个村(vid) 2001 年 - 2020 年的销售量(sale)、
销售单价(price)、总产量(output)与种植面积(plantarea),假设一种作物*/
clear
set obs 60
gen vid=1
replace vid=2 in 21/40
replace vid=3 in 41/60
gen cid=1
replace cid=2 in 41/60
egen year=seq(),from(2001) to (2020)
gen b1=int(100*runiform())
gen b2=int(10*runiform())
recode b1(0=.)
recode b2(0=.)
gen b3=b1+20
gen b4=runiform()
replace b2=-3 in 5
replace b2=. in 26
replace b2=. in 45
replace b2=100 in 60
replace b1=. if b2==.
list vid cid year b1 b2 b3 b4 in 37/41 //查看数据
save sale_data, replace
*单变量清理
sum b1 b2 b3
gen sale=b1
recode b2(-3=.),gen (price) //修改异常值,生成新变量
gen output=b3
sum b1 sale b2 price b3 output //对比新生成的变量和原变量
gen income_sale=sale*price //生成销售收入 (销售量*价格)
*极端值处理+弥补缺漏值
gen newprice=price //在生成新变量的基础上进行补漏
foreach i in vid cid{
bysort `i':egen mean_`i'_price=mean(price) //按村庄/县城生成价格均值数据
bysort `i':egen sd_`i'_price=sd(price) //按村庄/县城生成价格标准差数据
replace newprice =. if (abs(newprice - mean_`i'_price)>3* sd_`i'_price)
replace newprice =mean_`i'_price if newprice==. & mean_`i'_price!=.
drop mean_`i'_price sd_`i'_price
}
label var newprice "销售价格(处理了极端值和缺失值)"
sum newprice price /*将处理了异常值和进行了缺失值补漏的价格数据
与原价格数据进行对比*/
gen income_total=newprice*output //生成总收入(总产量*价格)
/*这里使用到了循环和暂元。foreach 允许将其后所跟变量列表作为循环的来源。
对暂元可以理解为是用一个字符串取代了另一个字符串,
引用局部暂元时需要加上 `' */
关于暂元的详细介绍,请参考以下推文:
第二个思路:通过插值进行补漏
对于长时期数据,可以根据时间变量进行插值来对缺失值进行弥补,如根据时间趋势来推测种植面积。
. *插值补漏
. use sale_data, clear
. sum b4
. gen plantarea=b4
. list vid year plantarea in 1/5 //查看前五个样本数据
. drop in 23/25 //构造一份不完整的数据
. xtset vid year
. tsfill, full //根据标识变量对数据进行填充,补充成完整面板数据
. list vid year plantarea if missing(plantarea) //查看缺失值
+-----------------------+
| vid year planta~a |
|-----------------------|
23. | 2 2003 . |
24. | 2 2004 . |
25. | 2 2005 . |
+-----------------------+
. gen plantarea_new=plantarea //在新变量基础上进行补漏
. bysort vid:ipolate plantarea year, gen(temp_plantarea) epolate
. /*使用 ipolate 时,第一年和最后一年的数据无法进行插补,
> 加epolate命令表示根据数据进行外推*/
. replace plantarea_new=temp_plantarea ///
> if plantarea_new==. & !missing(temp_plantarea)
. drop temp_plantarea
. sum plantarea_new plantarea //补漏后进行查验对比
. label var plantarea_new "种植面积(补漏后)"
第三个思路:采用周围非缺失值来填补缺失值
*采用上一年非缺失值进行弥补
gen plantarea2=plantarea
by vid :replace plantarea2=plantarea[_n-1] ///
if missing(plantarea2) //其中两个缺失值的前一年取值也为缺失值
sum plantarea2 plantarea //补漏后进行查验*采用上一年非缺失值进行弥补
save "sale_data_reg", replace //完成数据清理,进行保存供分析所用
除此之外,还可以通过参考相似样本进行取值、多重补漏分析以及一些非参数方法进行补漏。有几类问题,即使存在缺失值也不应该对其进行弥补:一是具有主观性的问题,二是样本值的缺失是随机的,此时对数据进行缺失值的弥补反而会造成偏误。
在数据的处理过程中,要注意缺失值和 0 的区别,参见推文「缺失值能否用零代替?-L117」。关于缺失值处理,更详细的介绍参见以下推文:
如何发现极端值?一是通过画散点图或箱线图来识别,二是通过描述统计进行判断 (如上文所举的例子,简单地以 “当观察值超过均值 3 倍标准差为异常值” 作为判断标准)。对于极端值的处理上文有所提及,可以考虑遵循以下步骤进行处理:
*发现极端值
use hh_data_new.dta,clear
sum a5,detail
gen outincome=a5
histogram outincome //第一种方法,画直方图查看变量是否存在左偏 (有偏) 的情况
graph box outincome /*第二种方法,画箱线图,将展示数据的最大值最小值、
上下四分位数、中位数*/
*对极端值的处理
gen logincome=log(outincome) //取对数
hist logincome
winsor2 outincome, cuts(1 99)
/*小于 1% 分位和大于 99% 分位的观察值分别被 1% 分位和 99%
分位上的观察值替代,缩尾后的变量将以 "_w" 结尾命名
winsor2 outincome, suffix(_t) cuts(1 99) trim
小于 1% 分位和大于 99% 分位的观察值将被替换为缺失值,
截尾后的变量以"_t”结尾命名*/
save hh_data_new, replace //保存数据
对于极端值的处理这里不再赘述,感兴趣的可以参考推文「Stata:离群值!离群值?离群值!」和「winsor2:离群值和异常值的缩尾处理」。对变量为何取对数不理解?请参见推文「取对数!取对数?」。
免费公开课
最新课程-直播课
专题 | 嘉宾 | 直播/回看视频 |
---|---|---|
⭐ 最新专题 | 文本分析、机器学习、效率专题、生存分析等 | |
研究设计 | 连玉君 | 我的特斯拉-实证研究设计,-幻灯片- |
面板模型 | 连玉君 | 动态面板模型,-幻灯片- |
面板模型 | 连玉君 | 直击面板数据模型 [免费公开课,2小时] |
⛳ 课程主页
⛳ 课程主页
关于我们
课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法
等
连享会小程序:扫一扫,看推文,看视频……
扫码加入连享会微信群,提问交流更方便
✏ 连享会-常见问题解答:
✨ https://gitee.com/lianxh/Course/wikis
New!
lianxh
命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh