Saltar al contenido

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

Solución:

[scroll down a bit to see what kind of output the code produces]

editar (7 de noviembre de 2019) Puse una versión más refinada de esto en un paquete que he estado escribiendo: https://epidemicsonnetworks.readthedocs.io/en/latest/_modules/EoN/auxiliary.html#hierarchy_pos. La principal diferencia entre el código aquí y la versión allí es que el código aquí le da a todos los hijos de un nodo dado el mismo espacio horizontal, mientras que el código que sigue a ese enlace también considera cuántos descendientes tiene un nodo al decidir cuánto espacio asignarlo. .

editar (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 lo haría tienen recursividad infinita: consulte la respuesta de user2479115 para conocer 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 de la parte inferior muestra una modificación simple para hacer esto.

editar (17 de septiembre de 2017) Creo que el problema con pygraphviz que OP estaba teniendo ya debería estar solucionado. Entonces, es probable que pygraphviz sea una mejor solución que la que tengo a continuación.


Aquí hay un programa recursivo simple para definir las posiciones. La recursividad ocurre en _hierarchy_pos, que es llamado por hierarchy_pos. El papel principal de hierarcy_pos es hacer un poco de prueba para asegurarse de que el gráfico sea apropiado antes de ingresar a la recursividad:

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

ingrese la descripción de la imagen aquí

Idealmente, esto debería cambiar la escala de la separación horizontal en función de qué tan anchas serán las cosas debajo de ella. No estoy intentando eso, pero esta versión lo hace: https://epidemicsonnetworks.readthedocs.io/en/latest/_modules/EoN/auxiliary.html#hierarchy_pos

Expansión radial

Digamos que quieres que la trama se vea así:

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

editar – 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 los nodos de manera uniforme 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í:
ingrese la descripción de la imagen aquí

Y este es un gráfico más complejo (renderizado usando plotly):ingrese la descripción de la imagen aquí

La forma más sencilla de obtener un gráfico de árbol atractivo 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 necesita si lo que busca 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 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 es necesario instalar Graphviz y PyDot para que lo anterior funcione correctamente.

ingrese la descripción de la imagen aquí

Advertencia: He tenido problemas al usar PyDot para dibujar gráficos con diccionarios de atributos de nodo exportados desde NetworkX; a veces, los diccionarios parecen exportarse con comillas que faltan en las cadenas, lo que hace que el write método para estrellarse. Esto se puede evitar omitiendo los diccionarios.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *