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

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

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

编者按:撰写这篇推文的目的是,即便 Stata 小白,也能直接照着这篇文章对数据库进行清理,为之后的数据分析打下基础。需要说明的是,针对一手数据,实际处理中可能更为复杂,我们应根据实际情况采取合理的方法进行操作,文中提到的一些方法也仅作为参考。现有的大型微观调查数据库如 CFPS、CGSS 等,在发布前已经对数据进行过初步清理,则不需要太复杂的清理工作。

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

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


目录


1. 初步准备

1.1 设置工作环境和读取数据

对于原始数据的录入,可以采用 “双录数据” 的清理策略,即把数据录入两次,通过对比这两个数据集来识别出数据录入的错误。两个数据集之间如果出现不一致,说明可能存在数据录入错误,这时只需要对照原始资料就能找出并纠正错误,具体操作步骤可参见《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

1.2 整理和提取变量

在开始处理数据前,一定要对原始数据进行备份 (在不同地方多做几个备份)。对于大型微观数据库来说,我们的研究只用到其中部分数据,所以只需要集中精力在可能使用的变量上,这就需要我们把相关变量和观测值提取出来 (如果我们获得了某个比较独特的数据库,想要通过探索数据来获得选题,可能就需要对整个数据集进行清理)。本文将以构造的村庄数据和农户数据演示如何进行数据清理。

保留数据通过保留列 (变量) 和保留行 (观测值) 两种方式实现,命令上可以采用 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"

在根据变量进行样本的删减前,应先对变量本身取值、分布等进行查看,可以使用 sumtabulatecodebookinspect 等命令进行查看,若发现异常值则需要剔除或结合其他变量进行修改。

. *变量的查看
. 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 命令一样,不过是其的逆向操作。需要注意的是,keepdorp 命令都是不可逆的,删除数据一定要非常谨慎,在不影响分析的前提下,数据应尽量保持完整并提前做好备份。

2. 合并与转换

数据的合并与转换主要涉及到 mergeappendreshapegatherspreadstack 几个命令。

2.1 数据合并

合并数据有两种方式,一是纵向添加观察值 (append),二是横向匹配变量 (merge)。merge 要求更为严格,一般来说,先 appendmerge 比较合适。

2.1.1 纵向添加 append

我们常将不同时期、不同地点的数据进行 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 

2.1.2 横向合并 merge

在清理数据库时常常需要将不同维度的数据进行匹配,比如将家庭数据和个人数据进行匹配,将地区层面数据和企业数据进行匹配等,需要使用到 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 小白 合并

2.2 数据转换

reshape 命令能实现长宽格式的转换,数据由行 (观测值) 与列 (变量) 构成,长宽格式的转换是指行和列之间的转换。具体转换过程如图所示。

*长数据转换为宽数据
reshape wide income, i(pid) j(year) 
/*选项 i() 里放要保留的 id 变量,
       j() 里放要换成列的变量*/

*宽数据转换为长数据
reshape long income, i(pid) j(year)
/*i() 放标识变量,
  j()里的变量在原数据中不存在,
 是要转换为的长数据中要生成的新变量*/

进一步学习 reshape 命令,请参考推文「reshape 命令一文读懂 (上)」「reshape 命令一文读懂 (下)」

2.3 数据堆叠

gatherspread 可以说是 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 (两列)*/

想进一步了解 gatherspread 这两个命令,请参考推文「Stata: 你还在用 reshape 转换长宽数据吗?那你就 Out 了!」

3. 检查数据

在进行正式的数据清理工作之前,应先检查标识变量和变量格式 (标签、注释等),以及是否存在重复数据等。

3.1 检查重复数据

一般来说,在两个环节需要检查是否存在重复数据。一是在 merge (横向合并) 之前,需要检查识别变量的唯一性。二是在进行正式的数据清理和变量构造之前。常用的命令包括 isiduniqueduplicates 等。

. *标识变量的检查,以下方法都可以用来检查是否存在重复值
. 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

3.2 检查变量基本情况

在正式进行数据清理前,先用 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 处理文字变量和字符变量」

4. 数据清理

4.1 单变量清理

对于单个变量的清理校验,因数据的属性而有所不同。就分类变量而言,应检验该变量的取值是否合理,比如性别取值一般来说只有两类,而对于连续变量,则需检验该变量的取值是否在合理的区间内。在清理过程中,应注意两点:

  • 一是最好不要直接对原变量进行修改,在生成新变量的基础上进行修改,这样能将修改过后的变量与原变量进行对比,以检验自己的修改是否正确;
  • 二是在生成新变量之前需对原变量取值进行查看,有时候除了存在系统缺失值 “.” 以外,还可能存在问卷收集者自己定义的、具有特殊含义的取值等,比如 “99999”。

4.1.1 分类变量的清理

清理目标:检查 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

4.1.2 连续变量的清理和生成

清理目标:检查第二个变量 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() 函数的潜在风险」

4.1.3 缺失值的查验和处理

缺失值在 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」。关于缺失值处理,更详细的介绍参见以下推文:

4.1.4 极端值的查验和处理

如何发现极端值?一是通过画散点图或箱线图来识别,二是通过描述统计进行判断 (如上文所举的例子,简单地以 “当观察值超过均值 3 倍标准差为异常值” 作为判断标准)。对于极端值的处理上文有所提及,可以考虑遵循以下步骤进行处理:

  • 一是查验极端值是否是因为数据输入错误造成的并进行相应更正;
  • 二是考虑极端值的生成是否与研究主题有关,必要时可以删除存在极端值的样本 (在进行数据的删除时一定要慎重!);
  • 三是可以通过取对数、缩尾 (winsor2) 等方法进行处理;
  • 四是在对极端值进行处理前,可以先试着跑一下回归结果,对比下处理与未处理极端值的回归结果。
*发现极端值
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小时]
  • Note: 部分课程的资料,PPT 等可以前往 连享会-直播课 主页查看,下载。

课程主页

课程主页

关于我们

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

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

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

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

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