import networkx as nx
# Creating a graph of each of the basic 4 types
G1 = nx.Graph();
G2 = nx.DiGraph();
G3 = nx.MultiGraph();
G4 = nx.MultiDiGraph();
# Showing what it prints
print(G1, G2, G3, G4, sep='\n');
Graph with 0 nodes and 0 edges DiGraph with 0 nodes and 0 edges MultiGraph with 0 nodes and 0 edges MultiDiGraph with 0 nodes and 0 edges
Creation approaches¶
There are essentially 3 ways to create a graph:
- Adding edges and nodes explicitly
- Graph generators (standard algorithms to create network topologies)
- Importing data from pre-existing (usually file) sources (see reading and writing graphs)
Adding edges and nodes explicitly¶
Here is our first example of an undirected graph:
# Create an empty undirected graph
G = nx.Graph()
# Create some nodes
G.add_node(1) # create a single node
G.add_nodes_from([2,3]) # create nodes from a list
G.add_node("four") # node labels can be of different types (anything hashable)
# Create some edges
G.add_edge(1,2)
G.add_edges_from([(2,3),(3,"four"), ("four", 1), (1,3)])
G.add_edges_from([(2,5),(2,6)]) # if a node does not exist, it is created
# Show nodes and edges
print(G.nodes)
print(G.edges)
# Draw the graph (more on this later)
nx.draw(G, with_labels = True)
[1, 2, 3, 'four', 5, 6] [(1, 2), (1, 'four'), (1, 3), (2, 3), (2, 5), (2, 6), (3, 'four')]
Here an example directed graph (a feedforward loop):
# And now directed graph
G = nx.DiGraph()
G.add_edges_from([(42,21), (42,0), (21,0)])
nx.draw(G, with_labels=True)
Attributes¶
Nodes and edges can have arbitrary attributes (graphs are essentially a "dictionary of dictionaries")
G = nx.Graph() # a new empty undirected graph
# Example of node attributes
G.add_node(1, name="Pedro", number=42, color="orange") # add a node with three attributes
G.add_nodes_from([(2, {"color": "red"}), (3, {"color": "green"})]) # attributes can be added as dictionaries
print(G.nodes.data()) # nodes and their attributes
# Changing node attributes
G.add_node(1, color="blue") # adding again will change the attribute
G.nodes[1]["name"] = "Peter" # we can also use as any dictionary
print(G.nodes.data())
# Traversing nodes using node dictionary keys
for v in G.nodes: print("node", v, G.nodes[v]) # get individuallly all nodes and their attributes
# We can get just one specific node attribute
print("colors:", G.nodes.data("color")) # a list of tuples with all node values for "color"
print("colors: ", nx.get_node_attributes(G, "color")) # a dictionary of all node values for "color"
# Example of edge attributes
G.add_edge(1, 2, weight=1.5) # add an edge with one attribute
G.add_weighted_edges_from([(2, 3, 3.1), (1, 3, 1.2)]) # some attributes have specialized functions
print(G.edges.data()) # get all data from edges
print("weights:", nx.get_edge_attributes(G, "weight")) # a dictionary of all edge values for "weight"
[(1, {'name': 'Pedro', 'number': 42, 'color': 'orange'}), (2, {'color': 'red'}), (3, {'color': 'green'})] [(1, {'name': 'Peter', 'number': 42, 'color': 'blue'}), (2, {'color': 'red'}), (3, {'color': 'green'})] node 1 {'name': 'Peter', 'number': 42, 'color': 'blue'} node 2 {'color': 'red'} node 3 {'color': 'green'} colors: [(1, 'blue'), (2, 'red'), (3, 'green')] colors: {1: 'blue', 2: 'red', 3: 'green'} [(1, 2, {'weight': 1.5}), (1, 3, {'weight': 1.2}), (2, 3, {'weight': 3.1})] weights: {(1, 2): 1.5, (1, 3): 1.2, (2, 3): 3.1}
Graph generators¶
Graph generators are standard algorithms to create specific network topologies
# a clique of size 5
G = nx.complete_graph(5)
nx.draw(G, with_labels=True)
# a barbelll graph of two cliques of size 4 connected by a path of size 3
G = nx.barbell_graph(4, 3)
nx.draw(G, with_labels=True)
# You know this network, right? :)
G = nx.karate_club_graph()
nx.draw(G, with_labels=True)
# Returns a random Erdős-Rényi graph with 10 nodes and 0.3 probability of coonnection
G = nx.erdos_renyi_graph(10, 0.3)
nx.draw(G, with_labels=True)
# Returns a random Watts–Strogatz graph with 10 nodes each with 4 neighbors and 0.0 rewiring probability
G = nx.watts_strogatz_graph(15, 4, 0.0)
nx.draw(G, with_labels=True)
# Returns a random Barabasi-Albert graph with n=20 nodes and each new node connects to m=1 nodes
G = nx.barabasi_albert_graph(20,1)
nx.draw(G, with_labels=True)
Reading and Writing Graphs¶
NetworkX provides for reading and writing in multiple formats:
# You can get this file at https://github.com/gephi/gephi/wiki/Datasets
G = nx.read_gml("lesmiserables.gml")
nx.draw(G, with_labels=True)
# You can get this file at https://doi.org/10.7910/DVN/T4HBA3/BIOFH6
G = nx.read_gexf("matrix.gexf") # Co-appeareance network of "The Matrix" movie
nx.draw(G, with_labels=True, font_size=10, width=1, edge_color="gray",
labels=nx.get_node_attributes(G, "label"))
# Writing a graph in GML
G = nx.cycle_graph(6)
nx.write_gml(G, "cycle.gml")
# Reading and printing the graph to check
H = nx.read_gml("cycle.gml")
print(H, H.nodes, H.edges, sep="\n")
Graph with 6 nodes and 6 edges ['0', '1', '2', '3', '4', '5'] [('0', '1'), ('0', '5'), ('1', '2'), ('2', '3'), ('3', '4'), ('4', '5')]
Reporting graph data¶
Graphs have four "views" that report information:
- nodes
- edges
- adjacency list
- degree
G = nx.complete_graph(4)
print(G)
print(type(G))
print("nodes:", G.nodes)
print(type(G.nodes))
print("edges:", G.edges)
print(type(G.edges))
print("adj:", G.adj)
print(type(G.adj))
print("degree:", G.degree)
print(type(G.degree))
Graph with 4 nodes and 6 edges <class 'networkx.classes.graph.Graph'> nodes: [0, 1, 2, 3] <class 'networkx.classes.reportviews.NodeView'> edges: [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] <class 'networkx.classes.reportviews.EdgeView'> adj: {0: {1: {}, 2: {}, 3: {}}, 1: {0: {}, 2: {}, 3: {}}, 2: {0: {}, 1: {}, 3: {}}, 3: {0: {}, 1: {}, 2: {}}} <class 'networkx.classes.coreviews.AdjacencyView'> degree: [(0, 3), (1, 3), (2, 3), (3, 3)] <class 'networkx.classes.reportviews.DegreeView'>
We can use this to acess specific parts of the graph
G = nx.cycle_graph(5)
print("Properties of node 0:")
print(" - neighbors:", list(G.adj[0]));
print(" - degree:", G.degree[0]);
print("Edges of nodes 1 and 2:", G.edges([1,2]))
print("Degrees nodes 1 and 2:", G.degree([1,2]))
print("Neighbors of node 1", G[1]) # same as G.adj[1]
Properties of node 0: - neighbors: [1, 4] - degree: 2 Edges of nodes 1 and 2: [(1, 0), (1, 2), (2, 3)] Degrees nodes 1 and 2: [(1, 2), (2, 2)] Neighbors of node 1 {0: {}, 2: {}}
Graph Operations¶
We can use graph operations to obtain new graphs:
G1 = nx.cycle_graph(4)
G2 = nx.complement(G1)
G3 = nx.subgraph(G1, [1,2,3])
print("G1:", G1.edges)
print("G2:", G2.edges)
print("G3:", G3.edges)
G1: [(0, 1), (0, 3), (1, 2), (2, 3)] G2: [(0, 2), (1, 3)] G3: [(1, 2), (2, 3)]
Graph Analysis¶
There are many algorithms you can choose to analyse a graph (see Algorithms and also Functions).
We now show a selected short list of existing algorithms (mainly covering some of the topics discussed on the course).
Example degree metrics¶
G = nx.balanced_tree(2, 3)
nx.draw(G, with_labels=True)
print("Degrees:", [d for _,d in G.degree])
print("Degree Histogram:", nx.degree_histogram(G))
Degrees: [2, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1] Degree Histogram: [0, 8, 1, 6]
Example distance and shortest path metrics¶
G = nx.balanced_tree(3, 2)
nx.draw(G, with_labels=True)
print("Diameter:", nx.diameter(G))
print("Average Shortest Path:", nx.average_shortest_path_length(G))
print("Eccentricity:", nx.eccentricity(G))
print("Center:", nx.center(G))
print("Periphery:", nx.periphery(G))
print()
print("Shortest path between 9 and 12:", nx.shortest_path(G, 9, 12))
print("Its length:", nx.shortest_path_length(G, 9, 12))
print("Has path?", nx.has_path(G, 9, 12))
print()
print("SSSP from node 10:", nx.single_source_dijkstra(G,10))
Diameter: 4 Average Shortest Path: 2.769230769230769 Eccentricity: {0: 2, 1: 3, 2: 3, 3: 3, 4: 4, 5: 4, 6: 4, 7: 4, 8: 4, 9: 4, 10: 4, 11: 4, 12: 4} Center: [0] Periphery: [4, 5, 6, 7, 8, 9, 10, 11, 12] Shortest path between 9 and 12: [9, 2, 0, 3, 12] Its length: 4 Has path? True SSSP from node 10: ({10: 0, 3: 1, 0: 2, 11: 2, 12: 2, 1: 3, 2: 3, 4: 4, 5: 4, 6: 4, 7: 4, 8: 4, 9: 4}, {10: [10], 3: [10, 3], 0: [10, 3, 0], 11: [10, 3, 11], 12: [10, 3, 12], 1: [10, 3, 0, 1], 2: [10, 3, 0, 2], 4: [10, 3, 0, 1, 4], 5: [10, 3, 0, 1, 5], 6: [10, 3, 0, 1, 6], 7: [10, 3, 0, 2, 7], 8: [10, 3, 0, 2, 8], 9: [10, 3, 0, 2, 9]})
Example connectivity metrics¶
G = nx.barbell_graph(3,4)
nx.draw(G, with_labels=True)
print("Original Graph")
print("Nr Components:", nx.number_connected_components(G))
print("Components", list(nx.connected_components(G)))
print()
G.remove_nodes_from([3,6])
print("After removing nodes 3 and 6")
print("Nr Components:", nx.number_connected_components(G))
print("Components:", list(nx.connected_components(G)))
Original Graph Nr Components: 1 Components [{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}] After removing nodes 3 and 6 Nr Components: 3 Components: [{0, 1, 2}, {4, 5}, {8, 9, 7}]
Example centralities and clustering¶
G=nx.read_gexf("matrix.gexf")
nx.draw(G, with_labels=True, font_size=10, width=1, edge_color="gray",
labels=nx.get_node_attributes(G, "label"))
print("Clustering coefficient:", nx.clustering(G))
print("Closeness centrality:", nx.closeness_centrality(G))
print("Betweenness centrality:", nx.betweenness_centrality(G))
print("PageRank:", nx.pagerank(G))
print()
k = 5
decimal = 3
print("Top", k, "for:")
lst = [(G.nodes[k]["label"],round(v,decimal)) for (k,v) in sorted(nx.clustering(G).items(), key=lambda x : x[1], reverse=True)]
print("Clustering: ", lst[:k])
lst = [(G.nodes[k]["label"],round(v,decimal)) for (k,v) in sorted(nx.closeness_centrality(G).items(), key=lambda x : x[1], reverse=True)]
print("Closeness: ", lst[:k])
lst = [(G.nodes[k]["label"],round(v,decimal)) for (k,v) in sorted(nx.pagerank(G).items(), key=lambda x : x[1], reverse=True)]
print("Betweenness:",lst[:k])
lst = [(G.nodes[k]["label"],round(v,decimal)) for (k,v) in sorted(nx.betweenness_centrality(G).items(), key=lambda x : x[1], reverse=True)]
print("PageRank: ", lst[:k])
Clustering coefficient: {'3372518': 1.0, '3368683': 0.4, '3368424': 0.42857142857142855, '3368801': 1.0, '3369601': 0.9642857142857143, '3371227': 0.9642857142857143, '3368585': 1.0, '3372710': 1.0, '3368276': 0.6515151515151515, '3370502': 0.9047619047619048, '3368815': 1.0, '3369674': 1.0, '3368427': 0, '3368259': 1.0, '3371823': 0, '3368515': 0.3235294117647059, '3371166': 0.9642857142857143, '3368749': 0.2134387351778656, '3374399': 1.0, '3372009': 0, '3373932': 1.0, '3371904': 0.6666666666666666, '3371868': 0, '3369067': 0, '3374068': 0, '3371940': 1.0, '3371343': 1.0, '3369113': 1.0, '3370993': 0.8611111111111112, '3368301': 0.3464052287581699, '3368269': 1.0} Closeness centrality: {'3372518': 0.375, '3368683': 0.5263157894736842, '3368424': 0.5555555555555556, '3368801': 0.45454545454545453, '3369601': 0.5357142857142857, '3371227': 0.5357142857142857, '3368585': 0.45454545454545453, '3372710': 0.45454545454545453, '3368276': 0.6, '3370502': 0.5263157894736842, '3368815': 0.45454545454545453, '3369674': 0.4918032786885246, '3368427': 0.3614457831325301, '3368259': 0.5172413793103449, '3371823': 0.4225352112676056, '3368515': 0.6818181818181818, '3371166': 0.5357142857142857, '3368749': 0.7894736842105263, '3374399': 0.46875, '3372009': 0.44776119402985076, '3373932': 0.4918032786885246, '3371904': 0.4918032786885246, '3371868': 0.3, '3369067': 0.44776119402985076, '3374068': 0.3488372093023256, '3371940': 0.45454545454545453, '3371343': 0.5084745762711864, '3369113': 0.4838709677419355, '3370993': 0.5454545454545454, '3368301': 0.6976744186046512, '3368269': 0.5172413793103449} Betweenness centrality: {'3372518': 0.0, '3368683': 0.08996168582375477, '3368424': 0.11080459770114946, '3368801': 0.0, '3369601': 0.0009852216748768472, '3371227': 0.0009852216748768472, '3368585': 0.0, '3372710': 0.0, '3368276': 0.021559934318555008, '3370502': 0.0009195402298850575, '3368815': 0.0, '3369674': 0.0, '3368427': 0.0, '3368259': 0.0, '3371823': 0.06666666666666667, '3368515': 0.234176245210728, '3371166': 0.0009852216748768472, '3368749': 0.4551231527093595, '3374399': 0.0, '3372009': 0.0, '3373932': 0.0, '3371904': 0.004980842911877394, '3371868': 0.0, '3369067': 0.0, '3374068': 0.0, '3371940': 0.0, '3371343': 0.0, '3369113': 0.0, '3370993': 0.0029392446633825942, '3368301': 0.16393541324575805, '3368269': 0.0} PageRank: {'3372518': 0.020328889546499553, '3368683': 0.03878678964170653, '3368424': 0.049403905682109034, '3368801': 0.016796708551484324, '3369601': 0.033752045546087746, '3371227': 0.0314229459041882, '3368585': 0.013125955906409458, '3372710': 0.013125955906409458, '3368276': 0.05792041332486743, '3370502': 0.023137818300922236, '3368815': 0.016796708551484324, '3369674': 0.014051234529587805, '3368427': 0.0076383360095689246, '3368259': 0.025855729951174723, '3371823': 0.01771925246431629, '3368515': 0.1000955590494523, '3371166': 0.03346539863557677, '3368749': 0.14923710176401914, '3374399': 0.009482964974704869, '3372009': 0.007278143513020814, '3373932': 0.01277994070963189, '3371904': 0.016680736145136463, '3371868': 0.012368960596351009, '3369067': 0.007278143513020814, '3374068': 0.008135685412346374, '3371940': 0.010822781284203272, '3371343': 0.02458706257244466, '3369113': 0.009641591606219694, '3370993': 0.07542800732730655, '3368301': 0.11932140266496855, '3368269': 0.02353383041478075} Top 5 for: Clustering: [('AGENT BROWN', 1.0), ('ANTHONY', 1.0), ('COP', 1.0), ('COPS', 1.0), ('DUJOUR', 1.0)] Closeness: [('NEO', 0.789), ('TRINITY', 0.698), ('MORPHEUS', 0.682), ('CYPHER', 0.6), ('AGENT SMITH', 0.556)] Betweenness: [('NEO', 0.149), ('TRINITY', 0.119), ('MORPHEUS', 0.1), ('TANK', 0.075), ('CYPHER', 0.058)] PageRank: [('NEO', 0.455), ('MORPHEUS', 0.234), ('TRINITY', 0.164), ('AGENT SMITH', 0.111), ('AGENT JONES', 0.09)]
Example community detection¶
G = nx.karate_club_graph()
coms = nx.community.louvain_communities(G)
print(coms)
print("Modularity:", nx.community.modularity(G, coms))
print()
coms = nx.community.girvan_newman(G)
print(list(coms)[0]) # Communities after first division
[{0, 1, 2, 3, 7, 11, 12, 13, 17, 19, 21}, {16, 4, 5, 6, 10}, {24, 25, 28, 31}, {32, 33, 8, 9, 14, 15, 18, 20, 22, 23, 26, 27, 29, 30}] Modularity: 0.4438541256723075 ({0, 1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 19, 21}, {2, 8, 9, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33}) ({0, 1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 19, 21}, {2, 8, 9, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33})
Drawing graphs¶
Introduction¶
NetworkX provides basic functionality for visualizing graphs, but its main goal is to enable graph analysis rather than perform graph visualization.
Citing NetworkX documentation: "Proper graph visualization is hard, and we highly recommend that people visualize their graphs with tools dedicated to that task. Notable examples of dedicated and fully-featured graph visualization tools are Cytoscape, Gephi, Graphviz and, for LaTeX typesetting, PGF/TikZ. To use these and other such tools, you should export your NetworkX graph into a format that can be read by those tools."
The draw
command we have been using uses matplotlib:
G = nx.balanced_tree(4,3)
nx.draw(G, with_labels=True)
Drawing arguments¶
It accepts many possible arguments:
G = nx.balanced_tree(4,3)
nx.draw(G, with_labels=True,
node_size=200, # size of nodes
font_size=10, # text label size
node_color="red", # color of nodes
font_color="white", # color of nodes
width=2, # width of edges
pos=nx.spring_layout(G, iterations=500, k=1.5)
)
Graph layout¶
Node positioning is controled by the pos
argument, and we can use other layout algorithms other than de default spring layout:
G = nx.balanced_tree(4,3)
# Concentric shells of nodes (can you see what's happening here?)
shell1 = list(range(4**0))
shell2 = list(range(4**0, 4**0 + 4**1))
shell3 = list(range(4**0 + 4**1, 4**0 + 4**1 + 4**2))
shell4 = list(range(4**0 + 4**1 + 4**2, 4**0 + 4**1 + 4**2 + 4**3))
nx.draw(G, with_labels=True,
node_size=200, # size of nodes
font_size=10, # text label size
node_color="red", # color of nodes
font_color="white", # color of nodes
width=2, # width of edges
pos=nx.shell_layout(G,rotate=-0.15,nlist=[shell1, shell2, shell3, shell4])
)
We can even have a manual layout:
# Example adapted from networkx gallery
G = nx.Graph()
G.add_edges_from([(1, 2), (1, 3), (1, 5), (2, 3), (3, 4), (4, 5)])
# explicitly set positions
pos = {1: (0, 0), 2: (-1, 0.3), 3: (2, 0.17), 4: (4, 0.255), 5: (5, 0.03)}
options = {
"with_labels": True,
"font_size": 25,
"node_size": 1000,
"node_color": "white",
"edgecolors": "blue",
"linewidths": 3,
"width": 5,
}
nx.draw(G, pos, **options)
Coloring¶
You can use different colors for different nodes (and the same could be done for edges):
G = nx.karate_club_graph()
# Give colors using already defined communities in attribute "club"
colors = []
for _,att in G.nodes.data():
if att["club"] == "Mr. Hi": colors.append("red")
elif att["club"] == "Officer": colors.append("blue")
nx.draw(G, node_color=colors)
G = nx.read_gexf("matrix.gexf")
# Give colors using communities computed with greedy modularity optimization
coms = nx.community.greedy_modularity_communities(G)
colors = ["black"] * G.number_of_nodes()
use=["red","green","blue","orange","yellow","cyan","brown","gray"]
for i in range(len(coms)):
for v in coms[i]:
colors[list(G.nodes).index(v)] = use[i] # slow but just to demonstrate
nx.draw(G, with_labels=True, node_color=colors, font_size=10, width=1, edge_color="gray",
labels=nx.get_node_attributes(G, "label"))
G = nx.erdos_renyi_graph(50, 0.025, 20)
# Give colors for connected components with more than one element
comps = nx.connected_components(G)
colors = ["black"] * G.number_of_nodes()
use=["red","green","blue","orange","yellow","cyan","brown","gray"]
cur_color = 0
for cc in comps:
if len(cc) > 1:
for v in cc: colors[v] = use[cur_color]
cur_color += 1
nx.draw(G, node_size=30, node_color=colors)
Gallery of graph drawings¶
The documentation includes an extensive gallery of example drawings with downloadble code. \
Other Features¶
Here we have just given a brief overview of NetworkX.
Here are some areas not explored here that could be of interest to you (among others):
Efficiency:
The documention of algorithms will often include the ***complexity** of the associated function and an associated paper. \
Sometimes there might be different versions of the same function. See for example: fast_gnp_random_graph vs gnp_random_graphThere are approximation versions of some algorithms that trade accuracy for better execution times.
See the Approximations and Heuristics section of the documentation.There are parallel and GPU backends for NetworkX that might provide faster alternatives for part of the library.
See the Backends section of the documentation.
LaTeX integration:
- If you writing a paper in LaTeX you can export a NetworkX graph drawing using TikZ in such a way that you can then include the drawing with very high quality and resolution.
See the LaTeX section in the documentation.
- If you writing a paper in LaTeX you can export a NetworkX graph drawing using TikZ in such a way that you can then include the drawing with very high quality and resolution.
Linear Algebra:
- There are functions related to a linear algebra interpretation of graphs that allow you to approach the networks from a spectral graph theory perspective. See the Linear Algebra section in the documentation.
Algorithms:
- The algorithms section is vast and includes many areas that were not discussed in this notebook, such as graph isomorphism, graph distance and graph minors; flows, matchings and cuts; link prediction; cliques and cycles; tree and DAGs related computations such as LCA and arborescence, etc.
See the Algorithms section in te documentation.
- The algorithms section is vast and includes many areas that were not discussed in this notebook, such as graph isomorphism, graph distance and graph minors; flows, matchings and cuts; link prediction; cliques and cycles; tree and DAGs related computations such as LCA and arborescence, etc.
Contributing to NetworkX:
- You can contribute to NetworkX as a developer and help implement new functionality or improve the existing code. See the Developer section.