40年城市年鉴面板数据整理:清洗、编码与校验实战

40年城市年鉴面板数据整理:清洗、编码与校验实战 做城市经济、区域发展或者历史地理研究的人几乎人手一册《中国城市统计年鉴》。但真要把1985—2025这40年的年鉴整理成一份能直接跑回归的面板数据工作量比大多数论文“数据来源”段落里写的那两行字要残酷得多。不同版本的表格结构变了、指标名称换了、单位飘忽不定连“同一座城市叫什么”这种问题都能让新手抓狂。这篇内容就是围绕这样一份面板数据展开的。我把自己在整理、清洗、校验这份数据时的完整思路写出来包括怎么处理版本差异、怎么定城市编码、怎么统一口径、怎么做交叉验证以及最后拿去做分析之前必须排掉的雷。适合正在建库的科研人员、数据工作者也适合想搞清楚“面板数据到底是怎么被造出来”的读者。1. 为什么要把40年城市数据折腾成面板1.1 从“每年一张表”到“一张长表”《中国城市统计年鉴》的原始形态本质上是一年一本的截面数据——每一卷告诉你某一年各个城市是什么情况。想研究一个城市的演变你得自己动手把分散在各卷里的同一行数据挑出来拼在一起想比较城市之间的差异随时间怎么变化又要来回翻不同年份的表格。数据量小还好一旦跨度到40年、城市数量到几百个手工操作就是灾难。面板数据的核心价值在于把“时间”和“个体”两个维度同时放进一张表里。每一行不再是一座城市某年的孤立记录而是“城市-年份”的唯一组合。这样一来城市固定效应、时间趋势、滞后项、差分项这些分析手段才能真正落地。没有面板结构很多计量模型根本没法定系数。1.2 一张面板能支撑什么研究我见过用这套数据做的课题类型非常广。比较常见的是城市经济增长核算把GDP、固定资产投资、从业人员、财政收支这些变量拼成平衡面板估计生产函数或全要素生产率也有做城市基础设施与民生关系的用市辖区人口、用水用电量、道路面积这些指标做面板回归。还有人做城市层级演变研究靠的就是地级市数量在不同年份的增减变化。这几类研究的共同特点是既要有时间上的连续性也要有城市间的可比性。单独一年的截面数据给不了这种“跨时空可比”的基础。所以把年鉴整理成面板不是单纯做数据库而是在给后续一堆研究打地基。1.3 建立城市级面板的三个底层难点理论上讲整理工作好像很机械——读进来、堆起来就行。实际操作时三个问题会一直缠着你城市身份会变。40年里有县变成县级市有县级市升成地级市有地级市被合并有市辖区重新划界。如果不提前弄一套稳定的城市编码后面所有合并都会对不上号。指标定义会变。人口指标用过“非农业人口”也用过“城镇人口”GDP统计范围在“全市”和“市辖区”两个口径之间反复切换稍不留神就把不同尺度的东西加到了一起。数据单位会变。GDP有按万元、亿元两种习惯人口有按万人、千人两种精度如果只靠肉眼判断几百个城市几十列变量下来必然出错。这三个难点就是后面所有技术设计的出发点。能把这三点处理明白这份面板就算成功了大半。2. 不同年代的原始版本坑点完全不一样整理第一步是摸清手上到底有哪些“原料”。我按出版形态和表格风格把40年大致分成三个阶段每个阶段的坑都不一样。2.1 早期纸本扫描件OCR再准也有极限1985年到2000年前后年鉴还是典型的印刷品时代。我接触到的多数是扫描PDF分辨率参差不齐有些页面发黄、倾斜、甚至装订边压住了数字。这一阶段的表格有几个通病横表头跨页第一页的指标名和第二页的数据列经常对不上。数字是铅字打印识别时6和8、0和8容易混淆。小数点经常被扫描噪点吃掉2000.5会变成20005。部分表使用“—”表示无数据但有的表用“...”有的表干脆留空白。处理这个阶段纯靠OCR远远不够。我的做法是先跑一遍OCR再用列和列之间的数字规律去做后校验。比如某列是“年末总人口”那它的量级应当和相邻年份在同一水平突然翻10倍就是典型的识别错误。2.2 中期电子表格结构自由但表头混乱2000年以后年鉴开始以电子文档形式流传有些是Word转换的PDF有些直接是Excel表格。数据精度提高了但新的问题出现了编辑排版不再像铅字时代那样几十年不变表头经常重新设计。举例来说“地区生产总值”这个指标在不同年份的卷册里就有好几种写法“地区生产总值当年价格”“GDP”“全市生产总值”“国内生产总值当年价市辖区”如果不做映射光靠关键词匹配这些都会被当成不同变量。这个阶段最考验表头标准化能力我后面专门讲映射表怎么做。2.3 近年发布形态规范了但路径复杂近几年年鉴发布渠道相对稳定很多研究机构直接从统计数据库下载分年数据。这个阶段的表格结构规范很多指标编码相对统一但也不是完全省心。常见的问题是部分指标只发布了市级汇总没有分市辖区口径。有些表格附加了注释行、脚注行读入时会污染数据。下载工具默认导出是宽表格式需要先变成长表才能统一入库。下面是三个阶段特征的快速对照阶段主要载体最头疼的问题相对可靠的字段1985—2000扫描PDF/复印本OCR识别错误、“—”与“...”混用行政区划代码、城市名称2001—2015电子表格/文档转PDF表头名称不统一、口径切换城市代码、多数总量指标2016—2025统计数据库导出注释行干扰、部分口径缺失机器可读、指标编码规范这个阶段划分不绝对但能帮你快速判断某卷数据最需要重点核查的位置。拿到一份新年份的数据先想清楚它属于哪个阶段再决定清洗策略效率会高很多。3. 面板结构设计必须定死的四个技术决策刚开始做面板的人容易一上来就急着写代码把表格堆在一起。我强烈建议先停下把下面四个决定做了再动手。3.1 城市识别编码永远不要用城市名称做关联键城市名称是最不靠谱的字段。“北京”在早期表格里可能写作“北京市”“宣武区”后来并入了“西城区”有些年份用全称、有些年份用简称甚至同一个城市在不同表格里有不同的排序逻辑。我的做法是自定义一套稳定的city_id编码规则不依赖任何版本里现有的城市代码。具体规则可以这样设计前4位省份代码与通用行政区划代码前4位保持一致中4位城市代码按2010年前后的行政区划框架确定基准后2位市区/全县扩展位全市口径统一为00市辖区口径统一为01这样在合并时只认city_id城市名称只是辅助展示字段。遇到行政区划调整比如某县撤县设区我不会改历史数据的city_id而是单独维护一张“行政变更表”记录该县的变更类型、生效年份、原来归属、新归属。数据分析时可以根据需要把city_id对应到最新行政区划框架做聚合。3.2 指标标准化建一张“同义指标映射表”前面提到过“地区生产总值”可能叫法有五种。我在建库时维护了一张indicator_mapping表把原始表头统一映射到标准指标名。标准指标名的设计要有规律通常是{指标类别}_{细化对象}_{口径}例如gdp_total_city全市GDPgdp_total_district市辖区GDPpop_total_city全市年末总人口pop_nonagri_city全市非农业人口finance_revenue_city全市地方财政收入映射表至少包含四列原始表头原文、原始表头所在年份、标准指标名、备注。有了这张表每年的数据读进来后先用它做自动翻译翻译不了的人工确认确认结果再回填到映射表里。第一次建表很痛苦但维护几轮之后新数据源的适配速度会越来越快。3.3 单位统一所有金额类指标统一为“万元”人口统一为“万人”单位问题是新手最容易忽略的。早期年鉴里GDP可能以“万元”为单位中后期以“亿元”为单位。人口也是有“万人”也有“千人”的精度。我建议从源头就统一金额类变量一律换算成“万元”保留两位小数。人口类变量一律换算成“万人”保留两位小数。人均类变量比如人均GDP、人均可支配收入如果折算后数值特别大或特别小要回查原始单位是否看反了。统一单位不只是为了好看更重要的是防止后续回归时出现“系数大得离谱”的尴尬局面。我自己就经历过某核心解释变量单位没换跑完回归系数8.7×10^6一开始还以为发现了什么惊人效应最后发现是“亿元”和“万元”差了四个数量级。3.4 缺失值策略先保留原始缺失别急着插补很多做分析的人喜欢直接对缺失数据插补我建议库本身保持纯正——缺失就是缺失在数据文件里用NA表示不做任何插补。原因很简单插补方法的选择本质上是一种研究决策应该由最终的分析者根据研究假设去决定而不是在基建阶段就替他做了。当然缺失情况要在数据说明文档里记录清楚。我一般会在字段说明里标注某指标在哪些年份缺失率高、缺失可能是完全随机还是受统计口径变化影响。这样分析者拿到数据后可以自己决定用插补、删除还是多重填补。4. 从纸面到电脑一条可复制的流水线数据设计定了之后真正繁琐的录入清洗阶段就开始了。下面是我实际使用的四条操作主轴。4.1 扫描件的OCR与双人复核对早期扫描PDF我用的是一套“OCR抽样复核”的思路。先批量转成图片再通过OCR工具识别成结构化表格。光学识别之后必须跑核验脚本我会随机抽取每卷5%的页面做双人独立复核不一致的位置返工。OCR过程中最容易被忽视的是表格中数字之间的分隔线。如果分隔线被识别成字符数字列就会被顶开产生错位。我的经验是识别前先用图像处理工具把横竖线检测出来并去除只留文字和数字准确率能提升不少。4.2 表格结构统一从宽表到长表不同年份Excel的表格布局不一样。有的年份城市在行、指标在列有的年份反过来还有的年份一张表里塞了多个指标的分块。我的标准做法是先把所有原始数据读取成“城市代码-指标名-数值”的长表再通过透视表转换成最终的面板宽表形式。读入时最怕的是表头有多层合并单元格。处理这种表我不会直接让程序去猜第几行是列名而是先人工指定表头行号再配合程序校验指标名是否在映射表里存在。如果某个表头翻译不了程序会单独输出到一个待处理列表而不是静默丢弃。4.3 数据校验脚本整理过程离不开自动化校验。我常用的一段Python逻辑大致是这样的import pandas as pd def validate_year_panel(df, city_colcity_id, year_colyear, value_colvalue): # 检查是否有重复的城市-年份组合 dup df.duplicated(subset[city_col, year_col]).sum() # 检查数值是否超出合理区间 absurd df[(df[value_col] 0)].shape[0] # 检查同一城市相邻年份变化率是否超过阈值 df df.sort_values([city_col, year_col]) df[pct_change] df.groupby(city_col)[value_col].pct_change() spikes df[df[pct_change].abs() 5].shape[0] return {重复记录: dup, 负值数量: absurd, 异常跳变数量: spikes}这段代码不复杂但很实用。pct_change的阈值我设成5也就是说某城市某指标一年内翻了超过5倍就要回原始表检查是真实变化还是录入错误。现实里经济指标年际变化超过50%都少见5倍几乎必然是数据问题。4.4 人工校对环节不能省不管自动化脚本多完善人工校对依然是兜底。我习惯让一个人在原始PDF界面和标准化数据之间切换着抽查另一个人只负责看“异常跳变名单”。正常情况下一卷数据几百行人工核对的重点不是逐格读而是对着异常列表定向复查。另外早期卷册里偶尔会发现整列数据错位——比如“工业总产值”那列实际装的是“固定资产投资”。这种错误脚本很难发现靠的是人工浏览时对量级的敏感。比如工业产值和固定资产投资这两个指标在某市的量级通常不会相同一旦发现十几年的变化趋势完全一致就要回头怀疑是否错位。5. 数据质量的交叉验证方法数据整理完不能直接说“可以用了”。我会做三轮验证确认面板本身没有结构性硬伤。5.1 纵向加总检验利用年鉴本身的多级汇总关系做校验。多个地级市组成一个省份所以每年全省地级市GDP加总应当接近省统计年鉴里的数据。偏差超过一定范围我一般容忍1%-2%说明有城市数据录入或者口径不对。这种加总校验对部分指标尤其好用GDP、财政总收入、固定资产投资额都是上下级关系非常清晰的指标。人口指标受“市辖区”与“全市”口径干扰大一些加总校验的阈值会放宽。5.2 横向关联指标检验有些指标之间存在近似恒等关系。比如“年末总人口”应该大致等于“非农业人口”加“农业人口”“建成区面积”一般不会小于“城区面积”。利用这些内在逻辑做横向比对能发现单个指标看不出的问题。举一个真实例子某城市2005年“市辖区年末总人口”是1200万人而同一年“市辖区建设用地面积”只有98平方公里。人口1200万无论如何也不是98平方公里能容纳的回去一查发现原始表数据列错位人口列引的是全市总人口。这种错误靠单列检查几乎查不出来。5.3 跨年连续性检验连续年份差值检验用来抓“跳变”但不能机械理解。城市数据里的确存在一些合理的剧烈变化比如行政区划调整后某市辖区面积突然增加或者某县划入导致人口翻倍。这些属于真实变化不应被当作错误处理。所以我的做法是标记不自动修改。所有被标记的“剧烈变化”都要在变更表里找依据找不到依据的再回原表人工核对。通过这种“标记-解释-复核”三步既不会漏掉错误又不会误删真实的结构性变动。校验层用什么逻辑容错范围主要风险纵向加总城市加总≈省份偏差1%-2%口径不一致导致锚点失效横向关联子指标加总≈父指标视指标而定指标概念本身有交叉定义跨年连续相邻年份变化率阈值5倍真实行政调整被误判6. 面板数据拿去分析之前的最后排雷数据基本成型后还有几个几乎所有人都会踩的坑我单独整理出来。6.1 先看平衡性再跑模型面板数据不要求完全平衡但你先要知道自己有多少非平衡。如果某核心变量在2000年以前只有50个城市有数据、之后才扩展到全部城市而你没有做任何处理就直接跑固定效应结果可能被样本构成变化带偏。我的习惯是建表后立刻输出一张“城市×年份覆盖表”行是城市列是年份有数据的地方填1没有的填0。这张矩阵能让你一眼看出数据缺失是否有规律。分析时要么明确样本区间比如只用1995—2025年的子集要么用非平衡面板允许的估计方法而不是假装数据平衡。6.2 区分口径再做分析这是最重要的提醒。年鉴里“全市”和“市辖区”是两个完全不同的经济空间概念。市辖区口径通常更偏向城市核心区的经济发展水平全市口径包含了广大农村县域两者在研究问题上的含义差别很大。使用面板数据时务必看清楚每个变量后缀是_city还是_district并且在同一回归中使用相同口径的变量。最怕的是把A变量的全市口径和B变量的市辖区口径混在一个方程里结果系数的解释就成了灾难。我给每个表格文件名都加了口径后缀就是怕自己三个月后忘记当时用的是哪套口径。6.3 维护一份“数据变更日志”几十年的项目做下来修改无处不在。今天发现1988年某市GDP录错了明天确认2003年某指标单位不统一后天又调整了城市编码规则。如果不记录半年后回看你根本不知道现在这份数据跟最初那份差别在哪。我的做法是在库里放一个CHANGELOG.md每做一次修改就追加一行修改时间、涉及文件、修改原因、原始值、新值、操作人。这看起来繁琐但对于需要长期维护的数据集来说这比任何“自动化清洗脚本”都更值钱。6.4 分析前跑一遍描述性统计最后建议拿到面板之后先跑描述性统计而且要看两个维度的分布按年份看所有变量的均值、标准差按城市看核心变量的时间均值。这两张表能帮你快速发现“异常城市”——比如某个城市GDP30年基本没变那大概率是数据复制粘贴错了而不是这座城市真的停滞了。我自己踩过这样一个坑某城市1988年的“地方财政预算内收入”和1989年的值一字不差一开始以为是巧合后来发现是录入时整行重复粘贴把下一年的真实数据覆盖掉了。如果当时没有先看描述性统计后面所有用到这个变量的模型都会被这条脏数据污染。7. 这些年整理数据攒下的一点经验从一张张扫描页变成一行行规范记录这个过程没有太多技术奇迹拼的就是耐性和规则意识。我最大的体会是面板数据不是“收集”出来的而是“设计”出来的。编码规则、指标映射、单位基准、口径标注这些在设计环节做扎实了后面所有步骤都会顺反过来设计偷懒后面就会用无数个小时的重复劳动来还债。整理这套1985—2025年城市面板的过程中我最常提醒自己的话就是数据里的每一个数字都对应一座城市某一年的真实状态清洗它们时不较真后面的分析结果就不值得相信。希望这篇内容能帮你把地基打得比我当初更稳、更少踩坑。