¿Se pueden obtener gráficos jerárquicos de networkx con python 3?

Estoy tratando de mostrar un gráfico de árbol de mi jerarquía de clases usando networkx. Lo tengo todo graficado correctamente, y se muestra bien . Pero como una gráfica circular con bordes cruzados, es una jerarquía pura, y parece que debería poder mostrarla como un árbol.

He buscado en Google de forma extensa, y cada solución ofrecida implica el uso de pygraphviz … pero PyGraphviz no funciona con Python 3 (documentación del sitio de pygraphviz) .

¿Alguien ha podido obtener una visualización de gráfico de árbol en Python 3?

edit (19 de enero de 2019) He actualizado el código para que sea más robusto: ahora funciona para gráficos dirigidos y no dirigidos sin ninguna modificación, ya no requiere que el usuario especifique la raíz y prueba que el gráfico es un árbol antes de ejecutarse (Sin la prueba, tendría una recursión infinita; consulte la respuesta del usuario 2479115 para ver una forma de manejar los no árboles).

Editar (27 de agosto de 2018) Si desea crear un gráfico con los nodos que aparecen como anillos alrededor del nodo raíz, el código que se encuentra en la parte inferior muestra una modificación simple para hacer esto.

editar (17 de septiembre de 2017) Creo que el problema con Pygraphviz que tenía OP ya debería estar solucionado. Por lo tanto, es probable que Pygraphviz sea una solución mejor que lo que tengo a continuación.


Aquí hay un progtwig recursivo simple para definir las posiciones. La recursión ocurre en _hierarchy_pos , que es llamada por hierarchy_pos . El rol principal de hierarcy_pos es hacer un poco de prueba para asegurarse de que la gráfica sea apropiada antes de ingresar a la recursión:

 import networkx as nx import random def hierarchy_pos(G, root=None, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5): ''' From Joel's answer at https://stackoverflow.com/a/29597209/2966723. Licensed under Creative Commons Attribution-Share Alike If the graph is a tree this will return the positions to plot this in a hierarchical layout. G: the graph (must be a tree) root: the root node of current branch - if the tree is directed and this is not given, the root will be found and used - if the tree is directed and this is given, then the positions will be just for the descendants of this node. - if the tree is undirected and not given, then a random choice will be used. 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 ''' if not nx.is_tree(G): raise TypeError('cannot use hierarchy_pos on a graph that is not a tree') if root is None: if isinstance(G, nx.DiGraph): root = next(iter(nx.topological_sort(G))) #allows back compatibility with nx version 1.11 else: root = random.choice(list(G.nodes)) def _hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5, pos = None, parent = None): ''' see hierarchy_pos docstring for most arguments pos: a dict saying where all nodes go if they have been assigned parent: parent of this branch. - only affects it if non-directed ''' if pos is None: pos = {root:(xcenter,vert_loc)} else: pos[root] = (xcenter, vert_loc) children = list(G.neighbors(root)) if not isinstance(G, nx.DiGraph) and parent is not None: children.remove(parent) if len(children)!=0: dx = width/len(children) nextx = xcenter - width/2 - dx/2 for child in children: nextx += dx pos = _hierarchy_pos(G,child, width = dx, vert_gap = vert_gap, vert_loc = vert_loc-vert_gap, xcenter=nextx, pos=pos, parent = root) return pos return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter) 

y un ejemplo de uso:

 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') 

introduzca la descripción de la imagen aquí

Idealmente, esto debería volver a escalar la separación horizontal según la amplitud de las cosas debajo de ella. No estoy intentando eso ahora.

Expansión radial

Digamos que quieres que la ttwig se vea como:

introduzca la descripción de la imagen aquí

Aquí está el código para eso:

 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) 

Edición : gracias a Deepak Saini por señalar un error que solía aparecer en los gráficos dirigidos.

Aquí hay una solución para árboles grandes. Es una modificación del enfoque recursivo de Joel que espacia uniformemente los nodos en cada nivel.

 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) for neighbor in neighbors: if not neighbor == parent: 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) for neighbor in neighbors: if not neighbor == parent: 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({}) 

El ejemplo de Joel se verá así: introduzca la descripción de la imagen aquí

Y este es un gráfico más complejo (renderizado utilizando gráficamente): introduzca la descripción de la imagen aquí

Modifiqué un poco para que no retrocediera infinitamente.

 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) 

La forma más sencilla de obtener una bonita visualización de gráfico de árbol en Python 2 o 3 sin PyGraphviz es usar PyDot ( https://pypi.python.org/pypi/pydot ). Mientras que PyGraphviz proporciona una interfaz para todo Graphviz, PyDot solo proporciona una interfaz para la herramienta Dot de Graphviz, que es la única que necesitas si lo que buscas es un gráfico jerárquico / un árbol. Si desea crear su gráfico en NetworkX en lugar de PyDot, puede usar NetworkX para exportar un gráfico de PyDot, como se muestra a continuación:

 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') 

Tenga en cuenta que Graphviz y PyDot deben instalarse para que lo anterior funcione correctamente.

introduzca la descripción de la imagen aquí

Advertencia: he experimentado problemas al usar PyDot para dibujar gráficos con los diccionarios de atributos de nodo exportados desde NetworkX; a veces los diccionarios parecen estar exportados con comillas que faltan en las cadenas, lo que hace que el método de write se bloquee. Esto puede evitarse dejando fuera los diccionarios.

Para un gráfico dirigido, los vecinos (x) incluyen solo los sucesores (x), por lo que debe eliminar las líneas:

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

Además, una mejor opción sería esta:

 pos=nx.graphviz_layout(G,prog='dot')