基于SCATS的城市路网建模与瓶颈识别:从有向图到流量预测
2026/9/19 17:29:51 网站建设 项目流程

简介:PDF文档《第20期_交通路网的建模、评价及预测-报告版》面向交通工程、城市规划及相关数据科学领域的学习者与从业者,聚焦利用复杂网络理论对城市路网进行建模、效率评价与状态预测,并延伸到SCATS信号控制场景及路网数据修复方法,适合作为课程报告、项目预研或技术综述的参考。资源为单个PDF文件,大小4.76MB,内容结构完整,包含背景与意义、路网建模、路网评价、路网预测、路网数据修复五大板块,可系统了解节点出/入度、平均路径长度、聚类系数等核心指标,以及基于SCATS的周末/工作日路网状态对比、Top 5关键节点识别等具体案例和基于时间序列、机器学习的状态预测思路。目前已有146人学习,适合需要快速建立城市交通路网分析框架、撰写相关报告或开展入门研究的读者。

1. 从SCATS到复杂网络:交通路网为什么要先建模再评价

做交通数据的人应该都遇到过这个局面:SCATS信号系统里线圈、信号配时、子区划分的数据全都有,看起来什么都能做,可真要回答"哪个路口在拖累整个片区"时,却拿不出一张能算的图。这份报告给我最大的启发是,它把路网先抽象成 URN=(N, E, ES) 这样的有向图,再在图上做瓶颈识别和状态预测,所有讨论才有公共底座。对做智慧城市、交通规划或数据分析的人,这套方法的价值在于:不依赖特定厂商的数据格式,只要有交叉口、路段和方向信息就能复现。接下来按建模、评价、预测、数据修复的顺序拆开讲,每步都给出可以照着改的脚本和参数口径。

2. 城市路网有向图建模:URN、边属性与SCATS子区映射

2.1 节点、边与属性集合:把路网变成图论能处理的结构

交通路网建模最常见做法是以交叉口为节点 N,路段为边 E,ES 存放边的属性,比如道路长度、车道数、限速、方向限制等。这个抽象看起来简单,落地时有两个容易出错的地方。

第一个是方向。现实中两条单向道并排,如果建模成无向边,就会丢失禁左、禁右、单行线这些约束。报告里 URN 明确用了有向图,边集合 E 里的每条边带方向,转向上需要允许掉头或左右转时,得额外维护一个转向边或转向罚函数集合,而不是简单在邻接矩阵里填 1。

第二个是层级。路网有快速路、主干道、次干道、支路之分,直接混在一个图里,平均路径长度会被大量短途支路拉低。工程上常见做法是分成两层:底层是完整路网,用于局部路径计算;上层是干道路网,用于宏观评价。这样第 3 章要算的聚类系数和全局效率才不会被细枝末节干扰。

2.1.1 边属性里该放什么

ES 不是一个死结构,建议至少包含:

  • length:路段长度,用于计算行程时间和平均路径长度
  • lanes:车道数,用于容量估计
  • speed_limit:限速,用于自由流时间估计
  • area_type:建成区/郊区,用于速度折减
  • scats_subarea:SCATS 子区编号,用于与信号控制联动

其中 scats_subarea 这个属性在报告里比较关键,它把信号控制逻辑单元和地理路网打通了,后面第 4 章的预测特征要依赖它。

2.2 用Python快速构建URN并计算基础特征

我一般用 NetworkX 的 DiGraph 来承载 URN,读取一个 CSV 边表就能建图。边表里每条记录是一段有向路段,字段包括起点节点、终点节点、长度、车道数和方向。

import networkx as nx import pandas as pd edges = pd.read_csv("urn_edges.csv") G = nx.DiGraph() for _, row in edges.iterrows(): G.add_edge( row["from_node"], row["to_node"], length=row["length_m"], lanes=row["lanes"], speed_limit=row["speed_kmh"], subarea=row["scats_subarea"], ) print("节点数:", G.number_of_nodes()) print("有向边数:", G.number_of_edges()) print("弱连通分量数:", nx.number_weakly_connected_components(G))

代码逻辑分三段:先建空有向图,再逐行把边表和属性写入,最后输出三个基础统计量。弱连通分量数尤其要留意,如果大于 1,说明路网被人工切断了,后面算平均路径长度会得出错误结论。参数方面,length_m 和 speed_limit 用来算路段自由流行程时间;lanes 在容量约束分析里会用到;subarea 在第 4 章做区域级预测时按它聚合。

2.3 把SCATS子区映射到图结构

SCATS 的子区是信号控制层面的逻辑单元,一个子区通常包含多个交叉口,且子区边界并不总是和行政区边界重合。常见做法不是新建一张子区表,而是把 subarea 编号作为属性写进边,这样既保留地理拓扑,又能按 subarea 做聚合计算。

这里有一个容易踩的坑:SCATS 子区在信号周期切换时可能重新划分,同一路段在不同时段的子区编号会变。如果你要按子区统计流量,就得在边属性里再存一个生效时间段,或者单独维护一个时段子区映射表,而不是把 subarea 当成固定属性。报告里 SCATS 下的路网建模之所以能支撑评价和预测,正是因为建模阶段就把这个动态属性纳入了数据结构。

3. 路网评价指标落地:出入度、平均路径长度与聚类系数的计算口径

3.1 XU等人在2020年那套指标到底在算什么

报告引用了 XU Dong-wei 等人在 IEEE Intelligent Transportation Systems Magazine 2020 年发表的方法,核心是把路网评价拆成节点和网络两个层次。

节点层看的是出度和入度。某个交叉口的出度表示从它直接出发的路段数量,入度表示直接汇入它的路段数量。报告里的符号 A(VNi) 和 B(VNi),本质上就是对全图所有节点的出度序列和入度序列做聚合。高出入度节点通常是区域性枢纽,但未必是拥堵点;真正值得关注的是出入度差值大的节点,比如入度远大于出度,大概率是汇入瓶颈,信号配时需要考虑延长绿灯或增加蓄车空间。

网络层看的是平均路径长度 L 和聚类系数 C。平均路径长度反映全网通达效率,聚类系数反映相邻节点之间互相连通的紧密程度,聚类系数高意味着局部回路多、绕行选择多,抗拥堵能力更强。这两个指标一个管全局、一个管局部,组合起来能判断瓶颈是单点问题还是结构性问题。

3.1.1 全局效率的出入方向

报告里还有两个式子涉及出方向效率和入方向效率。简单理解:出方向效率评估的是从任意节点出发向外扩散信息或车流的能力,入方向效率评估的是向任意节点汇入的能力。不对称路网里这两者差距明显,比如放射性路网出方向效率高,入方向效率可能被市中心节点拖累。计算时不要只算全图平均效率,要把出、入两个方向分开算,否则会掩盖这种不对称性。

3.2 在NetworkX里计算这批指标

import networkx as nx # 承接上一节的 G # 节点度 in_degrees = dict(G.in_degree()) out_degrees = dict(G.out_degree()) # 有向图平均路径长度:需要先取最大弱连通分量,否则会因断点报错 giant = max(nx.weakly_connected_components(G), key=len) Gg = G.subgraph(giant).copy() avg_path_length = nx.average_shortest_path_length(Gg, weight="length") clustering = nx.average_clustering(Gg) # 全局效率:有向图没有内置函数,按定义计算 def global_efficiency(graph, weight="length"): n = graph.number_of_nodes() if n < 2: return 0.0 lengths = dict(nx.all_pairs_dijkstra_path_length(graph, weight=weight)) total = 0.0 count = 0 for u in lengths: for v, d in lengths[u].items(): if u != v and d != 0: total += 1.0 / d count += 1 return total / (n * (n - 1))

这段先取最大弱连通分量,原因是有向图里两个节点如果不在同一个弱连通分量,最短路径长度为无穷大,任何平均计算都会失真。average_shortest_path_length 的 weight 参数用 length 属性,这样得到的是按实际路段长度加权的路径,而不是路段数量。聚类系数在 NetworkX 里有向图版本和无向图版本行为不同,有向图会把方向纳入计算,官方文档对此有明确说明,建议跑之前先确认自己要不要方向信息。自定义的 global_efficiency 函数里,边长用 Dijkstra 计算,倒数求和再归一化,时间复杂度和空间占用都要心里有数,几百个节点的路网没问题,上万个节点时建议用采样估计。

3.3 指标参数表与瓶颈识别边界

指标对应报告代号计算方式典型用途
节点出度A(VNi)G.out_degree(node)识别交通发生源,判断流出压力
节点入度B(VNi)G.in_degree(node)识别汇入瓶颈,配合信号配时
平均路径长度Lnx.average_shortest_path_length全网通达效率,越低越好
聚类系数Cnx.average_clustering局部绕行能力,越高越稳健
全局效率(出)C(DVNet_G)按出方向边集计算评估从源点扩散的效能
全局效率(入)D(DVNet_G)按入方向边集计算评估向终点汇集的效能

报告中把周末和工作日分开对比,并列出 Top 5 in-degree 节点,这是评价部分最实用的操作方式:不只看某一天的绝对值,而是看工作日与周末的排名变化。如果某个节点在工作日入度排名很高、周末掉出前列,说明它主要承担通勤压力,改善措施盯早晚高峰即可;如果周末排名依然高,则是常态性片区枢纽,需要考虑路网结构层面的分流。

边界情况也要说明:平均路径长度对断开的网络极其敏感,只要有一条关键路段断裂,最大弱连通分量缩小,指标值可能大幅跳变,所以修复数据、保证网络连通,是评价计算的前置条件。

4. SCATS路网状态预测与数据修复:时间序列插补和异常检测

4.1 SCATS下的预测流程:从信号周期到流量特征

SCATS 能直接提供的原始数据包括信号周期长度、相位绿灯时间、线圈检测的流量和占有率。预测任务通常不是直接预测速度,而是预测下一时段的饱和度或排队长度,因为信号控制直接干预的就是这两个量。

推荐的建模流程分四步。第一步是数据对齐,把 SCATS 的历史记录按 15 分钟或 5 分钟粒度对齐到路网边属性上,每一条边对应一个时间序列。第二步是数据修复,由于线圈故障、通信中断导致的缺失值必须处理,否则特征工程会把这些洞带进模型。第三步是特征构造,除了当前时刻流量和占有率,还要加入滞后 1 个周期、前一日同时刻、周末标记、SCATS 子区聚合流量这些变量。第四步才是选模型训练和验证。

4.1.1 特征构造的几个实用参数
  • 滞后阶数:信号周期一般在 60 到 180 秒之间,15 分钟粒度下取 1 到 4 阶滞后比较稳妥
  • 日周期性:加入前一天同时段流量,能捕捉通勤规律
  • 周模式:周一早高峰与周二早高峰特征不同,用 one-hot 编码星期几
  • 子区联动:相邻子区同时段流量也是有用特征,SCATS 子区之间信号协调本身就有联动关系

4.2 缺失流量插补与异常检测的工程实现

路网数据修复报告里提了两类手段:插补缺失值、检测异常值。常见做法是先用时间序列插补把洞填上,再做异常检测,顺序不能反,否则异常检测模型会被缺失值干扰。

import numpy as np import pandas as pd from sklearn.experimental import enable_iterative_imputer from sklearn.impute import IterativeImputer from sklearn.ensemble import IsolationForest # df: 每一列是一条路段的流量时间序列, 缺失值记为 NaN imputer = IterativeImputer(max_iter=20, random_state=42) df_filled = pd.DataFrame(imputer.fit_transform(df), columns=df.columns) # 对修复后的序列做异常检测 iso = IsolationForest(contamination=0.05, random_state=42) anomaly_labels = iso.fit_predict(df_filled)

IterativeImputer 是多重插补的一种工程化实现,它对每一列缺失值用其他列建立回归模型来预测填充,迭代多轮逼近稳定值。max_iter 控制迭代轮数,一般 10 到 20 轮足够,太多只会徒增计算时间。contamination=0.05 表示假设 5% 的数据点是异常值,这个比例要根据实际路况调,如果某条路段正在施工,流量长期偏低,正常状态本身就会被标成异常,此时要把施工时段单独标记出来,不参与异常检测。

异常点处理有个细节:检测出来之后不要直接删掉或改成均值。SCATS 数据里流量异常往往对应真实事件,比如事故、临时管制,这类数据对预测模型来说是宝贵的样本。建议单独存一列 anomaly_flag,让模型自己学习异常时段与正常时段的流量关系,比硬性清洗效果更好。

4.3 短时流量预测:梯度提升与LSTM的取舍

报告里提到 SVM 和神经网络都是候选方法。从工程实践看,如果数据量在几万到几十万条这个量级,梯度提升树比神经网络更稳,原因有三个:一是对表格型特征天然友好,SCATS 特征大多是数值型和类别型混合;二是对时间序列的缺失值有一定鲁棒性,树模型不会因为个别异常值剧烈波动;三是调参路径成熟,LightGBM 的早停机制能省下不少验证时间。

import lightgbm as lgb feature_cols = ["flow_lag1", "flow_lag2", "occupancy", "prev_day_same_time", "subarea_flow", "hour", "is_weekend"] X = df_filled[feature_cols] y = df_filled["flow_next"] model = lgb.LGBMRegressor( n_estimators=800, learning_rate=0.05, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42, ) model.fit(X, y, eval_set=[(X_val, y_val)], callbacks=[lgb.early_stopping(50)])

特征列里有几项值得解释。flow_lag1 和 flow_lag2 是前两个时间步的流量,用来捕捉短时惯性;prev_day_same_time 是昨日同时段流量,用来锚定通勤节奏;subarea_flow 是 SCATS 子区聚合流量,相当于把信号控制范围内的协同信息加了进来。early_stopping 的 50 表示验证集损失连续 50 轮不下降就停止训练,防止过拟合。LSTM 更适合长周期依赖强的场景,比如预测未来 1 到 2 小时的路况,但在 15 分钟尺度上,梯度提升的性价比更高。

5. 验证技巧:用弱连通分量和流量守恒核对建模结果

5.1 用弱连通分量定位建模错误

图中出现多个弱连通分量通常意味着数据有问题。最常见的两个原因:一是转向数据不完整,左转、掉头允许关系没有补充进边集合,导致交叉口之间存在逻辑断点;二是坐标系转换时把部分节点坐标偏移了,不再是同一个路网。检查方法很简单,把每个分量的节点数打印出来,如果出现节点数只有 1 的孤立分量,逆查对应路段数据。

wcc = list(nx.weakly_connected_components(G)) isolated = [comp for comp in wcc if len(comp) == 1] print("弱连通分量数量:", len(wcc)) print("孤立节点数量:", len(isolated))

孤立节点多了以后,评价指标计算毫无意义。处理时不要直接删节点,回到源头检查边表里是否有指向该节点的路段因为转向限制被过滤掉了。如果有,补充转向边即可;如果确实是断头路,才删除。这个技巧在报告第 5 章数据修复之前就应该执行,它决定后续修复和预测的数据基础是否成立。

5.2 流量守恒校验修复结果

数据修复效果验证多数人只看缺失率是否下降,这不够。我建议用流量守恒做二次校验:一个交叉口的流入总量应等于流出总量与内部停车、转弯损耗之和,如果修复后的数据在某个节点严重不平衡,说明插补序列之间互相矛盾。具体做法是分时段汇总每个节点所有入边的流量和出边流量,计算偏差率。

node_flow_diff = {} for node in G.nodes(): in_flow = sum(df_filled.loc[df_filled["to_node"] == node, "flow"].sum()) out_flow = sum(df_filled.loc[df_filled["from_node"] == node, "flow"].sum()) node_flow_diff[node] = abs(in_flow - out_flow) / max(in_flow, out_flow)

偏差率超过 0.15 的节点优先检查,优先怀疑插补模型在该节点附近没有学对上下游关系。此时回看 IterativeImputer 的迭代轮数是否过少,或者该节点所在子区的异常点是否被误填充。把守恒校验和弱连通分量检查合在一起,就是一套低成本的路网数据体检流程,不用等到预测效果差再回头排查数据。

本文还有配套的精品资源,点击获取

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

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

立即咨询