
做县域面板分析的朋友基本都找过同一个东西——各县公路里程数据集。2000年到2023年这一跨度对研究交通基础设施、县域经济增长、区域差异的人来说是非常理想的面板区间。但这个数据的口径多、来源杂拿过来直接跑回归十有八九会踩坑。这篇文章我把这类数据从统计口径、字段设计、清洗流程到建模复现完整拆一遍按我自己的实操经验整理成一份能直接照着做的参考手册。适合正在写论文、做课题或者需要给自己项目搭一张基础设施底表的同学。1. 数据集从哪里来公路里程的统计口径与生成逻辑1.1 公路分层体系决定字段怎么设计县公路里程数据集不是简单的“一个县一条总数”。多数官方统计都会把公路按行政等级切分常见口径包括国道、省道、县道、乡道有些年份还会单列“村道”或者“专用公路”。这里有个很容易被忽视的点不同年份、不同省份对“公路”的认定范围不完全一致早期村道不一定纳入统计后来随着农村公路普查推进村道里程逐渐被大规模收录导致部分县出现某一年总里程突然跳增的情况。我在做数据核对时会先确认数据集里到底给的是“总里程”还是“分等级里程”。如果只有一列总数后续做差异分析时就得特别小心因为你无法判断增长是来自真实建设还是来自统计范围扩大。如果字段是按等级拆开的建议保留原始列并同步生成一个“统计范围说明”至少记下该县当年是否包含村道。这个信息在论文稳健性检验里经常能救命。1.2 里程数字是怎么来的报表汇总与地图测量目前公开的县域公路里程主要来自两种生成方式。第一种是行政报表汇总由县级交通主管部门逐级上报养护里程和新建工程里程年终汇总进入统计年鉴。这种方式的特点是和工程管理台账挂钩数字通常比较“整”但滞后性强且容易受考核指标影响。第二种是基于地理信息系统的地图量算利用遥感影像和路网矢量图层自动计算长度特点是很精确、可复现但是对历史年份无能为力因为早期没有可用的电子路网底图。清理这类数据时我建议把数据来源字段保留下来不要顺手删掉。原因是同一县不同年份可能混合了两种统计方式一但混用面板数据里就会出现系统性的测量误差。理想的做法是以某一年为基期对不同来源的序列做比例校准或者至少在模型里加入地区与年份的交互固定效应把这部分方差吸收掉。1.3 为什么2000到2023是常见区间2000年前后基层交通统计的信息化程度开始提升县级行政区代码也有了相对统一的版本这给长面板数据的拼接提供了基础。再往前推不少县的数据是纸质档案翻录的缺记、错记非常多。2023作为截止年份则是因为最新公开的完整统计口径通常滞后一两年太新的年份反而不一定能拿到全量县级数据。所以这个区间不是随便选的它基本对应了数据质量相对可信的时间窗口。如果你拿到的数据集是2000到2023恭喜你这个窗口刚好覆盖了若干轮行政区划调整、大规模路网升级和高速公路向县域延伸的阶段。这意味着你的数据天然适合做事件研究或者双重差分分析只要能把“变化发生的时间和地点”精确对应到县域上。2. 数据集的典型结构与核心字段2.1 一份规范数据集应该长什么样以我习惯的处理方式来说一份能直接用的县级公路里程面板至少要有以下字段字段名类型说明province_code字符串省级行政区代码province_name字符串省级名称county_code字符串县级行政区代码county_name字符串县级名称year整数年份road_total浮点数公路总里程单位公里road_national浮点数国道里程部分数据集为空road_provincial浮点数省道里程road_county浮点数县道里程road_township浮点数乡道里程data_source字符串原始来源标注需要强调县代码必须是字符串而不是数字。很多县级代码以0开头一旦用数字类型读取前面的0会被丢掉后续和行政区划代码表关联时就对不上。这是最常见、也最隐蔽的一个坑。2.2 总里程和分等级里程的关系很多数据集同时给总里程和分等级里程但两者未必能加和。原因在于分等级的统计口径与总里程口径可能不一致比如总里程里含村道但分等级里程只统计到乡道。所以核对数据时不要迷信“分项之和等于总数”先画一个分项加总值与总里程的散点图看看哪些年份系统性偏离。偏离点通常代表口径切换需要单独处理。如果你拿到的数据集只有总里程没有分项还有一种补救方式通过各省历年交通统计公报中的结构比例去反推。虽然粗糙但作为缺失值插补的辅助变量是够用的。我个人不推荐直接插值因为公路里程存在很强的政策脉冲特征插值会把真实拐点抹平。2.3 一个县如何唯一标识县级行政区的识别最稳妥的方式是使用行政区划代码。不过要注意代码会随区划调整而变化同一个县在不同年份可能对应不同代码。比如撤县设市、撤县设区都会引发代码更换。单纯使用“县名”关联更是危险同名县不少还有大量历史名称变更。我建议在清洗的第一步就生成两个辅助字段一个是当年的原始代码另一个是“稳定的地理单元编号”。稳定地理单元编号可以按2023年的行政区划反推历史数据把曾经属于该县但现在被拆分出去的区域合并回来。这样虽然会损失一部分行政边界精度但对面板分析来说可比的单元比精确的边界更重要。3. 数据清洗与整理实操流程3.1 第一步读取文件并检查基本结构拿到一个数据集文件后我习惯先用Python做一次快速体检确认年份覆盖、县数量、缺失情况。下面这段代码是我常用的初检模板import pandas as pd df pd.read_csv(county_road_2000_2023.csv, dtype{county_code: str}) # 检查年份覆盖 print(df.groupby(year)[county_code].count()) # 检查每个县每个年份是否都有记录 key df.groupby([county_code, year]).size().reset_index(namen) print(key[key[n] 1]) # 如果有重复记录需要去重 # 缺失检查 print(df.isna().sum())初检阶段最重要的不是急着补缺失而是确认数据集是不是平衡面板。很多所谓2000至2023年数据实际上中间某些年份存在整体缺失那和单点缺失的处理方式完全不同。如果某一年缺了三分之一以上的县优先怀疑是统计范围调整而不是简单漏报。3.2 第二步行政区划调整的处理行政区划调整是县级面板数据最大的敌人。撤县设区之后原县代码直接失效两个县合并成一个则两个旧县对应一个新县还有少部分县拆分出新县导致后续年份多出一个单元。粗暴的做法是直接删掉出问题的县但这样会损失样本而且删除往往和经济发展水平相关带来选择偏差。我的做法是建一个区划变更映射表然后按“当前口径”把历史数据重新归属。例如某县2020年被并入市区我会把其历史里程累加到对应城区单元并记录一条“变更类型”变量供后续稳健性检验使用。核心操作代码如下# 假设 mapping 是旧代码到新代码的映射字典 # {old_code: new_code, ...} df[new_county_code] df[county_code].map(mapping).fillna(df[county_code]) df[is_merged] df[county_code].ne(df[new_county_code]).astype(int) # 按新代码和年份汇总 df_clean df.groupby([new_county_code, year])[ [road_total, road_county, road_township] ].sum().reset_index()这里有个细节重新归属时里程是加总关系但如果是人均指标或密度指标就不能直接加总必须把分子分母拆开分别重新计算。否则合并县的人均里程会被高估。3.3 第三步缺失值和离群值的处理策略缺失值要先区分“真缺失”和“零值误填”。交通统计里新建县挂牌成立的第一年各项里程数可能确实很低但不会是零。如果看到某县某年总里程恰好是0大概率是当年数据没报上来被习惯性填了0。这类错误不能简单保留否则面板回归时这个离群点会明显拉偏系数。我的一般流程是先对每个县计算里程的一阶差分标记出变化率超过±50%的年份再结合行政区划调整表判断这些突变是源于区划调整还是数据异常最后对确认异常的点做逻辑校正或标记后替代。计算示例df_clean df_clean.sort_values([new_county_code, year]) df_clean[road_lag] df_clean.groupby(new_county_code)[road_total].shift(1) df_clean[growth] (df_clean[road_total] - df_clean[road_lag]) / df_clean[road_lag] # 找出异常变化点 outliers df_clean[df_clean[growth].abs() 0.5]需要说明±50%这个阈值只是起始值。对于里程基数很小的县新建几十公里公路就能带来100%的增长这不一定是错的。所以我不会自动修正而是生成一个异常标记列把裁量权留在后续分析里。3.4 第四步派生常用指标清洗完后我会立刻构建三个最常用的派生变量路网密度等于公路总里程除以行政区划面积单位公里/平方公里。人均公路里程等于公路总里程除以常住人口单位公里/万人。公路里程五年增量用当前年份减五年前的里程用于平滑年度波动。这三个指标覆盖了大部分经济学和交通研究的常规需求。计算时需要把面积和人口数据单独准备好注意面积数据也要随行政区划调整同步变更。行政区划代码会重编面积同样会变直接拿旧面积套新里程密度指标做出来就是错的。4. 数据集能干什么典型应用场景与复现思路4.1 面板回归研究路网对县域经济的影响这是我见过最多的用途。把县公路里程作为核心解释变量县域GDP或人均收入作为被解释变量能做固定效应面板回归。基础模型可以写成import statsmodels.api as sm from linearmodels.panel import PanelOLS df_panel df_clean.set_index([new_county_code, year]) df_panel[log_road] np.log(df_panel[road_total]) df_panel[log_gdp] np.log(df_panel[gdp]) model PanelOLS( df_panel[log_gdp], df_panel[[log_road]], entity_effectsTrue, time_effectsTrue, ) result model.fit(cov_typeclustered, cluster_entityTrue) print(result)实际跑出来的系数一般不会太大因为县级公路里程和GDP之间存在明显反向因果经济好的县有更多财力修路修路又进一步带动经济。想识别因果关系建议用历史路网规划作为工具变量或者利用“上级路网规划节点”这类外生冲击做事件研究。直接用当期里程对当期GDP做OLS结论参考价值有限。4.2 空间可视化一张图看懂区域差异把里程数据渲染到地图上往往是项目汇报里最直观的一步。我这里推荐用GeoPandas将数据与县级边界矢量数据关联再做分级设色import geopandas as gpd # 读取县级边界 boundary gpd.read_file(county_boundary_2023.shp) boundary[county_code] boundary[code].astype(str) # 选择某一年合并 gdf boundary.merge( df_clean[df_clean[year] 2023], oncounty_code, howleft ) # 计算密度并绘图 gdf[density] gdf[road_total] / gdf[area_km2] ax gdf.plot(columndensity, schemequantiles, legendTrue, cmapYlOrRd)可视化时要特别注意边界数据与里程数据是否同一年份。如果边界是2023年的而里程是2005年的合并后会出现大量空值因为2005年存在的县在2023年可能已经被合并。历史年份可视化应该使用历史边界数据这一点比颜色方案重要得多。4.3 机器学习特征把路网变量喂进模型公路里程数据集作为时间特征输入机器学习模型也很常见比如预测县域GDP增长、城市扩展潜力、物流可达性。使用这类时序特征时要避免一个典型错误把未来信息泄漏进训练集。建特征时只能用截至预测年份的数据。# 示例构造滚动3年平均值作为特征 df_feature df_clean.copy() df_feature df_feature.sort_values([new_county_code, year]) df_feature[road_3y_mean] ( df_feature.groupby(new_county_code)[road_total] .rolling(3, min_periods1) .mean() .reset_index(level0, dropTrue) )用路网数据做特征工程时我通常同时生成水平值、对数值、增量、密度和人均值让模型自己选择。不过要注意这些变量之间高度共线树模型尚可容忍线性模型则容易出问题需要做特征筛选或正则化。5. 避坑指南我从实际使用中总结的规律5.1 单位问题再怎么强调都不为过公路里程数据常见的单位是公里但早期统计里偶尔会出现“华里”或“千米”混用。如果发现某个县的里程数是周边县的2倍左右先别急着认为是误差可能是单位没换算干净。我处理时会把每个县与相邻县的里程比值全部算一遍超过正常区间的单独核查。这个检查成本很低但能找到很多隐藏问题。5.2 零值不一定代表缺失部分数据集会用0表示“该县当年无此项统计”但也存在真实里程极短的情况。区分方法很简单看该县前后年份的里程。如果前后年份都有几百公里中间某年是0那必然是缺失如果该县本来就是新成立的0作为初始值还说得通。处理时要留标注列别直接删掉因为样本量本来就宝贵。5.3 里程突增未必是公路变多了一个常见迷惑现象是某年一个县的公路总里程突然增加30%表面看是交通建设提速实际可能是因为统计范围从“县道及以上”扩大到“含乡道、村道”。判断方法有两个一是看分等级数据如果只有乡道、村道在变总里程增长就是口径变化二是看全省同期的总里程有没有同步跳增如果全省一起跳基本就是范围调整。我把这部分常见问题整理成了速查表方便随时对照现象可能原因处理建议里程突增50%以上县道改国道、统计范围扩大查阅当年交通公报确认连续多年不变上报数据模板复制粘贴标记可疑用邻近年份均值替代里程锐减至接近0县拆分或数据填错核对行政区划调整记录分等级里程之和不等于总里程口径不一致保留原始列不强行合并代码以0开头但读成了数字类型转换错误读取时强制指定字符串类型5.4 行政区划代码版本统一问题不同年份的代码更新可能不完整。有些数据集年份较早用的还是旧代码有些年份又开始用新代码。如果不做转换直接按代码合并会出现同一县被拆成两条记录、或者完全匹配不上。我建议先拿一个最新行政区划代码表做基准建立旧代码映射关系处理完再进入正式统计流程。现在有现成的历史行政区划代码包可以直接用不必自己手工整理。手动维护这个映射表不仅容易漏还会因为县级名称与代码并非一一对应而制造更多错误。6. 从数据集到支持决策一些实用的扩展操作6.1 与其他数据集的合并顺序县级公路里程很少单独使用通常要和人口、GDP、财政数据合并。合并时要先统一行政区划口径再做字段连接。否则先合并再处理区划调整中间产生的错误关联会很难排查。顺序一定是统一代码、清洗异常值、派生指标、再合并外部数据。这个顺序我反复踩过坑倒过来做的代价远超想象。6.2 构建多年变化指标的方法如果想用这个数据集做增量分析比如“五年公路里程增长率”建议以五年为窗口做滚动计算。公式是当前年份里程除以五年前的里程再减1。这个指标比同比增长更稳定能够有效避开单年异常波动。df_clean[road_5y_growth] ( df_clean[road_total] / df_clean.groupby(new_county_code)[road_total].shift(5) - 1 )注意如果中间发生了行政区划合并五年前的里程对应的是旧县直接算出的增长率会混入区划调整效应。稳妥做法是只在“没有经历区划调整”的县上计算该指标或者至少在模型中加入“是否经历调整”的控制变量。6.3 数据核查的外部参考源最后再分享一个核查小技巧。当你对某个县某年的里程数据高度怀疑时可以用该省的交通统计年鉴或交通运输经济运行分析报告去交叉验证。省级数据通常比县级数据更受重视质量更可控。你可以验证该县当年里程占全省的比例是否在合理范围内一旦偏离过大说明县级数字极大概率存在问题。这个办法不能替代审查所有数据但作为抽查手段非常好用。我在实际使用这套数据集时最深的体会是清理公路里程数据的时间往往比后续建模更长。但只要行政代码、统计口径、区划调整这三个基础问题处理到位后面无论做回归、画图还是机器学习特征都能省出大量返工时间。这套流程我反复用稳定可靠。如果你要做2000到2023年的县域路网分析建议先把基础功夫下足后面的成果才立得住。