我正在尝试使用
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
)专为此类绘图而设计。
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
NetworkX
和
matplotlib
,但在 Stack Overflow 上没有找到好的答案。在浏览文档后,我发现有一个很好的方法可以使用
multipartite_layout()
中的
NetworkX
来实现这一点。您甚至可以单独绘制边缘和边缘标签,以获得您在概念中可视化的加权箭头不透明度。弄清楚这一点后,我认为这将是一个很好的第一个 Stack Overflow 评论,因此我创建了一个 Stack Overflow 帐户和一个
公共 GitHub 存储库,演示如何仅使用 matplotlib
和
NetworkX
创建多部分加权有向图/流程图.您可以在公共 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()