如何在`networkx`中使用`pos`参数来创建流程图样式的Graph? (Python 3) [英] How to use the `pos` argument in `networkx` to create a flowchart-style Graph? (Python 3)
问题描述
我试图使用 Python
创建线性网络图(最好使用 matplotlib
和 networkx
,虽然会对 bokeh
>感兴趣)。
在Python中使用 networkx
?有效地构造( pos
?)我想使用它来获得更复杂的示例所以我觉得为这个简单的例子硬编码的位置将是没有用的:(。 networkx
是否有解决方案?
注意:唯一的另一种方式,我可以想到做到这一点将在matplotlib
中创建一个散点图,每个刻度表示一个迭代(5包括初始样本),然后用不同的权重将点连接起来。这将是一些相当凌乱的代码,尤其是试图排列连接标记的边缘...但是,我不确定这是否和networkx
是最好的方法来做到这一点,或者如果有一种工具(例如bokeh
或plotly
)解决方案Networkx为探索性数据
分析提供了体面的绘图工具,它不是使发布质量数字,
由于各种原因,我不想在这里进入。因此,我
重新编写了部分代码库,并创建了一个名为netgraph的
独立绘图模块,可以找到
我选择了颜色表示边缘强度,因为您可以
1)表示负值,并且
2)更好地区分小值。
但是,您也可以传递边缘宽度改为netgraph(参见netgraph.draw_edges()
)。
分支的不同顺序是数据结构(字典)的结果,表示没有固有的顺序。您将不得不修改您的数据结构和
_parse_input()
下面的函数来解决该问题。
代码:
import itertools
import numpy as np
import matplotlib.pyplot as plt
import netgraph ; reload(netgraph)
def plot_layered_network(weight_matrices,
distance_between_layers = 2,
distance_between_nodes = 1,
layer_labels = None,
** kwargs):
便捷功能绘制分层网络
参数:
----------
weight_matrices:[w1 ,w2,...,wn]
定义图层之间连通性的权重矩阵列表;
每个权重矩阵是一个带有行索引源和列索引目标的2-D ndarray;
the源数必须与最后一层中的目标数相匹配
distance_between_layers:int
distance_between_nodes:int
layer_labels:[str1,str2 ,...,strn + 1]
图层标签
** kwargs:传递给netgraph.draw()
返回:
- ---- ---
ax:matplotlib轴实例
$ b $$
nodes_per_layer = _get_nodes_per_layer(weight_matrices)
node_positions = _get_node_positions(nodes_per_layer,
distance_between_layers,
distance_between_nodes)
w = _combine_weight_matrices(weight_matrices,nodes_per_layer)
ax = netgraph.draw(w,node_positions,** kwargs)
如果不是layer_labels是None:
ax.set_xticks(distance_between_layers * np.arange(len(weight_matrices)+1))
ax.set_xticklabels(layer_labels)
ax.xaxis。 set_ticks_position('bottom')
返回轴
def _get_nodes_per_layer(weight_matrices):
nodes_per_layer = []
for weight_matrices:
来源,目标= w.shape
nodes_per_layer.append(来源)
nodes_per_layer.append(目标)
返回否des_per_layer
def _get_node_positions(nodes_per_layer,
distance_between_layers,
distance_between_nodes):
x = []
y = []
for ii,n in枚举(nodes_per_layer):
x.append(distance_between_nodes * np.arange(0。,n))
y.append(ii * distance_between_layers * np.ones((n)))
x = np.concatenate(x)
y = np.concatenate(y)
返回np.c_ [y,x]
def _combine_weight_matrices(weight_matrices,nodes_per_layer):
total_nodes = np.sum(nodes_per_layer)
w = np.full((total_nodes,total_nodes),np.nan,np.float)
a = 0
b = nodes_per_layer [0 ]
for ii,ww in enumerate(weight_matrices):
w [a:a + ww.shape [0],b:b + ww.shape [1]] = ww
a + = nodes_per_layer [ii]
b + = nodes_per_layer [ii + 1]
返回w
def test():
w1 = np.random.rand (4,5)#< 0.50
w2 = np.random.rand(5,6)#< 0.25
w3 = np.random.rand(6,3)#< 0.75
导入字符串
node_labels = dict(范围(18),列表(字符串.ascii_lowercase)))
fig,ax = plt.subplots( 1,1)
plot_layered_network([w1,w2,w3],
layer_labels = ['start','step 1','step 2','finish'],
ax = ax,
node_size = 20,
node_edge_width = 2,
node_labels = node_labels,
edge_width = 5,
)
plt.show()
返回
def test_example(input_dict):
weight_matrices,node_labels = _parse_input(input_dict)
fig,ax = plt.subplots(1,1)
plot_layered_network (weight_matrices,
layer_labels = ['','1','2','3','4'],
distance_between_layers = 10,
distance_between_nodes = 8,
ax = ax,
node_size = 300,
node_edge_width = 10,
node_labels = node_labels,
edge_width = 50,
)
plt.show()
return
$ b $ def _parse_input(input_dict):
weight_matrices = []
node_labels = []
#初始化源
sources = set()
for v in input_dict [1] .values():
for v.keys():
sources.add(s)
sources = list(sources)$ (len(input_dict)):b
$ b:
inner_dict = input_dict [ii + 1]
targets = inner_dict.keys()
w = np.full((len(来源),len(目标)),np.nan,np.float)
为ii,s在枚举(来源):
为jj,t在枚举(目标):
尝试:
w [ii,jj] = inner_dict [t] [s]
除了KeyError:
通过
weight_matrices.append(w)
node_labels.append(sources)
sources = targets
node_labels.append(targets)
node_labels = list(itertools。 chain.from_iterable(node_labels))
node_labels = dict(enumerate(node_labels))
return weight_matrices,node_labels
#--------- -------------------------------------------------- ---------------------
#脚本
#------------------ -------------------------------------------------- ------------
if __name__ ==__main__:
#test()
input_dict = {
1:{
Group 1:{sample_0:0.5,sample_1:0.5,sample_2:0,sample_3:0,sample_4:0},
组2:{sample_0:0,sample_1:0,sample_2:1,sample_3:0,sample_4:0},
Group 3:{ sample_0:0,sample_1:0,sample_2:0,sample_3:0.5,sample_4:0.5}
},
2:{
Grou p 1:{组1:1,组2:0,组3:0},
组2:{组1:0,组2:1 ,Group 3:0},
Group 3:{Group 1:0,Group 2:0,Group 3:1}
},
3:{
Group 1:{Group 1:0.25,Group 2:0,Group 3:0.75},
Group 2:{Group 1 0.25,组2:0.75,组3:0}
},
4:{
组1:{组1:1,组2 :0},
Group 2:{Group 1:0.25,Group 2:0.75}
}
}
test_example(input_dict)
传递
I am trying create a linear network graph using
Python
(preferably withmatplotlib
andnetworkx
although would be interested inbokeh
) similar in concept to the one below.How can this graph plot be constructed efficiently (
pos
?) in Python usingnetworkx
? I want to use this for more complicated examples so I feel that hard coding the positions for this simple example won't be useful :( . Doesnetworkx
have a solution to this?I haven't seen any tutorials on how this can be achieved in
networkx
which is why I believe this question will be a reliable resource for the community. I've extensively gone through thenetworkx
tutorials and nothing like this is on there. The layouts fornetworkx
would make this type of network impossible to interpret without careful use of thepos
argument... which I believe is my only option. None of the precomputed layouts on the https://networkx.github.io/documentation/networkx-1.9/reference/drawing.html documentation seem to handle this type of network structure well.Simple Example:
(A) every outer key is the iteration in the graph moving from left to the right (e.g. iteration 0 represents samples, iteration 1 has groups 1 - 3, same with iteration 2, iteration 3 has Groups 1 - 2, etc.). (B) The inner dictionary contains the current grouping at that particular iteration, and the weights for the previous groups merging that represent the current group (e.g.
iteration 3
hasGroup 1
andGroup 2
and foriteration 4
all ofiteration 3's
Group 2
has gone intoiteration 4's
Group 2
butiteration 3's
Group 1
has been split up. The weights always sum to 1.My code for the connections w/ weights for the plot above:
D_iter_current_previous = { 1: { "Group 1":{"sample_0":0.5, "sample_1":0.5, "sample_2":0, "sample_3":0, "sample_4":0}, "Group 2":{"sample_0":0, "sample_1":0, "sample_2":1, "sample_3":0, "sample_4":0}, "Group 3":{"sample_0":0, "sample_1":0, "sample_2":0, "sample_3":0.5, "sample_4":0.5} }, 2: { "Group 1":{"Group 1":1, "Group 2":0, "Group 3":0}, "Group 2":{"Group 1":0, "Group 2":1, "Group 3":0}, "Group 3":{"Group 1":0, "Group 2":0, "Group 3":1} }, 3: { "Group 1":{"Group 1":0.25, "Group 2":0, "Group 3":0.75}, "Group 2":{"Group 1":0.25, "Group 2":0.75, "Group 3":0} }, 4: { "Group 1":{"Group 1":1, "Group 2":0}, "Group 2":{"Group 1":0.25, "Group 2":0.75} } }
This is what happened when I made the Graph in
networkx
:import networkx import matplotlib.pyplot as plt # Create Directed Graph G = nx.DiGraph() # Iterate through all connections for iter_n, D_current_previous in D_iter_current_previous.items(): for current_group, D_previous_weights in D_current_previous.items(): for previous_group, weight in D_previous_weights.items(): if weight > 0: # Define connections using `|__|` as a delimiter for the names previous_node = "%d|__|%s"%(iter_n - 1, previous_group) current_node = "%d|__|%s"%(iter_n, current_group) connection = (previous_node, current_node) G.add_edge(*connection, weight=weight) # Draw Graph with labels and width thickness nx.draw(G, with_labels=True, width=[G[u][v]['weight'] for u,v in G.edges()])
Note: The only other way, I could think of to do this would be in
matplotlib
creating a scatter plot with every tick representing a iteration (5 including the initial samples) then connecting the points to each other with different weights. This would be some pretty messy code especially trying to line up the edges of the markers w/ the connections...However, I'm not sure if this andnetworkx
is the best way to do it or if there is a tool (e.g.bokeh
orplotly
) that is designed for this type of plotting.解决方案Networkx has decent plotting facilities for exploratory data analysis, it is not the tool to make publication quality figures, for various reason that I don't want to go into here. I hence rewrote that part of the code base from scratch, and made a stand-alone drawing module called netgraph that can be found here (like the original purely based on matplotlib). The API is very, very similar and well documented, so it should not be too hard to mold to your purposes.
Building on that I get the following result:
I chose colour to denote the edge strength as you can
1) indicate negative values, and
2) distinguish small values better.
However, you can also pass an edge width to netgraph instead (seenetgraph.draw_edges()
).The different order of the branches is a result of your data structure (a dict), which indicates no inherent order. You would have to amend your data structure and the function
_parse_input()
below to fix that issue.Code:
import itertools import numpy as np import matplotlib.pyplot as plt import netgraph; reload(netgraph) def plot_layered_network(weight_matrices, distance_between_layers=2, distance_between_nodes=1, layer_labels=None, **kwargs): """ Convenience function to plot layered network. Arguments: ---------- weight_matrices: [w1, w2, ..., wn] list of weight matrices defining the connectivity between layers; each weight matrix is a 2-D ndarray with rows indexing source and columns indexing targets; the number of sources has to match the number of targets in the last layer distance_between_layers: int distance_between_nodes: int layer_labels: [str1, str2, ..., strn+1] labels of layers **kwargs: passed to netgraph.draw() Returns: -------- ax: matplotlib axis instance """ nodes_per_layer = _get_nodes_per_layer(weight_matrices) node_positions = _get_node_positions(nodes_per_layer, distance_between_layers, distance_between_nodes) w = _combine_weight_matrices(weight_matrices, nodes_per_layer) ax = netgraph.draw(w, node_positions, **kwargs) if not layer_labels is None: ax.set_xticks(distance_between_layers*np.arange(len(weight_matrices)+1)) ax.set_xticklabels(layer_labels) ax.xaxis.set_ticks_position('bottom') return ax def _get_nodes_per_layer(weight_matrices): nodes_per_layer = [] for w in weight_matrices: sources, targets = w.shape nodes_per_layer.append(sources) nodes_per_layer.append(targets) return nodes_per_layer def _get_node_positions(nodes_per_layer, distance_between_layers, distance_between_nodes): x = [] y = [] for ii, n in enumerate(nodes_per_layer): x.append(distance_between_nodes * np.arange(0., n)) y.append(ii * distance_between_layers * np.ones((n))) x = np.concatenate(x) y = np.concatenate(y) return np.c_[y,x] def _combine_weight_matrices(weight_matrices, nodes_per_layer): total_nodes = np.sum(nodes_per_layer) w = np.full((total_nodes, total_nodes), np.nan, np.float) a = 0 b = nodes_per_layer[0] for ii, ww in enumerate(weight_matrices): w[a:a+ww.shape[0], b:b+ww.shape[1]] = ww a += nodes_per_layer[ii] b += nodes_per_layer[ii+1] return w def test(): w1 = np.random.rand(4,5) #< 0.50 w2 = np.random.rand(5,6) #< 0.25 w3 = np.random.rand(6,3) #< 0.75 import string node_labels = dict(zip(range(18), list(string.ascii_lowercase))) fig, ax = plt.subplots(1,1) plot_layered_network([w1,w2,w3], layer_labels=['start', 'step 1', 'step 2', 'finish'], ax=ax, node_size=20, node_edge_width=2, node_labels=node_labels, edge_width=5, ) plt.show() return def test_example(input_dict): weight_matrices, node_labels = _parse_input(input_dict) fig, ax = plt.subplots(1,1) plot_layered_network(weight_matrices, layer_labels=['', '1', '2', '3', '4'], distance_between_layers=10, distance_between_nodes=8, ax=ax, node_size=300, node_edge_width=10, node_labels=node_labels, edge_width=50, ) plt.show() return def _parse_input(input_dict): weight_matrices = [] node_labels = [] # initialise sources sources = set() for v in input_dict[1].values(): for s in v.keys(): sources.add(s) sources = list(sources) for ii in range(len(input_dict)): inner_dict = input_dict[ii+1] targets = inner_dict.keys() w = np.full((len(sources), len(targets)), np.nan, np.float) for ii, s in enumerate(sources): for jj, t in enumerate(targets): try: w[ii,jj] = inner_dict[t][s] except KeyError: pass weight_matrices.append(w) node_labels.append(sources) sources = targets node_labels.append(targets) node_labels = list(itertools.chain.from_iterable(node_labels)) node_labels = dict(enumerate(node_labels)) return weight_matrices, node_labels # -------------------------------------------------------------------------------- # script # -------------------------------------------------------------------------------- if __name__ == "__main__": # test() input_dict = { 1: { "Group 1":{"sample_0":0.5, "sample_1":0.5, "sample_2":0, "sample_3":0, "sample_4":0}, "Group 2":{"sample_0":0, "sample_1":0, "sample_2":1, "sample_3":0, "sample_4":0}, "Group 3":{"sample_0":0, "sample_1":0, "sample_2":0, "sample_3":0.5, "sample_4":0.5} }, 2: { "Group 1":{"Group 1":1, "Group 2":0, "Group 3":0}, "Group 2":{"Group 1":0, "Group 2":1, "Group 3":0}, "Group 3":{"Group 1":0, "Group 2":0, "Group 3":1} }, 3: { "Group 1":{"Group 1":0.25, "Group 2":0, "Group 3":0.75}, "Group 2":{"Group 1":0.25, "Group 2":0.75, "Group 3":0} }, 4: { "Group 1":{"Group 1":1, "Group 2":0}, "Group 2":{"Group 1":0.25, "Group 2":0.75} } } test_example(input_dict) pass
这篇关于如何在`networkx`中使用`pos`参数来创建流程图样式的Graph? (Python 3)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!