CHARLS数据清洗教程:从原始.dta到可跑回归面板的Stata源码包
简介这份资源是CHARLS数据库系列教程第二部分的配套项目源码面向健康经济学、社会学、人口统计学方向的研究者与数据分析学习者重点解决中国健康与养老追踪调查数据清洗、拼接与整理流程复杂、缺乏成熟查对系统的问题。压缩包共8个文件约12KB以R脚本为主包含数据清洗、示例分析与环境依赖等代码文件另附示例数据、说明文档与网页预览便于直接运行和对照学习。教程以甘油三酯葡萄糖指数与新发糖尿病关系的研究为例完整演示数据下载、清洗与拼接过程代码量超过100行并预告后续cox回归、分位数回归与多模型比较等内容。目前已有403人学习适合希望掌握CHARLS数据处理技能、为实证分析打基础的中级用户参考。1. CHARLS 数据清洗到底在洗什么从原始 .dta 到可跑回归的面板CHARLS 数据清洗教程配套源码包解决的是把官方发布的原始.dta文件变成能直接进 Stata 跑回归的面板数据这件事。做过 CHARLS 的人都知道官网下载的压缩包里动辄几十个模块文件变量名像da001、da002这种编码缺失值用.、.a、.b等一堆特殊码表示跨年份合并时 ID 对不上、变量改名、样本流失每一步都能让人翻车。这套源码包把整个流程脚本化了从解压、变量重命名、缺失值处理、跨期合并到面板构建一条龙跑通。适合正在做 CHARLS 相关课题的研究生、青年老师以及需要快速复现清洗流程的从业者。如果你还在手动一个个 rename 变量、用 Excel 对 ID这套东西能省你至少两周的玄学调试时间。2. 环境准备与目录结构Stata 版本、依赖包和文件摆放2.1 为什么选 Stata 而不是 R 或 PythonCHARLS 官方数据以 Stata.dta格式发布变量标签、值标签、缺失值编码都是 Stata 原生体系。用 R 的haven包读进来值标签会丢一部分缺失值.a.b会被转成NA你根本分不清“拒答”和“不知道”。Python 的pandas.read_stata同样有这个问题而且大文件内存占用高。所以清洗环节用 Stata 是最省事的源码包也是基于 Stata 写的。常见做法是Stata 做清洗和变量构造导出.dta或.csv后再进 R/Python 做可视化或机器学习。2.2 依赖包安装与版本要求源码包依赖三个社区命令recode不是内置那个、labutil、missings。安装命令如下* 安装社区命令只需跑一次 ssc install recode, replace ssc install labutil, replace ssc install missings, replace * 检查版本建议 Stata 15 及以上 which recode which labutil which missings逻辑说明recode用于批量重编码变量比内置recode灵活labutil用来批量管理值标签跨年份合并时标签冲突是高频坑missings用于统计和替换缺失值。参数说明replace表示已安装则覆盖避免版本不一致。Stata 14 及以下对 Unicode 支持差中文变量标签会乱码建议 15 以上。2.3 目录结构怎么摆源码包解压后建议按以下结构摆放不要随意改路径脚本里用了相对路径charls_clean/ ├── raw/ # 放官网下载的原始压缩包解压后的 .dta │ ├── 2011/ │ ├── 2013/ │ ├── 2015/ │ └── 2018/ ├── do/ # 源码包里的 .do 文件 │ ├── 01_rename.do │ ├── 02_missing.do │ ├── 03_merge.do │ └── 04_panel.do ├── temp/ # 中间文件可随时删 └── output/ # 最终面板数据注意raw下按年份分文件夹因为 CHARLS 不同 wave 的模块文件命名有差异混在一起容易覆盖。temp目录用来存中间结果方便断点调试不用每次从头跑。3. 变量重命名与缺失值处理批量 rename 和特殊码转换3.1 变量重命名的核心逻辑CHARLS 原始变量名是da001、da002这种直接跑回归根本不知道哪个是哪个。源码包的做法是建一张映射表用循环批量 rename。核心代码如下* 读取原始文件 use raw/2011/da001.dta, clear * 定义重命名映射旧名 新名 local oldnames da001 da002 da003 da004 local newnames gender birth_year edu_level marital_status * 循环重命名 forvalues i 1/4 { local old : word i of oldnames local new : word i of newnames rename old new } * 检查结果 describe gender birth_year edu_level marital_status逻辑说明word函数从字符串中取第 i 个词forvalues循环 4 次完成批量重命名。参数说明oldnames和newnames必须一一对应顺序不能错。实际项目中变量可能上百个建议把映射表单独存成.do文件用include引入方便维护。我一般会先跑describe看原始变量列表再对照问卷码本建映射不要凭记忆写。3.2 缺失值特殊码的处理CHARLS 的缺失值不是简单的.而是分.不适用、.a拒答、.b不知道、.c缺失等。直接sum会把它们当缺失但做频数分布时又需要区分。源码包用missings命令统一处理* 统计各变量缺失情况 missings report gender birth_year edu_level marital_status * 将 .a .b .c 统一转为系统缺失 . mvdecode gender birth_year edu_level marital_status, mv(.a .b .c) * 再次检查 missings report gender birth_year edu_level marital_status逻辑说明missings report先摸清缺失分布mvdecode把特殊码转为系统缺失。参数说明mv(.a .b .c)指定要转换的特殊码不写则默认转换所有扩展缺失值。注意如果要做缺失值机制分析MCAR/MAR转换前先备份原始文件否则信息丢失。常见做法是保留一份带特殊码的版本另存一份转换后的用于建模。3.3 值标签的批量处理跨年份合并时同一变量在不同 wave 的值标签可能不一致比如 2011 年edu_level的 1 是“小学”2015 年 1 变成“未上学”。源码包用labutil统一* 查看当前值标签 label list edu_level * 定义统一标签 label define edu_label 1 未上学 2 小学 3 初中 4 高中 5 大专及以上 * 应用到变量 label values edu_level edu_label * 检查 tab edu_level, missing逻辑说明label define定义新标签label values绑定到变量。参数说明标签数字必须和原始编码对应建议对照各年份码本逐一核对。如果不同年份编码规则不同需要先用recode统一数值再打标签。这一步是血泪经验标签错了后面回归结果全歪。4. 跨年份合并与面板构建ID 对齐、样本追踪和平衡面板4.1 跨年份合并的 ID 对齐CHARLS 不同 wave 的 ID 变量名可能不同2011 年叫ID2013 年叫householdID2015 年又变成hhid。合并前必须统一* 2011 年 use temp/2011_clean.dta, clear rename ID pid save temp/2011_ready.dta, replace * 2013 年 use temp/2013_clean.dta, clear rename householdID pid save temp/2013_ready.dta, replace * 2015 年 use temp/2015_clean.dta, clear rename hhid pid save temp/2015_ready.dta, replace逻辑说明统一 ID 变量名是合并的前提。参数说明rename后建议isid pid检查唯一性如果报错说明有重复 ID需要先去重。常见做法是保留pid和wave两个关键变量wave标记年份方便后续 reshape。4.2 纵向合并与面板声明* 逐个追加 use temp/2011_ready.dta, clear append using temp/2013_ready.dta append using temp/2015_ready.dta append using temp/2018_ready.dta * 生成年份变量 gen wave . replace wave 2011 if _n 10000 replace wave 2013 if _n 10000 _n 20000 * 更稳妥的做法是在各年数据里就生成 wave * 声明面板 xtset pid wave逻辑说明append纵向堆叠xtset声明面板结构。参数说明wave必须是数值型且每个 pid 内唯一。上面用_n判断年份是偷懒写法正确做法是在各年清洗时就gen wave 2011等。xtset后如果报“repeated time values”说明同一 pid 同一年有多条记录需要去重或聚合。4.3 平衡面板与非平衡面板的选择* 查看面板结构 xtdescribe * 生成平衡面板标记 bysort pid: gen n_waves _N gen balanced (n_waves 4) * 筛选平衡面板 preserve keep if balanced save output/panel_balanced.dta, replace restore * 保存全样本 save output/panel_full.dta, replace逻辑说明xtdescribe看每个个体的期数分布n_waves统计每个 pid 出现次数。参数说明balanced 1表示四期都在。注意平衡面板样本量会大幅减少CHARLS 追踪流失率高强行平衡可能只剩几千人是否用平衡面板取决于研究问题。我一般两个版本都存跑回归时对比。5. 避坑与排查那些让你怀疑数据的常见问题5.1 合并后变量对不上全是缺失现象append之后某些变量全是.但各年单独看都有值。原因不同年份变量名不一致append只按变量名匹配名字不同就各自生成新变量。解决合并前用describe对比各年变量列表统一变量名后再append。或者用merge按 ID 合并但merge是横向纵向还是得append。5.2 值标签冲突导致 tab 结果错乱现象tab edu_level显示的数字对但标签是乱的比如 1 显示“高中”。原因不同年份的值标签定义冲突Stata 默认保留第一个遇到的标签。解决合并前用labelbook检查各年标签统一用label define重新定义再label values绑定。源码包里labutil就是干这个的。5.3 ID 重复导致 xtset 报错现象xtset pid wave报“repeated time values within panel”。原因同一 pid 同一年有多条记录可能是家庭成员多条或者数据本身有重复。解决先duplicates report pid wave看重复情况再决定是去重、聚合还是换 ID。常见做法是保留户主或主要受访者或者用bysort pid wave: keep if _n 1粗暴去重但会丢信息。5.4 缺失值转换后样本量骤减现象mvdecode之后reg的样本量比预期少很多。原因.a.b.c被转为.后回归默认剔除所有缺失如果这些特殊码占比高样本损失大。解决先missings report看缺失比例如果某变量缺失超过 30%考虑插补或换变量。另外做描述性统计时可以用tab, missing保留缺失信息。5.5 中文路径导致文件读取失败现象use命令报“file not found”但文件明明存在。原因Stata 对中文路径支持不稳定尤其是 Windows 下。解决项目路径全用英文不要有空格和中文。源码包里所有路径都是相对路径且英文就是这个原因。如果非要放中文目录用cd切换到英文路径再跑。6. 进阶技巧用循环批量清洗多期数据与结果验证6.1 用循环批量处理所有年份手动一年年跑太慢源码包里有个master.do用循环一次性跑完* 定义年份列表 local years 2011 2013 2015 2018 * 循环清洗 foreach y of local years { di 正在处理 y 年数据... do do/01_rename.do y do do/02_missing.do y do do/03_merge.do y } * 合并所有年份 do do/04_panel.do逻辑说明foreach遍历年份每个do文件接收年份参数。参数说明01_rename.do里用args year接收参数再拼路径raw/year/。这样加新年份只需改local years列表。我一般会在每个do文件开头加di 开始执行 XXX方便看日志定位卡在哪一步。6.2 结果验证描述性统计对比清洗完不能直接跑回归先做描述性统计和官方报告对比* 加载最终面板 use output/panel_full.dta, clear * 关键变量描述统计 sum gender birth_year edu_level marital_status * 分年份看均值 bysort wave: sum gender birth_year edu_level marital_status * 和官方报告对比假设官方报告均值已知 * 如果偏差超过 5%回去检查清洗步骤逻辑说明bysort wave: sum看各年分布是否合理比如性别比例应在 0.5 左右教育年限逐年提升。参数说明对比基准来自 CHARLS 官方发布的描述性统计报告偏差大说明某步清洗有问题。常见做法是把sum结果导出到 Excel和官方报告并排看。6.3 一个具体技巧用assert做数据质量检查* 检查性别只有 1 和 2 assert inlist(gender, 1, 2) * 检查出生年份在合理范围 assert birth_year 1900 birth_year 2000 * 检查教育年限非负 assert edu_level 0 * 检查面板无重复 isid pid wave逻辑说明assert在条件不满足时报错并终止适合做硬性检查。参数说明inlist检查值是否在列表中isid检查唯一性。我习惯在清洗完每个模块后加几行assert一旦报错立刻知道哪步出问题比跑完回归才发现强。从那以后我每次清洗新数据都强制走一遍assert检查希望帮到你。本文还有配套的精品资源点击获取