Peut-on obtenir des graphiques hiérarchiques à partir de networkx avec python 3?

j'essaie d'afficher un graphique arborescent de ma hiérarchie de classe en utilisant networkx. j'ai tout de dessiner correctement, et il affiche fine. Mais comme un graphique circulaire avec la traversée des arêtes, c'est un pur hiérarchie, et il semble que je devrais être en mesure de l'afficher comme un arbre.

j'ai beaucoup cherché sur Google, et chaque solution proposée implique d'utiliser pygraphviz... mais PyGraphviz ne fonctionne pas avec Python 3 (documentation de la site de pygraphviz).

quelqu'un a-t-il pu obtenir un affichage de graphe arborescent en Python 3?

12
demandé sur Joel 2015-04-12 09:06:57

5 réponses

modifier (27 Août 2018) si vous voulez créer un graphe avec les noeuds apparaissant comme des anneaux autour du noeud racine, le code en bas montre une simple modification pour le faire

modifier (17 Septembre 2017) je crois que le problème avec pygraphviz que L'OP avait devrait être réglé maintenant. Donc pygraphviz est probablement une meilleure solution que ce que j'ai en dessous.


voici un programme récursif simple pour définir positions:

import networkx as nx

def hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5, 
                  pos = None, parent = None):
    '''If there is a cycle that is reachable from root, then this will see infinite recursion.
       G: the graph
       root: the root node of current branch
       width: horizontal space allocated for this branch - avoids overlap with other branches
       vert_gap: gap between levels of hierarchy
       vert_loc: vertical location of root
       xcenter: horizontal location of root
       pos: a dict saying where all nodes go if they have been assigned
       parent: parent of this branch.'''
    if pos == None:
        pos = {root:(xcenter,vert_loc)}
    else:
        pos[root] = (xcenter, vert_loc)
    neighbors = list(G.neighbors(root)) 
    if parent != None:   #this should be removed for directed graphs.
        neighbors.remove(parent)  #if directed, then parent not in neighbors.
    if len(neighbors)!=0:
        dx = width/len(neighbors) 
        nextx = xcenter - width/2 - dx/2
        for neighbor in neighbors:
            nextx += dx
            pos = hierarchy_pos(G,neighbor, width = dx, vert_gap = vert_gap, 
                                vert_loc = vert_loc-vert_gap, xcenter=nextx, pos=pos, 
                                parent = root)
    return pos

et un exemple d'utilisation:

import matplotlib.pyplot as plt
import networkx as nx
G=nx.Graph()
G.add_edges_from([(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9), (4,10),
                  (5,11), (5,12), (6,13)])
pos = hierarchy_pos(G,1)    
nx.draw(G, pos=pos, with_labels=True)
plt.savefig('hierarchy.png')

enter image description here

idéalement, cela devrait modifier la séparation horizontale en fonction de la largeur des choses. Je ne vais pas tenter ça maintenant.

expansion radiale

disons que vous voulez l'intrigue ressemble à:

enter image description here

Voici le code pour que:

pos = hierarchy_pos(G, 0, width = 2*math.pi, xcenter=0)
new_pos = {u:(r*math.cos(theta),r*math.sin(theta)) for u, (theta, r) in pos.items()}
nx.draw(G, pos=new_pos, node_size = 50)
nx.draw_networkx_nodes(G, pos=new_pos, nodelist = [0], node_color = 'blue', node_size = 200)

modifier - merci à Deepak Saini pour avoir noté une erreur qui se produit dans les graphiques dirigés (les commentaires dans le code d'exemple montrent maintenant comment corriger cela)

20
répondu Joel 2018-08-27 03:20:22

j'ai légèrement modifié pour qu'il ne se reproduise pas indéfiniment.

import networkx as nx

def hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5 ):
    '''If there is a cycle that is reachable from root, then result will not be a hierarchy.

       G: the graph
       root: the root node of current branch
       width: horizontal space allocated for this branch - avoids overlap with other branches
       vert_gap: gap between levels of hierarchy
       vert_loc: vertical location of root
       xcenter: horizontal location of root
    '''

    def h_recur(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5, 
                  pos = None, parent = None, parsed = [] ):
        if(root not in parsed):
            parsed.append(root)
            if pos == None:
                pos = {root:(xcenter,vert_loc)}
            else:
                pos[root] = (xcenter, vert_loc)
            neighbors = G.neighbors(root)
            if parent != None:
                neighbors.remove(parent)
            if len(neighbors)!=0:
                dx = width/len(neighbors) 
                nextx = xcenter - width/2 - dx/2
                for neighbor in neighbors:
                    nextx += dx
                    pos = h_recur(G,neighbor, width = dx, vert_gap = vert_gap, 
                                        vert_loc = vert_loc-vert_gap, xcenter=nextx, pos=pos, 
                                        parent = root, parsed = parsed)
        return pos

    return h_recur(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5)
7
répondu user2479115 2015-07-28 19:28:07

la façon la plus simple d'obtenir un bel affichage de graphe arborescent en Python 2 ou 3 sans PyGraphviz est D'utiliser PyDot (https://pypi.python.org/pypi/pydot). Tandis que PyGraphviz fournit une interface à L'ensemble de Graphviz, PyDot fournit seulement une interface à L'outil point de Graphviz, qui est le seul dont vous avez besoin si ce que vous recherchez est un graphe hiérarchique / un arbre. Si vous voulez créer votre graphique dans NetworkX plutôt que PyDot, vous pouvez utiliser NetworkX pour exporter un graphique PyDot, comme dans le code suivant:

import networkx as nx

g=nx.DiGraph()
g.add_edges_from([(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9),
                  (4,10), (5,11), (5,12), (6,13)])
p=nx.drawing.nx_pydot.to_pydot(g)
p.write_png('example.png')

notez que Graphviz et PyDot doivent être installés pour que ce qui précède fonctionne correctement.

enter image description here

write méthode pour planter. Cela peut être évité en laissant de côté les dictionnaire.

5
répondu Westcroft_to_Apse 2016-08-30 09:41:06

Voici une solution pour les grands arbres. Il s'agit d'une modification de L'approche récursive de Joël qui permet d'espacer uniformément les noeuds à chaque niveau.

def hierarchy_pos(G, root, levels=None, width=1., height=1.):
    '''If there is a cycle that is reachable from root, then this will see infinite recursion.
       G: the graph
       root: the root node
       levels: a dictionary
               key: level number (starting from 0)
               value: number of nodes in this level
       width: horizontal space allocated for drawing
       height: vertical space allocated for drawing'''
    TOTAL = "total"
    CURRENT = "current"
    def make_levels(levels, node=root, currentLevel=0, parent=None):
        """Compute the number of nodes for each level
        """
        if not currentLevel in levels:
            levels[currentLevel] = {TOTAL : 0, CURRENT : 0}
        levels[currentLevel][TOTAL] += 1
        neighbors = G.neighbors(node)
        if parent is not None:
            neighbors.remove(parent)
        for neighbor in neighbors:
            levels =  make_levels(levels, neighbor, currentLevel + 1, node)
        return levels

    def make_pos(pos, node=root, currentLevel=0, parent=None, vert_loc=0):
        dx = 1/levels[currentLevel][TOTAL]
        left = dx/2
        pos[node] = ((left + dx*levels[currentLevel][CURRENT])*width, vert_loc)
        levels[currentLevel][CURRENT] += 1
        neighbors = G.neighbors(node)
        if parent is not None:
            neighbors.remove(parent)
        for neighbor in neighbors:
            pos = make_pos(pos, neighbor, currentLevel + 1, node, vert_loc-vert_gap)
        return pos
    if levels is None:
        levels = make_levels({})
    else:
        levels = {l:{TOTAL: levels[l], CURRENT:0} for l in levels}
    vert_gap = height / (max([l for l in levels])+1)
    return make_pos({})

Joël exemple ressemble à ceci: enter image description here

et ceci est un graphe plus complexe (rendu en utilisant plotly):enter image description here

5
répondu burubum 2017-03-10 16:31:12

pour un graphique dirigé, puisque neighbors(x) n'inclut que les successeurs (x), vous devez donc supprimer les lignes:

if parent != None:
        neighbors.remove(parent)

aussi, une meilleure option serait celle-ci:

pos=nx.graphviz_layout(G,prog='dot')
1
répondu Deepak Saini 2016-04-09 05:05:45