☰
2000-2023县域公路里程面板数据:口径解析、清洗流程与建模应用
2026/10/2 9:28:08 网站建设 项目流程

做县域面板分析的朋友,基本都找过同一个东西——各县公路里程数据集。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(name="n") 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_effects=True, time_effects=True, ) result = model.fit(cov_type="clustered", cluster_entity=True) 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], on="county_code", how="left" ) # 计算密度并绘图 gdf["density"] = gdf["road_total"] / gdf["area_km2"] ax = gdf.plot(column="density", scheme="quantiles", legend=True, cmap="YlOrRd")

可视化时要特别注意边界数据与里程数据是否同一年份。如果边界是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_periods=1) .mean() .reset_index(level=0, drop=True) )

用路网数据做特征工程时,我通常同时生成水平值、对数值、增量、密度和人均值,让模型自己选择。不过要注意,这些变量之间高度共线,树模型尚可容忍,线性模型则容易出问题,需要做特征筛选或正则化。

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年的县域路网分析,建议先把基础功夫下足,后面的成果才立得住。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询