networkx(番外)画图——(1)自定义节点布局
networkx虽然非常方便,但在一些超大规模的图数据上,依然显得吃力。所以大多数时候,它仅仅是被用来做一些实例性的分析和可视化展示的,这需要学会如何灵活的画图。
最重要的就是布局,即每个节点在图上的什么位置。我们以networkx学习与使用——(3)路与圈中的ARPANET为例:
但是,我们如何将其抽象成图之后仍能大致保留其原本的位置呢?构图上不需要太多的变化:
import networkx as nx #载入networkx包
import matplotlib.pyplot as plt #用于画图
G = nx.Graph() #无向图
edges = [('UCSB','SRI'),('UCSB','UCLA'),
('SRI','UCLA'),('SRI','STAN'),('SRI','UTAH'),
('UCLA','STAN'),('UCLA','RAND'),
('UTAH','SDC'),('UTAH','MIT'),
('RAND','SDC'),('RAND','BBN'),
('MIT','BBN'),('MIT','LINC'),
('BBN','HARV'),
('LINC','CASE'),
('HARV','CARN'),
('CASE','CARN')]
G.add_edges_from(edges)
主要是画图:
labels={}
for node in G.nodes():
labels[node]=node
pos=nx.spring_layout(G) # 生成节点位置信息
plt.rcParams['figure.figsize']= (6, 4) # 设置画布大小
nx.draw_networkx_nodes(G,pos) # 画节点
nx.draw_networkx_edges(G,pos) # 画边
nx.draw_networkx_labels(G,pos,labels) # 画标签
plt.axis('off') # 去掉坐标刻度
# 保存并显示图片
# plt.savefig("ARPA.png")
plt.show()
这里面影响节点布局的核心代码是:
pos=nx.spring_layout(G) # 生成节点位置信息
这里使用的是networkx提供的自动生成节点位置的函数,其他常用的还有:
pos=nx.circular_layout(G) # 生成圆形节点布局
pos=nx.random_layout(G) # 生成随机节点布局
pos=nx.shell_layout(G) # 生成同心圆节点布局
pos=nx.spring_layout(G) # 利用Fruchterman-Reingold force-directed算法生成节点布局
pos=nx.spectral_layout(G) # 利用图拉普拉斯特征向量生成节点布局
pos=nx.kamada_kawai_layout(G) #使用Kamada-Kawai路径长度代价函数生成布局
我们将不同的布局都画出来:
labels={}
for node in G.nodes():
labels[node]=node
pos_list = [nx.circular_layout(G), nx.random_layout(G), nx.shell_layout(G),
nx.spring_layout(G), nx.spectral_layout(G), nx.kamada_kawai_layout(G)]
plt.rcParams['figure.figsize']= (12, 6) # 设置画布大小
for i,pos in enumerate(pos_list):
ax1 = plt.subplot(2,3,i+1)
nx.draw_networkx_nodes(G,pos) # 画节点
nx.draw_networkx_edges(G,pos) # 画边
nx.draw_networkx_labels(G,pos,labels) # 画标签
plt.axis('off') # 去掉坐标刻度
plt.show()
由于是这个图有实际的地理意义,我们会认为第四张和第六张看上去好看些,但还是不能满足我们的需求,因此我们希望能够自己定义这些位置。那么首先就要观察一下这个pos到底是什么,我们以spring_layout为例:
pos=nx.shell_layout(G)
print(pos)
out:{'UCSB': array([1.00000000e+00, 6.87745905e-09]), 'SRI': array([0.88545603, 0.46472317]),
'UCLA': array([0.56806475, 0.82298386]), 'STAN': array([0.12053668, 0.99270886]),
'UTAH': array([-0.35460484, 0.93501627]), 'RAND': array([-0.74851078, 0.66312265]),
'SDC': array([-0.97094184, 0.23931567]), 'MIT': array([-0.97094184, -0.23931561]),
'BBN': array([-0.74851084, -0.66312259]), 'LINC': array([-0.35460502, -0.93501621]),
'HARV': array([ 0.12053674, -0.99270886]), 'CASE': array([ 0.56806457, -0.82298398]),
'CARN': array([ 0.88545603, -0.4647232 ])}
到这里应该就相对明了了,pos是一个字典,字典里的内容是个numpy的array(事实上,这里用列表和元组也是可以的),是一个二维的坐标,因此我们只需要更改这个坐标就能够得到一个和位置上符合原图的布局(我这里的布局是参照书上自己大概给个坐标画的)。
plt.rcParams['figure.figsize']= (12, 4) # 设置画布大小
pos={'UCSB': [1.1,1.8], 'SRI': [2.4, 3.3],
'UCLA': [2.4, 0.5], 'STAN': [3.4, 1.8],
'UTAH': [4.7, 3.3], 'RAND': [4.7, 0.5],
'SDC': [5.3, 1.8], 'MIT': [8, 2.6],
'BBN': [8, 1.4], 'LINC': [9.5, 3.2],
'HARV': [ 9.5, 0.7], 'CASE': [11, 2.6],
'CARN': [ 11, 1.4]}
nx.draw_networkx_nodes(G,pos) # 画节点
nx.draw_networkx_edges(G,pos) # 画边
nx.draw_networkx_labels(G,pos,labels) # 画标签
plt.axis('off') # 去掉坐标刻度
plt.show()
这么看,是否好多了呢?当然,现实情况不可能一个个去填坐标,但是了解了pos的本质,我们就可以自己写random_layout这样简单的布局,或者根据自己的需求写一些布局,例如画推荐系统中的二部图,我们可以为将项目和用户的横坐标固定为不同值,只改变纵坐标。
参考
networkx官网地址:https://networkx.org/