
1. 拆解CHARLS数据的基本特征——先搞懂你要洗的到底是什么1.1 认识CHARLS的数据结构与关键文件做实证研究的人一提到CHARLS中国健康与养老追踪调查大概都会又爱又恨。爱的是它是国内少有的、覆盖全国且免费开放的微观追踪数据库变量极其丰富——从人口学特征、健康状况、医疗支出到收入、资产、社保参与甚至还有血检和基因数据样本量也够做多数研究设计恨的是它的原始数据交付格式极其“粗糙”下载下来之后你面对的是一大堆低质量命名的dta文件——比如Demographic_Data.dta、Health_Status.dta、HHShares.dta——字段名几乎全是英文缩写很多还是一题一变量的纵排格式直接拿来做回归几乎不可能。我最初接触CHARLS是2018年的一个卫生经济学课题当时跑完use HHShares.dta之后整个人是懵的。同一个家庭里有多个成员每行的变量并不完全对齐收入部分分了十几类明细医保状态既有现状又有历史更重要的是同一受访者ID在不同模块里命名规则还不一样。所以标题里那一句“stata清洗charls数据”看着简单实则是实证研究前期工作量最重的环节。在动手之前建议先在Stata里做一次“数据体检”。CHARLS各模块数据文件通常包含的关键字段有ID个人标识号、householdID家庭标识号、communityID社区标识号以及各年度对应的wave后缀。我习惯用describe和codebook, compact两连招快速过一遍所有变量的类型、缺失率、取值范围。特别是codebook, compact大量输出对新手极其友好的字段统计几秒钟就能告诉你哪些变量基本全缺失、哪些是纯文本类型需要转换、哪些编码里有不该出现的数值比如性别字段出现了9。这一步一定要做后面所有清洗策略都是在这次“体检”基础上定的。1.2 为什么说清洗比分析更耗时——核心需求解析我记得有个前辈说过一句话实证研究里真正的门槛不是计量方法而是把数据弄到能跑回归的状态。这句话放在CHARLS数据上尤其贴切。CHARLS是追踪调查样本在2011、2013、2015、2018、2020年等多轮次重复访问但也存在受访者死亡、失访、拒访等样本流失情况于是各年度样本ID并不完全一致。如果你要做面板数据光是把四个wave拼成一个长表或宽表就需要仔细处理ID匹配、样本非平衡、时间变量生成等一连串问题。更麻烦的是CHARLS的问卷逻辑中有大量“跳转”skip logic。比如受访者没有配偶那么配偶相关模块自动跳过对应变量在原始数据里表现为缺失值。这类缺失统称为“结构型缺失/合法缺失”它与“非随机缺失”性质完全不同——如果直接当缺失值丢进回归估计量是有偏的但如果当成有效值比如0处理那么语义又不对。所以清洗CHARLS数据的核心需求不只是“把空值填上”而是搞清楚每一个空值到底属于“没问”“拒绝回答”“不知道”还是“不适用”并分别作对应处理。这一点我在后文的缺失值处理部分会详细展开。同时CHARLS原始数据里有很多变量单位、时点、口径不一致。举个例子家庭收入部分的INCOME_FARMING可能是按“去年全年”统计而社会保障部分的缴费额是按“每月”或“每次”统计有些是元有些是万元部分模块早期版本出现过单位不统一不统一调整的话后续任何加总、比较、构造变量的操作都会失真。所以清洗阶段就要把这些“隐藏规则”摸清楚。宁可多花两天做数据字典的梳理也不要后面回归跑出一堆诡异结果再回头找数据问题——那个成本高得多。2. 基于Stata的数据清洗框架设计2.1 清洗前的前置准备版本、内存与log文件很多人都是一打开Stata就直接use数据结果中道崩殂要么内存不足、要么运行时间慢到怀疑人生、要么中途报错后原始文件被覆盖无法回滚。我个人的经验是清洗CHARLS这种大样本微观数据前必须先把“工地”搭好——这听起来像废话但真能避免大量返工。第一版本选择。早年间我用Stata 12做过类似数据处理几万条样本加几百个变量还勉强但后来CHARLS开放的文件越来越大变量数动辄几百上千Stata 12处理起来就很吃力了。目前建议使用14及以上版本最好16或17它们在Unicode字符、大文件超过20亿条观测、以及frame功能上都有明显优化。如果你需要读取或保存旧版本格式的dta文件在16以上版本用useold命令也能兼容不必再单独装转换工具。第二内存设置。CHARLS某个模块的dta文件可能并不算大几十MB到几百MB但当你merge多个模块、再生成派生变量后内存占用经常呈指数级上升。建议在Stata顶部提前执行set memory 4g或根据你电脑物理内存调整提前把内存“喊满”而不是等运行中自动扩充——后一种情况Stata往往只会一点一点加内存频繁“内存不足”就在所难免。第三log文件和do文件的规范管理。清洗不是一次成型的中间一定会经过反复试错和回溯。我见过太多人直接在命令窗口手动点然后关掉软件第二天什么都找不回来了。更稳妥的做法是每轮清洗都建立一个带日期的do文件比如charls_clean_20240125.do并在开头写log using charls_clean_YYMMDD.log, replace text全程记录操作。这样即使中途做错了看着log也能快速定位是哪个命令、哪个参数出了问题。这个习惯在我做数据清洗的这十年里救我太多次了。2.2 数据清洗的标准化流程与思路清洗CHARLS数据我总结了一个标准化的六步流程拷贝备份 → 变量重命名与标签检查 → 数据合并与ID匹配 → 缺失值分类与处理 → 派生变量生成 → 数据质量核查与导出。这六步不是线性走一遍就完事了很多时候需要来回循环。比如你生成年龄变量之后发现部分样本年龄小于18、大于110这说明出生年份字段本身可能有异常编码就需要退回第三步重新清洗原始字段。数据清洗本质上是一个“清洗→核查→再清洗”的循环过程。为什么强调“拷贝备份”放在第一步因为原始数据是研究者的“母本”一旦在Stata里replace误操作很可能造成不可逆的损坏。我有位同事曾经直接对源文件执行drop if missing(HD001)之后又存盘原始文件被覆盖而CHARLS部分早期版本在国内下载需要重新申请耽误了一整个星期的进度。正确做法是在项目文件夹下建一个raw_data只读和一个working_data可写两个子目录所有清洗操作只针对working_data中的副本绝对不动raw_data原始文件。六步流程中的关键环节——比如合并、缺失值处理、变量生成——将在下一部分逐一拆解。在真正操作之前我建议每一个研究者先建一份“数据清洗备忘录”把每一个模块文件里用到的变量、它们的原始字段名、编码含义、缺失规则、单位、时间口径全部列出来。以CHARLS常见的ID变量为例它看起来是一个整数ID但要区分它是“个人ID”还是“家庭ID”不同问卷模块的ID前缀和后缀规则不完全一样。只有备忘录足够清晰后续所有merge、collapse、join才不至于张冠李戴。3. 核心清洗操作实战从字段处理到样本筛选3.1 ID匹配与数据纵向合并CHARLS数据分析第一步大考一定是ID匹配。别看CHARLS官方文档里把ID说得好像很统一实际打开几个模块你会发现变量名并不一致比如个人基本信息模块里叫ID健康状况模块里也可能叫ID但家庭成员模块里叫memID或者ID_mem又或者同一个受访者在不同wave下ID编码有细微追加后缀。这意味着光靠字段名无法确定它们是否同一标识。我常用的第一个命令是codebook ID先看这个变量在Stata里的类型、展示格式、取值范围。如果两个待合并文件的ID都是long或str8而且取值区间一致那大概率可以直接merge如果一边是str8、另一边是long那就需要先把它们统一成字符串再比较。用tostring ID, replace format(%08.0f)可以把数值ID统一成八位字符串反之也可以用destring ID, replace把字符串转成数值。关键是两个文件在合并前都要做一次ID的唯一性检查。举个例子。你从人口学文件Demographic_Data.dta中保留了ID、gender、age等变量准备与健康模块Health_Status.dta做一对一合并。合并前先执行use Demographic_Data.dta, clear duplicates report ID如果结果显示有重复ID那么说明这个模块文件中ID并不是个人级别的唯一标识比如一个受访者有多个观测行对应多次访问直接merge 1:1就会报错“variables ID do not uniquely identify observations”。这时候就要先搞清楚重复的原因——是同一个wave内多行记录还是同一个人出现在多条重复样本里。处理方式要么把ID改成更细粒度的唯一标识如ID wave要么先对ID进行去重聚合。记住一点先duplicates report再merge这是合并前一分钟必做的动作不要偷懒。3.2 缺失值与异常值处理如果说ID匹配是宏观框架那缺失值处理就是微观的水磨功夫。CHARLS的缺失值编码体系非常“丰富”.表示系统缺失-1或-2可能表示“不知道”或“拒绝回答”在某些模块里还有-8“不适用”、-9“缺失”这类编码。默认状态下Stata只把.当作缺失值而-1、-8等会被当成有效负数参与计算如果直接跑回归结果会被污染得面目全非。所以我每次拿到一个模块后的标准操作是先用tab variable, missing或者codebook variable查看该变量的频数分布和缺失值类型再把负的编码值显式转换为Stata缺失值并根据问卷纸质版或代码本确定每个编码的具体含义。比如健康状况里的自评健康DA001如果编码里有-1不知道和-2拒绝回答而你在研究中只需要“好/一般/不好”三类那就应该执行recode DA001 (-1/-2 .a) // 用扩展缺失值.a标记“不知道/拒答”.a这种扩展缺失值是Stata比其他软件灵活的一个地方它在tab、summarize中默认不计入计算但你又可以单独识别它tab DA001 if DA001 .a。等后期做敏感性分析时如果你想检验“不知道/拒答”这类样本是否对结果产生系统性影响还可以把它们单独分组跑一遍不必回到清洗阶段重新改代码。这一点对实证研究的稳健性检验特别有用。异常值的处理则是另一个常见难题。以家庭收入为例CHARLS的家庭收入通常由几十个分项加总得出但分项中偶尔会出现极端值——比如某人填报的转移性收入一年几百万远超正常范围。判断它是否异常不能只看绝对值还要结合变量分布和样本特征。我常用的办法是先对变量进行summarize, detail观察p1、p99分位数和上下限再用winsor2对连续变量做缩尾处理——一般取1%和99%的缩尾比较常见但具体比例要看你的样本量和研究设计不是越缩越好。过度缩尾会人为降低变异影响估计效力不缩尾又容易被个别极端值带偏。对于CHARLS收入类变量我通常先缩尾1%跑基准回归再尝试不缩尾或缩尾5%做敏感性分析三组结果对比之后才放心写进论文。3.3 变量生成与复刻清洗阶段生成的关键变量通常会伴随你整个研究周期所以生成逻辑必须极其清晰且可追溯。最典型的就是年龄。CHARLS直接提供的出生年份是BA001或者分wave字段有时候还包含出生月份BA002。如果研究需要精确到“访问时的年龄”不能简单用“调查年份减去出生年份”了事——因为受访者的生日可能还没到真实年龄会比这个差值小一岁。更严谨的做法是使用每次wave的访问日期变量如IW_Date和出生年月差异计算实际年龄。实际代码可以参考gen interview_date date(IWDATE, YMD) format interview_date %td gen birth_date mdy(BA002, 15, BA001) // 如果只知道月份和年份取月中15日近似 format birth_date %td gen age_interview floor((interview_date - birth_date) / 365.25)这段代码用floor做向下取整避免了简单年份相减带来的年龄高估问题。可能有人会觉得“差一岁有什么关系”但在养老保险、劳动力供给这类年龄敏感型研究里一岁的系统误差可能导致政策评估结论明显偏差而且这种误差在不同出生月份群体中还呈现出非随机分布上半年出生的人偏大、下半年出生的人偏小后果更隐蔽。所以变量生成的精度直接决定了你研究的严谨度。除此之外教育年限、婚姻状态二值化、收入加总、家庭规模等派生变量的生成建议统一在do文件里用注释注明来源字段和口径。比如“教育年限根据EDUCATION原始编码映射得到小学6年初中9年高中12年大专15年本科16年研究生及以上19年”这样审稿人或合作者未来检查数据时能快速理解每个变量的构造逻辑你也省得每次都要从头解释。写这个注释看似多花了十秒钟其实是在给未来的自己铺路。4. 实证研究中的数据质量核查与可视化诊断4.1 用describe、codebook检查数据完整性清洗完毕并不意味着工作收尾了数据质量核查一定不能省。每次清洗完一个模块或生成一批新变量我都养成了用describe和codebook做“交付验收”的习惯。具体来说会检查三个层面第一观测数量是否符合预期——比如你合并了2011和2013两年的样本那观测数应该在两个wave原始样本数之间而不是莫名其妙膨胀或骤减第二关键变量缺失率是否可控——比如核心被解释变量如果缺失率超过20%那就要考虑是不是清洗过程中误删了样本第三变量取值范围是否符合常识——比如性别变量只能是1或2年龄变量应在合理范围内家庭收入不应出现负数。这里有一个很实用的小技巧用misstable summarize一键查看所有变量的缺失率分布。它会在输出中列出每个变量的缺失条数和缺失百分比特别适合在清洗完成后快速找出那些“意外的高缺失”变量。如果某个变量缺失率从清洗前的5%变成了清洗后的30%那八成是你在某一步drop if条件写错了殃及了不该删的观测。别笑这种错误连老手都会犯。4.2 离群值、极端值与数据分布的诊断办法数据核查不只靠命令输出表格图形化诊断同样重要。我经常用histogram看连续变量的分布形状比如家庭收入分布通常是右偏的如果画出来出现一个异常陡峭的尖峰或离群点集群就要警惕是否有多报、漏报或单位不一致的问题。同样scatter散点图适合看两个变量之间的关系比如年龄与自评健康之间一般存在单调递减趋势如果你发现某个年龄段的健康评分反而异常高可能是该年龄段样本量过小或编码有问题。另外一个容易被忽视但极其实用的是“交叉表一致性检查”。不同模块或不同wave之间若出现逻辑矛盾说明数据质量存疑。比如你在健康模块里看到某人性别为男性但配偶模块里的对应记录显示他和“丈夫”同住这显然是数据冲突。用tab ID if ...定位到具体样本后再回看原始问卷确认是哪个模块的字段出错。需要提醒的是数据诊断发现异常后不要急着“删掉”或以“错误值”处理先保留在数据里并在do文件中记录异常样本ID和怀疑原因。因为有时候这些“异常”其实是真实存在的特殊情况比如收入极高的小企业主、年龄极高的超高龄老人删除它们反而会损失信息。先标记、后判断让研究结论经得起审计。5. 常见问题与排查技巧实录5.1 典型报错与解决方案速查以下是我用Stata清洗CHARLS数据这五年里最常遇到的几类报错和解决方案整理成一个速查表各位可以直接收藏报错信息通常原因解决思路ID does not uniquely identify observations合并变量存在重复观测先用duplicates report检查重复再决定是聚合还是改用多对一合并variable label already defined / already defined变量名冲突常见于重复执行do文件在do文件开头对关键变量执行capture drop 变量名避免重复创建no observations条件筛选写错比如if age 18但年龄变量是字符串用tab先确认变量的类型和取值必要时destring转换type mismatch字符串变量与数值变量比较或运算用destring或tostring统一类型后再操作missing values generatedgen新变量时默认将非数值内容设为缺失检查原变量编码必要时使用encode或recode预处理variable not found变量名拼写错误或该变量不在当前数据集中describe列出所有变量确认准确名称注意下划线、大小写这六条覆盖了我遇到过的80%的问题。看起来都不是什么高深技术但恰恰是最容易让新手卡壳的地方。尤其值得注意的是历史遗留的do文件在换电脑、换Stata版本后原来能跑通的代码可能因为默认设置不同而报错。建议统一用version 16类似的固定版本声明写在do文件最上方减少跨版本兼容问题。5.2 清洗中容易踩的坑与心得除了报错还有几个“不报错但结果错”的坑是我亲身踩过的值得单独拿出来说。第一个坑是合并方向选错。merge 1:1和merge m:1看似简单但一旦选错方向观测数会戏剧性膨胀。举个例子健康模块里一个受访者可能有多条观测比如多个部位的体检记录如果你想保留一条主记录再合并家庭层面的变量应该以健康模块为主体merge m:1 householdID——否则用了merge 1:1会报一堆非唯一性错误。经验法则先判断“我手头这个数据集里合并变量是否唯一”不唯一就要考虑m:1还是1:m。第二个坑是字符串变量隐藏的不可见字符。CHARLS数据中某些字符串ID变量看起来一样但用duplicates report却总显示两行后来我发现是其中一个样本ID末尾带着空格或换行符。用replace ID strtrim(ID)或replace ID subinstr(ID, , , .)去除空格之后匹配才正常。这个小问题当年让我排查了整整一个下午如果早用assert检查匹配后的结果可能十分钟就定位到了。第三个坑是跨wave变量名不一致。CHARLS不同年度版本的变量命名并非完全统一。即使同一个问题在2011和2013的问卷中字段名可能分别是HD001和HD001_2013或者干脆把年份后缀统一加上了。如果你要合并多年数据做面板必须逐字段核对wave版本之间的对应关系。我建议首先用lookfor搜索关键词比如lookfor 医保 insurance把相关字段全部罗列出来再对照问卷确认字段异同最后用rename统一命名。这个工作量听起来很大但这是保证面板数据质量最基础的一步。第四清洗数据时保持“可复现性”比什么都重要——这是老生常谈但几乎每个人都栽过。不要只保存“清洗后的最终数据”中间过程的临时数据文件也要保留版本号或时间戳。以后写论文、应对审稿人的数据质疑、或者自己回看旧项目你会发现这些中间版本是找到问题根源的唯一线索。或者说得更直白一点数据清洗这件事慢就是快。你今天为了赶进度跳过的检查步骤未来一定会以更折磨人的方式回来找你。我给自己的强制要求是无论多么简单的清洗至少用一个独立do文件记录全部操作结尾加上assert或count验证确保清洗后的数据在关键维度上符合预期。6. 收尾工作导出数据与项目存档清洗完成后最后一步往往是导出数据、生成描述性统计表和绘图。推荐的做法是把清洗好的数据保存为一个主分析文件比如charls_analysis_202401.dta同时配合一个变量说明书.xlsx或variable dictionary文档。这个变量说明书不需要很复杂但至少包含变量名、变量标签、类型、单位、编码含义、缺失值定义、取值范围、来源文件。这么做的好处是方便你之后写论文时引用变量也方便合作者快速上手。针对导出还有一个实用小技巧如果期刊或审稿人要求提供数据可用性说明你可以额外导出一份样本筛选流程图或表格展示“原始样本量→合并后样本量→排除了多少缺失→最终分析样本量”。CHARLS这类追踪调查论文经常会遇到这类“样本筛选过程描述”的问题提前在清洗阶段把每一步count记录下来后面写方法部分时直接拿来用。我个人习惯在项目存档时把所有文件整理成如下结构charls_project/ ├── raw_data/ # 官方原始数据只读 ├── working_data/ # 清洗过程中的中间数据 ├── do_files/ # 所有do文件按执行顺序编号 ├── logs/ # 运行日志 ├── output/ # 统计表格、图形、导出数据 └── readme.txt # 项目总说明含数据来源版本、关键决策点这套结构看着简单但确实帮我省掉了大量“寻找文件、回忆逻辑”的时间。每个项目结束时在readme.txt里写清“日期、目标、完成的清洗工作、遗留问题”哪怕几个月后再回头看也能快速进入状态。实话说CHARLS数据是所有做中国健康老龄研究的人绕不开的宝库但“宝物”也需要打磨才能发光。那些在论文里漂亮的回归表格背后无一例外都是几百行乃至上千行清洗代码打底。用Stata清洗CHARLS数据不是天才算法而是需要耐心和系统化的“工匠活”。你建立起来的那一套清洗流程和变量说明文档才是未来研究效率的真正分水岭。