Understand: Do your pseudo-code and comments show evidence that you recall and understand technical concepts?
Apply: Are you able to execute code (using the supplied examples) that performs the required functionality on supplied or generated data sets?
Analyze: Are you able to pick the relevant method or library to resolve specific stated questions?
By the end of this notebook, you will be expected to:
- Create basic graphs using NetworkX;
- Use graph generators to explore classic graphs;
- Visualize graph objects; and
- Compute neighborhood information from a NetworkX graph object.
Exercise 1: Create and manipulate NetworkX graphs.
This week, the practical assessments will focus on the study of networks. In this notebook, you will start with an introduction to NetworkX.
NetworkX is a Python language software package used to create, manipulate, and study the structure, dynamics, and function of complex networks. The first version of this software package was designed and written by Aric Hagberg, Dan Schult, and Pieter Swart between 2002 and 2003.
A network or graph is a set of vertices or nodes, with relationships between nodes represented by a set of lines. These lines can include arrows to depict a directional relationship.
With NetworkX you can load and store networks in standard and nonstandard data formats, generate numerous types of random and classic networks, analyze network structure, build network models, design new network algorithms, draw networks, and much more.
To access and use the NetworkX module functionality, it first needs to be imported into your notebook.
Here are some additional links that will provide you with solid foundational knowledge of NetworkX:
In [ ]:
# Load relevant libraries.
import networkx as nx
import matplotlib.pylab as plt
%matplotlib inline
import pygraphviz as pgv
import random
from IPython.display import Image, display
With NetworkX, graph objects can be created in one of three ways:
Adding edges and nodes explicitly.
Importing data from data sources.
Graph generators.
This notebook predominantly investigates graph exploration using the first approach, with a few remarks made on the other graph creation approaches.
First, create a graph object by explicitly adding nodes to said object.
In [ ]:
# Instantiate an empty network undirected graph object, and assign to variable G.
G=nx.Graph()
In [ ]:
# Add a node (1) to G.
G.add_node(1)
In [ ]:
# Add another node ('x') to G.
G.add_node('x')
A graph is an abstract mathematical object without a specific representation in the Cartesian coordinate space. Therefore, graph visualization is somewhat arbitrary. Notebook 2 will look at algorithms that have been proposed to aid in presenting graph objects. This notebook, however, will use a function - "pydot" - defined below, and which has some appealing aesthetics.
In [ ]:
def pydot(G):
'''
A function for graph visualization using the dot framework
'''
pdot = nx.drawing.nx_pydot.to_pydot(G)
display(Image(pdot.create_png()))
You can now visualize the simple graph that you have defined and populated with node information.
In [ ]:
pydot(G)
Alternatively, you can populate node information by starting off from an edge pair or a list of edge pairs. Such a pairing may or may not include the strength, or other attributes, that describe the relationship between the pair(s). The special edge attribute "weight" should always be numerical, and holds values used by algorithms requiring weighted edges. When specifying edges as tuples, the optional third argument refers to the weight.
In [ ]:
# Add an edge between two nodes, 1 and 3.
# Note that nodes are automatically added if they do not exist.
G.add_edge(1,3)
In [ ]:
# Add edge information, and specify the value of the weight attribute.
G.add_edge(2,'x',weight=0.9)
G.add_edge(1,'x',weight=3.142)
In [ ]:
# Add edges from a list of tuples.
# In each tuple, the first 2 elements are nodes, and third element is value of the weight attribute.
edgelist=[('a','b',5.0),('b','c',3.0),('a','c',1.0),('c','d',7.3)]
G.add_weighted_edges_from(edgelist)
In [ ]:
# Visualize the graph object.
pydot(G)
In [ ]:
# Visualize the graph object, including weight information.
for u,v,d in G.edges(data=True):
d['label'] = d.get('weight','')
pydot(G)
A node can be any of the so-called hashable objects:
An object is hashable if it has a hash value that never changes during its lifetime (it needs a "_hash_()" method), and can be compared to other objects (it needs an "_eq_()" or "_cmp_()" method). Hashable objects that compare equal must have the same hash value.
Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.
While all of Python’s immutable built-in objects are hashable, no mutable containers (such as lists or dictionaries) are. Objects that are instances of user-defined classes are hashable by default; they all compare unequal, and their hash value is their id().
Examples of hashable objects in Python include strings, numbers, files, functions, etc. In the following two examples, a node that is a math function and a node that is a file object are added to the graph object.
In [ ]:
# Add a sine function, imported from the math module, as a node.
from math import sin
G.add_node(sin)
In [ ]:
# Add file handle object as node.
fh = open("../data/CallLog.csv","r") # handle to file object.
G.add_node(fh)
You can examine the nodes and edges in your graph using various commands.
In [ ]:
# List the nodes in your graph object.
list(G.nodes())
In [ ]:
# How many nodes are contained within your graph model?
G.number_of_nodes()
In [ ]:
# Alternative method for getting the number nodes.
G.order()
In [ ]:
# List the edges in the graph object.
list(G.edges())
In [ ]:
# How many edges do you have?
G.number_of_edges()
In [ ]:
# Alternative method for getting number of edges.
G.size()
In [ ]:
for (u, v, wt) in G.edges.data('weight'):
if wt != None:
print('(%s, %s, %.3f)' % (u, v, wt))
if wt is None:
print(u,v, wt)
In the dict
output, the label key was added above when you wanted to show weight attribute information when visualizing the graph. By default, it is not included when adding weight information to the edges.
Print the weight information for all of the edges in your graph object.
In [ ]:
for n1,n2,attr in G.edges(data=True):
print(n1,n2,attr)
In [ ]:
list(G.neighbors('x'))
You can also print the list of all nodes and their corresponding neighbors. The code below prints the node, and the node's neigbhors as list (that is, enclosed between two square brackets).
In [ ]:
for node in G.nodes():
print(node, list(G.neighbors(node)))
In [ ]:
# Add a set of edges from a list of tuples.
e = [(1 ,2) ,(1 ,3)]
G.add_edges_from(e)
In [ ]:
# Remove edge (1,2).
G.remove_edge(1,2)
In [ ]:
# Similarly, you can also remove a node, and all edges linked to that node will also fall away.
G.remove_node(3)
In [ ]:
# Multiple edge or node removal is also possible.
G.remove_edges_from(e)
In [ ]:
# Trying to remove a node not in the graph raises an error.
G.remove_node(3)
In [ ]:
# Close the file handle object used above.
fh.close()
# Remove the graph object from the workspace.
del G
In [ ]:
# Generate some of the small, famous graphs.
petersen=nx.petersen_graph()
tutte=nx.tutte_graph()
maze=nx.sedgewick_maze_graph()
tet=nx.tetrahedral_graph()
In [ ]:
# Plot one of the small, famous graphs.
pydot(petersen)
In [ ]:
# Generate some classic graphs.
K_5=nx.complete_graph(5)
K_3_5=nx.complete_bipartite_graph(3,5)
barbell=nx.barbell_graph(10,10)
lollipop=nx.lollipop_graph(10,20)
In [ ]:
# Plot one of the classic graphs.
pydot(barbell)
In [ ]:
# Generate some random graphs for arbitrary parameter values.
er=nx.erdos_renyi_graph(10,0.15)
ws=nx.watts_strogatz_graph(30,3,0.1)
ba=nx.barabasi_albert_graph(10,5)
red=nx.random_lobster(20,0.9,0.9)
In [ ]:
# Plot one of the random graphs.
pydot(ba)
Note:
This exercise contains five sections. It is broken up into these sections in order to make it easier to follow. Complete all five sections before saving and submitting your notebook.
Create an Erdos Renyi random graph. Your graph should have 30 nodes, where each of the edges are chosen with a probability of 0.15, using NetworkX's graph generator methods. Set the argument for the seed parameter to "
random.seed(10)
". Assign your graph to a variable "G".Hint: An Erdos Renyi random graph is generated with NetworkX using the following:
G = nx.erdos_renyi_graph(nodes, probability, seed_value)
In [ ]:
# Your answer here.
In [ ]:
# Your answer here.
In [ ]:
# Your answer here.
In [ ]:
# Your answer here.
In [ ]:
# Your answer here.
Exercise complete:
This is a good time to "Save and Checkpoint".
In [ ]: