NetworkX Tutorial

In this tutorial we will take a look at ways of combining the analysis tools provided by NetworkX with the visualization capailities of AlgorithmX.

Simple Graph

Let’s start by creating a simple NetworkX graph. We will use add_path to quickly add both nodes and edges.

import networkx as nx

G = nx.Graph()

nx.add_path(G, [1, 2, 3])
nx.add_path(G, [4, 2, 5])

print('Nodes:', G.nodes)
print('Edges:', G.edges)
Nodes: [1, 2, 3, 4, 5]
Edges: [(1, 2), (2, 3), (2, 4), (2, 5)]

Now that we have all the data we need, we can create an AlgorithmX canvas to display our nodes and edges.

import algorithmx

canvas = algorithmx.jupyter_canvas()

canvas.nodes(G.nodes).add()
canvas.edges(G.edges).add()

canvas

So we have our simple graph, but we think it could look a little more interesting. Let’s define a custom style for our nodes, and also give each one a different color. We can take advantage of the fact that nearly any argument in AlgorithmX can be passed as a lambda function, making our code much more concise.

canvas = algorithmx.jupyter_canvas()

node_style = {
    'shape': 'rect',
    'size': (20, 12)
}
node_colors = {1: 'red', 2: 'green', 3: 'blue', 4: 'orange', 5: 'purple'}

canvas.nodes(G.nodes).add() \
    .set(node_style) \
    .color(lambda n: node_colors[n])

canvas.edges(G.edges).add()

canvas

Making the graph directed is easy - all we have to do is call G.to_directed(), and then tell AlgorithmX that the edges should be rendered with an arrow.

Weighted and Directed Graphs To create a directed graph, all we need to do is use a NetworkX DiGraph, and tell AlgrithmX that edges should be rendered with an arrow.

G = nx.DiGraph()

G.add_nodes_from([1, 2, 3])
G.add_edges_from([(1, 2), (2, 3), (3, 1)])

canvas = algorithmx.jupyter_canvas()

canvas.nodes(G.nodes).add()
canvas.edges(G.edges).add().directed(True)

canvas

To create wighted graph, we will first ensure that our NetworkX edges have a ‘weight’ attribute. Then, we will add a label to each edge displaying the attribute.

G = nx.Graph()

G.add_nodes_from([1, 2, 3])
G.add_weighted_edges_from([(1, 2, 0.4), (2, 3, 0.2), (3, 1, 0.3)])

canvas = algorithmx.jupyter_canvas()

canvas.nodes(G.nodes).add()
canvas.edges(G.edges).add() \
    .label().add() \
        .text(lambda e: G.edges[e]['weight'])

canvas

Finally, AlgorithmX provides a uility to simplify this process.

from algorithmx.networkx import add_graph

G = nx.DiGraph()

G.add_nodes_from([1, 2, 3])
G.add_weighted_edges_from([(1, 2, 0.4), (2, 3, 0.2), (3, 1, 0.3)])

canvas = algorithmx.jupyter_canvas()

add_graph(canvas, G)

Random Graph

NetworkX provides a range of functions for generating graphs. For generating a random graph, we will use the basic gnp_random_graph function. By providing a seed, we can ensure that we get the same graph every time (otherwise there is no guarantee of it being connected!).

G = nx.gnp_random_graph(10, 0.3, 138)

canvas = algorithmx.jupyter_canvas()
canvas.nodes(G.nodes).add()
canvas.edges(G.edges).add()

canvas

To make the graph directed, we will simply use G.to_directed. To make the graph weighted, we will need to configure a weight attribute for each edge. Since our graph is random, we’ll make our edge weights random as well. For this we will use the set_edge_attributes function.

from random import randint

G = G.to_directed()
nx.set_edge_attributes(G, {e: {'weight': randint(1, 10)} for e in G.edges})

We can now display the graph using the utility from before.

canvas = algorithmx.jupyter_canvas()
add_graph(canvas, G)

Detailed Graph

Now we are going to create a graph that displays a range of interesting properties. Let’s begin by generating a random weighted graph, as before.

G = nx.gnp_random_graph(10, 0.3, 201)
nx.set_edge_attributes(G, {e: {'weight': randint(1, 10)} for e in G.edges})

Next, we will use NetworkX to calculate the graph’s coloring and edge centrality.

coloring = nx.greedy_color(G)
centrality = nx.edge_betweenness_centrality(G, weight='weight', normalized=True)

We can now begin displaying the graph. First, we will add the nodes and assign them a color based on their calculated priority. We happen to know that any graph requires at most 4 different colors, and so we prepare these beforehand.

canvas = algorithmx.jupyter_canvas()

color_priority = {0: 'red', 1: 'orange', 2: 'yellow', 3: 'green'}

canvas.nodes(G.nodes).add() \
    .color(lambda n: color_priority[coloring[n]])

print(coloring)
{4: 0, 2: 1, 3: 2, 0: 1, 1: 2, 6: 0, 8: 1, 7: 2, 9: 2, 5: 0}

Afterwards, we will add the edges. Each one will have two labels; one to display it’s weight, and another to display it’s calculated centrality.

init_edges = canvas.edges(G.edges).add()

formatted_centrality = {k: '{0:.2f}'.format(v) for k, v in centrality.items()}

init_edges.label().add() \
    .text(lambda e: G.edges[e]['weight']) \

init_edges.label('centrality').add() \
    .color('blue') \
    .text(lambda e: formatted_centrality[e])

print(formatted_centrality)
{(0, 1): '0.07', (0, 3): '0.22', (0, 4): '0.00', (1, 4): '0.00', (1, 8): '0.13', (2, 3): '0.13', (2, 4): '0.27', (2, 5): '0.20', (2, 6): '0.04', (2, 7): '0.18', (3, 4): '0.20', (3, 6): '0.13', (4, 8): '0.40', (4, 9): '0.00', (6, 7): '0.02', (8, 9): '0.20'}

Finally, we can see the whole graph.

canvas