title: "Lecture 8: Introduction to Graphs in Python" author: "Junaid Hasan" date: "July 9, 2021"¶

Announcement: Project Guidelines will be posted on Monday.¶

  • Start thinking about a project.
  • You will have to do a real world modelling and present.
  • Max 2 participants per group
  • More details on Monday

Homework for Week 4 posted¶

Graphs in Python¶

Creating a graph¶

Today we will use the NetworkX module to draw graphs in Python. Please !pip install networkx if you do not have networkx installed. Lets begin by creating a graph $G$

In [161]:
import networkx as nx
G = nx.Graph()

Adding Nodes¶

Now that we have an empty graph $G$, let us add vertices and edges to $G$. In networkx vertices are known as nodes. Nodes can be added individually or from a list. They may be numbered by integers or strings.

In [162]:
list_nodes = range(1,9)
G.add_nodes_from(list_nodes)
In [164]:
list(G.nodes)
Out[164]:
[1, 2, 3, 4, 5, 6, 7, 8]

Adding edges¶

Just like nodes, edges can also be added one at a time, for from a list of tuples. Use add_edge() or add_edges_from() respectively.

In [166]:
G.add_edge(1,3)
listedges = [(1,4), (2,3), (4,5), (5,9), (9,6), (9,8), (6,7), (6,8), (7,8)]
G.add_edges_from(listedges)
In [167]:
G.edges
Out[167]:
EdgeView([(1, 3), (1, 4), (2, 3), (4, 5), (5, 9), (6, 9), (6, 7), (6, 8), (7, 8), (8, 9)])
In [ ]:
 

Removing Nodes and Edges¶

Instead of adding edges or nodes we can remove by remove_node() or remove_edge() or remove a list of nodes or edges by remove_nodes_from() or remove_edges_from()

In [168]:
G.remove_node(3)
In [169]:
G.nodes
Out[169]:
NodeView((1, 2, 4, 5, 6, 7, 8, 9))
In [170]:
G.edges
Out[170]:
EdgeView([(1, 4), (4, 5), (5, 9), (6, 9), (6, 7), (6, 8), (7, 8), (8, 9)])

Information about the graph¶

To check out the number of nodes or edges use number_of_nodes() or number_of_edges() respectively. Moreover nodes and edges stores the nodes and edges explicitly

In [171]:
print(G.number_of_nodes())
print(G.number_of_edges())
print(G.nodes)
print(G.edges)
8
8
[1, 2, 4, 5, 6, 7, 8, 9]
[(1, 4), (4, 5), (5, 9), (6, 9), (6, 7), (6, 8), (7, 8), (8, 9)]

We can also view the neighbours via G.adj and know the degree of a node via G.degree.

In [177]:
print(G.adj)
print(G.degree[6])
{1: {4: {}}, 2: {}, 4: {1: {}, 5: {}}, 5: {4: {}, 9: {}}, 6: {9: {}, 7: {}, 8: {}}, 7: {6: {}, 8: {}}, 8: {9: {}, 6: {}, 7: {}}, 9: {5: {}, 6: {}, 8: {}}}
3

Drawing the graph¶

The simplest way to draw is by networkx.draw() function. But for more control it is preferred to use it with matplotlib. To install matplotlib use !pip install matplotlib

In [180]:
G.add_edge(1,2)
In [184]:
nx.draw(G)
No description has been provided for this image

To draw with the node labels, use the with_labels flag.

In [185]:
nx.draw(G, with_labels=True)
No description has been provided for this image

We observe that the draw function draws a different graph everytime. To get more control we can use other types of graph drawing. For more info check the documentation for drawing

Adding attributes to graphs¶

Networkx stores the graph as a dictionary of dictionaries. This allows us to add multiple attributes to the edges (say weights or color or anything else).

Node attributes¶

We can access the attributes of a node by G.nodes[node]

Suppose in the above graph the nodes represent classes or timings we can add that info during graph creation.

In [188]:
G.add_node(10, time='9 AM')
G.nodes[1]
Out[188]:
{}

All other nodes do not have that time attribute yet, for example node 7

In [191]:
G.nodes[7]
G.add_node(3)
G.add_edge(3,2)
In [195]:
G.nodes[1]['color'] = 'red'
G.nodes[2]['time'] = '1 PM'
G.nodes[3]['time'] = '2 PM'
In [196]:
G.nodes[3]
Out[196]:
{'time': '2 PM'}
In [197]:
G.nodes.data()
Out[197]:
NodeDataView({1: {'time': '12 PM', 'color': 'red'}, 2: {'time': '1 PM'}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {'time': '9 AM'}, 3: {'time': '2 PM'}})

Edge attributes¶

Similar to node attributes, edge attributes can be added during edge creation or via G.edges

In [198]:
G.add_edge(10,3, weight=5)
In [202]:
G[6][7]['color'] = 'orange'
G[6][8]['weight'] = 2
G[9][6]['weight'] = 1
In [203]:
G.edges.data()
Out[203]:
EdgeDataView([(1, 4, {}), (1, 2, {}), (2, 3, {}), (4, 5, {}), (5, 9, {}), (6, 9, {'weight': 1}), (6, 7, {'weight': 4, 'color': 'orange'}), (6, 8, {'weight': 2}), (7, 8, {}), (8, 9, {}), (10, 3, {'weight': 5})])

Application: Coloring¶

Let us try to use edge and node attributes to color graphs. To have an interesting graph we will use a predetermined graph say the peterson graph. We use a random coloring, so we import numpy and for visualization we use matplotlib. To install these use !pip install numpy for example

In [204]:
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
In [205]:
G = nx.petersen_graph()
In [206]:
plt.figure()
nx.draw(G, with_labels=True)
No description has been provided for this image
In [208]:
nx.draw_circular(G)
No description has been provided for this image

The draw_shell() drawing takes two lists of nodes and the first list is placed inside while the second list is placed outside. There are multiple other ways of visualization, type nx.draw and press tab to view them. For example draw_circular places them on a circle

In [207]:
nx.draw_shell(G, nlist=[[5,6,7,8,9],[0,1,2,3,4]])
No description has been provided for this image

To show multiple plots in one row we use matplotlib pyplot subplot. The first two arguments are number of rows and columns and the next one is the index

In [210]:
options = {
    'node_color': 'orange',
    'node_size': 100,
    'width': 3,
}

plt.subplot(2,2,1)
nx.draw(G, **options)

plt.subplot(2,2,2)
nx.draw_shell(G, nlist=[[5,6,7,8,9],[0,1,2,3,4]], **options)

plt.subplot(2,2,3)
nx.draw_circular(G, **options)

plt.subplot(2,2,4)
nx.draw_random(G, **options)

plt.show()
No description has been provided for this image

Coloring the graph¶

Suppose numbers represent colors (we can use the hex code and stuff but for simplicity let us start with numbers).

The function random_coloring_nodes assigns a random integer to each node representing the color.

In [211]:
def random_coloring_nodes(graph, n_colors):
    coloring = {}
    for node in graph.nodes():
        coloring[node] = np.random.randint(0, n_colors)
    return coloring
In [212]:
def random_coloring_edges(graph, n_colors):
    coloring = {}
    for edge in graph.edges():
        coloring[edge] = np.random.randint(0, n_colors)
    return coloring
In [213]:
some_node_coloring = random_coloring_nodes(G, 5)
some_node_coloring
Out[213]:
{0: 3, 1: 4, 2: 2, 3: 1, 4: 3, 5: 3, 6: 4, 7: 2, 8: 4, 9: 3}
In [215]:
some_edge_coloring = random_coloring_edges(G, 6)
some_edge_coloring
Out[215]:
{(0, 1): 3,
 (0, 4): 3,
 (0, 5): 2,
 (1, 2): 4,
 (1, 6): 5,
 (2, 3): 1,
 (2, 7): 0,
 (3, 4): 5,
 (3, 8): 1,
 (4, 9): 1,
 (5, 7): 3,
 (5, 8): 1,
 (6, 8): 5,
 (6, 9): 5,
 (7, 9): 5}

Before visualizing the graph we must assign the colors to the numbers. There are many ways of doing this. For example we could take a list color_list = ['red', 'blue', 'green', 'yellow', 'purple'] and use the five colors for the five numbers. But what if we want to keep the number of colors as a variable

In [217]:
def get_cmap(n, name='Spectral'):
    '''Returns a function that maps each index in 0, 1, ..., n-1 to a distinct 
    RGB color; the keyword argument name must be a standard mpl colormap name.'''
    return plt.cm.get_cmap(name, n)
In [220]:
cmap = get_cmap(5)
cmap
Out[220]:
hsv
hsv colormap
under
bad
over
In [221]:
get_cmap(6)
Out[221]:
hsv
hsv colormap
under
bad
over

Let us now draw the coloring.

In [224]:
def draw_coloring(G,node_coloring, edge_coloring, pos="0"):
    if pos == "0":
                  pos = nx.random_layout(G)
    fig = plt.figure()
    n_node_colors = max(node_coloring[i] for i in node_coloring)+1
    n_edge_colors = max(edge_coloring[i] for i in edge_coloring)+1
    
    cmap_node = get_cmap(n_node_colors+1)
    cmap_edge = get_cmap(n_edge_colors+1)
    
    
    for i in range(n_node_colors):
        nx.draw_networkx_nodes(G, pos, [x for x in G.nodes() if node_coloring[x]==i],node_color=cmap_node(i), node_size = 100)
    
    for i in range(n_edge_colors):
        nx.draw_networkx_edges(G, pos, [x for x in G.edges() if edge_coloring[x]==i],edge_color=cmap_edge(i), width=2)
    
        
    plt.axis('off')
    plt.show() 
    return fig
In [225]:
fig2 = draw_coloring(G,some_node_coloring, some_edge_coloring, nx.shell_layout(G, nlist=[[5,6,7,8,9],[0,1,2,3,4]]))
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
*c* argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with *x* & *y*.  Please use the *color* keyword-argument or provide a 2D array with a single row if you intend to specify the same RGB or RGBA value for all points.
No description has been provided for this image

Homework:¶

Write a linear program to solve the coloring problem. Use networkx to visualize the graph.

In [ ]: