如何使用`networkx`中的`pos`参数创建流程图风格的图表? (Python 3)

问题描述 投票:0回答:2

我正在尝试使用

Python
(最好使用
matplotlib
networkx
,尽管会对
bokeh
感兴趣)创建一个线性网络图,其概念与下面的类似。

如何使用

pos
在 Python 中有效地构建此图表(
networkx
?)?
我想将其用于更复杂的示例,所以我觉得对这个简单示例的位置进行硬编码是没有用的:( 。
networkx
有解决办法吗?

pos(字典,可选)——以节点为键的字典, 作为价值观的立场。如果没有指定弹簧布局定位将 被计算。有关计算节点的函数,请参阅 networkx.layout 职位。

我还没有在

networkx
中看到任何关于如何实现这一点的教程,这就是为什么我相信这个问题将成为社区的可靠资源。我已经广泛浏览了
networkx
教程
,但那里没有类似的内容。如果不仔细使用
networkx
参数,
pos
的布局将使这种类型的网络无法解释......我相信这是我唯一的选择。 https://networkx.github.io/documentation/networkx-1.9/reference/drawing.html文档上的预计算布局似乎都不能很好地处理这种类型的网络结构。

简单示例:

(A) 每个外键都是图中从左向右移动的迭代(例如迭代 0 表示样本,迭代 1 具有组 1 - 3,迭代 2 相同,迭代 3 具有组 1 - 2 等) 。 (B) 内部字典包含该特定迭代中的当前分组,以及代表当前组的先前组合并的权重(例如

iteration 3

 具有 
Group 1
Group 2
 以及 
iteration 4
 的所有 
iteration 3's
 
Group 2
 已合并为 
iteration 4's
 
Group 2
,但 
iteration 3's
 
Group 1
 已被拆分。权重之和始终为 1。

我的连接代码与上图的权重:

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} } }

这就是我在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()])

注意:我能想到的唯一其他方法是

matplotlib

 创建一个散点图,每个刻度代表一次迭代(5 个,包括初始样本),然后用不同的权重将点相互连接。这将是一些非常混乱的代码,特别是试图将标记的边缘与连接对齐...但是,我不确定这和
networkx
是否是最好的方法,或者是否有工具(例如 
bokeh
plotly
)专为此类绘图而设计。

python matplotlib plot graph networkx
2个回答
18
投票
Networkx 拥有不错的探索性数据绘图工具 分析,它不是制作出版质量数据的工具, 由于各种原因我不想进入这里。我因此 从头开始重写了那部分代码库,并做了一个 可以找到名为 netgraph 的独立绘图模块

here(就像纯粹基于 matplotlib 的原始版本一样)。 API 是 非常非常相似并且有据可查,所以不应该太相似 很难根据您的目的进行塑造。

在此基础上我得到以下结果:

我选择颜色来表示边缘强度,你可以

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): """ 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
    

0
投票
我最近的任务是在实验室中制作一个与此类似的图表,仅使用

NetworkX

matplotlib
,但在 Stack Overflow 上没有找到好的答案。在浏览文档后,我发现有一个很好的方法可以使用 
multipartite_layout()
 中的 
NetworkX
 来实现这一点。您甚至可以单独绘制边缘和边缘标签,以获得您在概念中可视化的加权箭头不透明度。

弄清楚这一点后,我认为这将是一个很好的第一个 Stack Overflow 评论,因此我创建了一个 Stack Overflow 帐户和一个

公共 GitHub 存储库,演示如何仅使用 matplotlib

NetworkX 创建多部分加权有向图/流程图
.

Resulting NetworkX Graph

您可以在公共 GitHub 存储库中找到用于创建演示的代码:

import networkx as nx import matplotlib.pyplot as plt 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} } } # Create a NextworkX directed graph g = nx.DiGraph() g.add_nodes_from(['sample_0', 'sample_1', 'sample_2', 'sample_3', 'sample_4'], subset=0) g.add_nodes_from(['Group 1.1', 'Group 2.1', 'Group 3.1'], subset=1) g.add_nodes_from(['Group 1.2', 'Group 2.2', 'Group 3.2'], subset=2) g.add_nodes_from(['Group 1.3', 'Group 2.3'], subset=3) g.add_nodes_from(['Group 1.4', 'Group 2.4'], subset=4) # Add Title to PLot plt.title('Multipartite Weighted Directed Graph') # Create a list of the edges and alphas (opacity) edges = [] alphas = [] for subset, subset_stuff in D_iter_current_previous.items(): for node, prev_nodes in subset_stuff.items(): for prev_node, weight in prev_nodes.items(): if subset > 1: edges.append((f'{prev_node}.{subset-1}', f'{node}.{subset}')) alphas.append(weight) else: edges.append((f'{prev_node}', f'{node}.{subset}')) alphas.append(weight) # Make a dict of the edge labels edge_labels = dict(zip(edges, alphas)) # Draw the nodes with the multipartite layout pos = nx.multipartite_layout(g, align='vertical') nx.draw(g, pos, with_labels=True, node_size=1500, font_size=8) # Draw the edges with their corresponding alphas nx.draw_networkx_edges(g, pos, edgelist=edges, alpha=alphas, arrows=True, node_size=1500) # Draw the edge labels with their corresponding alphas counter = 0 for edge, alpha in edge_labels.items(): if alpha > 0: nx.draw_networkx_edge_labels(g, pos, edge_labels={edge: float(alpha)}, alpha=float(alpha), font_size=7) # Show the plot plt.show()
    
© www.soinside.com 2019 - 2024. All rights reserved.