七条建议:用Stata处理文字变量和字符变量

发布时间:2020-10-30 阅读 176

Stata 连享会   主页 || 视频 || 推文

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

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

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

作者:陈勇吏(上海交通大学)

 


致谢: 本文以 Cox and Schechter (2018, Stata Journal, -PDF-) 为基础进行了翻译,部分内容做了调整和增补。



目录


Stata 中经常需要对 字符变量 做处理,将 字符变量 转换为 数值变量。但 字符变量 的形式多样,包括 标识符变量/分类变量日期变量纯数字变量 等,在转换过程中需要使用不同的转换方法。尤其当字符串内容为数字时(如 日期变量纯数字变量),需要格外小心。

本篇推文将提供七个建议,涵盖多种字符串形式的处理方法,帮助大家更好的处理字符型变量。

1. 保留备份

1.1 保留原始数据

  • 保留原始 dta 数据 使用 save 命令将修改后的数据另存为新的 .dta 文件,不要直接修改替换原始数据。
  • 保留导入的原始数据 不要丢弃用于导入(import)数据的任何原始数据文件,例如文本或电子表格文件。有时,可能需要返回到源头并重新开始数据导入和处理工作。

1.2 保留原始变量

因为字符串转换很容易出错,所以在字符串操作过程中,通常生成一个新的变量来保存转换结果,而不是替换原有的字符串变量。有些命令提供 replace 选项,可以将原始变量替换为转换后的结果,但这种情况下,如果转换出错将找不到原始的字符串变量。更好的方式是使用 generate() 选项生成新的变量,如果出现错误,也可以回到原始变量,重新修改。

译者注: 需要补充说明的是,如果遵循了第一条建议,即我们的 do 文档 的首行始终是以 import xxx.txtimport xxx.xlsx 的方式导入原始 TXT 或 Excel 文档,那么可以在 dofile 中采用 replace 的方式替换到原变量,以免产生过多的新变量。后续处理过程中若需返回,则可以重新导入原始数据。

2. “日期型”字符串变量

使用 listeditbrowse 命令查看字符串变量,如果变量中包含形如 20jan2010 09:15:2220/01/20102010w32010q1 的数据,这种变量就是**“日期型”**字符串变量,通常包含以下几种类型:

日期类型 该类型的显示格式
datetime 20jan2010 09:15:22.120
(daily) date 20jan2010, 20/01/2010, ...
weekly date 2010w3
monthly date 2010m1
quarterly date 2010q1
half-yearly date 2010h1
yearly date 2010

2.1 日历年(yearly date)

如果日期型变量是以字符串保存的形如 19842018 这样的年度日期,可以直接使用 destring 命令转换为数值格式,具体见 help destring

2.2 包含日期的字符串变量

如果字符串变量包含日期或时间,需要使用日期-时间函数转换为数字格式的日期或时间变量。具体转换过程可以分为如下两个步骤: Step 1: 使用日期-时间函数 help datetime,将字符串日期转换为数字日期 Step 2: 使用 format 命令,将数字显示为想要的日期格式

2.2.1 数字日期

数字日期,可以理解为某一日期距离原点时间间隔。Stata 将 1960 年的第一个日期设为原点 0(如:日度数据的原点为 1960年1月1日;月度数据的原点为 1960 年第 1 月;周数据的原点为 1960 年第 1 周)。不同的日期类型,计算时间间隔的单位不同(如:日度数据为 间隔天数,月度数据为 间隔月数,周数据为 间隔周数)。

以日度数据为例,1776年7月4日 的数字日期是 -67019,表示 1960年1月1日 之前的第67019 天。从这里可以看出一个问题:对于字符串日期来说,含义是很直观的;但对于数字日期,含义通常比较模糊(如 67019),很难直观判断这个数字代表的是什么日期。可以通过 format 命令,将数字日期显示为日期格式,详情参见 help format

format 的语法为 format varlist %fmt,其中 varlist 为数字日期变量,%fmt 为显示的日期格式,需要与日期类型相对应(如:日度数据的日期格式为 %td)。常见的日期类型与对应的日期显示格式如下表所示:

日期格式 %fmt 日期类型 举例
%tc date/time format varlist %tc
%tC date/time format varlist %tC
%td date format varlist %td
%tw week format varlist %tw
%tm month format varlist %tm
%tq quarter format varlist %tq
%th half-year format varlist %th
%ty year format varlist %ty
%tg generic format varlist %tg

2.2.2 日期-时间函数

日期-时间函数 的功能是将文本日期转换为数字日期,可以通过 help datetime 查找。具体使用哪一个函数,根据字符串变量的日期类型确定。比如对于日度数据 20jan2010,可以使用 date() 或者 daily() 函数。(这两个函数的功能是一样的,但 daily() 的函数名更直观,所以推荐使用 daily() 函数。)

举个简单的例子:

. clear
. set obs 1
. gen strDate = "July 4, 1776" //生成“日期型”字符串变量

*-(一)根据日期函数 daily(),
*  将 字符串日期(strDate) 转换为 数字日期(numDate)
. gen numDate = daily(strDate, "MDY")
. list
     +------------------------+
     |      strDate   numDate |
     |------------------------|
  1. | July 4, 1776    -67019 |
     +------------------------+

*-(二)设定数字日期(numDate)的显示格式
. format numDate %td
. list
     +--------------------------+
     |      strDate     numDate |
     |--------------------------|
  1. | July 4, 1776   04jul1776 |
     +--------------------------+

上述代码中,daily() 函数将字符串日期 "July 4, 1776" 转换成数字日期 -67019,这个数值是 "1776年7月4日" 距离原点 "1960年1月1日" 的天数。format 命令将数字日期 -67019 显示为日度数据格式 04jul1776

2.3 例子:日度数据形式的季度数据

(1)对于字符串形式的季度数据 "2018q1"、"2018q2",可以通过 quarterly() 函数转换为数字形式的季度数据。

(一)生成字符串形式的季度数据
. clear
. set obs 2
. input str20 date
        date
  1.  "2018q1"
  2.  "2018q2"

(二)将字符串形式的季度数据(date)转换为 数字形式的季度数据(numdate)
. gen numdate = quarterly(date, "YQ")
. format numdate %tq
. list
     +------------------+
     |   date   numdate |
     |------------------|
  1. | 2018q1    2018q1 |
  2. | 2018q2    2018q2 |
     +------------------+

(2)如果用每一季度的最后一天(日度数据形式)来表示季度数据,如 "2018-3-31"、"2018-6-30",如何将字符串转换为数字形式的季度数据格式(2018q1、2018q2)? help datetime 可以找到不同类型的数字日期之间的转换函数,其中 qofd() 函数可以将数字形式的日度数据转换为数字形式的季度数据,结合 daily() 函数,可以很好的解决上述问题。

解决方案: qofd(daily()),具体实现过程如下:

* (一)生成日度数据形式的季度数据
. clear
. set obs 2
. input str20 date
         date
  1. "2018-3-31"
  2. "2018-6-30"

* (二)(1)将字符串形式的日度数据(date)转换为 数字形式的日度数据(daily)
*      (2)将数字形式的日度数据(daily)转换为数字形式的季度数据(quarter)
. gen daily = daily(date, "YMD")
. gen quarter = qofd(daily)
. list
     +-----------------------------+
     |      date   daily   quarter |
     |-----------------------------|
  1. | 2018-3-31   21274       232 |
  2. | 2018-6-30   21365       233 |
     +-----------------------------+

* (三)设定日期的显示格式
. format daily %td
. format quarter %tq
. list
     +---------------------------------+
     |      date       daily   quarter |
     |---------------------------------|
  1. | 2018-3-31   31mar2018    2018q1 |
  2. | 2018-6-30   30jun2018    2018q2 |
     +---------------------------------+

3. 标识符变量、分类变量

对于标识符变量(如:公司、国家、地区名称)或者分类变量,可以通过 (1)encode命令;(2)egen 命令的 group() 函数 两种方式,将字符变量映射到数字变量。

在开始之前,我们生成一个分类变量,用以详细说明两种命令的使用方式:

// 生成一个 yesno 变量,保存为 yesno.dta 数据。
// yesno 是一个分类变量,包括三个取值(yes,no,Dont know)
clear
set obs 3
input str20 yesno
	"Yes"
	"No"
	"Dont know"
save yesno.dta, replace

3.1 encode 命令

默认情况下,Stata 中的 encode 命令会将字符变量按字母排序。字符变量的不同取值,按照排列顺序分别映射到数值 1,2,...,并且为生成的数字变量添加值标签,用以说明字符串-数字的对应关系。

encode 命令的语法为:

encode varname [if] [in], generate(newvar) [label(name)]

其中 varname 为需要转换为数字的分类/标识符变量;选项 generate(newvar) 表示生成新的变量(newvar)存储转换后的结果;选项 label(name) 表示按照变量值标签(name)中的“字符串-数字”的对应关系来转换(默认按照字母排序转换)。

* (一)查看数据:变量 yesno 中包括三个取值("Yes", "No", "Dont know")
. use yesno.dta, clear
. list
     +-----------+
     |     yesno |
     |-----------|
  1. |       Yes |
  2. |        No |
  3. | Dont know |
     +-----------+

* (二)生成两个变量值标签(yesno1, yesno2)
. label define yesno1 1 "Yes" 2 "No" 3 "Don't know"
. label define yesno2 1 "Yes" 2 "No" 3 "Dont know"
. label list
yesno2:
           1 Yes
           2 No
           3 Dont know
yesno1:
           1 Yes
           2 No
           3 Don't know

* (三)将字符变量 yesno 转换为数值变量
. encode yesno, gen(newvar)  //按默认排序(字母顺序)转换
. encode yesno, gen(newvar1) label(yesno1)  //按值标签 yesno1的“字符串-数字”对应关系转换
. encode yesno, gen(newvar2) label(yesno2)  //按值标签 yesno2的“字符串-数字”对应关系转换
. label list
yesno1:
           1 Yes
           2 No
           3 Don't know
           4 Dont know
newvar:
           1 Dont know
           2 No
           3 Yes
yesno2:
           1 Yes
           2 No
           3 Dont know

. list, nolab  //生成的三个转换变量的数字取值情况
     +----------------------------------------+
     |     yesno   newvar   newvar1   newvar2 |
     |----------------------------------------|
  1. |       Yes        3         1         1 |
  2. |        No        2         2         2 |
  3. | Dont know        1         4         3 |
     +----------------------------------------+

. list  //显示三个转换变量的值标签
     +-----------------------------------------------+
     |     yesno      newvar     newvar1     newvar2 |
     |-----------------------------------------------|
  1. |       Yes         Yes         Yes         Yes |
  2. |        No          No          No          No |
  3. | Dont know   Dont know   Dont know   Dont know |
     +-----------------------------------------------+

可以看出,当使用值标签 yesno1 时,由于值标签中没有 dont know 的数字对应关系,Stata 会自动在值标签后面追加一个数字,作为 dont know 字符串的映射。

3.2 egen 的 group() 函数

egen 命令的 group() 函数可以起到同样的效果: egen newvar = group(varname), label,其中 varname 为标识符/分类变量,newvar 为生成的数值变量,选项 label 表示按字母顺序添加值标签(默认情况下不添加值标签)。

. use yesno.dta, clear
. egen b = group(yesno)
. egen b1 = group(yesno), label

. label list
b1:
           1 Dont know
           2 No
           3 Yes

. list, nolab
     +--------------------+
     |     yesno   b   b1 |
     |--------------------|
  1. |       Yes   3    3 |
  2. |        No   2    2 |
  3. | Dont know   1    1 |
     +--------------------+
. list
     +---------------------------+
     |     yesno   b          b1 |
     |---------------------------|
  1. |       Yes   3         Yes |
  2. |        No   2          No |
  3. | Dont know   1   Dont know |
     +---------------------------+

3.3 分类变量转换中的常见问题

如果字符串中存在多余的空格,比如 "I Love Python"" I Love Python""I Love Python "" I Love Python",Stata 会认为这是四个不同的字符串,转换为数字变量的时候,也会对应到四个不同的数字。在处理这类问题的时候,通常使用 trim() itrim() 函数,去掉字符串两端的空格,或者规范字符串内部的空格,将上述四个字符串处理成相同的字符串 "I Love Python"。具体详见 help string functions

4. 某些字符串标识变量可以不作转换

有些字符串变量可以保留为字符串形式,没必要转换成数字格式。比如:

  • 当出现异常时,根据标识变量检查个别案例;
  • 使用标识变量合并其他数据集(参见 [D] merge);
  • 唯一标识符提供了以特定顺序获取数据集的标准、可重复的方式。

5. 不要导入元数据

数据中可能包含一些与数据的定义、解释、注释相关的信息(元数据),这类信息可以作为变量名称、变量标签、值标签或注释等反映到数据中。有时元数据也会解释缺失值或异常值的产生原因及编码方式,这些文字说明很有用,但这些文字并不需要全部包含在数据集中。

Stata 并不能识别哪些文字是说明性的文字,不需要读入;哪些文字是数据的一部分,需要以数据形式读入;哪些文字可以作为变量名或者变量标签。如果直接使用 import 命令,Stata 会将所有文本全部导入数据中,生成很多的字符变量来存储元数据中的文字信息。但很多时候并不需要这些元数据,或者有些元数据仅需要作为变量名称、变量标签,此时需要从导入的数据中删除或者处理元数据信息。

针对元数据信息,通常有两种处理方式: (1)如果已经导入了元数据,可以根据具体情况,删除不需要的元数据所在的行;使用 destring 命令将元数据导致的字符变量转换为数字变量。 (2)重新导入数据(import excelimport delimited),使用 import 命令提供的选项,可以跳过数据文件中元数据所在的行和列,也可以将首行内容作为变量标签或者变量名。

6. 最常用的 destring 命令

字符串变量主要可以分为 “日期型”字符变量、“标识变量/分类变量”、由于元数据的存在导致的字符变量。对于“日期型”字符变量,使用日期-时间函数 help datetime 转换为数值变量;对于“标识变量/分类变量”,使用 encode varnameegen newvar = group(varname) 两种方式编码为数值变量;如果是元数据造成的字符变量,通过 import 选项 或者 drop 元数据所在行 来删除元数据。

若逐个排除了上述过程,剩下的字符变量通常可以使用 destring 来转换为数值变量。Stata 会以数字格式读入纯数字变量,但如果变量中包含非数字内容(如将 110 误写为 11o),或者受到了元数据污染(如:数字变量的最后几行写了一些注释信息),则这些纯数字内容会以字符串的形式导入数据,形成字符变量。当删除元数据以后,纯数字形成的字符变量可以使用 destring 命令转换为数值变量。

destring 命令有点类似于 real() 函数,可以将字符串形式的数字转换成数值形式的数字,但 destring 更灵活的地方在于提供了很多选项,可以处理字符变量中的非数字形式的字符,比较常用的几个选项包括 forceignore()percentdpcomma

force 选项将纯数字字符转换成数字,同时强制将无法识别的字符处理成缺失值,是不得已情况下才会使用的选项;ignore("chars") 选项去除字符中的 "chars" 字符,将剩余的内容转换为数字。percent 选项将数字字符串转换成小数形式;dpcomma 选项将字符中的逗号作为小数点转换为十进制格式(如 "12,3" 转换为 12.3)。

我们生成一个字符串变量,用来详细介绍各个选项的用途。

// 生成字符串变量
clear
set obs 7
input str20 whatever
	"209"
	"1560"
	"52o"
	"ll9"
	"NA"
	"12,3"
	"79%"
save temp.dta, replace

各选项的使用方法和结果如下:

. // force 选项
. use temp.dta, clear
. destring whatever, generate(wanted) force
. list, sep(0)
     +-------------------+
     | whatever   wanted |
     |-------------------|
  1. |      209      209 |
  2. |     1560     1560 |
  3. |      52o        . |
  4. |      ll9        . |
  5. |       NA        . |
  6. |     12,3        . |
  7. |      79%        . |
     +-------------------+

. //ignore 选项
. use temp.dta, clear
. destring whatever, generate(wanted) ignore("NA%ol,") 
. list, sep(0)
     +-------------------+
     | whatever   wanted |
     |-------------------|
  1. |      209      209 |
  2. |     1560     1560 |
  3. |      52o       52 |
  4. |      ll9        9 |
  5. |       NA        . |
  6. |     12,3      123 |
  7. |      79%       79 |
     +-------------------+

. //dpcomma 选项
. use temp.dta, clear
. destring whatever, generate(wanted) ignore("NAlo%") dpcomma
. list, sep(0)
     +-------------------+
     | whatever   wanted |
     |-------------------|
  1. |      209      209 |
  2. |     1560     1560 |
  3. |      52o       52 |
  4. |      ll9        9 |
  5. |       NA        . |
  6. |     12,3     12.3 |
  7. |      79%       79 |
     +-------------------+

. //percent 选项
. use temp.dta, clear
. destring whatever, generate(wanted) ignore("NAlo") percent dpcomma
. list, sep(0)
     +-------------------+
     | whatever   wanted |
     |-------------------|
  1. |      209     2.09 |
  2. |     1560     15.6 |
  3. |      52o      .52 |
  4. |      ll9      .09 |
  5. |       NA        . |
  6. |     12,3     .123 |
  7. |      79%      .79 |
     +-------------------+

7. 找出无法转换的内容

destring 可以将纯数字的字符串转换成数字格式,如果变量中包含某些特殊字符(如输入错误: 52o;缺失值:NA;百分号:%),我们需要使用 destring 的选项来处理这些特殊字符。为了更好的使用 destring 选项,我们希望查看 destring 无法自动处理的内容。

具体查看命令:tabulate whatever if missing(real(whatever)),其中 whatever 为需要转换成数字的字符串变量。

// 原始数据中包含一个变量:whatever
. use temp.dta, clear
. list, sep(0)
     +----------+
     | whatever |
     |----------|
  1. |      209 |
  2. |     1560 |
  3. |      52o |
  4. |      ll9 |
  5. |       NA |
  6. |     12,3 |
  7. |      79% |
     +----------+

// 打印 destring 无法转换的内容
. tabulate whatever if missing(real(whatever))

            whatever |      Freq.     Percent        Cum.
---------------------+-----------------------------------
                12,3 |          1       20.00       20.00
                 52o |          1       20.00       40.00
                 79% |          1       20.00       60.00
                  NA |          1       20.00       80.00
                 ll9 |          1       20.00      100.00
---------------------+-----------------------------------
               Total |          5      100.00

除此之外,destring 可能需要与其他字符串函数配合使用(如: char()uchar()),其他常用的字符串函数可以通过 help string functions 查找。

8. 参考资料和扩展阅读

相关课程

连享会-直播课 上线了!
http://lianxh.duanshu.com

免费公开课:


课程一览

支持回看,所有课程可以随时购买观看。

专题 嘉宾 直播/回看视频
最新专题 DSGE, 因果推断, 空间计量等
Stata数据清洗 游万海 直播, 2 小时,已上线
研究设计 连玉君 我的特斯拉-实证研究设计-幻灯片-
面板模型 连玉君 动态面板模型-幻灯片-
面板模型 连玉君 直击面板数据模型 [免费公开课,2小时]

Note: 部分课程的资料,PPT 等可以前往 连享会-直播课 主页查看,下载。


关于我们

  • Stata连享会 由中山大学连玉君老师团队创办,定期分享实证分析经验。直播间 有很多视频课程,可以随时观看。
  • 连享会-主页知乎专栏,300+ 推文,实证分析不再抓狂。
  • 公众号推文分类: 计量专题 | 分类推文 | 资源工具。推文分成 内生性 | 空间计量 | 时序面板 | 结果输出 | 交乘调节 五类,主流方法介绍一目了然:DID, RDD, IV, GMM, FE, Probit 等。
  • 公众号关键词搜索/回复 功能已经上线。大家可以在公众号左下角点击键盘图标,输入简要关键词,以便快速呈现历史推文,获取工具软件和数据下载。常见关键词:课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法

连享会主页  lianxh.cn
连享会主页 lianxh.cn

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

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

✏ 连享会学习群-常见问题解答汇总:
https://gitee.com/arlionn/WD