简介:针对《经济研究》2024年第8期丁浩员等提出的跨国供应链断裂与重构测度方法,这份Python代码包给出了从数据清洗到指标计算的完整复现路径。资源面向经济学研究者、供应链分析人员及数据科学从业者,基于FactSet Revere全球供应链数据库,先对company.dta与relations.dta中的关键字段做解读,再完成供应链关系筛选、时间顺序调整和缺失异常处理,最终逐一实现断裂、恢复与转移三个核心指标的测度逻辑,并配有可直接运行的Python脚本与Excel示例。包体共25个文件,压缩后约1.27MB,其中8个xlsx提供中间结果与演示数据,8个py按功能拆分为数据处理、筛选、断裂恢复测度和转移测度等脚本,另有txt说明、html报告、png可视化结果及README文档,便于边学边跑、对照复核。已有875人浏览学习;读者可借此掌握FactSet供应链数据的实际用法,理解指标边界判断和排错思路,为后续供应链稳定性与抗风险能力研究提供一条可复用的技术路线。
1. 供应链断裂与重构测度:量化“链断了”和“链又连上了”两种状态
一套供应链断裂与重构测度的Python代码,核心价值是把经济学研究中很难说的“脆弱性”变成可计算的数字。你手里可能有企业级贸易数据、投入产出表,或者某个行业的中游供应商清单,代码能回答的只有三个问题:哪里断了、断了损失多大、后续网络怎么重新组织起来。适合的研究者是对这一问题有建模需求但不想从零写图论算法的人——数据科学背景偏弱也能跑通,因为代码里已经把网络构建、级联失效、路径搜索都封装成了函数。
2. 数据形态与预处理:从原始贸易表到可计算的供应链网络
2.1 两类主流数据形态的取舍
进入代码之前,先确认你的数据长什么样。供应链断裂与重构测度这套代码,设计的出发点就是两种最常见的经济学研究数据形态。
第一类是投入产出表,行业对行业的中间消耗矩阵。它好在全——覆盖国民经济所有行业,而且官方统计定期发布,跨年对比容易。但行业粒度太粗,2020年代很多真实断裂发生在产品级,行业表里根本看不出来。比如某类芯片的替代路径,在行业矩阵里只表现为“电子元器件→计算机制造”一栏数字,拆不开。第二类是企业级供应链关系数据,典型结构是“供应商ID—采购方ID—交易金额”三条字段。这类数据粒度细,能还原真实网络拓扑,但通常覆盖不全,可能只包含某头部企业的一级供应商,二级供应商缺失。
我的处理习惯是:研究目标是宏观冲击波动,优先投入产出表;研究目标是单一产业韧性或企业级重构,优先企业关系数据。两种形态的数据,代码里都设计了对应的读取入口——一套函数处理邻接矩阵,另一套函数处理三元组边表。关键在于第一步就把数据结构选对,后面所有的测度逻辑才能复用。
2.2 从原始数据到“供应链矩阵”的标准化流程
以企业级边表为例,数据清洗和矩阵化是代码的第一步。假设你手里有一份CSV,三列分别是供应商ID、采购方ID、交易额:
import pandas as pd import numpy as np # 读取原始产业链数据 df = pd.read_csv("supply_edges.csv") print(df.columns.tolist()) # 筛选连续12个月有交易记录的活跃连接 df = df[df["trade_value"] > 0].dropna() df["trade_value"] = df["trade_value"].astype(float) # 以“供应商为行、采购方为列”构造方阵 s_matrix = df.pivot_table(index="supplier_id", columns="buyer_id", values="trade_value", aggfunc="sum", fill_value=0) print(s_matrix.shape)这段代码逻辑上做了三件事:读入边表、剔除缺失与零值交易、按供应商买方关系构建一个方阵。要注意两个参数——aggfunc="sum"处理同一对供应商和买方出现多条交易记录的情况,合并金额降到一列;fill_value=0保证矩阵完整没有NaN,这直接影响后续的特征值和网络指标计算,NaN在数值计算里会被默认当作强传播路径,导致断裂测度严重失真。
矩阵构建后,我一般还会做一步阈值化处理:把交易额小于某分位数(比如总量的0.5%)的连接置零。原因是弱关联连接在级联失效模拟中会带来大量噪声,供应链网络本质上是带权重有向图,阈值过低会把边缘的偶然交易当成关键链,阈值过高又会丢失真实重构路径。一套普适的阈值准则很难定,但代码里开放了这个参数,你要针对具体数据做一两次敏感性扫描再固定下来。
3. 断裂测度实现:级联失效视角下的损失量化
3.1 断裂指标拆解:局部移除与全局波及
拿到矩阵之后,下一个核心问题是“断”怎么定义。代码里给的思路不是简单的“删掉一条边看两端”,而是级联失效视角——这也是近年供应链韧性研究中更被认可的做法。
级联失效的基本假设是:某节点因冲击退出网络后,它的下游客户会把需求压力转移给其他可替代供应商;如果替代供应商产能没补齐,下游只能减产或停产,进而引发次级收缩。这个传导过程可以在矩阵上迭代模拟。代码里实现的两个核心指标,一个是局部损失率,衡量被移除节点在总产出中的直接占比;另一个是全局波及系数,衡量移除一个节点后整张网络的产出损失扩散到什么程度。
局部损失率和直接占比的区别在于:直接占比只看被移除节点的量级,全局波及系数会把订单转移带来的二次、三次影响叠进去。比如某核心零部件供应商产值仅占总产出的2%,直接占比算出来很轻微,但因为下游没有备份供应商,实际波及可达8%。代码的实现思路就是把这个8%模拟出来,而不是停留在直接占比的表面上。
3.2 Python逐行实现:级联失效与网络效率下降
核心模拟函数如下:
def cascade_loss(matrix, removed_idx, demand_loss_ratio=0.3, max_iter=20, tol=1e-6): A = matrix.copy().astype(float) n = A.shape[0] for idx in removed_idx: A[idx, :] = 0.0 # 该供应商无法供货 A[:, idx] = 0.0 # 该采购方不再下单 baseline = A.sum(axis=0) # 各采购方原始可得供给 new_supply = A.sum(axis=0) total_loss = 0.0 for it in range(max_iter): deficit = np.maximum(baseline - new_supply, 0) * demand_loss_ratio # 采购方按缺口比率削减自身需求 A = A * (1 - deficit / np.maximum(baseline, 1e-9)) updated_supply = A.sum(axis=0) diff = np.abs(updated_supply - new_supply).sum() new_supply = updated_supply total_loss = baseline.sum() - new_supply.sum() if diff < tol: break return total_loss / baseline.sum()参数里demand_loss_ratio是最关键的调节旋钮,它模拟的是“需求缺口中有多大比例会真实转化为下游减产”。设为0意味着纯结构性断供但不减产,设为1意味着完全刚性依赖。现实里这个值通常在0.3到0.7之间,取决于下游是否有库存缓冲。max_iter控制迭代上限,因为级联过程在数学上是一个逐步逼近的压缩映射,一般10步以内就收敛了;如果迭代20步还不稳定,优先检查矩阵是不是存在强连通分量导致的周期震荡,而不是增大迭代次数。
调用方式要注意,removed_idx需要传入行索引列表,而不是节点ID。如果你手里是字符串ID,务必先用id_to_index字典做一次映射。这个函数返回的是全局损失率,可以直接作为网络韧性水平的反向指标——损失率越低,网络越抗断。
3.3 韧性曲线的边界条件
单个节点的移除只是静态快照,这套代码真正进阶的用法是先把每个节点都移除一遍,画出“每个节点损失率”排序曲线。排序曲线有特别鲜明的关系:头部陡峭、尾部平缓,说明网络高度依赖少数关键节点;曲线相对平缓,说明网络冗余度高。这种曲线在经济学论文里通常作为“结构脆弱性证据”出现,审稿人很认这个图。
要注意两个边界:一是移除单个节点与移除一组节点的结果不是简单可加关系,一组关联节点同时断裂的级联损失往往大于逐个移除的和,因为替代路径被同时剪断了;二是节点带产能上限时,级联模型必须增加容量约束,否则替代路径会被高估。这套代码默认不带容量约束,如果你研究的是汽车或者芯片这种产能弹性有限的行业,建议自行加一个capacity向量,替代路径分配时先查剩余产能。
4. 重构测度实现:替代路径搜索与网络结构调整
4.1 重构方向的三种测度逻辑
断裂只是供应链研究的前半截,后半截是“断完之后,网络怎么重新组织”。代码里把重构测度分成了三种逻辑,对应经济学研究里经常被混在一起的三个概念。
第一种是路径重构,指原本依赖某条固定供应链的下游厂商,在断供后改走另一条备选路径。测度方法是给定源节点和目标节点,先移除断裂边,再搜索是否存在替代路径、替代路径比原路径长多少。第二种是节点补位重构,对应新供应商进入网络替代退出者,测度方法是比较退出前后的邻居重叠度。第三种是拓扑结构调整,即便是相似的路径改动,整个网络的中介中心度分布会发生明显迁移,这反映的是权力中心有没有从一个节点转移到另一个节点。
代码在这三种逻辑里都做了预置函数。个人建议如果你的数据时间跨度超过三期(比如断前、断中、断后三个年度),优先使用第三种测度,因为它对面板数据最友好;如果只有截面的断前断后数据,第二种测度最稳。
4.2 代码实现:BFS替代路径搜索与结构相似度对比
from collections import deque def find_alt_path(adj_list, source, target, removed_nodes): if source in removed_nodes or target in removed_nodes: return None queue = deque([(source, [source])]) visited = set([source]) while queue: node, path = queue.popleft() for nxt in adj_list[node]: if nxt in removed_nodes or nxt in visited: continue new_path = path + [nxt] if nxt == target: return new_path visited.add(nxt) queue.append((nxt, new_path)) return NoneBFS搜索在无权图上能找到跳数最短的替代路径,这是重建路径的最基础版本。removed_nodes传入断裂节点集合;adj_list需要在矩阵基础上转换邻接表结构。这段代码返回的替代路径如果为None,说明目标节点在给定断裂高端下完全失联,这在经济学意义上就是“断链不可重构”,比损失率数字更有政策含义。
路径长度只是重构成本的一个维度,真实网络里还需要考虑路径上的总交易量与瓶颈节点。我通常会对找到的替代路径再做两步后处理:第一步,求路径上所有边的权重最小值,这个值决定了新增路径的通量上限;第二步,将新路径的通量与断裂前的原路径通量对比,若新通量不足原通量的60%,我会在结果表里标记为“弱重构”。不是所有替代路径都是有效重构,弱重构本质上只恢复了连接形式,没恢复实际流量。
结构调整的测度实现相对更重一些:算断前断后的中心度矩阵,然后做归一化处理,最后比较单个节点的中心度排序变化百分比。当变化百分比超过30%时,可以认定网络权力结构发生了实质变化,这个阈值逻辑在代码注释中有说明,按你的研究背景可调整。重构测度最怕的是把“存在路径”等同于“恢复完成”,所以输出结果里路径存在性与路径通量两个字段必须同时保留。
5. 避坑与排查:供应链测度中常见的五个翻车现场
5.1 矩阵方向搞反,指标整体失真
现象:算出来的局部损失率普遍偏高,而且移除哪个节点都是同样的模式,经济直觉完全吻合不上。
原因:供应链矩阵的行列方向约定不统一。代码默认“行=供应商,列=采购方”,但原始数据里常有反着排的统计口径,导致移除节点时同时删错了上下游方向。
解决:矩阵构建后立刻做一次核验:手选一条已知的高权重边,检查matrix.loc[sup_id, buyer_id]是否等于原表金额。不是的话,转置一次再继续,这个动作花不到一分钟,能避免后面所有指标全部作废。
5.2 零值被当成有效连接,重构路径出现“幽灵通道”
现象:BFS找到的替代路径穿过从未发生过交易的两个节点,而且这类路径不在少数。
原因:pivot_table用了fill_value=0之后,矩阵里的零元素和真实弱连接在邻接表转换时被一视同仁,BFS会把这个零权重边当作可通行边。
解决:转换邻接表前先做一步二值化过滤,参考代码里的阈值参数设置,把矩阵元素中小于1e-6的置零并排除出邻接表。要记得这是经济学问题,不是图论题,零贸易量不代表隐性伙伴关系,除非你有明确的合同数据支撑。
5.3 级联迭代不收敛,损失率震荡
现象:cascade_loss函数跑到max_iter上限还不停下,损失率数值忽高忽低。
原因:矩阵里存在强连通分量,结构上构成了环。迭代中A矩阵的比例收缩在环形子网上产生周期震荡,A矩阵的谱半径大于1。
解决:先检查矩阵是否能通过置换变成下三角块结构;如果存在强连通块,在迭代内部加上阻尼系数,代码里给deficit乘以0.5的衰减因子。注意这个改动会同时略微压低损失率绝对值,论文里需要报告阻尼设置。
5.4 弱重构判断过松:路径通了但流量远低于原水平
现象:替代路径搜索结果全是连通状态,但下游企业实际运营数据却显示仍然半停产。
原因:只验证了路径可达性,没有验证路径容量。替代链路里的次要供应商规模很小,名义上能连接,实际交易体量远不够。
解决:我在这套代码的输出阶段加了一个后处理脚本,每找到一条替代路径就累加路径上的最小边权,标记为alt_flow_ratio。研究结论里以这个比值代替二进制的“是否连通”作因变量,模型解释力明显提升,这段后处理逻辑在代码包的postprocess.py里。
5.5 大矩阵性能崩塌:行业投入产出表还算流畅,企业级网络直接卡死
现象:上万节点的企业级供应链关系数据进cascade_loss,内存直接溢出,函数没有报错提示,但等待时间不可接受。
原因:企业内部关系数据大多数是稀疏的,而这里用稠密矩阵做级联迭代,存储和计算复杂度都是O(n²),万级节点的稠密矩阵光内存就上GB。
解决:对万级以上的网络,我把矩阵换成scipy.sparse.csr_matrix存储,迭代里的逐行乘法改成稀疏运算。改造后内存占用降两个数量级,级联迭代速度基本不变。代码包里保留了sparse_mode=True的参数开关,建议节点数超过5000就默认开启。
6. 进阶用法:批量断裂仿真实验与结果落盘
跑通单次测度只是起步,论文里真正有价值的往往是批量仿真。把“移除单一节点”扩展成“随机移除k个节点重复m次”,得到的韧性分布曲线比单点结果稳健得多,审稿人问“你的结果对节点选择敏感吗”时,这张分布图就是答案。
import random rng = np.random.default_rng(42) def batch_failure(matrix, k=3, trials=200): results = [] n = matrix.shape[0] for _ in range(trials): nodes = rng.choice(n, size=k, replace=False) results.append(cascade_loss(matrix, nodes)) return np.array(results) loss_arr = batch_failure(s_matrix, k=3, trials=200) print(np.percentile(loss_arr, [5, 50, 95]))随机种子在经济学研究里不是可有可无的细节点,写成固定单次实验是复现性最强的,seed=42保证不同人跑出的批量结果完全一致。批量仿真的三个分位数输出比均值更值得写进正文,因为它能同时回答“预期损失多重”和“最坏情况多坏”两个问题。条件允许的话,把k从1做到10,每个k做一次批量仿真,就能得到一条“断裂规模—最大损失”曲线,对应成本收益分析的边际维度。
批量结果要落盘存档,代码最后会生成一张三列的结果表,包括仿真轮次、移除节点组合、全局损失率。不要只保存一个均值数字,完整结果表能让后面换测度口径时省掉重跑的开销。这里也交代一下输出代码的用法习惯:CSV路径通过参数传入,不要在脚本里硬编码。
那之后我每次拿到新的供应链数据,都会强制走一遍同样的流程——先转置核验方向,再设阈值过滤零权重,然后跑一遍单点损失排序,最后才上批量仿真。这套流程救过我很多次,尤其是矩阵方向那一步,几乎每次换数据源都会中招一次。希望帮到你。
本文还有配套的精品资源,点击获取