42
votes

I have a large graph of nodes and directed edges. Furthermore, I have an additional list of values assigned to each node.

I now want to change the color of each node according to their node value. So e.g., drawing nodes with a very high value red and those with a low value blue (similar to a heatmap). Is this somehow easily possible to achieve? If not with networkx, I am also open for other libraries in Python.

2

2 Answers

69
votes
import networkx as nx
import numpy as np
import matplotlib.pyplot as plt

G = nx.Graph()
G.add_edges_from(
    [('A', 'B'), ('A', 'C'), ('D', 'B'), ('E', 'C'), ('E', 'F'),
     ('B', 'H'), ('B', 'G'), ('B', 'F'), ('C', 'G')])

val_map = {'A': 1.0,
           'D': 0.5714285714285714,
           'H': 0.0}

values = [val_map.get(node, 0.25) for node in G.nodes()]

nx.draw(G, cmap=plt.get_cmap('viridis'), node_color=values, with_labels=True, font_color='white')
plt.show()

yields enter image description here


The numbers in values are associated with the nodes in G.nodes(). That is to say, the first number in values is associated with the first node in G.nodes(), and similarly for the second, and so on.

7
votes

For the general case, in which we have a list of values indicating some attribute of a node, and we want to assign a colour to the given node which gives a sense of scale of that attribute (reds to blues for instance), here's one approach:

import matplotlib as mpl
from matplotlib import pyplot as plt
from pylab import rcParams
import networkx as nx

G = nx.Graph()
G.add_edges_from([('A', 'D'), ('Z', 'D'), ('F', 'J'), ('A', 'E'), ('E', 'J'),('Z', 'K'), ('B', 'A'), ('B', 'D'), ('A', 'J'), ('Z', 'F'),('Z', 'D'), ('A', 'B'), ('J', 'D'), ('J', 'E'), ('Z', 'J'),('K', 'J'), ('B', 'F'), ('B', 'J'), ('A', 'Z'), ('Z', 'E'),('C', 'Z'), ('C', 'A')])

Say that we have the following dictionary mapping a each node to a given value:

color_lookup = {k:v for v, k in enumerate(sorted(set(G.nodes())))}
# {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'J': 6, 'K': 7, 'Z': 8}

What we could do is to use mpl.colors.Normalize to normalize the values in color_lookup to the range [0,1] based on the minimum and maximum values that the nodes take, and then matplotlib.cm.ScalarMappable to map the normalized values to colours in a colourmap, here I'll be using mpl.cm.coolwarm:

low, *_, high = sorted(color_lookup.values())
norm = mpl.colors.Normalize(vmin=low, vmax=high, clip=True)
mapper = mpl.cm.ScalarMappable(norm=norm, cmap=mpl.cm.coolwarm)

rcParams['figure.figsize'] = 12, 7
nx.draw(G, 
        nodelist=color_lookup,
        node_size=1000,
        node_color=[mapper.to_rgba(i) 
                    for i in color_lookup.values()], 
        with_labels=True)
plt.show()

enter image description here

For another colour map we'd just have to change the cmap parameter in mpl.cm.ScalarMappable:

mapper = mpl.cm.ScalarMappable(norm=norm, cmap=mpl.cm.summer)
nx.draw(G, 
        nodelist=color_lookup,
        node_size=1000,
        node_color=[mapper.to_rgba(i) 
                    for i in color_lookup.values()], 
        with_labels=True)
plt.show()

Where we'd get:

enter image description here

Similarly, we could set the colour of a node based on the degree of a node by defining a dictionary mapping all nodes to their corresponding degree, and taking the same steps as above:

d = dict(G.degree)
# {'A': 6, 'D': 4, 'Z': 7, 'F': 3, 'J': 7, 'E': 3, 'K': 2, 'B': 4, 'C': 2}
low, *_, high = sorted(d.values())
norm = mpl.colors.Normalize(vmin=low, vmax=high, clip=True)
mapper = mpl.cm.ScalarMappable(norm=norm, cmap=mpl.cm.coolwarm)

nx.draw(G, 
        nodelist=d,
        node_size=1000,
        node_color=[mapper.to_rgba(i) 
                    for i in d.values()], 
        with_labels=True,
        font_color='white')
plt.show()

enter image description here