Search code examples
pythonnetworkxgraph-visualizationgraph-tooldigraphs

`graph-tool` edge bundling circular layout graph from weighted adjacency block matrix


I am trying graph-tool by Tiago Peixoto to build a graph (either directed or undirected) from a given weighted adjacency matrix with a block structure. So far, unsuccessfully. My question partly overlaps with this thread on SO, which, however, remains without a clear solution.

Suppose I have a function that generates my block matrix of weights J, which is in the form:

enter image description here

Each block Jij is some random binary matrix with entries drawn from a given distribution. The scalars s and g respectively denote weights for connections within diagonal blocks (i.e. when i = j) and blocks off the diagonal (i.e. i ≠ j).

I build my graph in graph_tool as follows:

import graph_tool.all as gt

directed = False  # True if we want the graph to be directed 
J = generate_adj_bmatrix(...,s=0.1,g=0.01,directed=directed) # Some function to generate the weighted adjacency matrix (here the matrix will be symmetric since we want the graph undirected)  

# Define graph
G = gt.Graph(directed=directed)
indexes = J.nonzero()
G.add_edge_list(np.transpose(indexes))
# Add weight information
G.ep['weight'] = G.new_ep("double", vals=J[indexes])

I can also add, if I want, some VertexProperty to my G graph to whose block my nodes belong. But how do I include this information in the code whereby I can build the circular graph? The code reads (pasted here from graph-tool docs):

state = gt.minimize_blockmodel_dl(G) # or should I consider instead state = gt.minimize_nested_blockmodel_dl(G)?
gt.draw_hierarchy(state)
t = gt.get_hierarchy_tree(state)[0]
tpos = pos = gt.radial_tree_layout(t, t.vertex(t.num_vertices() - 1), weighted=True)
cts = gt.get_hierarchy_control_points(G, t, tpos)
pos = G.own_property(tpos)
b = state.levels[0].b
shape = b.copy()
shape.a %= 14    # Have not yet figured out what I need it for
gt.graph_draw(G, pos=pos, vertex_fill_color=b, vertex_shape=shape, 
              edge_control_points=cts,edge_color=[0, 0, 0, 0.3], vertex_anchor=0)

Noteworthy is that the above code currently hangs seemingly too long. The minimize_blockmodel_dl(G) line appears to engage in an endless loop. Ideally, I should not sample my graph for clusters, since this information could already be provided as a property to the vertexes, based on my knowledge of the block structure of J. At the same time, minimize_blockmodel_dl(G) seems necessary in order to access the edge bundling option, doesn't it?


Solution

  • Here is the solution I came up with.

    def visualize_network(J,N_sizes):
    """
    Visualize a network from weighted block adjacency matrix in a circular layout with FEB.
    
    Input arguments:
    -- J : Weighted adjacency matrix (in block-matrix form, but can be any, as far as it is square). 
    -- N_sizes : {<block1_label>: size; <block2_label>: size,...} such that node indexes of block n follow immediately those of block n-1.  
    """
    
    import numpy as np
    import matplotlib.colors as mcolors
    import graph_tool.all as gt
    
    # Generate the graph
    G = gt.Graph(directed=True) # In my case, network edges are oriented
    eindexes = J.nonzero()
    G.add_edge_list(np.transpose(eindexes))
    # Add weight information
    weight = G.new_ep("double", vals = J[eindexes])
    
    # Assign color to each vertex based on the block it belongs to 
    colors = {'B1' : 'k',
              'B2' : 'r',
              'B3' : 'g',
              'B4' : 'b'}
    regs = np.asarray(list(N_sizes.keys()))
    rindexes = np.cumsum(list(N_sizes.values()))
    iidd = regs[np.searchsorted(rindexes,np.arange(np.shape(J)[0]))]
    region_id = G.new_vp("string",vals=iidd)
    vcolors = [colors[id] for id in iidd]
    vertex_color = G.new_vp("string",vals=vcolors)
    
    # Assigns edge colors by out-node.
    eid = regs[np.searchsorted(rindexes,np.arange(np.shape(J)[0]))]
    ecolors = [mcolors.to_hex(c) for c in regs[np.searchsorted(rindexes,eindexes[0]]] 
    edge_color = G.new_ep("string",vals=ecolors)
    
    # Construct a graph in a circular layout with FEB
    G = gt.GraphView(G, vfilt=gt.label_largest_component(G))
    state = gt.minimize_nested_blockmodel_dl(G)
    t = gt.get_hierarchy_tree(state)[0]
    tpos = gt.radial_tree_layout(t, t.vertex(t.num_vertices() - 1, use_index=False), weighted=True)
    cts = gt.get_hierarchy_control_points(G, t, tpos)
    pos = G.own_property(tpos)
    
    gt.graph_draw(G,
                  pos = pos,
                  vertex_fill_color = vertex_color,
                  edge_control_points = cts,
                  edge_color = edge_color,
                  vertex_anchor = 0)
    

    Additional documentation on the circular layout and this way of building the graph can be found at this graph-tool doc page.