第二章:网络分析工具:NetworkX入门、图的基本操作、节点与边的属性

说实话,做金融网络建模这些年,我换过不少工具。但要说最顺手、最离不开的,还得是 NetworkX。它就像瑞士军刀——小巧、全面、关键时刻总能派上用场。今天咱们就来好好聊聊这个工具,以及图的基本操作和属性管理。

2.1 NetworkX 是什么?为什么选它?

NetworkX 是 Python 生态里最流行的复杂网络分析库。它支持创建、操作和研究各种图结构——有向图、无向图、多重图,统统不在话下。

我个人习惯用它来做三件事:

  • 快速原型验证——写几行代码就能搭出一个网络模型
  • 网络指标计算——度中心性、介数中心性、聚类系数,一行函数搞定
  • 可视化探索——配合 Matplotlib 或 Pyvis,能直观看到网络结构

我在项目中遇到过一个问题:用其他工具处理 10 万个节点的图,内存直接爆了。换成 NetworkX 的稀疏矩阵存储方式,同样的数据跑得稳稳当当。嗯,这就是它的优势——底层用字典和列表存储,对内存很友好。

核心概念:NetworkX 中的图由节点(Node)和边(Edge)组成。节点代表实体(比如银行、股票、交易账户),边代表关系(比如资金往来、持股关系、交易行为)。

2.2 安装与导入

安装很简单,一行命令搞定:

pip install networkx

导入时我习惯给它起个别名:

import networkx as nx
import matplotlib.pyplot as plt  # 可视化必备

你想想看,整个金融网络分析的起点,就这么两行代码。是不是很简单?

2.3 创建你的第一个图

咱们先创建一个最简单的无向图。假设有 4 家银行,它们之间有资金拆借关系:

# 创建一个空的无向图
G = nx.Graph()

# 添加节点
G.add_node("工商银行")
G.add_node("建设银行")
G.add_node("农业银行")
G.add_node("中国银行")

# 添加边(资金拆借关系)
G.add_edge("工商银行", "建设银行")
G.add_edge("工商银行", "农业银行")
G.add_edge("建设银行", "中国银行")
G.add_edge("农业银行", "中国银行")

# 快速查看图的信息
print(G.nodes())   # 输出所有节点
print(G.edges())   # 输出所有边
print(G.number_of_nodes())  # 节点数量
print(G.number_of_edges())  # 边数量

运行结果:

['工商银行', '建设银行', '农业银行', '中国银行']
[('工商银行', '建设银行'), ('工商银行', '农业银行'), ('建设银行', '中国银行'), ('农业银行', '中国银行')]
4
4

你看,一个银行间资金拆借网络就搭好了。4 家银行,4 条拆借关系。工商银行和建设银行之间有直接往来,农业银行和中国银行之间也有直接往来。这就是一个典型的金融网络雏形。

小技巧:如果你需要处理有向关系(比如 A 向 B 转账),用 nx.DiGraph() 代替 nx.Graph()。我在做反洗钱资金追踪时,有向图是标配——资金流向必须明确。

2.4 节点与边的属性管理

光有节点和边还不够。真实金融场景里,每个节点都有属性——银行名称、资产规模、风险评级。每条边也有属性——交易金额、交易时间、交易类型。

NetworkX 允许你给节点和边附加任意属性,用字典存就行。我称之为「给网络加料」。

2.4.1 节点属性

# 添加节点时直接带属性
G.add_node("工商银行", 资产规模=30.5, 风险评级="A")
G.add_node("建设银行", 资产规模=25.2, 风险评级="A")
G.add_node("农业银行", 资产规模=20.1, 风险评级="B")
G.add_node("中国银行", 资产规模=22.8, 风险评级="A")

# 或者用字典批量添加
attrs = {
    "工商银行": {"总部城市": "北京", "员工数": 400000},
    "建设银行": {"总部城市": "北京", "员工数": 350000},
    "农业银行": {"总部城市": "北京", "员工数": 450000},
    "中国银行": {"总部城市": "北京", "员工数": 300000}
}
nx.set_node_attributes(G, attrs)

# 查看某个节点的属性
print(G.nodes["工商银行"])

输出:

{'资产规模': 30.5, '风险评级': 'A', '总部城市': '北京', '员工数': 400000}

2.4.2 边属性

# 添加边时带属性(交易金额,单位:亿元)
G.add_edge("工商银行", "建设银行", 交易金额=5.0, 交易类型="拆借")
G.add_edge("工商银行", "农业银行", 交易金额=3.2, 交易类型="拆借")
G.add_edge("建设银行", "中国银行", 交易金额=4.1, 交易类型="拆借")
G.add_edge("农业银行", "中国银行", 交易金额=2.8, 交易类型="拆借")

# 查看边的属性
print(G.edges["工商银行", "建设银行"])

输出:

{'交易金额': 5.0, '交易类型': '拆借'}

避坑指南:我曾经在项目中犯过一个低级错误——给同一个节点重复添加属性,后添加的会覆盖前面的。如果你需要保留多个值,建议用列表或嵌套字典。比如 {"历史交易": [{"日期": "2024-01-01", "金额": 100}, ...]}

2.5 图的基本操作

掌握了创建和属性管理,咱们来看看日常最常用的几个操作。这些操作我在金融网络分析中几乎天天用。

2.5.1 遍历与查询

# 遍历所有节点
for node in G.nodes():
    print(f"节点: {node}, 属性: {G.nodes[node]}")

# 遍历所有边
for u, v in G.edges():
    print(f"边: {u} - {v}, 属性: {G.edges[u, v]}")

# 查询某个节点的邻居(直接相连的节点)
neighbors = list(G.neighbors("工商银行"))
print(f"工商银行的邻居: {neighbors}")

# 判断两个节点是否相连
print(G.has_edge("工商银行", "建设银行"))  # True
print(G.has_edge("工商银行", "中国银行"))  # False

2.5.2 度与度分布

度(Degree)是网络分析最基础的指标。它表示一个节点有多少条边相连。在金融网络中,度越高意味着这个节点越「活跃」——要么是资金枢纽,要么是风险传染的关键节点。

# 计算每个节点的度
for node in G.nodes():
    degree = G.degree(node)
    print(f"{node} 的度: {degree}")

# 获取所有节点的度
degrees = dict(G.degree())
print(degrees)

输出:

工商银行 的度: 2
建设银行 的度: 2
农业银行 的度: 2
中国银行 的度: 2

这个例子中每个节点的度都是 2,因为咱们的图是个环形结构。真实金融网络里,度的分布往往极不均匀——少数节点连接大量边,多数节点只有少量连接。这就是「幂律分布」,也叫「二八法则」。

2.5.3 子图与连通分量

有时候你只关心网络中的某一部分。比如分析某个银行集团内部的交易网络,或者找出所有相互连通的节点群。

# 提取子图(只保留指定节点)
sub_nodes = ["工商银行", "建设银行", "农业银行"]
sub_G = G.subgraph(sub_nodes)
print(sub_G.edges())  # 只包含这三个节点之间的边

# 查找连通分量(相互连通的节点群)
components = list(nx.connected_components(G))
print(f"连通分量数量: {len(components)}")
for comp in components:
    print(f"分量中的节点: {comp}")

实战经验:我在做系统性金融风险分析时,经常用连通分量来判断「如果某家银行倒闭,哪些银行会被波及」。如果整个网络只有一个连通分量,那风险就是全局性的。如果有多个孤立的分量,风险传播就会被阻断。

2.6 可视化:让网络「看得见」

数据再漂亮,不如一张图来得直观。NetworkX 自带的绘图功能虽然不算华丽,但做快速探索足够了。

# 基础可视化
pos = nx.spring_layout(G, seed=42)  # 布局算法,让图更好看
nx.draw(G, pos, with_labels=True, node_color='lightblue', 
        node_size=1500, font_size=12, font_weight='bold')
plt.title("银行间资金拆借网络")
plt.show()

如果你想让节点大小反映资产规模,边粗细反映交易金额,可以这样:

# 高级可视化:节点大小按资产规模,边宽度按交易金额
node_sizes = [G.nodes[n]["资产规模"] * 50 for n in G.nodes()]
edge_widths = [G.edges[u, v]["交易金额"] * 2 for u, v in G.edges()]

nx.draw(G, pos, with_labels=True, node_color='lightgreen',
        node_size=node_sizes, width=edge_widths,
        font_size=10, font_weight='bold')
plt.title("银行间资金拆借网络(节点大小=资产规模,边宽=交易金额)")
plt.show()

嗯,这里要注意:可视化只是辅助工具,别过度依赖。我见过有人花几个小时调颜色、调布局,结果网络的核心结构反而没分析清楚。先跑指标,再画图,顺序别搞反了。

2.7 本章知识体系

为了让你对本章内容有个整体把握,我画了一张结构图:

第二章:NetworkX 入门与图操作 NetworkX 核心 图创建 Graph() / DiGraph() / MultiGraph() add_node() / add_edge() 属性管理 节点属性:资产规模、风险评级 边属性:交易金额、交易类型 基本操作 遍历 / 查询 / 邻居 度计算 / 子图 / 连通分量 可视化 spring_layout / draw() 实战应用 金融网络建模 / 风险分析 图2-1:本章知识体系结构图

2.8 避坑与经验总结

最后,分享几个我在实战中踩过的坑,希望能帮你少走弯路:

  • 节点命名别用数字——我刚开始做的时候,直接用 0、1、2 给节点编号。结果分析到一半,完全分不清哪个节点对应哪家机构。后来我养成了习惯:节点名用字符串,最好带上业务含义。
  • 大图别直接可视化——超过 1000 个节点的图,draw() 函数会卡死。我一般先抽样,或者用 Pyvis 生成交互式 HTML 图。
  • 属性别存太多——每个节点挂 20 个属性,10 万个节点就是 200 万个键值对。内存扛不住的。只存分析必需的字段,其他放数据库里。
  • 有向图和无向图别混用——我见过有人用无向图分析资金流向,结果方向信息全丢了。做金融网络,先想清楚关系是不是有方向的。

好了,NetworkX 的基础操作就聊到这儿。工具本身不难,难的是怎么用它讲好金融故事。下一章咱们会深入网络指标——度中心性、介数中心性、聚类系数,这些才是真正能发现「谁才是系统重要性银行」的关键指标。


公众号:蓝海资料掘金营,微信deep3321