前两天一个做实证研究的朋友发消息问我,说他用Stata跑固定效应,核心变量的系数和预期完全反着来,怀疑是模型设定出了问题。我让他把数据发过来扫了一眼,问题压根不在模型:他的"年份"列里同时存在2020、2O20(字母O)、2020年这三种写法,Stata把它们当成了三个不同的字符串,xtset之后整个面板结构就是错的,回归能对才怪。这种事我见得太多了。计量经济学的后半程是模型,前半程其实是数据清洗,而前半程恰恰是论文里最不愿意写、课上最不愿意讲、新手最容易糊弄过去的部分。stata数据清洗这件事,说起来简单,无非是把脏数据弄干净,但真正落到操作层面,从缺失值的编码规则到字符变量的前后空格,从面板主键的唯一性到merge时的匹配失败,每一步都埋着雷。这篇东西我按自己在实际项目里的清洗顺序来写,从拿到原始数据的第一步体检,一直写到交付前的自查,尽量把每个命令背后的"为什么"讲清楚,也让刚接触stata的新手能直接照着做。
1. 拿到原始数据先别碰模型:三项体检工作
很多人拿到Excel或者CSV的第一反应是直接import excel然后reg,跑出来系数显著就万事大吉。这个习惯非常危险。数据清洗的正确起手式是"只读不动"——先看清楚手里这堆东西到底是什么,再决定怎么动手。我一般会把体检拆成三件事:看结构、查主键、写字典。
1.1 先看数据长什么样:describe与codebook的分工
describe和codebook这两个命令看着像,用途其实完全不同。describe回答的是"有哪些变量、什么类型、占多少存储",属于骨架层面的信息;codebook回答的是"每个变量的取值分布长什么样",属于血肉层面的信息。清洗阶段两个都要用,但顺序是先describe后codebook。
use "raw_data.dta", clear describe describe, fullnamesdescribe的输出里我会重点盯三个地方。第一是变量类型,str开头的全是字符型,如果你预期某个变量应该是数值型(比如收入、年龄、GDP),结果它是str,那说明录入过程里混进了非数字字符。第二是存储类型,byte能存的整数范围只有-127到100,int是-32768到32767,很多时候从Excel导入的ID变量会被自动识别成float或double,做精确匹配时会出现1.0000001 ≠ 1这种鬼打墙。第三是变量名,Stata变量名不能有空格、不能以数字开头、不能超过32个字符,中文变量名虽然Stata 14之后能用,但强烈不建议,因为在do文件里敲中文变量名是自找麻烦。
codebook适合逐个变量细看,尤其是分类变量和疑似有问题的连续变量:
codebook, compact codebook 行业代码 地区 年份, problemsproblems这个选项经常被忽略,它会把变量里可能存在的问题列出来,比如全缺失、只有唯一值、字符串变量有前导空格等。我一般用codebook, compact先扫一遍全部变量,把那些"唯一值只有1个"或者"缺失率明显异常"的变量挑出来重点处理。这套组合下来,基本能在十分钟内对数据的质量有个整体判断,比直接跑回归然后回来debug要高效得多。
1.2 主键唯一性核对:isid是你的第一道防线
面板数据、合并数据、交叉表数据,都依赖一个东西:主键唯一。主键不唯一,后面所有的合并、所有的xtset都会出问题,而且报错信息往往指向别处,让人越查越乱。所以我在体检阶段一定会做一遍主键核对。
isid id isid id yearisid的作用是断言"这些变量的组合是唯一标识",如果出现重复它会直接报错并告诉你重复了几条。对比之下,duplicates report id year会给你一份详细的重复情况统计,更适合用来排查。我通常两个都用:isid确认结论,duplicates report了解细节。
注意:
isid报错不代表数据不能用,只代表主键设计需要调整。有时候是数据本身有多条记录(比如一个人同一年的多次调查),这时候要么改主键,要么先聚合到你要的分析层级。
这一步还有一个容易被跳过但很重要的动作:检查主键变量本身有没有缺失。一条记录的id是缺失的,那它既不属于任何一个个体,也无法被任何一次合并匹配到,属于彻底的脏数据。用count if missing(id)就能查出来,出现这种情况基本都是导入环节的错位。
1.3 手写一份数据字典:把清洗逻辑前置
这是我个人最坚持的一个环节,也是大多数人嫌麻烦会跳过的一环。所谓数据字典,就是一张表,写清楚每个变量的含义、单位、取值范围、缺失值编码、需要做的转换。看起来是在做无用功,实际上它把"边清边想"变成了"想清楚再清",能省掉后期大量的返工。
我的数据字典一般包含这么几列:
| 字段名 | 类型 | 含义 | 单位 | 缺失值编码 | 待处理事项 |
|---|---|---|---|---|---|
| id | str20 | 企业唯一标识 | - | 无 | 转数值型前先查非数字字符 |
| year | str8 | 统计年份 | 年 | "." | 剔除"年"字后缀,转数值 |
| revenue | double | 营业收入 | 万元 | -99, 空 | 负数视为缺失,缩尾1% |
| region | str12 | 省份名称 | - | 无 | 统一简称,encode为分类 |
这张表不用写得多正式,放在do文件开头用注释写出来也行。关键是它能让你在写清洗代码的时候心里有数,知道每一步在干什么、为什么要这么干。等到三个月后你要改数据或者别人要接手你的数据时,这张表的价值会成倍体现出来。我自己吃过这个亏,有一个项目半年后回来改样本,面对一份没有任何注释的do文件,光搞清楚当年为什么要剔除某些观测就花了一整天。
2. 缺失值处理:Stata的规则和别的软件不一样
缺失值大概是数据清洗里最考验判断力的一块。它不是简单的"有空的就补上"或者"有空的就删掉",而是要先弄清缺失的类型,再决定策略。而在做这些判断之前,你必须先理解Stata处理缺失值的一套独特规则,这套规则和Excel、SPSS、Python都不太一样,踩过坑的人都知道有多难受。
2.1 最大的那个数:缺失值在Stata里的真实身份
Stata里数值型变量的缺失值用.表示,很多人以为它是个"空",实际上它是一个数值,而且比任何正常数都大。这条规则带来的后果是:任何比较运算,缺失值都会被当成最大值来处理。
* 你可能以为这是在筛选"收入大于0"的样本 keep if income > 0 * 实际上缺失值也被保留了,因为 . > 0 在Stata里成立 * 正确写法 keep if income > 0 & !missing(income)这个坑我在至少三个不同的项目里遇到过。有人做完筛选兴冲冲地跑回归,样本量莫名其妙比预期多了几百条,查了半天才发现是缺失值混进来了。凡是涉及>、>=、<、<=的条件筛选,或者涉及排序、求最值、生成分位数的操作,都要先考虑缺失值会不会捣乱。max()函数在遇到缺失值时返回缺失值,egen的rowmax就会因为一个变量缺失而整行变空,这也是常见的陷阱。
Stata还支持扩展缺失值,.a到.z一共27个,而且.a < .b < ... < .z < .。这个设计看着有点怪,实际用途是区分不同原因的缺失:.a表示"拒绝回答",.b表示"不适用",.c表示"数据丢失"。做问卷数据的时候我经常用这套编码,这样后期可以针对不同原因采取不同的处理策略。比如"不适用"的缺失可以直接删除,"拒绝回答"的缺失可能需要做敏感性分析。
2.2 把"伪缺失"挖出来:mvdecode与misstable
很多原始数据里的缺失不是真的空,而是用-99、-999、9999、0或者"无"、"未知"、"NA"这类字符串标记的。如果你直接跑summarize,这些值会被当成正常数值参与计算,均值立刻就被拉偏了。所以处理缺失的第一步是识别"伪缺失"。
* 批量把数值型伪缺失码转成真正的缺失 mvdecode revenue profit assets, mv(-99 -999 9999) * 处理多个不同的缺失码 mvdecode revenue, mv(-99=.a \ -999=.b) * 查看缺失情况 misstable summarize misstable patternsmisstable summarize会给你每个变量的缺失数量、缺失比例,以及"唯一值数量",如果某个变量有大量缺失或者只有少量唯一值,它会被列出来警示。misstable patterns则展示缺失的组合模式,这个特别有用——如果缺失是随机分布的,各变量的缺失模式应该五花八门;如果你发现有一大批观测"同时在五个变量上都缺失",那很可能不是随机缺失,而是某一批样本本身的采集出了问题,需要单独查一查。
对于字符型变量的伪缺失,得先用replace批量替换,再考虑要不要destring:
replace 备注 = "" if inlist(备注, "无", "未知", "NA", "N/A", "-")这里提醒一个细节:inlist最多只能带10个参数,要匹配的字符串多的话得改用inlist嵌套或者strpos配合|。另外注意字符串比较是区分大小写的,"NA"和"na"在Stata眼里不是一回事,清洗前最好先tab 备注, missing看一眼所有的取值形态,别想当然。
2.3 删、补还是留:三种策略的适用边界
识别出缺失之后,怎么处理是纯判断问题,没有标准答案,但有三条基本的判断依据:缺失比例、缺失机制、以及你的模型对缺失的容忍度。
删除是最简单的做法,但适用范围有限。drop if missing(x)会丢掉所有x缺失的观测,如果缺失比例低于5%而且缺失是随机分布的(MCAR),这么做基本没问题。但如果缺失比例到了20%以上,删除会显著改变样本构成,万一缺失还和某个关键变量相关(比如低收入群体更不愿意报告收入),那你的样本就产生了选择性偏差,回归结果直接失效。
填补是另一个方向。最简单的是均值/中位数填补,但这种方法会人为地压缩变量的方差,让标准误变小,做显著性检验时容易"假显著"。稍微好一点的是分组均值填补,比如按行业、年份分组后用组内均值:
bysort industry year: egen mean_rev = mean(revenue) replace revenue = mean_rev if missing(revenue) drop mean_rev更严谨的做法是回归填补或者多重插补(Stata的mi系列命令)。但实话讲,如果缺失比例不高,多重插补带来的复杂度往往不值当;如果缺失比例很高,什么填补方法都救不回来,因为填补本质上是猜,猜得再准也不如没有缺失。
保留是我在很多场景下推荐的做法。Stata的回归命令默认会做"逐列表删除"(listwise deletion),也就是说它只会在每个回归里丢掉相关变量缺失的观测,不影响其他分析。这样做的好处是每一步都保留了最大样本,坏处是不同回归的样本量会不一样,比较系数时要注意这一点。我的习惯是在do文件里明确标注每一步的样本量,最后做一次"全变量完整样本"的稳健性检验,看结果是否一致。
提示:无论选哪种策略,都要在do文件里留下清晰的注释,说明为什么这么选。审稿人或者合作者看到你的缺失处理方式,往往比看到你的回归结果更能判断工作是否扎实。
3. 异常值与离群值:判断标准比处理手段更重要
异常值的处理,新手最容易犯的错误是"看到极值就想删"。实际上,处理异常值的前80%时间应该花在判断上——这个值是录入错误、真实极值,还是模型设定问题——只有剩下20%的时间才用来决定怎么处理。跳过判断直接动手,本质上是对数据的不尊重。
3.1 用描述统计和可视化把可疑区间圈出来
第一步永远是看分布,不是看数字本身。summarize, detail会给出一套完整的分位数信息,其中最有价值的是p1、p99以及最小值最大值:
summarize revenue, detail拿我自己处理过的一个企业数据来说,营业收入的最小值出现了-5000,最大值到了9.8e+11。最小值为负,在营收这个语境下显然是异常的(除非是退款或者更正),而最大值9.8e+11意味着9800亿,远超样本里任何一家企业的合理规模。这两个值一冒出来,整个变量的问题就暴露了。
光看数字还不够,画图能帮你发现描述统计发现不了的问题。直方图看整体形态,箱线图看离群点,散点图看变量间的关系:
histogram revenue, bin(50) graph box revenue, mark(1, mlab(id)) twoway (scatter revenue assets), mlabel(id) msymbol(oh)箱线图特别值得用,因为它的离群点定义是有统计学依据的:超出Q1-1.5IQR和Q3+1.5IQR的点。这个标准和你在论文里用的标准一致,两相印证比较放心。mark()选项可以把观测标签标出来,配合mlabel(id)能直接看到是哪些样本出了问题,便于回到原始记录核对。
3.2 缩尾和截尾:两种处理方式的机制差别
确认要处理之后,主流做法有两种:缩尾(winsorize)和截尾(trim)。两者一字之差,效果完全不同。
缩尾是把超出分位点的值"拉回来",比如99%分位是1000万,那把1500万改成1000万,样本量不变;截尾是把超出的观测直接删掉,样本量会减少。学术论文里绝大多数用的是缩尾,因为删样本会影响代表性。
Stata本身没有内置winsorize命令,需要装winsor2这个外部包,它比老牌的winsor好用得多:
ssc install winsor2, replace * 双侧1%缩尾 winsor2 revenue, replace cuts(1 99) * 单侧缩尾,只处理右侧 winsor2 revenue, replace cuts(0 99) * 生成新变量而不覆盖原变量 winsor2 revenue assets profit, suffix(_w) cuts(1 99)我一般用suffix(_w)生成新变量而不是直接replace,原因是保留原始变量可以在后面做稳健性检验——用缩尾前和缩尾后分别跑一遍回归,如果结果方向一致,说明你的结论不是靠处理异常值"做"出来的,这比什么稳健性检验都有说服力。
截尾用drop配合_pctile或者winsor2的trim选项:
_pctile revenue, p(1 99) drop if revenue < r(r1) | revenue > r(r2)缩尾比例的选择是个老生常谈的问题。1%是国内期刊最常见的,国外顶刊有时候用0.5%或者直接把所有连续变量都做1%缩尾。我的建议是:如果变量分布本身很偏(比如收入、企业规模),1%是合理的;如果变量本来就很规整(比如年龄、评分),做缩尾反而多余。另外,缩尾的变量数量要一致——不能只缩尾核心解释变量而不缩尾被解释变量,那样的处理是不对称的,容易引起质疑。
3.3 动手之前先问三个问题
缩尾命令就那么几行,难的是决定要不要缩。我在实际操作中会先问自己三个问题。
第一个问题:这个异常值是录入错误吗?如果是,找到原始记录核对,然后直接改正或者设为缺失。这比缩尾更合适,因为缩尾会把一个明显的错误值改成一个看似合理的值,反而掩盖了数据质量问题。
第二个问题:这个值是真实存在的极值吗?如果是,那它携带着真实信息,缩尾会损失这部分信息。比如研究企业创新,样本里有一家头部企业专利数远超其他样本,这个极值本身就是重要发现的一部分,直接缩掉可能就削弱了你的结论。
第三个问题:这个值是不是反映了变量构造的问题?比如你用"总资产/员工数"构建人均资产,结果有一家企业员工数录成了个位数,人均值直接爆表。这种情况下要改的是分母变量,而不是缩尾结果。
问完这三个问题再决定怎么处理,比上来就winsor2, cuts(1 99)要靠谱得多。顺便说一句,无论做了什么处理,都要在做论文的时候写清楚,包括缩尾的比例、涉及的变量、处理前后的描述统计对比。这是基本功,也是审稿人最容易抓住的地方。
4. 变量类型与字符串清理:让Stata真正"认得"你的数据
从Excel或者CSV导入的数据,字符型变量占的比例往往高得吓人。Excel里没有类型概念,一个"2020"可能被识别成数字,也可能被识别成文本,全看单元格格式;从网页抓的数据更是如此,金额里带着"¥"、百分比带着"%"、日期带着"年"和"月"。这些字符不清理干净,Stata就没法把它们当数值来算,后面的所有分析都无从谈起。
4.1 destring报错时的三种典型原因
destring是把字符型转数值型的标准命令,用法很直接:
destring revenue, replace destring revenue, replace ignore("¥,") destring revenue, generate(revenue_num) force但实际用起来,它经常报错,而且报错信息不够具体。我总结下来最常见的三种原因:
第一种是混入了非数字字符,最常见的包括千分位逗号、货币符号、百分号、前后空格。用ignore()可以忽略指定字符,用percent选项可以处理百分号。但要注意ignore()是"删除后再转换",所以"1,234"加ignore(",")能转成1234,而"12%"加percent选项会转成0.12。这个区别很重要,别搞混了。
第二种是前导或者末尾空格。"2020 "在Stata眼里不等于"2020",destring会直接报错。这种情况要先replace x = strtrim(x),或者用itrim()处理内部多余空格。空格问题在手工录入和从PDF复制数据时特别常见,我基本会在每个字符变量的清洗开头都加一句replace x = strtrim(x),属于防御性编程。
第三种是本身就含有字母,比如"2O20"里的字母O、"l0000"里的字母l,这类是录入错误,destring转不了。这种情况下不能硬转,得先定位问题记录。我常用的方法是:
* 找出所有含非数字字符的观测 gen flag = regexm(year_str, "[^0-9]") list id year_str if flag == 1regexm是Stata的正则匹配函数,[^0-9]表示"任何不是数字的字符"。这个思路可以套用到所有需要验证数据格式的场景,比如验证手机号、身份证号、邮编的格式是否合规。
4.2 字符串清洗:trim、subinstr与正则表达式
字符串清洗的核心就三类操作:去空格、替换字符、提取信息。Stata在这方面的函数不算丰富,但组合起来够用。
去空格有strtrim(去首尾空格)和itrim(把连续多个空格压缩成一个),还有stritrim这种直接作用到变量上的命令。替换用subinstr:
replace province = subinstr(province, "省", "", .) replace company = subinstr(company, "有限公司", "", .) replace company = subinstr(company, " ", "", .)subinstr的第四个参数是替换次数,写.表示全部替换。这个小细节容易错,写成数字的话只会替换前N次,剩下的还在,看起来像"替换没生效"。
更复杂的清洗用正则。Stata支持的正则函数主要有regexm(是否匹配)、regexs(提取匹配组)、regexr(替换),以及ustrregexm、ustrregexra这些支持Unicode的版本。处理中文的时候一定要用ustr开头的版本,否则会乱码。
* 从"2020年第一季度"中提取年份 gen year = regexs(1) if regexm(period, "([0-9]{4})年") * 把任意长度的空格替换成单个空格 replace text = ustrregexra(text, "\s+", " ", .) * 提取括号内的内容 gen note = regexs(1) if regexm(desc, "((.*))")这里要提醒一句,正则表达式虽然强大,但调试起来很痛苦,尤其是在Stata里没有实时预览的情况下。我的习惯是先用list挑几条有代表性的样本,把正则写出来单独测试,确认能匹配上了再批量应用。批量应用之前先gen一个新变量存结果,别直接replace原变量,出错了还能回退。
4.3 分类变量的编码与标签管理
分类变量是数据清洗里最需要"规划"的部分。Stata里分类变量有两种存储方式:字符串形式(比如"北京"、"上海")和数值形式配合值标签(1表示北京,2表示上海)。后者在回归和作图时更方便,所以标准流程是把字符串转成数值:
encode province, gen(province_id) label list province_idencode会自动按字母顺序给每个唯一值分配一个数字,并生成对应的值标签。这个自动排序有时候不符合你的需求(比如你想让"东部"排在"西部"前面),所以要label list看一眼,需要的话手动调整:
label define province_lbl 1 "东部" 2 "中部" 3 "西部", replace label values province_id province_lbl反过来,如果要把数值转成字符串,用decode。如果只是想把已有的数值标签换个名字,用label copy和label values配合。
值标签这个机制我刚接触的时候觉得多余,为什么不用个普通的字符串变量呢?后来才明白它的好处:回归的时候Stata会自动识别值标签并用它来命名虚拟变量,i.province_id生成的虚拟变量名是1.东部、2.中部这种可读性很强的形式;作图的时候图例直接显示标签文字;而且底层存储是数字,处理速度比字符串快得多。
提示:
encode会生成一个新的数值变量,原来的字符串变量还在。清洗完成后记得把不需要的字符串变量drop掉,一是节省内存,二是避免变量列表混乱。我一般是rename原变量加后缀_str,确认无误后再统一删除。
4.4 日期变量:把"2020-01"变成能算的数值
日期是另一类高频出错的数据类型。Stata处理日期的逻辑和Excel完全不同:它把日期存成一个整数,表示"从1960年1月1日起过了多少天",然后用显示格式%td把它显示成人类能看懂的样子。所以导入的日期如果是字符串,必须先用转换函数变成数值,再设置格式。
* 从"2020-01-15"这种标准格式转换 gen date = date(date_str, "YMD") format date %td * 从"2020年1月"这种中文格式转换 gen ym = monthly(ym_str, "YM") format ym %tm * 从Excel的日期序列号转换(Excel的起点是1899-12-30) gen date = date_excel - 21916 format date %tddate()、daily()、monthly()、quarterly()、yearly()是几个常用的转换函数,返回的都是数值型的日期。注意date()返回的是天数,如果要提取月份或者年份,要用month(date)、year(date):
gen year = year(date) gen month = month(date) gen quarter = quarter(date)Excel日期序列号转换这个坑我吃过一次。从Excel直接导入的数据,日期列有时候会被识别成数字(比如43831),那是因为Excel的日期本质上就是个序列号,起点是1899年12月30日。Stata的日期起点是1960年1月1日,两者差了21916天,所以要减掉这个差值。这个数字记不住没关系,help datetime里有完整的说明。
日期处理完一定要检查一遍,用list date date_str对照几条看转换是否正确。转换错误往往不会报错,只会静默地给你一个错的日期,后面按年份分组、按季度分析全部跟着错。
5. 面板数据结构搭建:xtset之前还有细活
面板数据是计量里最常用的数据结构,也是清洗里要求最严格的。截面数据只要每行一个个体就行,面板数据要求"每个个体在每个时点有且只有一条记录",这个约束说起来简单,实际数据里满足的比例并不高。
5.1 xtset报错时的排查路径
xtset的语法是xtset id year,看着简单,但它报错的时候信息不够具体,很容易让人陷入"改一下再试一下"的循环。我把常见的报错和对应的排查思路整理成一张表:
| 报错信息 | 根本原因 | 排查方法 |
|---|---|---|
| repeated time values within panel | 同一个体同一时点有多条记录 | duplicates report id year |
| 字符变量无法识别 | id或year是字符串 | destring year, replace |
| time variable not evenly spaced | 时间变量非等间距(用tsset时) | 检查是否有缺失年份 |
| panels not nested | 个体标识在不同时点发生变化 | xtdescribe |
duplicates report id year是排查"重复时间值"的第一步。它会告诉你重复的组合有多少、每组重复几条。看到结果之后再决定怎么办:如果是完全重复的记录,直接duplicates drop;如果是同一时点多条不同的记录(比如一个人同一年在两个城市工作过),那要思考你的分析层级到底是什么,可能需要聚合到个人层级。
xtdescribe这个命令值得单独说一下,它能给出一份"面板结构报告",包括有多少个个体、时间跨度、平衡面板和不平衡面板各占多少、每个个体的时间序列长度分布等。这份报告能帮你在正式分析前就心里有数,知道自己的数据是强平衡还是严重不平衡。
5.2 duplicates系列命令与去重陷阱
duplicates是一组命令,我在数据清洗里用得非常多,值得系统说一下。
duplicates report duplicates report id year duplicates list id year duplicates tag id year, gen(dup_tag) duplicates drop id year, forceduplicates report不带变量是检查整行是否完全重复,这意味着每个变量都相同。这个检查很有价值,因为整行重复通常是导入或者合并时的技术错误。但要注意,只要有一个变量不同就算不重复,所以如果你有一个自动生成的序号变量,这个检查就永远查不出重复。
duplicates list会把重复的记录列出来,配合sort能看到问题的全貌。我建议在drop之前一定先list看一眼,因为去重的标准是"保留哪一条",这个决定不能交给程序。比如同一个企业同一年有两条记录,一条来自年报、一条来自数据库,两条的收入数字还不一样,你该保留哪条?这必须人工判断,或者用规则判断(比如"优先保留年报数据")。
duplicates drop的force选项是很多人的痛。不加force它会拒绝执行并要求你确认,加了force它会直接删掉重复记录里的第一条(按当前排序)。所以用之前一定要先sort到你希望的保留顺序,否则保留哪条完全随机。
注意:去重之前强烈建议先备份。我用
preserve和restore,或者直接存一份save "data_before_dedup.dta", replace。去重是不可逆操作,误删之后除了重新导入没别的办法。
5.3 非连续年份和不平衡面板的处理
真实数据里,"每个个体每年都有一条记录"是理想状态,实际经常是缺几年。这种缺失有两种性质,处理方式完全不同。
一种是个体在某几年确实存在但没有被采集到,这属于数据缺失,如果有其他年份的其他数据可以利用,可能需要补全;另一种是个体在那几年本来就不存在(比如企业还没成立,或者已经注销),这属于真实缺失,不该补。
区分这两种情况的办法是结合背景知识。企业数据里,如果一家企业的注册时间是2015年,那么2015年之前的记录本来就是不存在的,这时候如果硬要补全,反而会引入虚假数据。这种情况下正确做法是让面板保持不平衡状态,Stata的很多命令(比如xtreg)对不平衡面板是支持的。
如果需要把不平衡面板补成平衡面板(有些模型要求平衡数据),可以用tsfill:
xtset id year tsfill, fulltsfill会为每个缺失的时点补一行,所有变量填充为缺失值。补完之后要注意,新补的行的id是继承的,year是自动生成的,其他变量都是.。对于某些变量(比如省份、行业这种不随时间变化的),可以用bysort id: replace province = province[1]这种方式填充。
要不要补全,取决于你的研究问题。我的经验是:能不补就不补。补全之后样本量变大了,但多出来的都是空值,做描述统计时会被自动排除,做分组时可能引入噪音,反而增加了出错的概率。
6. 多源数据合并:merge的匹配陷阱
稍微复杂一点的研究,数据都不会只有一个来源。企业数据要匹配地区数据、行业数据、政策数据;调查数据要匹配宏观数据、访谈数据。合并过程中最容易出问题的地方就是匹配,而且问题往往不会报错,只是静默地给你一个错的结果。
6.1 merge 1:1、m:1、1:m到底怎么选
Stata的merge命令有三个主要选项,选择标准是"哪边的键是唯一的"。
merge 1:1 id using "master.dta" * 两边id都唯一 merge m:1 id using "master.dta" * 主表id重复,using表id唯一 merge 1:m id using "master.dta" * 主表id唯一,using表id重复 merge m:m id using "master.dta" * 两边id都重复(危险)选择逻辑很简单,但实际操作里很多人直接用m:m图省事。m:m的机制是"两边的每个匹配项都互相配对",比如主表有3条id=1,using表有2条id=1,结果会产生6条记录。这几乎从来不是你想要的。我见过不止一个人因为用m:m导致样本量暴涨,而且自己还没发现。
正确的做法是在merge之前,先用isid确认两边的唯一性:
* 先确认两边的主键 isid id using "using_data.dta"如果你想用m:1但不确定using表是否唯一,Stata会在执行时报错并提示"variable id does not uniquely identify observations"。看到这个报错就应该回头检查using表,而不是改成m:m绕过。这是原则问题——报错是在保护你。
6.2 _merge变量的解读与冲突值处理
merge之后Stata会自动生成_merge变量,取值含义是:
| _merge值 | 含义 | 处理方式 |
|---|---|---|
| 1 | 只出现在主表 | 检查是否是using表缺数据 |
| 2 | 只出现在using表 | 检查是否是主表缺数据 |
| 3 | 两边都匹配上 | 正常情况 |
| 4 | 重复匹配(用update时) | 检查重复原因 |
我一贯的做法是merge之后立刻tab _merge看清楚匹配情况,然后决定要不要保留哪些类别。
merge 1:1 id year using "macro_data.dta" tab _merge * 只保留匹配上的 keep if _merge == 3 drop _merge这里有个细节要注意:keep if _merge == 3意味着你丢掉了所有匹配不上的观测,样本量会减少。如果减少得很多(比如超过10%),那要警惕了,可能两边的主键对不上,或者键的格式不一致(比如一边是字符、一边是数值,或者一边有前导零、一边没有)。我一般会在merge之前专门检查一边边的键变量格式:
* 检查两边的键变量类型 describe id using "macro_data.dta" * 如果类型不一致,先统一 tostring id, replace冲突值处理是另一个话题。如果两边的表里都有"revenue"这个变量,merge后Stata会生成revenue和using表的revenue(通常命名为revenue_1或者类似),这时候你要决定保留哪个。update选项可以让using表的值覆盖主表的值,用于"用新数据更新旧数据"的场景。
merge 1:1 id year using "revenue_new.dta", update replaceupdate和replace的区别在于:update只更新主表中缺失的值,replace会无条件用using表的值覆盖。用哪个取决于你对两边数据质量的判断。
6.3 合并前必须做的一致性检查
merge出问题的根源,八成在合并前就已经埋下了。我在merge之前一定要做的检查有这几项。
第一项是键变量的类型一致性,前面已经说了。这是最常见的坑,而且表现很隐蔽——Stata在匹配的时候会自动把字符型数字和数值型数字转换一下,看起来能匹配上,但实际可能漏掉一部分。
第二项是键变量里的空格和大小写。如果键是企业名称这种字符串,"阿里巴巴 "和"阿里巴巴"在Stata眼里是两个不同的值。我一般会在merge前对所有字符型键做一遍标准化:
replace name = strtrim(name) replace name = ustrlower(name)第三项是键变量的取值范围。检查一下两边的id有没有重叠,重叠了多少,这决定了merge的匹配率上限:
* 在主表里 levelsof id, local(master_ids) * 在using表里做对比(需要先保存当前数据)levelsof配合local可以拿到所有唯一值,然后手工对比或者写循环对比。这个过程有点麻烦,但省下来的debug时间绝对值。
第四项是观测层级是否一致。主表是"企业-年份"层级,using表是"年份"层级,这种层级不一致的合并是合理且常见的。但如果两边层级看起来一样(都是"企业-年份"),结果match率很低,那基本可以断定是键的格式问题。
7. 清洗结果的可复现:do文件、日志和数据快照
前面六部分讲的是技术,这部分讲的是习惯。数据清洗最怕的不是某一步做错,而是做完之后说不清自己做了什么。半年后改论文、合作者接手、审稿人要求补充检验,这些都是常见场景,如果清洗过程不可复现,那就是一场灾难。
7.1 从第一步就写do文件,别用命令行
用Stata的命令行窗口做数据清洗,是新手最大的习惯性错误。命令行敲完就没了,做错了要重新来,做对了要回忆自己做了哪几步。do文件把这些都记录下来,而且可以反复执行,改一个参数重新跑一遍,全部清洗流程自动重做。
我的do文件通常这样组织:
*============================================================== * 项目:企业创新数据清洗 * 作者:xxx * 日期:2024-xx-xx * 说明:从原始Excel到分析用的dta文件 *============================================================== clear all set more off set matsize 10000 version 17 *--- 路径设置 --- global raw "D:/project/raw" global clean "D:/project/clean" global output "D:/project/output" *--- 1. 导入原始数据 --- import excel using "$raw/raw_data.xlsx", /// sheet("Sheet1") firstrow clear *--- 2. 变量类型转换 --- destring revenue profit, replace ignore("¥,") *--- 3. 缺失值处理 --- mvdecode revenue profit, mv(-99 -999) *--- 4. 异常值处理 --- winsor2 revenue profit, suffix(_w) cuts(1 99) *--- 5. 保存 --- save "$clean/data_clean.dta", replace几个习惯值得说。用global或者local管理路径,这个习惯一开始可能觉得麻烦,但它能让你的do文件在任何机器上跑起来,只需要改开头的路径定义。用version指定Stata版本,避免因为版本差异导致结果不一致。set more off避免输出分页,clear all清空内存,这些都是防止"上次运行残留影响这次结果"的防御措施。
do文件里一定要有注释,注释不是给别人看的,是给三个月后的自己看的。我经常会在关键步骤旁边写一句"为什么"而不是"做什么",比如为什么要用1%而不是5%缩尾,为什么要剔除2010年前的样本。这些决策理由比命令本身更难回忆。
7.2 log与数据快照:让每一步都能回滚
log是把Stata的输出(包括命令和结果)保存到文件。它的价值在于事后追溯,尤其是当你发现某个数字对不上时,log能告诉你当时到底做了什么、输出了什么。
log using "$output/cleaning_log.smd", replace text * ... 各种清洗命令 ... log closetext选项生成纯文本,方便查看和搜索;不加的话生成.smcl格式,需要在Stata里打开。我一般两个都生成,.smcl自己看,.txt给别人看。
数据快照是另一个重要习惯。清洗过程涉及的很多操作是不可逆的(replace、drop、destring, replace),一旦出错就得从头来。在每个关键节点存一份数据快照,出错时可以快速回退:
save "$clean/step1_imported.dta", replace save "$clean/step2_typed.dta", replace save "$clean/step3_missing.dta", replace这个习惯会占用一些硬盘空间,但跟重新清洗一遍的时间成本比,完全不值一提。我一般保留3到5个关键节点的快照,中间的临时快照可以删掉。
现在很多人用Git管理do文件,这是个好习惯,但要注意:dta文件不要放进去。dta是二进制文件,Git无法有效diff,而且体积大。正确的做法是只管理do文件和说明文档,数据放在单独的地方。
7.3 交付前的自查清单
清洗完成之后,在真正开始跑回归之前,我有一套固定的自查清单。这套清单帮我避免过很多低级错误,也建议你根据自己的项目情况定制一份。
| 检查项 | 命令 | 关注点 |
|---|---|---|
| 样本量是否合理 | count | 和原始数据对比,减少是否符合预期 |
| 主键是否唯一 | isid id year | 面板数据必须唯一 |
| 面板是否设置正确 | xtdescribe | 个体数、时间跨度是否合理 |
| 关键变量是否有异常 | summarize var, detail | 最小最大值是否在合理区间 |
| 缺失值是否处理干净 | misstable summarize | 剩余缺失是否符合预期 |
| 分类变量取值是否完整 | tab var, missing | 有无意外的新类别 |
| 值标签是否正确 | label list | 标签和数值的对应关系是否合理 |
| 数据是否可复现 | 从头跑一遍do文件 | 结果是否一致 |
最后一行是重点。把自己的do文件从头到尾跑一遍,看看结果是否和之前一致。如果不一致,说明有某一步依赖了手工操作或者内存中的残留状态。这一步虽然机械,但它是检验"可复现性"的唯一有效方法。
我个人在实际操作中的体会是,数据清洗看起来是技术活,实际上更像是一种纪律。命令就那么几十个,翻来覆去地用,真正拉开差距的是有没有检查的习惯、有没有留痕的习惯、有没有在动手之前先想清楚"为什么"的习惯。我见过太多人模型玩得很花,但数据清洗做得马马虎虎,最后结果经不起推敲。反过来,那些看起来笨拙但每一步都留下记录、每个决策都能说清理由的人,做出来的东西往往最扎实。如果你现在正在处理一份新数据,不妨从写一个规范的do文件、建一份数据字典开始,这两件事看起来慢,实际上是最快的路。