改进Python NetworkX图形布局

64

我在使用Python的networkx库创建图形可视化时遇到了一些问题,我希望能够减少混乱并控制节点之间的距离(我也尝试了spring_layout,但它只是将节点以椭圆形排列)。请给予建议。

enter image description here

代码部分:

nx.draw_networkx_edges(G, pos, edgelist=predges, edge_color='red', arrows=True)
nx.draw_networkx_edges(G, pos, edgelist=black_edges, arrows=False, style='dashed')
# label fonts
nx.draw_networkx_labels(G,pos,font_size=7,font_family='sans-serif')
nx.draw_networkx_edge_labels(G,pos,q_list,label_pos=0.3)

1
我认为最好将你的图表导出到Gephi,它有很多布局算法和其他有用的可视化功能。 - Superdooperhero
5个回答

79
在networkx中,值得尝试一下通过nx.graphviz_layout使用graphviz提供的图形绘制算法。
我在使用neato时取得了不错的成果,但其他可能的输入也可以。
  • dot - "分层"或分层绘制有向图。如果边具有方向性,则使用此默认工具。

  • neato - "弹簧模型"布局。如果图形不太大(约100个节点)并且您对其它内容一无所知,则使用此默认工具。Neato试图最小化全局能量函数,这相当于统计多维缩放。

  • fdp - 与neato类似的“弹簧模型”布局,但通过减少力而不是处理能量来实现。

  • sfdp - fdp的多尺度版本,用于布置大型图形。

  • twopi - 径向布局,基于Graham Wills 97。节点根据与给定根节点的距离放置在同心圆上。

  • circo - 圆形布局,基于Six和Tollis 99、Kauffman和Wiese 02。适用于某些多个循环结构的图表,例如某些电信网络。

一般来说,图形绘制是一个难题。如果这些算法不够用,你需要编写自己的算法或让networkx单独绘制部分。

3
如果出现与 graphviz_layout 相关的问题,请参考 https://dev59.com/mVsW5IYBdhLWcg3wM0yF 。 - DiCaprio
9
用法: nx.draw(G, pos=graphviz_layout(G))意思是在使用networkx绘制图形时,使用graphviz_layout函数布置节点的位置,并将其作为参数传递给nx.draw函数。 - DiCaprio
6
在 @DiCaprio 的建议之上,首先安装 pygraphviz:pip install pygraphviz ,然后使用 nx.draw(G, pos=nx.nx_agraph.graphviz_layout(G)) 命令绘制图形。 - Nic Scozzaro
在Windows上运行pip install pygraphviz时出现错误(可能与“wheels”有关),但由于我使用的是Anaconda,所以conda install -c anaconda graphviz可以正常工作。 - curtisp
1
关于@curtisp的指示,不需要使用Anaconda,请查看安装说明:https://pygraphviz.github.io/documentation/stable/install.html#windows - micsthepick

19

我发现这对于快速可视化以CSV文件形式提供的交互数据(这里是基因数据)非常有用。

数据文件 [a.csv]

APC,TP73
BARD1,BRCA1
BARD1,ESR1
BARD1,KRAS2
BARD1,SLC22A18
BARD1,TP53
BRCA1,BRCA2
BRCA1,CHEK2
BRCA1,MLH1
BRCA1,PHB
BRCA2,CHEK2
BRCA2,TP53
CASP8,ESR1
CASP8,KRAS2
CASP8,PIK3CA
CASP8,SLC22A18
CDK2,CDKN1A
CHEK2,CDK2
ESR1,BRCA1
ESR1,KRAS2
ESR1,PPM1D
ESR1,SLC22A18
KRAS2,BRCA1
MLH1,CHEK2
MLH1,PMS2
PIK3CA,BRCA1
PIK3CA,ESR1
PIK3CA,RB1CC1
PIK3CA,SLC22A18
PMS2,TP53
PTEN,BRCA1
PTEN,MLH3
RAD51,BRCA1
RB1CC1,SLC22A18
SLC22A18,BRCA1
TP53,PTEN

Python 3.7虚拟环境

import networkx as nx
import matplotlib.pyplot as plt
G = nx.read_edgelist("a.csv", delimiter=",")

G.edges()
'''
  [('CDKN1A', 'CDK2'), ('MLH3', 'PTEN'), ('TP73', 'APC'), ('CHEK2', 'MLH1'),
    ('CHEK2', 'BRCA2'), ('CHEK2', 'CDK2'), ('CHEK2', 'BRCA1'), ('BRCA2', 'TP53'),
    ('BRCA2', 'BRCA1'), ('KRAS2', 'CASP8'), ('KRAS2', 'ESR1'), ('KRAS2', 'BRCA1'),
    ('KRAS2', 'BARD1'), ('PPM1D', 'ESR1'), ('BRCA1', 'PHB'), ('BRCA1', 'ESR1'),
    ('BRCA1', 'PIK3CA'), ('BRCA1', 'PTEN'), ('BRCA1', 'MLH1'), ('BRCA1', 'SLC22A18'),
    ('BRCA1', 'BARD1'), ('BRCA1', 'RAD51'), ('CASP8', 'ESR1'), ('CASP8', 'SLC22A18'),
    ('CASP8', 'PIK3CA'), ('TP53', 'PMS2'), ('TP53', 'PTEN'), ('TP53', 'BARD1'),
    ('PMS2', 'MLH1'), ('PIK3CA', 'SLC22A18'), ('PIK3CA', 'ESR1'), ('PIK3CA', 'RB1CC1'),
    ('SLC22A18', 'ESR1'), ('SLC22A18', 'RB1CC1'), ('SLC22A18', 'BARD1'), 
    ('BARD1', 'ESR1')]
'''
G.number_of_edges()
# 36

G.nodes()
'''
  ['CDKN1A', 'MLH3', 'TP73', 'CHEK2', 'BRCA2', 'KRAS2', 'CDK2', 'PPM1D', 'BRCA1',
    'CASP8', 'TP53', 'PMS2', 'RAD51', 'PIK3CA', 'MLH1', 'SLC22A18', 'BARD1',
    'PHB', 'APC', 'ESR1', 'RB1CC1', 'PTEN']
'''
G.number_of_nodes()
# 22

更新

这个过去可以使用(2018-03),但现在(2019-12)会出现pygraphviz导入错误:

from networkx.drawing.nx_agraph import graphviz_layout

nx.draw(G, pos = graphviz_layout(G), node_size=1200, node_color='lightblue', \
    linewidths=0.25, font_size=10, font_weight='bold', with_labels=True)

    Traceback (most recent call last):
    ...
    ImportError: libpython3.7m.so.1.0: cannot open shared object file:
      No such file or directory
    During handling of the above exception, another exception occurred:
      Traceback (most recent call last):
    ...
    ImportError: ('requires pygraphviz ', 'http://pygraphviz.github.io/')

解决方法

在 Python 外(venv 终端提示符下: $),安装 pydot

pip install pydot

回到Python运行以下代码。

import warnings
warnings.filterwarnings("ignore", category=UserWarning)

import networkx as nx
import matplotlib.pyplot as plt

G = nx.read_edgelist("a.csv", delimiter=",")
# For a DiGraph() [directed edges; not shown]:
#   G = nx.read_edgelist("a.csv", delimiter=",", create_using=nx.DiGraph)

nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), node_size=1200, \
    node_color='lightblue', linewidths=0.25, font_size=10, \
    font_weight='bold', with_labels=True)

plt.show()    ## plot1.png attached

主要的变化是替换了

nx.draw(G, pos = graphviz_layout(G), ...)
nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), ...)

参考文献

删除matplotlib的过时警告

什么原因导致NetworkX和PyGraphViz单独使用正常,但一起使用不正常?


改进绘图布局

在这些静态networkx/matplotlib图中减少拥挤很困难;一种解决方法是增加图形大小,参见此StackOverflow问题与回答:使用NetworkX和Matplotlib制作图形的高分辨率图像

plt.figure(figsize=(20,14))
# <matplotlib.figure.Figure object at 0x7f1b65ea5e80>

nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), \
    node_size=1200, node_color='lightblue', linewidths=0.25, \
    font_size=10, font_weight='bold', with_labels=True, dpi=1000)

plt.show()    ## plot2.png attached

要将输出图像大小重置为系统默认值:

plt.figure()
# <matplotlib.figure.Figure object at 0x7f1b454f1588>

奖励:最短路径

nx.dijkstra_path(G, 'CDKN1A', 'MLH3')
# ['CDKN1A', 'CDK2', 'CHEK2', 'BRCA1', 'PTEN', 'MLH3']


plot1.png plot1.png

plot2.png plot2.png


虽然这里没有做,但如果您想添加节点边框并加粗节点边框线(节点边缘厚度:linewidths),请执行以下操作。

nx.draw(G, pos = nx.nx_pydot.graphviz_layout(G), \
    node_size=1200, node_color='lightblue', linewidths=2.0, \
    font_size=10, font_weight='bold', with_labels=True)
# Get current axis:
ax = plt.gca()
ax.collections[0].set_edgecolor('r')
# r : red (can also use #FF0000) | b : black (can also use #000000) | ...
plt.show()

3
您,女士,是一位英雄。特别是,在这个答案中,对于 .draw 中选项的文档记录超过了实际 networkx 文档中的类似文档,我认为如此。 - Max von Hippel
2
@MaxvonHippel:你评论中的喜悦超过了我在这个答案上收到的任何赞!;-) - Victoria Stuart
1
当我后来回到这段代码(大约21个月后),我遇到了一个pygraphviz导入错误。执行pip install pygraphviz看起来很顺利,但是我却无法import pygraphviz(出现相同的错误;当我在我的Arch Linux系统上通过yay -S python-pygraphviz安装时也是如此)。我已经更新了我的答案,以上是解决方案。 - Victoria Stuart
1
@Bendemann:你在用Windows?我不确定;然而在我的(Arch Linux)系统上虽然NetworkX可作为软件包使用,但我没有通过操作系统安装它,而是通过Python中的pip安装(pip list | grep networkx; pip show networkx)。可以查看这些帖子的评论吗?https://dev59.com/SmUq5IYBdhLWcg3wN94t | https://stackoverflow.com/questions/56672123/filenotfounderror-winerror-2-neato-not-found-in-path - Victoria Stuart
1
@Bendemann... 另外,请参见上面的已接受答案--也许明确指定一个除了“neato”之外的“prog”可能会起作用?“prog(字符串(默认值:'neato'))-用于布局的GraphViz程序的名称。选项取决于GraphViz版本,但可能包括:'dot'、'twopi'、'fdp'、'sfdp'、'circo'”| https://networkx.github.io/documentation/stable/reference/generated/networkx.drawing.nx_pydot.graphviz_layout.html - Victoria Stuart
显示剩余4条评论

5

您的图表中有大量数据,因此去除杂乱会很困难。

我建议使用标准布局。您说您使用了 spring_layout。我建议您再试一次,但这次在添加边缘时使用 weight 属性。

例如:

import networkx as nx

G = nx.Graph();
G.add_node('A')
G.add_node('B')
G.add_node('C')
G.add_node('D')
G.add_edge('A','B',weight=1)
G.add_edge('C','B',weight=1)
G.add_edge('B','D',weight=30)

pos = nx.spring_layout(G,scale=2)

nx.draw(G,pos,font_size=8)
plt.show()

此外,您可以使用参数scale来增加节点之间的全局距离。

2
权重如何影响算法?更高的权重意味着节点更接近还是相反? - Fábio Dias

3
为了回答你的问题,如何调整节点之间的距离,我在Hooked's answer的基础上进行了扩展:
如果您通过Graphviz后端绘制图形,并且使用fdp算法,则可以通过边缘属性len来调整节点之间的距离。
这里是一个代码示例,如何绘制一个名为G的图形,并将其保存在Graphviz文件gvfile中,节点之间的距离更宽(fdp的默认距离为0.3):
A = nx.to_agraph(G)
A.edge_attr.update(len=3)
A.write(gv_file_name)

两点建议:

  1. 通常建议根据图中节点的数量调整len
  2. len属性仅被fdpneato算法识别,但不被sfdp算法识别。

请提供更多信息:AttributeError: 模块“networkx”没有属性“to_agraph”。 - user3352632
@user3352632 目前我没有使用NetworkX。自2015年以来,networkx包中的方法名称可能已更改。请查看NetworkX的文档。 - halloleo

0

所以,这可能对原问题并不有用,但对于未来的搜索者可能有用:

一个解决方案可以是

    pos = nx.nx_agraph.graphviz_layout(G)
    nx.draw(G, pos=pos)

首先考虑使用pip install agraph安装agraph。 (根据networkx的说法,使用nx.nx_pydot.graphviz_layout(G)的解决方案现在已经被弃用。)

希望这能有所帮助 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接