当前位置:   article > 正文

stata数据处理教学_stata bysort

stata bysort

本篇为下集,介绍数据处理部分,包括数据导入导出、清洗的常见命令。内容源于help文档与网络公开内容,优质参考文档会放链接。

文约3万字,内容详实,讲解细致,需花一定时间消化。小的不足以做大标题的知识点我都内嵌到模块种了,每一块包含的内容都很多,认真学完整篇文档,基本可以用 stata 进行数据处理。跑回归只是几行代码,数据处理才是大头,希望这篇文档能对你有所帮助。由于文字过多,网页格式不利于查找,文末放有本文的 word 文档及相关练习数据,方便搜索查看。当然这个只是我把网页版的复制了一遍,排版别指望了。

本文涉及许多函数,只涉及最常用的几种,有些函数功能部分重叠,但不同人喜欢的函数不一样,多学一点还是好的。以例子讲解函数设置,看明白后自然知道 help 文档内的参数是什么意思。

字好多,肯定有一些错误的地方,望谅解。

1. 外部命令下载与基础命令

1.1 外部命令下载

介绍数据处理之前,需安装一些外部命令。想要实现更为复杂的功能需要组合很长的代码,将这些代码封装到一个命令里,则可使用一条命令完成多条代码所作的事情,这就是外部命令。一般而言有两种获取方式。

第一种是:ssc install mypkg,mypkg可更改为任意想安装的命令,该包的功能在于展示所有安装的外部命令。由于我已经安装,再运行会进行更新。

输入 mypkg 将会显示所有安装的外部命令及日期,若想查找特定的命令,则用通配符,mypkg d* 只会显示 d 开头的外部命令。 如果说想查看特定外部命令的基本信息,则输入 which mypkg,会显示该命令存储位置以及命令初创与迭代日期。

第二种则是 findit reghdfe,findit 可以搜索内部与外部命令,并以搜索结果的方式呈现。Reghdfe是一个固定效应回归包,搜索后有23条结果,但并不是每条都可以下载 reghdfe包,例如第一个包是 gr0091包。 往下翻到 reghdfe 开头的包,点击后再点 click here to install 即可完成包的安装。

安装外部命令后,输入help 包名,例如 help total ,查找用法及案例。

如果某些命令很多人都下载,说明该命令有助于实证研究。输入 ssc hot, n(15)即可显示下载量最大的15个命令,挑选你需要的进行下载。

1.2 stata运行状态判断

如果输入命令代码,发现程序无反应,不知道是网卡还是程序正在跑,有两个辨别方法。第一,看上方栏的×号的颜色,运行代码时显示红色,否则为灰色。

第二,看命令窗口的左下角,如果显示如下,说明仍然在跑,耐心等待即可。 

1.3 基础函数介绍

gen 与 egen 函数

输入 gen a=17,gen命令为产生新变量,其为generate简写,也可简写为g。但该命令只能生成一些系统自带的函数,例如 gen a=17, gen a1=a^2, gen a2=ln(a),稍微高级点的都不行,输入 gen a3=mean(a),会显示 mean 为未知函数。

egen 同样可生成变量,其源于 egenmore 外部命令包,ssc install egenmore,该包提供了很多好用的函数,后续会介绍到。稍微高级一些的,如果 gen 无法生成就可以使用 egen,输入 egen a3=mean(a),此时不会报错。dis a3,a3数值即为a的均值,dis是display的简写,通常用于展示单个变量或函数结果

什么时候使用 gen?什么时候用 egen?我觉得跟数值相关的用 gen,跟函数、变量相关的用 egen。只是经验之谈,如果用的不恰当系统会报错,再用另一个就行。

gen 很多时候也会作为函数的附加选项,即保留原数据下,将处理后的数据存入一个新变量。这种命令通常为:duplicates tag price, g(du)。其中生成新变量选项也就是 g(du) 在逗号后,逗号后的基础函数通常没有等号,采用 () 的形式,该命令表示对价格的重复值进行标记,并将重复值数量存入新生成的变量 du,了解即可后面会介绍。

空值设定、键入与随机抽样

set obs 5,br查看情况,可看到有5个编号。 gen a=17,你会看到变量为a,5个值都是17,这是因为设定样本数量后 gen产生的数值默认为向量。gen x=runiform() 你会看到新增变量 x,runiform函数为随机生成符合均匀的数值。

数据输入通常为导入文件,但也有时候需利用几个简单数值验证下函数的用法,因此学习键入也是有必要的。输入 input x,input 为键入命令(简写为 inp), x 指生成的数值都存到变量 x 内。输入一个数值后按回车键,会进入下一条数据的录入。由于我们之前设置了长度为 5 ,因此在第五个数值输入后就会结束录入。clear 空间,输入 input x,由于未提前设置长度键入不会自动结束,在新的一行输入 end,则结束当前输入。

if, list 与 replace

if 为条件语句,很常用。在生成新变量、浏览数据、分组计算时都会用到 if,后直接跟条件即可。比如说 if a>1,if a!=1(不等于), if a==1(注意两个等号为判断,一个等号为赋值)。如果为多条件,则加 & 或者 |。如 if a!=1 & b!=1,if a!=1 | b!=1。

sysuse auto, clear。这是一份关于车的数据集,make是制造商信息,rep78是78年该车的维修次数, foreign 是该车是国产还是进口。我们现在想查看第 46到55行的数据,输入 list in 46/55,会在代码运行结果处立即列示相关信息。

如果只想看 46/55 的属于国产车的数据,则输入 list in 46/55 if foreign==0(foreign=0则无法运行)。

可以看到如果 list 后直接跟 if 会显示所有变量,输入 list make pr fo rep78 in 46/55 if foreign==0。由于 price 与 foreign 在变量中无相同前缀者,因此可以简写为 pr fo。

输入 list make-rep78 in 46/55, noobs sum(rep78) N(price) mean(mpg price),会得到下图。其中 make-rep78中的短划线指选中范围,从make到rep78间的变量都被选中,noobs指的是没有编号,可以和上图比较一下,左侧是没有 46/55 的数字。而 mean(mpg price)指分别对两者求均值,N(price)指计算列示出来的price有多少个。list 只提供了这三个统计选项,内可以包含多个变量。

replace 命令为覆盖命令。set obs 20后,gen x=runiform(),gen y=runiform(),br浏览数据,x与y不一样。输入 replace x=y,可看到 x 中数据均被替换为 y。replace 命令也常与 if 连用。

 _n  ,sort 与 gsort,by 与 bysort

sysuse auto, clear 可以看到数据无样本编号。输入 gen id = _n。_n 为按照样本当前或指定顺序生成1-n的编号,输入 br 可看到样本次序。_n 用处其实很大,仍以 auto 举例。假设我们现在想知道国产车和进口车中价格最便宜的五辆的制造商是谁。

我们第一步需进行排序,输入 sort foreign price。sort 命令为升序排列,首先对 foreign进行排列(为什么domestic是第一个,foreign为第二个,原因在于两者为值变量,输入label list,可看到domestic对应0,foreign对应1)。而后在国产车和进口车内部对 price 进行升序排列。如果说想知道不同组别车中,价格最贵(降序排列),维修次数最少(rep78升序排列),那么使用 gsort 命令。gsort foreign -price rep78,该命令默认升序,在变量前写 - 号表示降序排列 (+号为升序)。

回到正题,排序后,输入by foreign: gen id=_n,by命令表示根据组别进行分开计算,具体计算方式在冒号后表明。该命令表示根据分类变量 foreign 分别生成编号。而在前面,我们已经对 foreign和 price 进行了排序,此时的编号显示的是不同组别的车之间从最便宜的到最贵的。list make id foreign if id<5, sepby(foreign)。

如果你搜过 stata 相关分组处理的代码会经常见到 bysort 而不是 by。重新引用 auto 数据集,我们先对price进行降序排列,后查看数据,你会发现 foreign 的组别混杂,此时输入 by foreign: gen id=_n,会返回 not sorted 的错误信息。这是要进行分组运算时,需要将性质相同的放在一起,放在一起的方式就是进行排序。因此可以先对foreign排序后再使用 by 分组计算,此时便不会报错。

另外可以使用 bysort foreign: gen id2=_n,该命令会自动对 foreign 排序后分组生成编号,其与 by的区别在于,在组别不同的个体混杂时,可以自动排序后进行分组处理,而不是先排序,再处理。

我们想知道不同组别中,最便宜且维修次数最少的制造商是哪些。可以参照上文的先排列后生成变量。bysort 命令可以顺便对变量进行排序,使用该命令是否能完成上述操作呢?输入 bysort foreign price rep78: gen id3=_n,br查看,你会发现 id3 全部是1,这是因为该命令是将 foreign price rep78三个变量视作联合条件,三者完全相同的才会有编号123,但是数据里显然三者不一致,因此,每一个 id3 都为1。因此 bysort 最重要的是 by 分组计算,sort 只是附带操作,省去了一个 sort 变量的命令行而已。若想使用 bysort 完成目标,则需先对 price 和 rep78升序排列,输入 sort price rep78,再输入 bysort foreign: gen id4=_n,此时 list make id foreign if id4<5, sepby(foreign),该结果与前文一致。

所以满足多条件时分组计算,我建议先进行排序,而后根据分类变量计算。如果只需根据一个分类变量计算,使用 bysort 即可。

数列生成

我觉得要是随机生成数据分析不如拿R,拿stata通常都是写毕业论文的。我介绍点生成规律数字的,符合分布的命令我就不说了。

range 函数可以生成等差数列。清楚空间,输入 range x 1 100 100,range表生成范围,新建变量 x,将1到100的数字存储到其中,步长为第三个100除以第2个100,第三个100表示set obs 100(空间)。如果有数据集时,sysuse auto, clear 后输入 range x 1 100,查看数据见有74个数值,其步长为 100/74=1.35,所以第二个数值为2.35。快速生成编号时,可以输入 range id 1 _N。这个_N 是当前数据有多少个观测值的意思,例如 auto 数据有74行,所以 _N 为 74。

egen提供的 fill 函数更加灵活。 clear 后 set obs 20,输入 egen x=fill(1 2),再输入 egen y=fill(100 99),查看数据。fill 参数只有 2 位数时,生成等差数列。如果是多个参数,则将 fill 内参数视为规律,下面是 help 文档的例子。其中 c 的生成中括号3表示步长为3。

seq 函数也可以提供同样的功能。clear 后 set obs 20,再输入 egen a=seq(), b(2) 这个 b我猜是以多少个为一组,b(2)以2个为一组,从1开始生成 (1 1 2 2 3 3 这样的)。输入 egen b=seq(), t(5) 得到从1-5的重复数列,t是to的意思,默认从1开始。输入 egen c=seq(), f(15) t(20) 得到从15-20的重复数列,f是from的意思。

1.4 变量查看补充

上篇中提到过 describe 函数可以提供相应变量存储类型,展示格式,相应标签的信息。如果想简单了解整个数据集时,输入 d, short。其提供了观测值数量,变量个数,分类标准,数据集标签以及在哪里存储。

sum 函数可以展示变量观测值个数、均值、方差与取值范围,如果想进一步了解变量值的分布,则加入 detail 的选项。输入 sum price, detail。其展示了 price 的百分位数临界值与最小值,还有峰度等信息。

sum加detail选项虽然可以给出具体的分布数值,但图表更为直观,若要生成图又特别麻烦,那应该使用 inspect命令,该命令可以给出数据简要分布图表。输入 inspect rep78,左侧图是数据分布简要图,rep78有5个不同的取值,取值范围是1-5,点不是缺失值,而是说取值为1的很少,#则是很多的意思。右侧表指总观测值有74个, missing表示缺失值有5个,并且缺失值不视作不同的取值,但会报告出来。

sum与detail的另一个类似命令为 codebook,其可以给出更为简洁有效的数据信息。输入 codebook price,可以得到数值范围,非重复值个数,缺失值占比,均值方差与百分位数。很好用

lookfor 函数

lookfor 函数可以在变量非常多时找到相关变量。sysuse nlsw88, clear。输入 lookfor married,如果变量名和变量标签或者值标签中包含 married 字样,则对此返回。如果输入两个变量 lookfor married lives 会一个一个变量查找,而不是找同时包含 married 与 lives 字样的变量。

elabel 标签管理命令

elabel 是标签管理命令,支持模糊搜索、条件语句、定义值标签、修改变量名与值标签名,而这些操作若使用 label 非常麻烦。

sysuse nlsw88, clear 后输入 d,可看到值变量的情况。我们现在要查看 race 的值与标签对应情况,如果用 label 则需记得 race 的值标签名。我们可以看到 race 的值标签名为 racelbl,但若忘了,则需要输入 label list。但这一命令会列出所有值变量的标签很烦人,elabel 可以很好的解决这一点。

如果你记得 race 的值标签是以 r 开头的,则使用通配符辅助。输入 elabel list r*。通配符的意思是可以代表任意字符,其中 ? 表示一个模糊字符,* 代表一个或多个匹配字符。上图中有两个 s 开头的值标签名,可以输入 elabel list s*查看结果。如果时间过于久远不记得race的值标签名是什么了,可以输入 elabel list (race) 会显示同样的结果。race 是要查询的变量,注意需括起来,为啥我也不知道。这可以直接通过变量查询值与标签的对应情况,非常方便。

elabel 支持条件表达式使得在值非常多时可以迅速找到对应信息。输入 elabel list (occ*),会得到下图。

我们现在想查询1-6对应的标签,输入 elabel list (occ*) iff (#<7),可得到第一个结果。条件用 iff 表示,注意是 iff 而不是 if ,我觉得应该是和stata自带的 if 所区别而特意设置的。#表数字占位符,意思是这里我需要一个数值,但是我不知道这个数值是多少,采用 # 表示模糊替代。在许多命令的 help 文档里,函数选项里多有 #,例如 abb(#) 表示的任意字数的缩写,好像 # 有运算的含义,但在函数选项里是数字占位符。

如果想查询职业中以 F 开头的值与标签对应信息,输入 elabel list (occ*) iff strpos(@, "F")。其中strpos是字符位置查询函数,@表示字符占位符,"F"为筛选条件。这个条件的意思是说,查询 F 开头的字符在一群标签中的位置。也就是说,这是先查到 Farmers 与 Farm laborers 在标签中的位次为 9 和 10,再返回两者的对应情况。

如果我觉得这个值标签不对,应该在现在的基础上减1,elabel 也能做到。输入 elabel define (occ*) (= #-1) (= @), replace 然后再查看标签可得下图。之所以用 define 是因为要修改规则(参见上篇),(= #-1)我的理解是先修改值,等号左边默认为原先数值,原先数值等于自己减去 1,(= @)表示标签不变,注意加入 replace 覆盖原先值标签。

elabel 还能修改值标签名与变量标签,输入 d 查看所有变量对应标签,可以看到很多都有 lbl 的后缀,现在将其改为 VL的后缀,则输入 elabel rename (*lbl) (*VL),第一个括号内的是要更改的变量,第二个是要更改的名字。可以看到所有的标签名后缀都改为了VL。如果只改一个标签,输入 elabel rename (ra*) (r),输入d查看结果。

如果要更改变量标签,输入 elabel var (age) ("年龄"),可以看到右侧栏已经改为年龄字样。我的总结是,这个elabel输入变量时一定得是带括号。 


2.数据导入导出与子集选择

2.1 数据导入

txt 文件导入

输入 cdout 打开当前工作路径,新建一个 txt 文档,命名为 a。并且第一行输入x,y 第二行开始每行输入2个数字,中间为逗号,三行就够了。之后保存关闭。回到stata界面。

输入 insheet using a.txt, clear names 即可完成导入。names 表示第一行作为变量名。注意需加后缀名,文件名可以不加引号(我不喜欢加引号因为好麻烦)。如果通过输入具体路径导入文件时,则为 insheet using C:\Users\Desktop\a.txt,同样可以不加引号。若路径或文件名包含空格符时,一定要加引号。

若 txt 分隔符为空格时,输入 insheet using d1.txt, clear delimiter(“ ”),标注清分隔符为空格,否则默认为逗号。

xls 与 xlsx 文件导入与导出

再新建一个名为 a 的excel 文档。对于 excel文件,如果是 xls或xlsx(前者代表excel版本更老),输入 import excel a.xls, clear firstrow sheet(domestic),命令前缀为 import excel,文件名需写清后缀,firstrow 表示第一行作为变量名,sheet(domestic) 为导入具体工作簿,可以不写sheet,另外sheet名最好为英文。

以 nlsw88 文件为例进行导出。sysuse nlsw88.dta, clear后输入 export excel nlsw88.xls, firstrow(var/varlabel),该命令表示导出数据,需写清导出文件后缀名,firstrow()表示第一行识别为变量名,写入var表示将变量名作为第一行,varlabel则以变量标签作为变量名。注意,导出时一定要写 firstrow()里面var或varlabel都行,如果不写的话,循环取值导出时,会只有数据,而没有第一行的变量。

csv 文件导入与导出

导入 csv文件,输入 import delimited using a.csv, clear,注意delimited一定要加(分界符的意思),需写清文件后缀。

导出为csv文件仍以nlsw88数据集为例, export delimited using nlsw88.csv, replace。注意导入时逗号后加 clear,导出时加 replace 覆盖前文件。

dta 文件打开与 do 文档编辑

对于stata数据,系统自带需写 sysuse,调用当前工作路径数据为 use a.dta, clear,可在文件名前加入指定路径;另外对于do文件,输入doedit会默认新建一个do文档,doedit d5.do则会打开指定的do文档;另外对于某些网络数据文件,也会用webuse,例如 webuse ipolxmpl1,少见,多在help文档内。

数据保存为 dta 文件时,输入 save mydata.dta, replace 会保存 dta文件至当前文件夹,也可写清路径保存到指定文件夹,replace表示覆盖之前的文件,第一次保存无影响。

我觉得把文件保存为 dta 文件比较方便,一来命令很短,二来可以随便折腾后再导出,建议导入数据后第一步先保存为 dta文件,之后使用 dta 文件操作。

2.2 子集选择

在数据很大时,我们不一定需要全部的数据,需选取一个子集进行研究,以 auto 数据集为例。

对于不想要的变量输入 drop var1 var2 即可,若只是想要其中几个变量,先输入 preserve 存储当前状态,后输入 keep make price rep78 foreign,查看数据会发现除选定的变量外其余变量均删除。输入 save 单独保存,再输入 restore 恢复之前状态。这样我们既可以保存想要的数据,又不影响当前数据的状态,不用重新导入省去了很多麻烦。显然 preserve 与 restore 命令可以用于许多尝试性的操作。

除此之外还有一种方法,ssc install savesome。该外部命令功能为保存子集,以 auto 为例。savesome make price rep78 foreign using auto_sub.dta, replace。可看到子集已保存,savesome后紧跟需要的变量,using 表保存为叫做 auto_sub的 dta 文件。(事实上只能保存为dta的格式)

2.3 批量导入:元与循环语句

批量导入文件最原始的办法是使用循环命令,常用 foreach 与 forvalue 循环,while 少见不介绍。循环语句涉及元,也就是 local 与 global。local 为暂元,引用符号为`'(波浪号的键与单引号)只运行一次就失效,global 为全局元,整个 do 文档都可以用,用$表引用。

接下来运行命令仔细看暂元与全局元的区别。doedit 打开do文档,编写以下命令。

stata 中星号与双斜杠表注释,星号注释通常用于一行,双斜杠可用于命令后以及多行注释。写 do 文档时要注意代码可读性。stata 内,经验来看,只有函数后面需紧跟括号例如group()中间括号不能有空格,其他地方可以利用空格来保持代码的美观,美观并非形式主义,整洁的代码有助于我们更快的提取信息。另外,每一块实现不同功能的内容,记得用注释符分割开。

选中第6与第7行的代码,ctrl+D可快速运行,df 为 3,除常数项外共 3个参数。

再只运行第7行代码,可看到回归系数仅有常数项,这表明引用未定义的元并不会报错,只是运算为空。

运行第 9 与 第 10 行,和单独运行第10行结果是一样的,因为 global 定义的 zz 是在整个 do 文档内有效的。

循环语句

forvalues是用于数值的循环语句,帮助文档给出了以下示例。forvalues后跟暂元,等号后为划定数值范围,之后大括号内跟循环执行的内容。注意,i 是暂元,所以运行时需将整个循环代码选中运行,不能分开单行运行。

由于划定数值范围有4种方式,故文档列示了4个例子。以第一个例子为例,先设定空间 set obs 10,再写循环。这一循环先产生暂元 i,暂元 i 的范围是1到100,步长为1,生成100个新变量分别命名为x1, x2到x100,并且每一个变量都生成符号均匀分布的值。因为 forvalues 后的是暂元,所以每次涉及 i 都要用暂元的引用符号。其实和其他编程语言的循环一样。

foreach 可以适用任意 list,基本语句是 foreach 暂元 in/of list,一些普通的 list 写 in,特殊一些的写 of。下面依次介绍。

第一个例子,产生一个暂元 name,其指代"Annette Fett"等三个人名,构造循环输出每个名字有多长。由于 name 是暂元,所以加暂元引用号。length 字符长度函数,内为字符,因此 name 需加上双引号。后面的 " characters long -- `name' " ,其实是一个字符,内部引用了暂元。循环跑出来后,第一个结果为 12 characters long -- Annette Fett 。

第二个例子是提前设置了一个暂元,暂元参与循环时要用 of 而不是 in,并且需写清楚循环的 x 是什么类别,也就是必须加注 local 选项,必须写 local grains 而不是 grains。

第三个例子是已存在的多个间隔变量,这个是 auto 的数据。这个变量列表特点是隔着几个变量选一个,因此也算不规则列表,varlist 后面跟具体的变量,分别是 pri 到 rep78的变量(其实中间就一个mpg),加上 t 开头的变量。qui 表示静默,不报告结果但有记录,只当暂元 var 大于均值,这个 r(mean) 我不知道是哪个变量的 r(mean)。只有大于均值时才 sum。

第四个例子是不规则数列,注意写明 numlist。help 文档内建议,当需要规则数列时,采用 forvaluse 循环,好像是说 foreach 只存储列表, forvalues 是计算一次记录一次,咱也不懂,跟着官方来吧。

另外需注意,在使用一般 list 时,注意要一次写完变量,如下图第一个循环,由于 in 后面每一个变量都被视为单独的列表,因此循环是运行两次的。要么写成 mpg-turn  ,要么使用 of , 即第二个循环。

由于批量导入练习需要数据,我们先学习批量导出。需介绍 levelsof 函数,这个函数可对指定的变量取值排序后存入暂元中。现在想把 auto 数据集中,每个不同维修次数的数据单独存为一个文件。输入以下代码。

由于 rep78 为12345,因此生成暂元rep78lev将其存储,其内只有5个值,而非 rep78 那样的数字列表,如果想统计缺失值,加入missing即可,也就是 levelsof rep78, local(rep78lev) missing。

导出文件可以有两种方式,第一个是使用前文介绍的 savesome,第二个是 export。注意,如果导出的是 execl 文件,一定要加 firstrow(var),否则导出的数据是没有第一行的变量,可以自己试一下,而 csv 就不用。

批量导入

批量导入共5个方法。

第一个是先 cd 设置工作路径,使用 forvalues 或 foreach 语句循环导入并存为 dta文件。导入多个文件肯定是想合并成一个文档进行分析的,合并有两个命令,append 是纵向合并,merge是横向合并,这篇文档写的非常清楚,我就不写了。STATA学习笔记:数据合并_Mpeipeisu的博客-CSDN博客_stata纵向合并append

第二个方法是使用 local 设置暂元。第一个暂元设定路径,第二个暂元设定文件名,再使用 foreach 循环导入文件并保存为 dta,注意由于是暂元,所以 45-50需一起执行,不能45, 46, 47-50分开三次执行,那样没有效果。合并成文件时,使用的是 foreach 语句,这个 file 是文件类型的暂元,前面没有提及在这里补充。

第三个方法是使用 csvconvert,这个命令可批量导入 dta 与 insheet 文件,并将其合并生成一个新的文件。input_file 是可选项,可以指定导入合并的文件,output_file是合并文件导出名,dir是导出路径。

第四个方法是使用 openall,这个好像是外部命令要安装。注释写的很清楚了。

第五个是multimport,外部命令要安装。 这个命令的特点在于,可以选多个格式,help 文档查看示例。

后三个办法虽然更简洁,但仅适用于格式规整,只需合并的数据。如果涉及到文档还需处理,那么还是得用前面的循环语句。


3.重复值、缺失值与离群值的处理

数据通常会有三个问题,重复值、缺失值与异常值。进行实证研究前需对三者进行处理。

3.1重复值

重复值筛选

如何筛选重复值?如果是面板数据,使用 xtset id year(xtset为告诉系统这是面板数据),若有重复值会直接显示。

一般而言,duplicates是解决重复值的主要命令。我在这里使用我的毕业论文数据进行处理,cdout打开当前路径,将 firms_county文件导入stata。查看一下数据,你会发现有两种颜色,红色的数据为字符型,黑色的是数字型数据,如果有值变量(分类变量)就会是蓝色。该数据为面板数据,但是使用xtset会出现有重复时间,这表明存在重复值 stata 无法识别为面板。

输入duplicates report,该命令默认根据全部变量进行查找重复值,你会得到如下结果。第一行表示 copies为1(无重复)的观测值有15288个,第二行表示 copies为2(存在重复)的观测值有16个,其中重复 (surplus) 的有8个。这份数据时间跨度为8年,也就是说有一个县的数据重复了一份。注意,被重复县和重复县都被放在了 copies为2 的组别,而不是被重复值放在 copies为1,重复值放在copies为2 里。

 如果你想要查找某个特定变量的重复值,在 report后加入指定变量即可。duplicates report county。stata 会报告 county 的重复情况。8年数据重复两份,因此同一个县名出现16次,就县名而言是重复了15次。

我们知道了重复的大致情况,如何得知具体重复的是谁,就要使用到 duplicates example,该命令会给出前几个重复值。输入 duplicates example county year,会得到下图,stata告诉你谁被重复了(#下的2表示copies为2)。

重复值标记与删除

duplicates example 在重复值很多时不能满足我们查看与标记所有重复值的需求,需要采用别的办法。

第一种,输入 duplicates tag county year, g(du)。duplicates表重复值命令前缀,tag 表对重复值进行标记,county year表示以两者为联合条件标记重复值,g(du)实际上是 gen du。重复值会记录在新产生的变量du中。br du,你会看到基本上ndu都为0。list couty year if du!=0, sep(8) (!=表示判断符不等于),下图展示了重复值与被重复值。

第二种采取 group 函数。egen du2 = group(county year), label lanme(cy)。这一命令表示以 county year为联合条件,生成多个分类的值变量,通常会以将变量间加空格显示。值变量的标签命名为 cy(county year的意思)。比如说130185 2001,表示的就是一个分类,130185 2002为另一个分类,由于分类过多,使用 label list cy时无法显示全部数据。但是我们并不需要知道全部的标签,我们只是需要这种分类。另外group函数支持根据字符型变量分组,egen pc=group(province city), label lanme(pclabel),再输入label list pclabel,你会得到313个值变量对应标签。

如果不输入label lname,group函数只会生成分类标记,输入 egen aaa=group(county year),aaa并不是蓝色数字,因此也就没有办法用label list查看对应的分类,需转换为值变量后才行。

gen 同样可以生成 group,preserve一下后,清空,sysuse auto, clear 输入 gen gg=group(4),可以看到整个数据被均匀地分为了4组,而egen的group输入4会显示invalid name。如果在 gen 的 group输入变量名,如 gen gg2=group(rep78),生成的数字我不知道是什么,因为找不到帮助文档,建议普通的 gen 就输入数字等分分组好了。

在设置完分类变量后,由于每一个县和时间都构成了一个分类,因此我们只需计算每个分类包含的数量,bysort du2: egen cy_obs=count(du2),count表示为对值变量 du2 的计数,我们在上篇提到分类变量统计信息最好使用 table,输入table du2,可看到有 1与 2。每个县每个年份应该仅出现1次,因此 du2==2 的是重复值。

以上两种方法虽然对重复值进行了标记,但不能使用 drop du!=0, 或者drop if du2==2,因为被重复值与重复值都有一样的标记,使用两个命令会使得两者都被删除。应该使用 duplicates drop,默认根据全部变量删除重复值,若使用 duplicates drop county year, force 则是根据 county与year为条件删除重复值,但是注意,在限定条件删除重复值时,需加入force选项。(联合条件比较实用,比如说我可以删除 county和 year重复,但其他不变量一定重复的数据)

非重复值

有时除重复值外,我们更关心非重复值的个数。distinct 函数用于报告非重复值,外部命令需下载。导入 auto 数据集,输入 distinct。虽然样本有74个观测值,但 foreign就2个。注意 rep78 为5,因为其自动忽略缺失值,如果要加缺失值,输入 distinct, missing。

输入 distinct make-rep78, missing。短横表选取范围。输入 distinct foreign rep78, joint missing。其中 joint 表联合条件,也就是说 foreign 与 rep78组成一个新的变量,这个变量有10个不同的取值。如果将 missing去掉的话,这个非重复值应该是8个。

这个函数主要用于了解整体的数据非重复值状况,且联合条件的选项实用性很强。

tag 函数也可以用于非重复值标记,第一个值为1,剩下重复出现为0。sysuse auto, clear。输入 egen t1=tag(rep78),再输入 egen t2=tag(foreign rep78)。数据第一行,3第一次出现所以均为 1,而第二行由于3出现过了所以为0,对于其余维修次数也是如此。值得注意,对于第三行,当rep78为缺失值时为0,因为 tag 函数默认忽略缺失值,如果想将缺失值也纳入运算,输入 egen t3=tag(rep78), missing。加入missing后缀即可。这个函数很少单独用,通常和其他的函数一起。

unique 函数报告变量总观测值个数与非重复值个数,外部命令需下载。导入 auto 数据集,unique foreign,可以看到观测值74个,非重复取值 2个。unique rep78,非重复值6个,表明该函数可以识别缺失值。这个非常简短,适合判断单个变量情况时使用。

3.2缺失值

缺失值也是很大的问题。缺失值指这里本应有但没有数据。数字型数据用 . 表示,例如br if wage==. ,文字型的缺失值则为空值,采取 br if wage==”” 筛选(好像也可以不管空值)。在连享会的推文里介绍了missings外部命令,详情查看:Stata:让缺失值一览无余| 连享会主页,我做一些简要介绍。

不同函数对缺失值处理不一样

缺失值在不同函数间的处理方式不一样,sum regress gen等命令会自动忽略缺失值,而count keep等会将其视为无穷大的数值。

导入auto数据集,sum rep78 if rep78>4,其中obs=11,而count if rep78>4会得到16,原因在于count中缺失值视作数值,如果输入count if rep78>4&rep78!=. 则得到11。

特别提示前文提及的 group 函数会自动忽略缺失值,输入egen fo_rep=group(foreign rep78), label lname(frlabel)。将不同类型的车与不同的维修次数作为分类变量,创建标签时会显示产生5个缺失值,label list frlabel查看标签状况。如果想将缺失值也作为分类的一种,则加入missing。

强大的missings函数

接下来介绍查看缺失值的方法,首先下载强大的 missings 函数。ssc install missings。

我们以 nlsw88 数据集为例。输入 missings report 会给出一份各变量缺失值数量的报告。

missings list varlist 可以列示出所有含缺失值的样本,但缺失值有可能会很多,list 一下直接占满屏幕又只能在 log 文档中查看全部,属实没必要。

missings table 可以统计存在不同数量缺失值的观测数量,这个好。

missings tag 与 duplicates tag 类似,输入missings tag industry, g(mi_indu) 会根据 industry 是否为缺失值生成变量 mi_indu,非缺失值赋值0,缺失值赋值1。br if mi_indu!=0后再输入count if mi_indu!=0 都会显示 industry 缺失值为14个,与上图相符。如果 tag 后不加入具体变量,则会根据每个观测值的所有变量是否存在缺失值生成对应变量。

missings dropvars可以删除对应缺失值的变量,但是我用missings dropvars industry, force删除industry中的缺失值时显示失败,这个我用不明白,通常我会设置出缺失值变量查看是否应该删除,用 drop if mi!=0这种命令更简单一些。

missings包优点在于其附加选项。如果使用 missings table, string 则只会显示字符型变量的缺失值统计情况,改为numeric时显示数值型确实状况。另外,missings report, percent 不但会报告缺失值个数,还会显示占百分比,非常好用。

系统自带的 rmiss 函数与 missIng 函数

还有两个函数也可以标记缺失值。

一个是 rowmiss 函数简写为 rmiss。输入egen miss=rmiss(wage industry occupation) 。

另一个是 missing函数,好像是系统自带的所以要使用gen。输入gen miss2=missing(wage, industry, occupation)。

注意rmiss内各变量为空格,missing内是逗号。输入br if miss!=0 | miss2!=0,得到下图。

由于wage基本都有,所以缺失值出在 industry 和 occupation 里。可以看到 miss2 都是1,而 miss中却有2。我没有查看help文档,我觉得 rmiss 函数应该是返回样本观测值中缺失的变量个数(这一行中变量缺失值缺失几个),missing 函数是判断这一行是否有缺失值,没有返回0,有缺失值无论有几个都只返回1,即只是判断是否有缺失值。

我们尝试使用前文的 missings 函数,missings tag wage industry occupation, g(miss3),再br if miss!=0|miss2!=0|miss3!=0,可以看到miss与miss3完全一致。

删除缺失值无特殊命令,drop 标记出来的缺失值即可。

补充的 mdesc 函数

缺失值还有一个有意思的命令是 mdesc,安装该包。虽然 missings 函数非常强大,但数据集非常大时我们并不需要得知所有变量的缺失状况。使用auto数据集,使用 missings report price 可看到无缺失值。但是不能显示缺失值的占比,输入mdesc price会得到下图。

请输入 mdesc price rep78, any;mdesc price rep78, all; mdesc price rep78, none。你会得到下图。第一个命令的意思是price或rep78有缺失值时便报告,前文得知rep78有5个缺失值,price无缺失,该结果正确。第二个命令则是price和rep78都为缺失值时进行报告,此时为0;第三个命令是统计price和rep78都不是缺失值的个数,共74个观测值,rep78有5个缺失值,因此69是正确的。

 当然mdesc和其他函数一样可以与by, if, in一起使用,大部分函数都可以这样,要举一反三。

缺失值补漏

缺失值并非只有删除一个方法,也可以尝试补漏。但补漏前需介绍一下数据缺失类型。

数据缺失类型有三类。第一类完全随机缺失MCAR,指数据缺失与数据集中已知和未知的特征完全无关,例如调查问卷中的性别是否缺失是随机的;第二类是随机缺失MAR,数据缺失但是与数据集中其他变量有相关性,比如说某个教育数据集缺失了很多小孩的IQ测试分数,是因为4岁的孩子相比12岁的无法通过测试,所以缺失值和IQ无关与年龄相关;第三类是最麻烦的,非随机缺失MNAR,指缺失值不但与其他变量相关,并且还自相关。之所以要了解数据缺失是因为接下来的填补缺失数据的方法需依情况使用。我无法给出不同类型数据处理建议,只介绍基础的命令。

首先最简单的方法是批量填补,sysuse auto, clear,使用 rowmiss 生成缺失值变量,br if mi!=0。输入 mvencode rep78 if foreign==0, mv(998);mvencode rep78 if foreign==1, mv(999),输入br if mi!=0,可以看到原先rep78缺失值替换为了对应数值。当然也可以将数值变为缺失值,输入mvdecode rep78, mv(998=. \ 999=.a),注意是 decode 而非 encode,mv 里面需写清什么数值赋缺失值。

另外,fillmissing 函数也很好用。下载该包,调用auto数据集,使用 missings tag, g(mi) 标记缺失值。可看到某些摩托车缺少78年的维修数据,我们需进行填补。我觉得国产车和进口车各自组别的品控差不多,输入 bysort foreign: fillmissing rep78, with(median)。该命令根据组别的中位数对缺失值进行填补。fillmissing 后跟需填补的缺失值变量,注意不能是varlist,with()内指采用什么进行填充,help fillmissing查看可选择项。该命令缺点在于只能是在缺失值基础上填补,没有生成新变量的选项,因此可先复制一份缺失变量数据。

ipolate 也常用于缺失值填充。(我感觉这个好像就是内插法)ipolate 的帮助文档里举了两个例子,输入 webuse ipolxmpl1, clear 导入数据后可看到 x 完整 y是有缺失值的,输入 ipolate y x, g(y1) 再输入 ipolate y x, g(y2) epolate。y是需要填充缺失值的变量,x是参照变量,当有两个变量时,建立的是 y与x的方程解出两者关系从而填补y的缺失值。默认情况下不会填充第一个缺失值,只有输入 epolate后才能填补第一个,而第一个缺失值咋填的我不清楚。

第二个例子是,webuse ipolxmpl2, clear 输入  by magazine: ipolate circ year, gen(icirc)。表示根据杂志名称,依照年份填充 circ 的缺失值。但是此时不是一元函数关系,而是上下之和取均值,可以看填充的缺失值是不是这样。

文字型缺失值采取的是 replace 方式。导入mi_str 文件,insheet using mr_str.txt, clear,查看数据结构,请使用 mvencode 填补 x2 的缺失值。x1 数据为红色表示是字符型数据,想把 N/A 转化为缺失值,再将x1变为数值型。输入preserve 后 replace x1=”” if x1==”N/A”。可以看到替换为空值,注意空格也算字符型的一种,需引号加注,N/A也为字符型。再输入destring x1, replace。destring命令为将文本型转换为字符型,转换后如果想产生新变量,则输入g(x1_num),如果替换为原值则输入 replace。输入 restore 我们尝试另一种方法。

replace x1=”.” if x1==”N/A”,替换成点字符,再destring,效果和替换成空格一样。与空格不同的是,点字符时由于x1全为字符,可使用 real 函数将字符转换为数值。重新导入数据,替换为点字符,gen x1_new=real(x1),效果与前文一致。real函数相比destring来说优点在于可嵌套,但其缺点是无法识别特殊字符,我们后文文字变量处理时会介绍到。

3.3离群值

离群值是数据集中一个或几个数值与其他数值特别大,比如说大家微观都考80-90分,但有一个大神考100分,这就是离群值。离群值再拟合曲线时会有显著的影响从而使结果偏离我们的预期。输入lianxh 离群值查看更多信息。

查看离群值

查看离群值的第一个方法是使用箱线图。导入auto数据集,graph box price weight, by(foreign),会得到下图。

箱子的中间表示中位数,箱子上下的线分别表示1分位数(25%)与3分位数(75%),而箱子外的上下线分别为上界与下界。点表示离群值,四分位数的间距iqr=p75-p25 (p75为三分位数,p75-p25其实就是箱子的宽度),上界=p75+1.5iqr,下界=p25-1.5iqr。 

以上是图示,如何查看具体数值?egenmore提供了outside函数,输入egen outby=outside(price), by(foreign) factor(2)。outside 表示查找 price 的离群值,by 表示在不同组间进行查找,factor表示分位数间距默认为1.5,设置为2则扩大了箱体,减少了离群值。

查看一下具体的离群值,br if outby!=.;可以再生成一个默认间距的,egen out=outside(price),list price out* if outby!=. | out!=.,可以对比不同间距下离群值的区别。(不能 list price out* if out*!=.,会报错,为啥我也不知道)

离群值处理

离群值的第一种处理方式是删除。drop if outby!=.  。

第二种是使用对数进行转换。离群值一定程度上说可以是个度量问题。大家微观都在80-90分,100分视作离群值因为我们默认10分以内是一个水平,若认为30分内是一个水平,那么100分就不是离群值了。对数就可以缩小数值,也就是改变标尺,从而减少很多离群值。但要注意,ln后回归模型的系数解释发生了变化。

使用auto数据集,graph box price, by(foreign),gen ln_price=ln(price),graph box ln_price, by(foreign)。可以看到ln_wage的离群值比wage少。注意不能graph box ln_wage weight, by(foreign)这时由于ln_wage和weight的数值量级相差很大,会导致ln_wage完全压扁无离群值。

第三种方式是缩尾与截尾。缩尾指的是将离群值替换为其他数值,截尾值删除离群值。缩尾处理介绍winsor2命令,使用 nlsw88 数据集,对 wage 进行处理。输入histogram wage, normal,构建直方图查看分布状况,normal 指附加正态曲线。可以看到 40处的数据显然是离群值。

接下来进行缩尾处理。输入winsor2 wage, c(1 98),查看数据会发现生成了wage_w的变量,该变量对wage变量1分位数以下与98分位数以上的视为异常值,使用1分位数与98分位数进行替代。winsor2缩尾默认生成_w的后缀,若想更改则加入suffix(数字或字符无需加引号),若不想生成新变量输入replace。cuts内表示的是异常值的门槛设定,如果只对右端缩尾则输入cuts(0 95);

如果是截尾处理,则加入trim后缀,会生成wage_tr的变量。输入winsor2 wage, trim cuts(5 95),生成直方图可看到截尾成功。


4. 文字变量的处理 

文字变量以红色表示,如果发现本应是数值的变量变成红色,说明其中可能包含空格,逗号,斜线,百分比,破折号,需要仔细排查删除特殊符号后才会变为数字型。

我觉得字符型变量,第一某些数据错误地以字符存储需转换为数字型,第二种则是利用字符进行分类,涉及提取合并等操作。

4.1 文字存储的数字数据转化为数值

导入str_destring数据,insheet using str_destring.txt, clear names,可以看到均为字符型。preserve,输入destring code year date size lev, replace ignore(“ -,/%”),除 gov 外均转换为字符型。replace表示覆盖,ignore指字符型转换为数字型中需忽略的字符,加个引号后内部填写需要忽略的即可。restore。

如果只更改某个变量且生成新的,destring code, g(code2) ignore(“ ”),code2为数字型。输入gen size2=real(size)你会发现产生两个缺失值,这是因为real函数无法识别空格,逗号,百分号等字符。

根据字符转换为类别使用 encode 命令。use str_destring.dta, clear再输入encode gov, gen(gov1)。encode命令根据字符的不同设置不同分类,label list 可看到分成了三类。如果想加上标签,则补上 label(gov_l),这和之前egen group的 label lname(gov_1)不一样,我觉得这是不同包开发者习惯不同,如何得知应该采用哪种命令对值标签进行命名,输入 help 与对应函数名查看。

另外,rencode也能完成上述功能,区别在于其提供replace选项,所以是 rencode。输入help encode可以看到 encode里未附加 replace选项,而 rencode有。重新导入str_destring,输入rencode gov, replace,再 label list gov 结果不变。

4.2 数字转换为文字

我们还需介绍如何将数字转化为文字。为什么要这么做,有时候是将数字转换为文字时进行处理比较方便,有时候是用于提取数值的几个数值。stata 我不知道有没有提取数字的函数,例如 excel是可以用 left提取的,但是stata好像只能转化为字符提取,气死我了。

导入 exe 数据,可看到 year 是字符型而其余两个是数字型,如何利用三者构成一个日期,数字处理不太方便,因此需将 month 与 day 转换为字符。

第一步 tostring month day, replace,将两者替换为字符型。

第二步,egen date = concat ( day month year), p(“-”)。其中 concat 函数是 egenmore 包内的连接字符的函数,p是 punctuation(标点的意思)的缩写,输入字符以设置分隔符,时间最好用 - 或 进行表示。

第三步,gen date2 = date (date,”DMY”),date 函数是将字符型日期转换为数字型日期的函数,由于我们日期在前年在后所以是 DMY。

查看数据你会发现数字是一万多,这个好像是距离1970年1月1日多少天,只需设置格式即可转换为日期。 format date2 %td,t 表示时间变量,d 表示日期型数据,如果是月份数据则是 tm。

4.3字符串常规处理函数

code 是公司所处区域的行政区划代码,前两位表示所处省,中间两位是市或自治州,最后两位是县或辖区。现需分别提取出三者,则要涉及很多字符串处理函数,查看帮助文档请输入 help string functions。

字符串提取函数为 substr(),输入 substr(code, 1,4 )。该命令表示对变量 code 进行字符提取,从第一个开始往后提取 4 个,如果是 substr(code,2,2) 则表示从第二个往后提取两个。但可见 type mismatch,这是因为substr 不能对数字进行提取。因此需先转换,除了使用tostring code转换为字符外,可以使用 strofreal 函数,该函数与 real 相反,可以将数字转换为字符。输入 gen province = real( substr( strofreal(code), 1, 2))。市与县同理,注意 real 与 substr是系统自带,所以是 gen。如果想从左开始或右边开始提取字符,有这样的函数,但没必要记,下图是 substr 帮助文档内的例子,第二行就是从右边开始提取的例子,第三行的最后一个参数是点,指的是全部,也就是说从第二个开始提取全部的字符。

另外,strofreal 函数带格式设置选项,dis strofreal(123, "%6.3f")。注意格式设置一定要带引号,因为 strofreal 是将数字转化为字符,对字符的格式设置所以需带引号。为啥叫这名,我想 str 很好理解,real 指的是字符转数字,of 应该是 off 的简写吧,为啥不是 off 是 of,别问,我也不知道。

由于深圳早先隶属于广东省,所以两者开头省份代码完全相同,8个观测值有7个省份,可以生成分类变量。重新导入 exe 数据,输入 gen pro=substr(code,1,2)之后再输入 encode pro, g(pro_v)此时已经生成了分类变量,输入 label list 查看。注意提取字符时不能写 real,因为 encode 是根据字符型变量进行分组的。

我们现在尝试对公司位置中的省份名进行提取。输入 gen a=substr(location, 1, 2),查看数据会发现出现了一个不知道是什么东西的符号。这是因为 str 类函数都是在 ASCII 编码下使用的,中文字符属于 Uni 编码,在 ASCII 编码中一个中文字符占3个位置,所以 2 是提取不出任何东西的,将2 改为 3就可提取出第一个字符。但这并不能满足我们的需求,不能每次提取中文字符时都去算占多少位置。在 uni 编码下有 usubstr 函数,可以识别中文字符,更一般的来讲,基本上所有 str 类函数前面加个 u 就都可以使用。输入 gen b= usubstr(location,1,2),可看到结果正常。

字符串长度函数为 strlen() 与 ustrlen()。其中 ustrlen 真好用,英文数字字符和 strlen 返回一样。如果要计单词数,用 wordcount (),输入dis wordcount(location),可以看到都被视为 1个单词。输入 egen e=concat(location code industry year), p(" "),再输入 dis wordcount(e),可以看到河北省的单词数是3,因为其 industry 为缺失值。 

字符串重复并没有函数,查看文档时会显示 strdup,但是看清楚,介绍里面说没有这个函数。只需字符串乘倍数即可。gen f=year*2 可看到已经重复。dis ("中国人不说洋文")*4,注意使用 dis 展示字符串乘以倍数时,必须带括号,不信你去括号试一下。

字符串替换函数为 subinstr 与 subinword,前者替换字符,后者替换单词。导入 exe_str数据。str是一个单词,而strlist是4个单词构成。

输入 gen new= subinstr(str, "1", “省", .)。该命令表示将 str 中的字符 1 替换为省,点表示全部,若只想替换前2个则输入2,前n个输入n。

输入 gen new2= subinword( strlist, ”1", "省", . )。该命令表示将 strlist 中的所有单词 “1” 替换为省,但是输入后你可看到结果如下。这是因为 "12" 是一个单词,其虽然包含字符 “1”,但并不是单词 “1”,所以仍然不变。

输入 gen new3= subinstr(strlist, "1", "省", .)。该命令将 strlist 中的所有字符 “1” 替换为省。

字符串中空格删除函数为 trim。先从英文字符的空格删除开始介绍。dis trim(" abc  "),随便输入一个字符两端留下空格,trim会删除两端的空格。如果只想删除左端,则输入 ltrim,右端则为 rtrim。输入 dis itrim("I find a      love"),对于这种中间空格很多的情况,itrim可以将多个空格删除的只剩1个。回到数据中来,我们想把 new2 中的空格删除,输入 dis itrim(new2)会发现没有反应。对付中文空格来说,更好的方式是使用字符串替换函数将空格去掉,dis subinstr(new2, " ","", .)。此时完成了空格的删除。

字符串定位函数是 strpos 与 ustrpos。pos是position的简写,我们想知道字符 “1”在 strlist 中的第几个位置。输入 dis strpos(strlist, "1"),输入 dis ustrpos(strlist, "1"),会得到以下结果。在 ASCII编码下,中文字符一个占 3个位置,英文占1个,因此是第8个位置。而ustrpos可以识别中文字符,中文字符只占1个位置,因此结果是处于第4个位置。

另外,ustrpos 还可以根据位置查找是否存在字符返回逻辑值。输入 dis ustrpos(strlist, "1", 11),可以得到0。这表明在 strlist 的第11个位置并没有字符串“1”,若改为4,由于第四个位置确实有字符“1”,因此会返回位置4。

但是有时候一个字符串内字符串可能会重复,如果我们想要子串最后出现的位置,就需要使用 strrpos 和 ustrrpos,这个 r 我的理解是 repeat  right,重复的子串在最右边的位置。参展下图输入代码。

字符串匹配函数为 strmatch 和 regex 类。strmatch 是使用模糊字符匹配,dis strmatch("17.29","1*9"),通配符 * 表示0或0以上个模糊字符,输入 dis strmatch("17.29","1?.29"),通配符 ? 表示 1 个模糊字符。注意一定是模糊字符匹配,不能准确字符。输入下列第一行返回为 0,而使用通配符则为 1。

以 exe_str 数据为例,输入 gen a=strmatch(new1, "*区"),可以看到只有北京有区,所以其他的数值均为 0。 

regex 类函数有三个,均与正则表达式相关。若想详细了解,请查看这篇文章:stata命令详解-函数regexm/regexr/regexs - 简书

第一个是字符串匹配函数 regexm()。引用 auto 数据集,输入 gen pipei= regexm(make, "^B"),而后 list make if pipei == 1。 这一命令的意思是,对 make 中的字符,任何以 B 开头的都赋值为 1, 否则为0。其中 “^B” 是正则表达式, ^ 表示开头的意思。输入 list make if regexm(make, "^B" ) == 1 可以得到相同的结果。

当然,不用正则表达式也行,在正则表达式位置输入字符,输入 list make if regexm(make, "ck") == 1。以数字结尾的正则表达式值得学习,list make if regexm(make, "[0-9]$")==1。这一表达式,[]表示以括号内的表达式进行单个字符的匹配,- 表示范围,[0-9] 表示数字 0-9,美元符号表示结尾,因此 "[0-9]$" 表示以数字结尾的字符。这一命令列示以数字结尾的厂商名。

第二个是字符串替换函数 regexr(r的后缀表 replace)。gen make2 = make,再输入 replace make2 = regexr( make2, "^B.*[0-9][0-9][0-9][a-z]$", "found" ),而后 list make make2 if make!=make2。这个正则表达式,表示以 B开头,".* "其中点字符指的是匹配一个任意字符(我应该没理解错),星号表示前一个字符匹配 0 次或任意次,也就是说 “.*”符可以不匹配字符或匹配任意字符,[0-9][0-9][0-9][a-z]$ 表示三位数字字母结尾。make 2 是 make 的复制,可以看到符合该条件的只有第55个样本,其名字在备份列改成了 found。

再练习一下 regexm。如果想找出中间包含字符点的厂商名,则输入 list make if regexm(make,"^[A-Z].*[.].*")。

如果想找出名字中有数字在词开头的,则输入list make if regexm(make, "^[A-Z]*.*[0-9]+"),这一命令的意思是,制造商以 [A-Z]字符开头,[A-Z]*表示前面的字符匹配0次或多次,而“.*”表示不匹配或匹配任意字符,+号表示在其前面的字符[0-9]匹配至少一次。

regexs 是第三个函数,我没整明白,想了解的可以看前面附加的网址。与 subinstr, strmatch相比, regex 类函数可以实现更复杂的字符处理。

大小写

小写为 lower(),大写为 upper(),输入以下命令进行查看。


5.数据处理的一些补充函数

以上函数可以满足基本的处理,但搜集资料时我发现有一些函数比较常见,故补充。

cond 函数:根据条件返回指定值

cond 函数和 excel 里的 if 嵌套很像。调用 auto 数据,查看缺失值,知道 rep78是有 5 个缺失值的。输入 gen cond_a= cond(rep78>2, 1, 0),sort cond_a。该命令的意思是,如果 rep78 的值大于 2,返回数值1,否则返回 0。cond_a 的值符合我们的命令,但注意缺失值处为 1,这是因为 cond 函数将缺失值视作一个无穷大的数字。

如果想要缺失值参与运算,请输入 gen cond_b = cond(missing(rep78), . , cond( rep78>2, 1, 0))。查看数据可得下图。这一条命令是嵌套语句,如果 rep78 是缺失值返回缺失值,否则返回 cond(rep78>2, 1, 0),后者我们已经解释过了。

cond 还可以用于筛选非零非缺失值。输入 gen cond_c = cond(rep78, 1, 0 , . ),sort cond_c 查看一下数据。该命令的意思是,如果 rep78 非0 且不为缺失值,则返回 1,如果是 0 则返回 0 ,如果是缺失值则返回缺失值,可以看到排序后最下面五个是缺失值,其余均为 1。这个我觉得可能很有用。注意,这一条命令有 4 个参数,而上一段的只有 3个参数。4个参数的不能加判断条件,否则最后的缺失值项会被忽略。不信你可以输入一下, gen cond_d = cond(rep78>2, 1, 0, . )。

这函数很实用,use exe.dta, clear。我们现在想知道“省”与“自治区”字符所在位置。

输入 gen po=ustrpos(location, "省") | ustrpos(location, “自治区"),输出结果如下,可以看到联合时没用,别问,我也不知道为啥。(题外话,如果想实现字符的多条件时,不应该是在函数内输入或符号,而是函数写完后写个 | 与 &,再写一个函数)

输入 gen po2=ustrpos(location, "省"),gen po3=ustrpos(location, “自治州"),list location po* 结果如下。我们想将自治区的位置加 po2 上就能达成我们的目的。

 此时使用 cond 函数,replace po2=cond(po2=0, po3, po2)。

inrange 与 inlist:判断是否符合条件返回逻辑值

两者为判断函数。输入 gen ir = inrange(price, 5000, 10000 ),sort ir。若车的价格在 [5000, 10000] 之间则返回 1,否则为 0,注意是闭区间。输入 gen ir_2 = inrange(rep, 2, 4 ),可看到缺失值不参与运算。

输入 gen in_rep=1 if inlist(rep78, 1, 2, 3),list make price if in_rep==1。这一条命令的意思是,如果将维修次数 (rep78) 中为1,2,3 的赋予数值1,并存储到变量 in_rep 里。列示出我们可接受维修次数的品牌商对应的车的价格。inlist(var, a, b, c, d, …) 这是 inlist 函数的写法,其中 var 是需被判断的变量,abcd 等判断条件,可以写很多个。

inlist 也支持字符运算。输入 gen in_make=inlist(make,"Toyota Celica","Toyota Corolla", "Toyota Corona"),输入 br make in_make,可以看到选项中参数值为1,其余为 0。注意,inlist 不能模糊匹配,例如 gen in_make2 = inlist( make, "Toyota"),再查看你会发现都是 0。模糊匹配我觉得可能需要 regexm 写正则表达式。

inlist2

inlist 的升级版是 inlist2,ssc install inlist2。升级有二,第一输入字符时不用加引号,第二会自动生成新的变量存储结果,默认名字叫 inlist2。

我觉得这个用来分组好用。sysuse auto, clear 后输入 inlist2 rep78, values(2, 3, 4) name(inlist2)。以 rep78作为被判断变量,若其中包含 2,3,4则赋值为 1,存储到 inlist2中,如果要改名就把name里的换掉即可。结果如图。该函数也支持字符运算,输入时不加引号。

clip 函数

重新导入 auto 数据集。输入 gen clip_rep = clip(rep78, 2, 4),sort clip_rep。该命令指,如果rep78 <=2,其值为2;如果rep78>=4,其值为4;若 rep78取值在两者之间,不改变数值。

我们现在需找出变化的值。gen x=1 if clip_rep78!=rep78,br rep78 clip_rep78 x if x!=. ,可以得到下图。结果显示很好地显示了 clip 函数的运算结果。

作用有些像缩尾,但更加精细。winsor2 是按百分比,如果想要 a 超过 10000的被缩尾,使用 winsor2不一定能做到,而 clip 函数就可以。 

L. F. D.  :滞后项、提前项、差分项

use Q1.dta,清理一下重复值,设定面板数据。输入 bysort county: gen gdp_L=L.gdp,再输入 bysort county: gen gdp_F=F.gdp,bysort county: gen gdp_D=D.gdp 输入 br county year gdp*。

L.表示生成滞后项,F表生成提前项,D表示差分项,差分为后一年与前一年差分。如果想生成二阶滞后,三阶差分,则应该为 L2.gdp,D3.gdp。面板数据时很有用。

collapse 函数:面板数据浓缩

该函数用于分组统计。我们前面介绍了很多分组方法,可以分组后再用 by 进行分组计算。但这样处理面板数据是有缺陷的。输入 use Q1.dta, 设定为面板数据后,假设我们现在想要计算每个县8年的平均 gdp,分组计算会导致一个县对应8个重复值(因为有8年),如何将这些值浓缩成一个,也就是说一个县对应一个值,collapse 就可以做到。

输入 collapse (mean) gdp_mean=gdp, by(county) cw。这条命令的意思是说,生成县的均值,并存储到新变量 mean_gdp 里,cw 表示删除缺失值。注意,命令运行后会生成一个新文件,分类变量在第一列,而后是其他变量。

mean可以替换为其他统计值,help 查看。产生新变量无需写 gen,写新变量名=所算变量的统计值即可。重新导入Q1 文件,preserve,输入 collapse (median) gdp adm, by(county) cw。可以看到不写变量名时,新文件会用命令中的参数作为列名,restore。

astile 分组函数

我们之前介绍了很多生成分组的方法。现在回顾一下,第一种,生成新变量,利用 if 语句或 cond 生成分类值,用 label defince 与 label values 语句实现分类,优点在于有标签查看时很直观;第二种,group 函数,该函数支持数字与字符运算,并可多变量联合生成分类变量,非常方便;第三种,对已经做好的分类字符使用 encode 函数,会自动生成对应列表;第四种,inlist2 可以将一个列表中符合判断条件的值视作一类,其缺点在于不能设置多组,只能将一个组分成两类(也可能是我对这个函数的理解不够深刻);

astile 函数是可以实现分位数分组的函数。导入 auto 数据,输入 astile pr_1=price, nq(4) ,这一命令的意思是,依据 price 的值从小到大分成四组,也就是按照 4 分位数的方法分组,25%以下的赋值 1,75%以上的赋值 4。

如果说想在不同组间进行分类,那么输入 astile pr_2=price, nq(4) by(foreign),该命令指根据是否为国产车,在国产车和进口车内各自设置四分位数进行分组。但是这种方法会造成不同组的标准不一样。

输入 astile pr_3=price, nq(4) qc(foreign==0),这个 qc(foreign==0) 就是确立以国产车的分位数值为标准,进口车分组为 1 还是4根据国产车的分位数值。根据help文档,qc 选项是以某个类别划分分位数作为分组标准,其他组的数值分组依据基组的分组数值。简单来说 qc 的意义在于选择谁作为分组的基准。

接下来深入了解一下 astile 的运作原理,具体了解上述三个命令结果的区别。因为对数值进行分组是很常用的操作,我论文当时是想做个分组回归,根据一个市内不同的县的财力水平划分为穷县,发展县与富县,但是当时 stata 没学明白所以没做。看一下三个命令的结果对比,能够更深刻地理解,进而能放心使用这个函数来分组。

输入sum price, d 后输入 by foreign: sum price, d。这个 d是 detail的缩写,查看并记录分位数数值。

可以看到整体价格的 1 分位数临界值为 4195,2分位数是 5006.5。国产车 1 分位数是 4184,2 分位数是 4782.5。进口车的 1 分位数是 4499,2分位是 5759。对价格排序,输入 br make price foreign pr*。我们对下图进行解读。

pr_1 为整体划分四分位数分组,19及以上为1,20以下开始为2。看第20行,进口车卖4296元,pr_2为1,因为pr_2是分组设置分位数,进口车的1分位数是4499,小于该值所以为1。但是 pr_3却是2,pr_3是以国产车的分位数值为分组基础,国产车1分位数是4184,2分位数是4782.5,价格在两者之间,所以在第2组。

在第26行,进口车价格为4499,超过整体划分的1分位数值4195,所以pr_1为2。但是小于进口车的1分位数值4499,所以pr_2为1。以国产车分位数为基组时,大于国产车1分位数4184,小于2分位数,所以pr_3为2。第31行请自行判断。

pr_4 和 pr_5 是怎么来的呢?pr_4将 by 和 qc 都附加上了: astile pr_4=price, nq(4) qc(foreign==0) by(foreign)。注意如果 by 和 qc 选项都附加上了,那么 foreign 的组别无论如何都为1,别问为啥我也不知道,输入  list make price pr_4 if foreign==1&pr_4!=1,你会发现啥也不返回,那么我们换种方法,输入  list make price pr_4 if foreign==1&pr_4==1。如果不想数 foreign的情况,输入 table foreign pr_4, nototals。你会看到进口车都只为1。

而 pr_5 由这一条命令产生 astile pr_5=price if foreign==0, nq(4)。输入 missings report ,你可以看到 foreign 全是缺失值。因为 if 语句只生成了国产车的,进口车没生成所以为缺失值。

astile 好像源于 xtile,xtile 还有个兄弟 pctile,pctile还可以作为函数默认选项_pctile。但是我没整明白,网上查不到有用的信息,help文档又比较抽象。顺带吐槽一句,经管论坛上,有的人回答问题总是叫别人查 help 文档,或者啥都不看直接叫人先贴一部分数据,你有空打那段话早就把人家问题解决了,不会还在那墨迹浪费人时间,希望大家以这样的行为为耻,不懂就别装懂,营造高质量中文社区氛围,从你我做起!

xpose 数据转置

转置,webuse xposexmpl, clear 浏览数据。输入 xpose, clear varname format。clear是必加项,varname是转置后将原来变量名单独存到一列,否则无变量名,可以自己拿 preserve 和 restore 对比一下。format是规范格式,如果不规范,第一列小数点很长。如果设置具体格式,可以写 format(%6.2f)这种。

reshape 长宽数据转换

reshape命令有些难懂,但是很实用,我把help文档内的五个例子都讲一下。如果要转换为宽数据,后面就跟 wide,转换为长数据跟 long。

第一个例子,先输入如下代码。这份数据,inc80与ue80后面的数字是年份,我们希望得到 id year sex inc ue。

输入 reshape long inc ue, i(id) j(year)。这一条命令,long表示要转换成长数据,inc与ue表示要留下的变量,i(id)指的是唯一标识符,j(year)转换成长数据时表示新生成变量year。也就是说,上图中的 rep80, rep81, rep82只保留rep,剩余的存入新变量 year。

第二个例子,输入以下代码。想要转换成 id year sex inc的长数据,也就是留下rep,剩余的生成新变量 year,但是注意 id 是有重复的,所以用 id sex 做唯一编码,单独输入 id 时会报错,输入 reshape id 会显示具体错误。

 按照上文描述,输入 reshape long inc, i(id sex) j(year)。

第三个例子,输入以下代码。该数据与上述区别在于多了个字符r,想要将 inc80r中的80摘出去,需要用到占位符@,也就是 inc@r,这样保留的是 incr。至于明明中间是数字,应该用数字占位符#,但却用字符占位符@,我想不太明白。

 输入 reshape long inc@r ue, i(id) j(year)。

 第四个例子,输入以下代码。想要将其转换为 id sex kids inc 的长数据,也就是将 incm incf 拆成 inc 与 sex 变量,但 sex 内的 m 与 f 是字符,因此需加入 string。

 输入 reshape long inc, i(id) j(sex) string。

前面都是转换长面板,现在介绍如何转换为宽面板。preserve一下,现在是由宽转换为长的,还原之前只需要 reshape wide即可,长转宽后转长同理。restore。

长面板转换为宽面板参数意义有些不一样。我们现在想要 id kids incf incm,输入 reshape wide inc, i(id) j(sex)。j 里面表示的是存在的变量,inc 是被操作的变量,也就是想将 j 内的变量加到 inc 后。而之前的 "reshape long inc, i(id) j(sex)" 是表示将原先的变量名拆成两部分,一部分保留为 inc,另一部分存储到新生成的变量 sex 内。

长转宽是将 j() 内添加到 inc 后面,两个合成一个变量;宽转长是将原先的变量名拆成两部分。

 第五个例子,输入以下代码。想要 hid incf90 incf91 incm90 incm91 这种宽面板数据。

第一步,将 sex 加到 inc 后面。输入 reshape wide inc, i(hid year) j(sex) string,由于 sex 是字符存储所以要加 string。默认加到名字后面,若要加到名字前面,需要占位符,将 inc 改为 @inc。

 第二步,将 year 加到 incf 与 incm 后面。输入 reshape wide incf incm, i(hid) j(year)。如果变量很多,可以用占位符,将 incf 与 incm 改为 inc@ 即可。

练习数据及文档:

链接:https://pan.baidu.com/s/1wptLggEJsjY7ZYOd-_LirA 
提取码:49xa

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/534609
推荐阅读
相关标签
  

闽ICP备14008679号