NetworkX:Python图与网络模型基础
Posted 金融系程老师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NetworkX:Python图与网络模型基础相关的知识,希望对你有一定的参考价值。
在《运筹学》课堂上,我们学习过图与网络,当时用到R语言下的igraph包来计算和展示结果。Python下也有类似甚至更好的库: NetworkX
。
安装命令如下
conda install networkx
引入约定为
import networkx as nx
1 图的绘制
无向图
无向图由点和边构成,其绘制思路为:①新建空图→②添加点→③添加边。
新建空的无向图
G = nx.Graph()
以后所有的信息都添加在无向图G上。
添加点:addnode和add_nodes_from
# 添加一个点
G.add_node(1) # 点的名字叫1
G.add_node('a') # 点的名字叫a
# 添加一组点
G.add_nodes_from([2,3,4])
虽然还没有讲到怎么展示这张图,但你可能想看看自己已经画了啥;所以我们剧透一下:输入nx.draw(G)看看吧。
添加边:add_edge和add_edges_from
# 添加一条边
G.add_edge(1,2) # 在1、2之间添加边
G.add_edge(1,'a') # 在1、a之间添加边
# 添加一组边
G.add_edges_from([(2,3),(3,4,),('a',3)])
# 添加边时自动生成点
G.add_edge(3,'b') # 此前没有添加过b点
# 添加圈
G.add_cycle(['b','c','d'])
# 注意这些命令都有color参数,将来会用到
移除点或边使用remove_*系列方法。
展示图
NetworkX可以结合matpltlib库来展示图,因此需要载入plt:
import matplotlib.pyplot as plt
最常用的展示命令是 networkx.draw()
,所有参数都是可选的。
nx.draw()
简单介绍一些可选参数,如
ax:画纸名
nodecolor/edgecolor/font_color:点、边、字颜色
nodeshape/nodesize:点的形状和大小
style:边的形状(solid/dashed/dotted/dashdot)
alpha:点和边的透明度
with_labels:点是否显示标签
arrows/arrowstyle/arrowsize:有向图的箭头设定
我们并列展示默认和自定义结果:
fig = plt.figure(figsize=(20,5))
ax1 = plt.subplot(121)
nx.draw(G,ax=ax1)
ax2 = plt.subplot(122)
nx.draw(G,ax=ax2,node_color='pink',with_labels=True,node_size=500,node_shape='D',style='-.')
plt.draw()
另有 networkx.draw_networkx()
函数,支持自定义点的位置(类型)。
有向图
有向图和无向图的差别仅仅在边是有方向的:
新建空的有向图
G = nx.DiGraph() # 注意差别
添加点:略
添加有向边
G.add_edge(1,2)
G.add_edges_from([(2,3),(2,4),(3,4),(4,3)])
nx.draw(G,ax=ax2,node_color='pink',with_labels=True,arrowstyle='fancy',edge_color='pink')
2 从图到网络:权的添加
方法一
add_weighted_edges_from方法能够接受(起点,终点,权重)作为元素的序列。推荐这种方法。
G = nx.DiGraph()
elist = [('a', 'b', 5.0), ('b', 'c', 3.0), ('a', 'c', 1.0), ('c', 'd', 7.3)]
G.add_weighted_edges_from(elist)
方法二
add_edge方法可以添加weight参数。
G.add_edge(1,2,weight=5.5)
方法三
类索引方法,在修改权重时非常有用。
G[1][2]['weight']=5.5
G.edges[1,2]['weight']=5.5
添加权重标签
按照上述三个方法添加的边权重,将被记录在边属性下,我们可以通过G.edges(data=True)方法来查看:
G.edges(data=True)
特别注意参数data一定要为True,不指定data参数时默认只提取边的起点和终点。结果如下:
OutEdgeDataView([('a', 'b', {'weight': 5.0}), ('a', 'c', {'weight': 1.0}), ('b', 'c', {'weight': 3.0}), ('c', 'd', {'weight': 7.3})])
关系已经很明确了,我们用元组u,v,w来解包,并将其放在键为边,值为权的字典中:
# 方法一:for循环
label = {}
for (u,v,d) in G.edges(data=True):
label[(u,v)] = str(d['weight'])
# 方法二:字典推导式
label = {(u,v):str(d['weight']) for u,v,d in G.edges(data=True)}
绘制权重标签
我们分4步来画图:点→边→点标签→边标签(顺序不重要)。因为要分4个图层来画,所以需要明确点的位置,不能用nx.draw()这种随性的方法。
首先定义点的位置pos:
pos = nx.spring_layout(G)
这表明我们使用Fruchterman-Reingold的力引导算法来画图,目的是减少边的交叉(推荐)。可选的pos还有circular_layout、kamada_kawai_layout、random_layout、rescale_layout、shell_layout和spectral_layout,点少的时候看不出来,点多就不一样了。
nx.draw_networkx_nodes(G,pos)
nx.draw_networkx_labels(G,pos)
nx.draw_networkx_edges(G,pos)
nx.draw_networkx_edge_labels(G, pos, label) # 关注这里
plt.axis('off') # 不显示坐标
plt.draw()
默认的标签是放在边的中间,可以用label_pos调节,这对双箭头的有向边很重要。
3 最短路
《运筹学》课程中,我们学习了Dijkstra算法;NetworkX提供了相应的命令:
dijkstra_predecessor_and_distance:给出某起点到所有点的最短路径和最短路程,结果也包含两部分,可以用元组解包提取;
dijkstra_path:给出从某起点到某终点的最短路径;
dijkstra_path_length:给出从某起点到某终点的最短路程。
构造一个有向图
G = nx.DiGraph()
elist = [(1,2,3),(1,3,2),(1,4,5),
(2,6,7),
(3,4,1),
(4,6,5),
(5,3,5),(5,4,3),(5,6,1)]
G.add_weighted_edges_from(elist)
求点1到点6的最短路。
展示这个有向图
pos = nx.circular_layout(G)
label = {(u,v):str(d['weight']) for u,v,d in G.edges(data=True)}
nx.draw_networkx_nodes(G,pos,node_color='w',edgecolors='k')
nx.draw_networkx_nodes(G,pos,node_color='pink',edgecolors='k',nodelist=[1,6])
nx.draw_networkx_labels(G,pos,font_color='k')
nx.draw_networkx_edges(G,pos)
nx.draw_networkx_edge_labels(G, pos,label,label_pos=0.3)
plt.axis('off')
plt.draw()
Dijkstra算法求最短路
指定起点,不指定终点
pred, dist = nx.dijkstra_predecessor_and_distance(G,1)
print('到每个点的最短路的上一个点:',pred)
print('到每个点的最短路的路程:',dist)
结果:
到每个点的最短路的上一个点: {1: [], 2: [1], 3: [1], 4: [3], 6: [4]}
到每个点的最短路的路程: {1: 0, 3: 2, 2: 3, 4: 3, 6: 8}
指定起点和终点
path = nx.dijkstra_path(G,1,6)
length = nx.dijkstra_path_length(G,1,6)
print('从点1到点6的最短路径是',path)
print('从点1到点6的最短路程是',length)
将最短路径画出来:
附代码:
pos = nx.circular_layout(G)
label = {(u,v):str(d['weight']) for u,v,d in G.edges(data=True)}
nx.draw_networkx_nodes(G,pos,node_color='w',edgecolors='k')
nx.draw_networkx_nodes(G,pos,node_color='pink',edgecolors='k',nodelist=[1,6])
nx.draw_networkx_labels(G,pos,font_color='k')
nx.draw_networkx_edges(G,pos)
el = [(path[i],path[i+1]) for i in range(len(path)-1)]
nx.draw_networkx_edges(G,pos,edge_color='r',edgelist=el)
nx.draw_networkx_edge_labels(G, pos,label,label_pos=0.3)
plt.axis('off')
plt.draw()
4 最大流
掌握了最短路再来看最大流,就是很简单的事情了。
通过capacity参数为边添加最大容量;
使用
maximum_flow
函数求解。
构造包含最大容量的有向图
elist = [(1,2,6),(1,4,6),(2,3,2),(2,5,3),(3,5,2),(3,6,2),(4,3,3),
(4,6,1),(4,7,2),(5,7,5),(6,7,4)]
G = nx.DiGraph()
for u,v,c in elist:
G.add_edge(u,v,capacity=c)
绘制这个图
pos = nx.circular_layout(G)
label = {(u,v):str(d['capacity']) for u,v,d in G.edges(data=True)}
# 或者label = {(u,v):str(c) for u,v,c in elist}
nx.draw_networkx_nodes(G,pos,node_color='w',edgecolors='k')
nx.draw_networkx_nodes(G,pos,node_color='pink',edgecolors='k',nodelist=[1,7])
nx.draw_networkx_labels(G,pos,font_color='k')
nx.draw_networkx_edges(G,pos)
nx.draw_networkx_edge_labels(G, pos,label,label_pos=0.5)
plt.axis('off')
plt.draw()
图中边的标签表示最大容量(而非单位运价)。我们想求得从点1到点7的最大流。
计算最大流
flow_value, flow_dict = nx.maximum_flow(G, 1, 7)
通过提取flow_value,我们可以知道从点1到点7的最大流为10;
通过提取flow_dict,我们可以知道最大流情形下,每条边的实际流量:
{1: {2: 5, 4: 5},
2: {3: 2, 5: 3},
3: {5: 2, 6: 2},
4: {3: 2, 6: 1, 7: 2},
5: {7: 5},
6: {7: 3},
7: {}}
我们也可以通过 flow_dict[<起点>][<终点>]
来确定特定边上的实际容量,如 flow_dict[1][4]
表示边(1,4)上的实际流量为5。
5 结语
NetworkX是复杂网络计算库,能做的事情远不止最短路和最大流。手册在这里,进步靠自己:
https://networkx.github.io/documentation/stable/_downloads/networkx_reference.pdf
以上是关于NetworkX:Python图与网络模型基础的主要内容,如果未能解决你的问题,请参考以下文章