4. 网络构建实战:使用NetworkX构建供应链网络图

好,咱们直接动手。上一章我们把供应链金融的理论框架搭好了,这一章就来点实在的——用Python的NetworkX库,把那些抽象的企业、交易、账期,变成一张看得见摸得着的网络图。

我个人习惯,学任何网络建模,第一步不是写代码,而是先想清楚:节点是什么?边是什么? 供应链网络里,节点就是企业,边就是它们之间的交易关系。听起来简单,但实际项目中,我见过太多人一上来就画图,结果节点属性、边属性全乱套了。

4.1 环境准备与基础概念

先装好库。NetworkX是Python生态里最成熟的网络分析库,没有之一。

pip install networkx pandas matplotlib

导入的时候,我习惯这么写:

import networkx as nx
import pandas as pd
import matplotlib.pyplot as plt

NetworkX里最核心的两个东西:Graph(无向图)和DiGraph(有向图)。供应链金融里,交易是有方向的——A向B采购,钱从B流向A。所以,我们几乎只用DiGraph

核心概念速览:
  • 节点(Node):企业,每个节点有唯一ID
  • 边(Edge):交易关系,有方向
  • 属性(Attribute):挂在节点或边上的数据,比如企业规模、交易金额

4.2 构建基础供应链网络

咱们先造一个最简单的供应链:一个核心制造商,两家上游供应商,两家下游经销商。

G = nx.DiGraph()

# 添加节点
G.add_node('核心制造商')
G.add_node('供应商A')
G.add_node('供应商B')
G.add_node('经销商X')
G.add_node('经销商Y')

# 添加边(交易关系)
G.add_edge('供应商A', '核心制造商')
G.add_edge('供应商B', '核心制造商')
G.add_edge('核心制造商', '经销商X')
G.add_edge('核心制造商', '经销商Y')

嗯,这里要注意:边的方向代表物流方向。供应商把货发给核心制造商,所以边从供应商指向制造商。金融里常说的「反向保理」,其实就是顺着这个方向做融资。

画出来看看:

pos = nx.spring_layout(G, seed=42)
nx.draw(G, pos, with_labels=True, node_color='lightblue', 
        node_size=2000, font_size=12, arrows=True)
plt.title('基础供应链网络')
plt.show()

运行一下,你应该能看到五个节点和四条有向边。结构很简单,但这就是一切复杂网络的基础。

4.3 添加节点属性:企业规模与信用评级

光有节点名字不够。实际建模中,每个企业都有特征。比如企业规模(年营收)和信用评级(AAA到D)。

NetworkX添加节点属性有两种方式:

方式一:添加节点时直接带属性

G.add_node('核心制造商', 规模='大型', 营收=100, 信用评级='AAA')
G.add_node('供应商A', 规模='中型', 营收=20, 信用评级='A')
G.add_node('供应商B', 规模='小型', 营收=5, 信用评级='BBB')
G.add_node('经销商X', 规模='中型', 营收=15, 信用评级='A-')
G.add_node('经销商Y', 规模='小型', 营收=3, 信用评级='BB')

方式二:批量添加属性

我在项目中更常用这种方式,尤其是从数据库或Excel读数据时:

# 先添加节点
nodes = ['核心制造商', '供应商A', '供应商B', '经销商X', '经销商Y']
G.add_nodes_from(nodes)

# 再批量设置属性
node_attrs = {
    '核心制造商': {'规模': '大型', '营收': 100, '信用评级': 'AAA'},
    '供应商A': {'规模': '中型', '营收': 20, '信用评级': 'A'},
    '供应商B': {'规模': '小型', '营收': 5, '信用评级': 'BBB'},
    '经销商X': {'规模': '中型', '营收': 15, '信用评级': 'A-'},
    '经销商Y': {'规模': '小型', '营收': 3, '信用评级': 'BB'}
}
nx.set_node_attributes(G, node_attrs)
我的经验:信用评级最好用数值化处理。比如AAA=1.0, AA=0.9, A=0.8, BBB=0.7... 这样后面做风险计算时可以直接用数值,不用再转换。

检查一下属性是否添加成功:

print(G.nodes(data=True))

输出应该类似这样:

[('核心制造商', {'规模': '大型', '营收': 100, '信用评级': 'AAA'}),
 ('供应商A', {'规模': '中型', '营收': 20, '信用评级': 'A'}),
 ...]

4.4 添加边属性:交易金额与账期

边属性才是供应链金融建模的灵魂。交易金额决定了敞口大小,账期决定了资金占用时间。

# 添加边并带属性
G.add_edge('供应商A', '核心制造商', 交易金额=500, 账期=60)
G.add_edge('供应商B', '核心制造商', 交易金额=200, 账期=90)
G.add_edge('核心制造商', '经销商X', 交易金额=800, 账期=30)
G.add_edge('核心制造商', '经销商Y', 交易金额=300, 账期=45)

同样,也可以批量添加:

edge_attrs = {
    ('供应商A', '核心制造商'): {'交易金额': 500, '账期': 60},
    ('供应商B', '核心制造商'): {'交易金额': 200, '账期': 90},
    ('核心制造商', '经销商X'): {'交易金额': 800, '账期': 30},
    ('核心制造商', '经销商Y'): {'交易金额': 300, '账期': 45}
}
nx.set_edge_attributes(G, edge_attrs)
避坑指南:我曾经犯过一个错误——把交易金额的单位搞混了。有的数据源用「万元」,有的用「元」。建模前一定要统一单位,否则算出来的风险敞口会差好几个数量级。

查看边属性:

print(G.edges(data=True))

4.5 可视化:让网络图说话

光有数据不够,得能看出来。我一般会根据节点属性调整颜色和大小,根据边属性调整粗细。

# 根据信用评级设置节点颜色
color_map = {'AAA': 'green', 'AA': 'lightgreen', 'A': 'yellowgreen',
             'A-': 'gold', 'BBB': 'orange', 'BB': 'salmon', 'B': 'red'}

node_colors = [color_map[G.nodes[n]['信用评级']] for n in G.nodes()]

# 根据营收设置节点大小
node_sizes = [G.nodes[n]['营收'] * 30 for n in G.nodes()]

# 根据交易金额设置边宽度
edge_widths = [G.edges[e]['交易金额'] / 100 for e in G.edges()]

# 绘制
pos = nx.spring_layout(G, seed=42, k=2)
nx.draw(G, pos, with_labels=True, node_color=node_colors, 
        node_size=node_sizes, width=edge_widths, 
        font_size=10, arrows=True, arrowstyle='-|>')

plt.title('供应链金融网络(节点颜色=信用评级,大小=营收,边宽=交易金额)')
plt.show()

这张图一出来,信息量就大了。一眼能看出:核心制造商是最大的节点(营收高),供应商A的信用评级比供应商B好,核心制造商和经销商X的交易金额最大。

4.6 实战:从Excel构建完整网络

真实项目里,数据不会手动输入。我通常从Excel或数据库读。假设你有两个表:企业信息表交易记录表

# 读取企业信息
df_nodes = pd.read_excel('企业信息.xlsx')
# 假设列名:企业ID, 企业名称, 规模, 营收, 信用评级

# 读取交易记录
df_edges = pd.read_excel('交易记录.xlsx')
# 假设列名:买方ID, 卖方ID, 交易金额, 账期

# 构建网络
G = nx.DiGraph()

# 添加节点
for _, row in df_nodes.iterrows():
    G.add_node(row['企业ID'], 
               名称=row['企业名称'],
               规模=row['规模'], 
               营收=row['营收'], 
               信用评级=row['信用评级'])

# 添加边
for _, row in df_edges.iterrows():
    G.add_edge(row['卖方ID'], row['买方ID'], 
               交易金额=row['交易金额'], 
               账期=row['账期'])
注意:边的方向是「卖方→买方」,因为物流从卖方流向买方。如果你做的是资金流分析,方向要反过来。

4.7 网络的基本统计量

网络建好了,先看看基本指标:

print(f"节点数: {G.number_of_nodes()}")
print(f"边数: {G.number_of_edges()}")
print(f"密度: {nx.density(G):.4f}")
print(f"是否有环: {nx.is_directed_acyclic_graph(G)}")

密度接近0说明网络稀疏,供应链网络通常如此——每个企业只跟少数上下游交易。如果有环,说明存在循环交易,这在供应链金融里要特别警惕,可能是虚假贸易。

我还习惯算一下每个节点的度:

for node in G.nodes():
    in_deg = G.in_degree(node)
    out_deg = G.out_degree(node)
    print(f"{node}: 入度={in_deg}, 出度={out_deg}")

核心制造商的出度和入度都高,说明它既是主要买家也是主要卖家。这在供应链金融里,就是所谓的「核心企业」——银行做保理业务时最喜欢找这种节点。

4.8 本章小结

这一章我们干了三件事:

  • 用NetworkX的DiGraph构建了有向供应链网络
  • 给节点添加了企业规模和信用评级属性
  • 给边添加了交易金额和账期属性

说白了,就是把一张Excel表变成了一个可计算的网络结构。下一章我们会在这个基础上,做更深入的分析——比如风险传播路径、核心企业识别、账期优化等等。

但别急,先把今天的内容跑一遍。代码不多,但每一步都值得亲手敲一遍。我在项目里吃过亏——数据量一大,属性挂错了位置,查了半天才发现是节点ID重复了。嗯,这些坑,你们提前知道就好。


专注资料整理