CHARLS数据清洗全攻略:Stata合并、变量处理与常见坑
拿到CHARLS数据压缩包并解压之后很多人会卡在同一个地方文件夹里躺着几十个dta文件RemB_2018.dta、Demographic_Background.dta、Health_Status.dta……名字看起来都认识但就是不知道该先打开哪个、哪些能合并、合并之后样本量为什么突然少了三千。这个阶段我整整磨了一周后来回头看真正缺的不是Stata命令技巧而是对CHARLS数据“基因”的理解。这篇文章就把我用Stata清洗CHARLS数据的完整思路、关键命令和踩坑细节整理出来适合正在做实证研究、被微观数据库洗数据折磨的研究生和青年学者。文章不会教你跑多复杂的模型而是聚焦在“怎么把原始数据变成一份能直接做回归的干净样本”。1. 动手前先看懂CHARLS的数据基因文件结构与ID逻辑1.1 三个层级决定合并方向CHARLS数据在结构上分三个层级社区、家户、个人。社区层是村居层面的信息一个社区里有多个家户家户层是家庭层面的信息一家之中有多个受访个人个人层紧接着挂在家户下面。理解了这三层关系合并的路子自然就清楚了——社区变量要往下合并到家户/个人家户变量可以往下挂到个人而个人变量绝不能往上反推家户或社区。我见过不少人把社区文件与个人文件做 1:1 合并报错之后又随手换成 1:m结果一个社区的变量被漫天复制。正确的逻辑是个人文件是分析的主表社区文件是辅助表合并方向永远是辅助表向主表汇入。具体到Stata命令就是merge m:1或merge 1:1的区别后面第二章会细讲。1.2 模块化文件存储与Codebook的配合CHARLS的原始数据按问卷模块切分存放比如人口学背景、健康状况、医疗保健、收入消费、工作退休、认知模块等等。这样做的好处是每个文件体积可控坏处是研究者要自己做拼接。拼接的第一步不是打开数据文件而是打开Codebook。Codebook是清洗工作的唯一权威依据。它记录了每个变量的名称、标签、取值范围、缺失编码和跳转逻辑。以年龄变量为例有些年份直接给出年龄有些年份只给出生年份还有些年份的年龄变量在特定条件下才有效。这些细节Codebook里都会写但没人提醒就很容易漏看。我自己的习惯是下载数据后先把所有dta文件名的列表打出来再对照Codebook目录把每个文件对应的模块范围标记在旁边。这一步看起来费时间实际上能省掉后面大量返工。1.3 多期追踪数据的面板结构CHARLS是追踪调查2011年基线之后每两年追访一次目前公开年份覆盖2011、2013、2015、2018、2020。做面板研究的人要注意同一受访者在不同年份的个人ID可能会有系统性变化尤其是家户分裂之后。官方提供了跨期匹配的ID对照表用于把不同年份的同一受访者串起来。如果你做的是多期面板务必用官方Crosswalk文件不能想当然地认为同名ID在不同年份就是同一个人。如果只做单期截面分析就不用处理这个问题直接用当期文件里的ID和ID_2合并即可。2. 清洗主流程从原始dta到分析用样本2.1 先定研究问题再定清洗方案数据清洗最忌讳一上来就闷头洗。没有研究问题驱动你根本不知道该保留哪些变量、该把样本限制在什么范围。比如研究退休对健康的影响样本要限制在接近退休年龄的人群可能要删掉大量年轻样本研究教育回报核心样本就是成年劳动力年龄下限得卡住研究老年贫困那年龄口径又不一样。所以第一步永远是写清楚你的研究问题、核心解释变量、被解释变量和控制变量清单再倒推需要保留哪些模块的哪些变量。我用2018年数据分析“收入对自评健康的影响”举例核心变量包括年龄、性别、教育、收入、自评健康控制变量还需要婚姻状况、居住地类型、医疗保险等。需要的模块就是人口学背景、收入消费、健康状况、医疗保健四块。2.2 合并前的预处理统一ID格式与变量名很多人merge之后发现匹配率极低最常见的元凶是ID类型不一致。CHARLS的ID变量在导出时有时是字符串有时被读成数值型不同模块文件里可能一个存成str12另一个存成str14肉眼看着都是“110020120301”但Stata根本不认为它们相等。处理办法很简单合并之前先describe ID ID_2看类型再用destring或tostring把两边统一成同一种格式。我个人偏好统一成字符串因为有些ID前导数字有特殊含义转成数值型会丢前导零。变量名冲突也要提前处理。不同模块里可能有同名的变量比如Health文件里有个IDDemographic文件里也有IDmerge之后Stata会自动生成ID2这倒不会报错但很容易让后续代码逻辑混乱。我建议在保存中间文件时用keep把需要的变量挑出来只保留ID变量和本模块的核心变量删减之后再合并。2.3 merge命令的选择什么时候用1:1什么时候用m:1合并命令的选择取决于两个文件的层级关系。个人模块文件之间用1:1因为每个文件里一人一条记录。比如把人口学背景和健康状况合并主键就是ID ID_2use D:\charls2018\Demographic_Background.dta, clear keep ID ID_2 gender birth_year marriage educ save D:\temp\demo.dta, replace use D:\charls2018\Health_Status.dta, clear keep ID ID_2 self_health merge 1:1 ID ID_2 using D:\temp\demo.dta keep if _merge 3如果是社区变量向个人文件合并就得用m:1因为一个社区的变量要匹配到多个受访者use D:\charls2018\Community.dta, clear keep communityID community_type duplicates drop communityID, force save D:\temp\community.dta, replace use D:\temp\merge1.dta, clear merge m:1 communityID using D:\temp\community.dta这里有个必须做的动作先把社区文件里的communityID去重否则m:1合并会报错。同样的道理家户层面的变量向个人合并时也要保证家户ID在家户文件中唯一。2.4 长表还是宽表面板结构的选择如果你做的是多期面板还要决定数据存成宽表还是长表。宽表适合做特定年份的截面回归每一行是一个个体但同一变量的不同年份被拆成不同列长表适合做面板固定效应一行是一个个体-年份观测。Stata的reshape命令可以在两者之间转换但要保证ID变量在转换后的唯一性。用2013和2018两期数据做例keep ID ID_2 year self_health income reshape wide self_health income, i(ID ID_2) j(year)面板分析则反过来reshape long。注意这里的ID ID_2必须是个体级跨期稳定ID如果用的是原始当期ID跨期匹配会把不同年份的同名ID当成同一个体结果基本就废了。建议先去官方跨期匹配文件里找稳定个体标识再接回主数据。3. 人口学与社会经济变量的清洗实操3.1 年龄变量用出生年份统一口径CHARLS部分年份数据集中有年龄变量但没有的话也很常见。更稳妥的做法是用出生年份计算。比如做2018年数据分析年龄应该是gen age 2018 - birth_year replace age . if age 16 | age 100处理完年龄之后切记检查有效区间。我见过有人算出来年龄等于负数根源是出生年份变量里混入了9997、9998这类编码分别表示“不知道”和“拒绝回答”。如果不清洗直接减2018得到的就是离了个大谱的数值。所以在生成年龄之前要把birth_year的异常编码先置为缺失replace birth_year . if birth_year 0 | birth_year 2018 replace birth_year . if birth_year 9997 | birth_year 99983.2 教育变量的编码变迁与年限换算教育变量在不同年份的编码方式有差异这点特别容易踩坑。有些年份是1到11的编码分别对应文盲、小学、初中、高中、中专/技校、大专、本科、硕士、博士等有些年份直接把学历和受教育年限合并在一起。做教育回报率的实证研究通常要把教育变量转换为受教育年限这时候一定要查当年的Codebook确认编码含义再逐条对应。我常用的转换方式* 以2018年编码为例具体码值以当年Codebook为准 gen edu_years . replace edu_years 0 if educ 1 replace edu_years 6 if educ 2 replace edu_years 9 if educ 3 replace edu_years 12 if educ 4 replace edu_years 12 if educ 5 replace edu_years 15 if educ 6 replace edu_years 16 if educ 7 replace edu_years 19 if educ 8 replace edu_years 22 if educ 9注意不同年份数值可能对应不同的学历层级比如某一年编码5是“中专”另一年编码6才是“中专”。跨年比较模型时务必先统一编码口径。3.3 收入指标加总口径与极端值处理CHARLS的收入模块非常庞杂包括工资性收入、农业收入、个体经营收入、养老金收入、政府转移支付、其他家庭成员的转移支付等。第一步是决定研究口径你关心的是个人收入还是家庭人均收入两种口径的加总变量完全不同。如果是家庭人均收入需要先把家庭中各成员的各类收入加总到家庭层面再除以家庭规模最后生成人均收入。收入变量本身容易出现极端值我通常先看分布sum income, detail如果最大值比P99高出几十倍基本可以确定有录入错误或极端异常值。处理办法有两种。一种是用winsor2做缩尾外部命令先ssc install winsor2winsor2 income, cut(1 99) replace另一种是直接剔除超出合理区间的观测比如年收入为负数或超过某个不合理阈值。缩尾和剔除各有适用场景如果样本量小、怕丢掉有效信息优先缩尾如果偏态极其严重缩尾后仍不理想再考虑剔除。4. 缺失值、负值编码与异常值CHARLS清洗的隐形战场4.1 负数编码并不都是无效信息CHARLS大量变量里藏着负数编码很多新手一看到负数就全部replace x . if x 0这其实是有问题的。不同负值代表不同的应答状态常见的有-1表示“不知道”-2表示“拒绝回答”-8表示“不适用/跳问”-9表示“缺失”。它们在实证研究中的处理方式并不相同。比如“跳问”的负值往往意味着受访者因前一题回答为否而被跳过这类信息其实包含了“事件未发生”的含义扔进缺失会损失信息。而“拒绝回答”和“不知道”则通常按真正的缺失处理。具体哪些负值该怎么处理必须以当年Codebook的变量说明为准不能一刀切。4.2 缺失值的三种处理策略清洗完负值编码之后缺失值处理就成了下一个问题。第一种是列表删除只用完整样本做回归简单直接但样本量可能大幅缩水第二种是多重插补适用于缺失比例较高、且变量间相关性强的情况但CHARLS这类复杂抽样数据对插补模型设定有要求操作不当会引入新的偏误第三种是把“缺失”本身作为一个类别构造缺失虚拟变量放入回归适合分类变量。我的建议是先用列表删除跑主回归再针对关键变量做敏感性检验。如果某个控制变量缺失比例超过20%最好不要硬塞进回归而是把它删掉或换一个相关性更高的替代指标。4.3 分年、分模块的异常值排查异常值排查要分模块做。每个模块我都固定跑一遍三件套describe tab1 变量1 变量2 变量3, missing sum 变量1 变量2 变量3, detailtab1 ..., missing配合missing选项可以同时检查取值分布和缺失数量能一眼看出变量是否出现了不符合Codebook取值的编码。比如自评健康应该只有1到5的等级值如果出现6、7就是非常规编码需要回溯原始问卷做判断。我还有一个习惯按省份交叉检查关键变量。CHARLS是多阶段分层抽样某些地区的编码异常往往只在局部出现。按省份分组的tab有时比全样本tab更能暴露问题。5. 清洗完成后的质量校验清单5.1 样本量对账法清洗完成的最后一步必须做样本量对账。CHARLS官方发布的用户手册和技术报告里通常包含各年份的基线样本量和追访样本量。虽然不会有清洗前后样本量逐一对应的标准答案但你合并后的样本量应该与官方报告的“完成问卷人数”在同一量级。对账有三个关键节点原始各模块文件的观测数、合并后的总样本数、剔除缺失后进入回归的样本数。每一步都要在do文件里用count记录日志。全样本合并后如果只剩下一半观测大概率是merge写错了层级或主键重复。5.2 描述性统计的合理区间验证做完样本量对账再用sum检查所有核心变量的均值、标准差、最小值和最大值。检验标准很简单最小值和最大值必须在理论合理范围内。年龄不能有负数也不能有200收入不能有负的工资自评健康不能出现6以上。如果发现极端区间回到原始变量检查是不是负值编码没清理干净或者生成变量时公式写错。性别变量的合理性检查更直接男性和女性的比例在中年及老年样本里应该在0.45到0.55之间如果某个清洗样本里女性比例超过0.6大概率样本选择出了问题。5.3 关键变量的交叉验证单变量检查通过后还要做变量之间的交叉验证。一个常用的套路是检验“个人收入”与“家庭收入”之间的数量关系个人收入不应大于家庭收入检验“配偶是否在世”与“婚姻状况”的匹配性离婚的人不应该出现配偶在世编码检验“退休状态”与“年龄”的逻辑性30岁的人不应该退休。交叉验证的具体命令很简单tab retire_status if age 40 tab marriage if spouse_alive 1但这一步的价值极高很多编码错乱的问题只有靠变量间逻辑才能发现。6. 我踩过的坑CHARLS数据清洗的进阶教训6.1 跨期ID的“隐形变化”做2013和2018两期匹配时我直接拿两年的ID ID_2做匹配结果样本量从两万掉到一万二而且匹配后的样本来源极不均衡。查了官方说明才发现家户分裂、整户失访都会造成个人ID变动。后来改用官方Crosswalk文件把两年的受访者统一到2018年的个体标识上匹配率才恢复到合理水平。现在我的习惯是拿到多期数据第一步就查ID稳定性不直接信任同名ID跨年唯一。6.2 值标签的历史包袱CHARLS有些变量在不同年份虽然数值编码一致但值标签含义换了。教育变量是最典型的其他像婚姻状态、居住地类型也出过类似问题。Stata里变量本身带值标签合并数据时如果直接沿用其中一个文件的label很可能把另一年的真实含义掩盖掉。我的做法是先label list查看具体映射跨年比较前先建立统一的编码字典再用recode重新编码到标准码值上。6.3 社区变量与个体变量合并时的单位错配有一次我把社区层面的卫生服务变量直接按家户ID合并到个人文件结果同一社区内的人没有共享同一社区特征回归结果怎么看怎么别扭。后来查了原始数据才发现当年社区文件的ID变量名虽然叫ID但含义与个人文件中的ID并不同构。CHARLS的社区ID、家户ID、个人ID之间存在映射关系合并时要用对应的桥梁变量。所以每个文件在合并前我都会画一个简单的变量关系表把ID的层级和作用写清楚再动手merge。清洗CHARLS数据的整个过程下来我自己体会最深的一件事真正的技术瓶颈从来不是某个Stata命令记不住而是你对自己手头这批数据结构的理解程度。从ID格式不统一导致的merge翻车到跨期ID变动引发的样本断崖再到负值编码一刀切造成的信息损失每一步的背后都是对数据结构本身不够熟悉。现在我做任何微观数据项目都会把“读Codebook、梳理变量层级、确认ID逻辑”作为第一天必须完成的任务。建议你也试试这个习惯虽然前期看起来进展很慢但后面清洗和回归的效率会成倍提升。