Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group nodes together in networkx

I have a visualization problem involving a graph. I have N nodes, which belong to say some M networks. The nodes can have inter-network edges (within the same network) and intra-network edges (edges from a node in one network to another one).

image

When I visualize the graph in networkx I am looking for a way to place/cluster the networks together so that I can easily make out the inter/intra network connections. So ideally all the blue nodes would be clustered together as network (in no particular order). Similarly for the orange or green ones.

By the way I am not trying to find hubs/clusters, I know which nodes are in which networks, I just am trying to find a way to visualize it neater. Is there some easy way to do this? Something like an advanced spring layout where I can specify some nodes should show up together regardless of the edge weights / spring force?


Minimal working generator

import string, random
import networkx as nx
import matplotlib.pyplot as plt
from scipy.sparse import random as sparse_random


# Random string generator
def rand_string(size=6, chars=string.ascii_uppercase):
    return ''.join(random.choice(chars) for _ in range(size))


# Set up a nodes and networks randomly
nodes = [rand_string() for _ in range(30)]
networks = [rand_string() for _ in range(5)]
networks_list = networks*6
random.shuffle(networks_list)

# Define what nodes belong to what network and what their color should be
node_network_map = dict(zip(nodes, networks_list))
colors = ['green', 'royalblue', 'red', 'orange', 'cyan']
color_map = dict(zip(networks, colors))

graph = nx.Graph()
graph.add_nodes_from(nodes)
nodes_by_color = {val: [node for node in graph if color_map[node_network_map[node]] == val]
                  for val in colors}

# Take random sparse matrix as adjacency matrix
mat = sparse_random(30, 30, density=0.3).todense()
for row, row_val in enumerate(nodes):
    for col, col_val in enumerate(nodes):
        if col > row and mat[row, col] != 0.0: # Stick to upper half triangle, mat is not symmetric
            graph.add_edge(row_val, col_val, weight=mat[row, col])

# Choose a layout to visualize graph
pos = nx.spring_layout(graph)
edges = graph.edges()

# Get the edge weights and normalize them 
weights = [abs(graph[u][v]['weight']) for u, v in edges]
weights_n = [5*float(i)/max(weights) for i in weights] # Change 5 to control thickness

# First draw the nodes 
plt.figure()
for color, node_names in nodes_by_color.items():
    nx.draw_networkx_nodes(graph, pos=pos, nodelist=node_names, node_color=color)

# Then draw edges with thickness defined by weights_n
nx.draw_networkx_edges(graph, pos=pos, width=weights_n)
nx.draw_networkx_labels(graph, pos=pos)
plt.show()
like image 592
ITA Avatar asked Apr 18 '19 16:04

ITA


People also ask

How many nodes can NetworkX handle?

For NetworkX, a graph with more than 100K nodes may be too large. I'll demonstrate that it can handle a network with 187K nodes in this post, but the centrality calculations were prolonged. Luckily, there are some other packages available to help us with even larger graphs.

What is Nbunch in NetworkX?

nbunch. An nbunch is a single node, container of nodes or None (representing all nodes). It can be a list, set, graph, etc.. To filter an nbunch so that only nodes actually in G appear, use G.

What is a DiGraph in NetworkX?

A DiGraph stores nodes and edges with optional data, or attributes. DiGraphs hold directed edges. Self loops are allowed but multiple (parallel) edges are not. Nodes can be arbitrary (hashable) Python objects with optional key/value attributes.


1 Answers

To get better nodes layout, I start with using circular layout (replacing your spring-layout). Then I move each group of the nodes to their new locations along perimeter of a bigger circle.

# --- Begin_myhack ---
# All this code should replace original `pos=nx.spring_layout(graph)`
import numpy as np
pos = nx.circular_layout(graph)   # replaces your original pos=...
# prep center points (along circle perimeter) for the clusters
angs = np.linspace(0, 2*np.pi, 1+len(colors))
repos = []
rad = 3.5     # radius of circle
for ea in angs:
    if ea > 0:
        #print(rad*np.cos(ea), rad*np.sin(ea))  # location of each cluster
        repos.append(np.array([rad*np.cos(ea), rad*np.sin(ea)]))
for ea in pos.keys():
    #color = 'black'
    posx = 0
    if ea in nodes_by_color['green']:
        #color = 'green'
        posx = 0
    elif ea in nodes_by_color['royalblue']:
        #color = 'royalblue'
        posx = 1
    elif ea in nodes_by_color['red']:
        #color = 'red'
        posx = 2
    elif ea in nodes_by_color['orange']:
        #color = 'orange'
        posx = 3
    elif ea in nodes_by_color['cyan']:
        #color = 'cyan'
        posx = 4
    else:
        pass
    #print(ea, pos[ea], pos[ea]+repos[posx], color, posx)
    pos[ea] += repos[posx]
# --- End_myhack ---

The output plot will be similar to this:

enter image description here

EDIT

Usually, no particular layout is best in all situations. So, I offer the second solution which uses concentric circles to separate individual groups of the nodes. Here are the relevant code and its sample output.

# --- Begin_my_hack ---
# All this code should replace original `pos=nx.spring_layout(graph)`
import numpy as np
pos = nx.circular_layout(graph)
radii = [7,15,30,45,60]  # for concentric circles

for ea in pos.keys():
    new_r = 1
    if ea in nodes_by_color['green']:
        new_r = radii[0]
    elif ea in nodes_by_color['royalblue']:
        new_r = radii[1]
    elif ea in nodes_by_color['red']:
        new_r = radii[2]
    elif ea in nodes_by_color['orange']:
        new_r = radii[3]
    elif ea in nodes_by_color['cyan']:
        new_r = radii[4]
    else:
        pass
    pos[ea] *= new_r   # reposition nodes as concentric circles
# --- End_my_hack ---

fig2

like image 90
swatchai Avatar answered Sep 29 '22 12:09

swatchai