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

Posted

技术标签:

【中文标题】如何使用 `networkx` 中的 `pos` 参数创建流程图样式的图表? (Python 3)【英文标题】:How to use the `pos` argument in `networkx` to create a flowchart-style Graph? (Python 3) 【发布时间】:2017-02-09 15:26:36 【问题描述】:

我正在尝试使用Python(最好使用matplotlibnetworkx,尽管对bokeh 感兴趣)创建一个线性网络图,其概念类似于下面的。

如何在 Python 中使用 networkx 有效地构造此图表(pos?)? 我想将其用于更复杂的示例,因此我觉得为此很难对位置进行编码简单的例子不会有用:(。networkx 有解决方案吗?

pos (dictionary, optional) – A dictionary with nodes as keys and positions as values. If not specified a spring layout positioning will be computed. See networkx.layout for functions that compute node positions.

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

简单示例:

(A) 每个外键是图中从左向右移动的迭代(例如,迭代 0 表示样本,迭代 1 具有组 1-3,与迭代 2 相同,迭代 3 具有组 1-2,等等.)。 (B) 内部字典包含该特定迭代中的当前分组,以及表示当前组的先前组合并的权重(例如,iteration 3 具有 Group 1Group 2 以及 iteration 4 的所有 @987654346 @Group 2 已进入 iteration 4's Group 2iteration 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 是否是最好的方法或者是否有一个工具(例如bokehplotly)专为此类绘图而设计。

【问题讨论】:

你看过networkx tutorial吗? Plotting networkx graphs 在 matplotlib 中非常简单。您在什么时候遇到问题? 我知道如何使用networkx,但布局会使它们变得一团糟。我可以使用 pos 参数,但自定义位置字典很奇怪。我认为没有关于线性网络的教程 我认为有一种方法可以通过使用 graphviz 布局来做到这一点,但我不太确定它如何与最新版本的 networkx 交互(API 发生了一些变化)。如果我有时间,我将尝试启动并运行有效的安装。同时,尝试查看this answer - prog='dot' 很重要,但很可能会让您到达您想去的地方。请注意,除了networkxmatplotlib 之外,您还需要安装graphviz 和pygraphviz 【参考方案1】:

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

【讨论】:

嘿,谢谢你,我想试试。你能用 conda 或 pip 安装 netgraph 吗? @O.rka 我还没有为此设置它,我必须在 3 周内提交我的博士学位,所以我不会在那之前。只需下载 .py 文件(它只是一个文件),并暂时将其放到您的工作目录中。它只是依赖于 matplotlib,所以只要你安装了它,一切都应该工作。 酷,我会尝试一下!祝你的论文好运。 当你有 - 我拖延 - 我的意思是:工作 - 为那些想象中的互联网点努力时,不要忘记接受答案。 ;-) @moritzschaefer:我尝试维护我的代码和我的代码文档。我也没有时间维护我所有的 *** 答案。 netgraph 是一个新的主要版本,API 以向后不兼容的方式发生了变化(略微)。如文档中所述,node_positions 现在是一个字典,将节点 ID 映射到浮点数的 2 元组(或等效项)。如果您提供权重矩阵,则节点 ID 必须是与矩阵索引相对应的整数(即从零开始)。如果您向 MWE 发布新问题,我们很乐意提供帮助;很高兴接受对此答案的修改。

以上是关于如何使用 `networkx` 中的 `pos` 参数创建流程图样式的图表? (Python 3)的主要内容,如果未能解决你的问题,请参考以下文章

具有特定位置的networkx add_node

Networkx 和 Matplotlib:如何访问节点属性并将它们显示为注释

networkx绘制拓扑图节点的边和权,Python

使用 NetworkX 和 Matplotlib 动画网络增长

改进 Python NetworkX 图形布局

networkx节点2D网格,Python