4、NetworkX入门:创建图、添加节点和边、图的基本属性查看、图的绘制基础

好,咱们正式开始动手了。

前面聊了那么多图论的概念,什么节点、边、度、路径……说实话,光看理论容易晕。我自己的经验是,上手敲代码才是最快的学习方式。今天我们就用Python里最经典的NetworkX库,把那些抽象的概念变成实实在在能跑、能看的东西。

4.1 安装与导入:准备工作

NetworkX是Python的第三方库,安装很简单。我个人习惯用pip,一行命令搞定:

pip install networkx matplotlib

这里顺便把matplotlib也装上,因为后面画图要用到它。嗯,注意一下,如果你用的是Anaconda环境,可能已经自带了这两个库,可以跳过安装步骤。

导入的时候,我一般这样写:

import networkx as nx
import matplotlib.pyplot as plt

为什么要起别名?说白了就是图个方便。后面每次调用nx.Graph()比敲networkx.Graph()省事多了,你试试就知道。

4.2 创建图:空图起步

创建一个空图,就像准备一张白纸。代码极其简单:

G = nx.Graph()  # 创建一个无向图
print(type(G))  # <class 'networkx.classes.graph.Graph'>

这里创建的是无向图。如果你需要处理有向关系(比如资金流向、A给B转账),那就用nx.DiGraph()。我在做反欺诈项目时,经常用有向图来追踪资金链路,这个后面会细讲。

另外还有nx.MultiGraph()nx.MultiDiGraph(),支持两个节点之间有多条边。比如两个人之间既有微信转账又有支付宝转账,就可以用多重图来表示。不过刚开始学,咱们先聚焦在基础的无向图上。

4.3 添加节点和边:给图注入灵魂

图建好了,接下来就是往里塞东西。节点和边是图的两个基本元素,咱们一个一个来。

4.3.1 添加节点

添加单个节点:

G.add_node(1)          # 添加一个节点,标签为1
G.add_node("Alice")    # 节点可以是字符串
G.add_node((1, 2))     # 甚至可以是元组

添加多个节点:

G.add_nodes_from([2, 3, 4, "Bob"])

节点可以是任何可哈希的对象。整数、字符串、元组都行,但列表不行。为什么?因为列表是可变的,不能作为字典的键。NetworkX底层就是用字典来存节点的,这个细节你了解一下就好,不用死记。

4.3.2 添加边

添加一条边:

G.add_edge(1, 2)          # 在节点1和2之间连一条边
G.add_edge("Alice", "Bob")

添加多条边:

edges = [(1, 2), (2, 3), (3, 4), (1, 4)]
G.add_edges_from(edges)

这里有个小细节:如果添加边时,两端的节点还不存在,NetworkX会自动帮你创建这些节点。比如:

G.add_edge(5, 6)  # 节点5和6会自动添加到图中

这个特性挺方便的,但有时候也会带来隐患。我曾经在项目中因为拼写错误,不小心多了一个节点,排查了半天才发现。所以建议你养成好习惯:先加节点,再加边,或者至少确认节点名称是正确的。

4.3.3 带属性的节点和边

现实中的金融网络,节点和边往往带有额外信息。比如一个节点代表一家公司,它可能有“市值”、“行业”等属性;一条边代表一笔交易,可能有“金额”、“时间”等属性。NetworkX支持给节点和边添加属性:

# 添加带属性的节点
G.add_node("腾讯", 市值=45000, 行业="科技")
G.add_node("阿里", 市值=40000, 行业="科技")

# 添加带属性的边
G.add_edge("腾讯", "阿里", 金额=5000, 时间="2024-01-15")

属性可以随时查看和修改。我个人觉得,属性是让图变得“有血有肉”的关键。没有属性的图只是一堆点和线,有了属性,它才能反映真实世界的复杂关系。

4.4 图的基本属性查看:你的图长什么样?

图建好了,怎么知道它里面有什么?NetworkX提供了一系列查看属性的方法。我挑几个最常用的:

方法 作用 示例输出
G.nodes() 查看所有节点 [1, 2, 3, 4, 'Alice', 'Bob']
G.edges() 查看所有边 [(1, 2), (2, 3), (3, 4), (1, 4)]
G.number_of_nodes() 节点数量 6
G.number_of_edges() 边的数量 4
G.degree() 查看每个节点的度 [(1, 2), (2, 2), (3, 2), (4, 2)]
G.neighbors(1) 查看节点1的邻居 [2, 4]
G.has_edge(1, 2) 判断边是否存在 True

举个例子,咱们把刚才创建的图拿出来看看:

print("节点列表:", list(G.nodes()))
print("边列表:", list(G.edges()))
print("节点数量:", G.number_of_nodes())
print("边的数量:", G.number_of_edges())
print("每个节点的度:", dict(G.degree()))

输出结果:

节点列表: [1, 2, 3, 4, 'Alice', 'Bob', '腾讯', '阿里']
边列表: [(1, 2), (2, 3), (3, 4), (1, 4), ('Alice', 'Bob'), ('腾讯', '阿里')]
节点数量: 8
边的数量: 6
每个节点的度: {1: 2, 2: 2, 3: 2, 4: 2, 'Alice': 1, 'Bob': 1, '腾讯': 1, '阿里': 1}

看到没?degree()返回的是一个字典,键是节点,值是该节点的度。度这个概念,咱们前面讲过,就是节点连接的边的数量。这里节点1的度是2,因为它连接了节点2和节点4。

小技巧: 如果你只想看某个节点的度,可以用 G.degree(1)。如果想看所有节点的度排序,可以用 sorted(G.degree(), key=lambda x: x[1], reverse=True)。这在分析“谁是最重要的节点”时非常有用。

4.5 图的绘制基础:一图胜千言

数据看完了,但说实话,光看数字不够直观。尤其是当图比较大的时候,你很难从数字中看出结构。这时候就需要画图了。

NetworkX本身不负责绘图,它把绘图的任务交给了matplotlib。不过它提供了一套非常方便的封装,让你几行代码就能出图。

4.5.1 最简单的画法

nx.draw(G, with_labels=True)
plt.show()

就这么两行,一张图就出来了。with_labels=True表示在节点上显示标签(也就是节点的名称)。如果不加这个参数,节点就是一个个小圆点,你分不清谁是谁。

4.5.2 自定义布局

默认的布局是spring_layout,它模拟了物理弹簧系统,让相互连接的节点靠得更近。效果通常不错,但有时候节点会叠在一起。你可以试试其他布局:

# 圆形布局
pos = nx.circular_layout(G)
nx.draw(G, pos, with_labels=True)
plt.show()

# 分层布局(适合树状结构)
pos = nx.shell_layout(G)
nx.draw(G, pos, with_labels=True)
plt.show()

# 随机布局(不推荐,但了解一下)
pos = nx.random_layout(G)
nx.draw(G, pos, with_labels=True)
plt.show()

我个人最常用的是spring_layoutkamada_kawai_layout。后者在节点数量不多时,布局效果非常漂亮,节点分布均匀,很少重叠。

4.5.3 美化你的图

默认的图是黑白的,有点单调。咱们可以加点颜色和样式:

pos = nx.spring_layout(G, seed=42)  # seed固定随机种子,让每次布局一致

nx.draw_networkx_nodes(G, pos, node_color='skyblue', node_size=500)
nx.draw_networkx_edges(G, pos, edge_color='gray', width=2)
nx.draw_networkx_labels(G, pos, font_size=12, font_color='black')

plt.axis('off')  # 关闭坐标轴
plt.show()

这里我把节点画成天蓝色,边画成灰色,还调整了节点大小和字体。看起来专业多了吧?

重要提醒: 画图时加上 plt.axis('off') 是个好习惯。否则图上会显示坐标轴刻度,对于网络图来说毫无意义,反而显得杂乱。

4.5.4 带属性的图怎么画?

如果节点有属性,比如“市值”,你可以根据市值大小来调整节点颜色或大小:

# 假设节点有市值属性
node_sizes = [G.nodes[n].get('市值', 100) / 100 for n in G.nodes()]
node_colors = ['red' if G.nodes[n].get('行业') == '科技' else 'blue' for n in G.nodes()]

nx.draw(G, pos, node_size=node_sizes, node_color=node_colors, with_labels=True)
plt.show()

这样,市值大的节点就大,科技行业的节点是红色,其他行业是蓝色。一眼就能看出哪些是“大块头”,哪些是“科技公司”。

4.6 本章知识体系总览

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

NetworkX入门 创建图 Graph / DiGraph 添加节点 add_node / add_nodes_from 添加边 add_edge / add_edges_from 属性查看 nodes / edges / degree 绘制基础 draw / layout / 美化 带属性操作 节点属性 / 边属性 核心:创建 → 添加 → 查看 → 绘制 从零构建一个可用的网络图

4.7 避坑指南:我踩过的几个坑

最后,分享几个我实际项目中遇到的坑,希望能帮你省点时间。

坑1:节点名称类型不一致
我曾经在一个项目中,有的节点用整数(比如1、2、3),有的节点用字符串(比如"1"、"2"、"3")。结果在查询边的时候,G.has_edge(1, "1") 返回False,但实际上它们应该是同一个节点。因为NetworkX把整数1和字符串"1"当成两个不同的节点。所以,统一节点名称的数据类型,要么全用整数,要么全用字符串。
坑2:忘记固定布局种子
每次运行nx.draw(),如果不指定seed参数,布局都会变化。这在调试时很烦人——你刚记住某个节点的位置,重新运行一次它就跑了。我的习惯是:pos = nx.spring_layout(G, seed=42),固定种子,布局不变。
坑3:大图绘制卡死
如果你有几千个节点,直接用nx.draw()可能会卡死。因为matplotlib要渲染那么多元素,很吃内存。这时候我建议用nx.draw_networkx_nodes()nx.draw_networkx_edges()分开绘制,并且关闭标签显示(with_labels=False)。如果图实在太大,可以考虑用nx.write_gexf()导出到Gephi等专业工具中可视化。

好了,这一章的内容就到这里。你现在应该能自己创建一个图,往里面加节点和边,查看它的基本属性,并且把它画出来了。别光看,打开你的Python环境,敲几行代码试试。动手才是硬道理。


专注资料整理