Lecture 9 - (10/03/2026)
import networkx as nx
import matplotlib.pyplot as pltSimple Graph Metrics¶
Networks come in all different shapes and sizes. Some are quite simple while others are more complex. For this reason, knowing the right metrics is very important in order to better understand what is going on in the network.
Degree¶
The degree of a node is just the number of connections or edges it has. In a directed graph, we break it down into two parts: in-degree (edges coming in) and out-degree (edges going out). Usually, we look at this as a distribution, which comes from the degree sequence.
Knowing a network’s degree sequence (or a node’s degree) helps us understand how many connections each node has. In many networks—especially social ones—most nodes will have a degree around one, since they need at least one connection to be part of the network.
G = nx.path_graph(4)
G.add_edges_from([(0,3),(0,2)])
nx.draw(G, with_labels=True)
for i in range(4):
print(f"Node {i} has degree {G.degree(i)}")Node 0 has degree 3
Node 1 has degree 2
Node 2 has degree 3
Node 3 has degree 2

nx.to_pandas_adjacency(G)Density¶
Density (or clustering coefficient) is a handy metric for figuring out how clustered a network is. It’s basically the ratio of how many edges the network actually has compared to the total number of edges it could have if every node was connected to every other node.
This metric gives you a quick snapshot of how many edges are filled in the network. In big, complex networks, this number is usually pretty low. But in smaller, sparse networks, you often get a higher density because there’s a better chance that most of the possible edges are filled.
nx.density(G)0.8333333333333334# https://networkx.org/documentation/stable/reference/generated/networkx.generators.directed.gnr_graph.html#networkx.generators.directed.gnr_graph
""
H = nx.random_k_out_graph(6, 3, alpha=1)
nx.draw(H, with_labels=True)
nx.density(H)0.6nx.to_pandas_adjacency(H)Reciprocity¶
In social network analysis, one useful way to spot meaningful connections is by looking at reciprocity. This only applies to directed networks because we’re talking about ties that go both ways—edges that point in both directions between two nodes.
Why should you care about reciprocity?
Reciprocity is a big deal when it comes to understanding how people interact. Think of it like this: in a social setting, a reciprocated edge might show that one user is replying to another or returning a favour. This can hint that these users prefer to engage with each other more than with others.
With NetworkX, you can measure this easily using a built-in function that gives you a value representing the proportion of reciprocated edges to the total number of edges in the network. The higher the value, the more back-and-forth ties there are. For example, reciprocity is calculated with the following.
nx.reciprocity(H)0.1111111111111111Transitivity¶
In network analysis, a triad (basically a group of three nodes) is seen as one of the key building blocks of any network. Transitivity is like the likelihood that connected nodes are all linked together within the network.
It helps show how complete a network is. For example, social interactions like indirect reciprocity (think “the enemy of my enemy is my friend”) depend on triads—where three people or entities are involved. Similar to density, transitivity gives a good sense of how interconnected the whole network is.
nx.transitivity(G)0.75
Eigenvector centrality¶
Eigenvector centrality is another diagnostic that considers non-direct connections. If node degree measures the number of connections of a node, eigenvector centrality measures the extent to which those connected to a node are themselves highly connected nodes. Eigenvector centrality is an indicator of the influence of a node in a network. People with high eigenvector centrality in social and information networks are influential because they are connected to others who are themselves highly connected and, thus, can easily reach many other people in the networks.
D1, D2, D3 = nx.star_graph(5), nx.star_graph(5), nx.star_graph(5)
D2 = nx.relabel_nodes(D2, lambda x : x + 5)
D3 = nx.relabel_nodes(D3, lambda x : x + 10)
D = nx.compose_all([D1, D2, D3])
D.add_edges_from([(0,5),(5,10),(0,10)])
nx.draw(D, with_labels=True)
nx.eigenvector_centrality(D){0: 0.4707165996958812,
1: 0.14202248562291092,
2: 0.14202248562291092,
3: 0.14202248562291092,
4: 0.14202248562291092,
5: 0.4707165996958811,
6: 0.14202248562291092,
7: 0.14202248562291092,
8: 0.14202248562291092,
9: 0.14202248562291092,
10: 0.5213296685683192,
11: 0.15729211730526232,
12: 0.15729211730526232,
13: 0.15729211730526232,
14: 0.15729211730526232,
15: 0.15729211730526232}Assortative mixing¶
Assortativity or assortative mixing (also called homophily) is the preference of nodes with similar attributes to interact between each other. In other words, “similarity breeds connection” McPherson et al. To illustrate the tendency for assortative mixing, we first add node attributes.
G.clear()
G.add_nodes_from([1,2,3,4,5,6])G.nodes[1]["gender"] = "female"
G.nodes[2]["gender"] = "female"
G.nodes[3]["gender"] = "male"
G.nodes[4]["gender"] = "male"
G.nodes[5]["gender"] = "female"
G.nodes[6]["gender"] = "female"nodes_colors = []
for node in G.nodes:
if G.nodes[node]["gender"] == "female":
nodes_colors.append("#40E0D0")
else:
nodes_colors.append("#E6E6FA")nodes_colors['#40E0D0', '#40E0D0', '#E6E6FA', '#E6E6FA', '#40E0D0', '#40E0D0']G.add_edges_from([(1,2), (2,5), (1,6), (5,6), (1,4), (2,4), (4,3)])nx.draw(G, with_labels=True, node_color=nodes_colors, node_size=600)
Measuring assortativity¶
Let’s see if indeed similarity with respect to gender attributes breeds connections in our toy network. Networks in which similar nodes are more likely to connect than dissimilar nodes are called assortative.
nx.attribute_assortativity_coefficient(G, attribute="gender")0.29999999999999993Nodes may be linked because they have similar social attributes but also because they have similar number of links or a similar role in a network. Let’s see if nodes with similar node degree are more likely (i.e., degree assortativity) or less likely (i.e., degree disassortativity) to be linked in our example network.
nx.degree_assortativity_coefficient(G)-0.10526315789473463The Matrix Movie Network¶
Each node in the The Matrix network represents a character and each edge represents the number of times a pair of characters appeared together in a scene of the movie. Edges are undirected and weighted.
import requests
import json
import pandas as pd
url = "https://moviegalaxies.com/movies/view/543/the-matrix/download"
response = requests.get(url)
data = json.loads(response.text)
data['network'].keys()dict_keys(['edges', 'nodes', 'positions'])edges = data['network']['edges']
nodes = data['network']['nodes']
positions = data['network']['positions']matrix_graph = nx.Graph()
matrix_graph.add_nodes_from((node['name'], node) for node in nodes)
matrix_graph.add_edges_from((edge['source'], edge['target'], edge) for edge in edges)
pos = {p['name']: (p['x'], p['y']) for p in positions}matrix_graph['NEO']['MORPHEUS']{'source': 'NEO', 'target': 'MORPHEUS', 'weight': 10}plt.figure(figsize=(12,12))
node_position = nx.spring_layout(matrix_graph)
nx.draw(matrix_graph, node_position, node_color="#F4ABAA", with_labels=True)
edge_labels = nx.get_edge_attributes(matrix_graph, "weight")
nx.draw_networkx_edge_labels(matrix_graph, node_position, edge_labels=edge_labels){('LIEUTENANT',
'AGENT SMITH'): Text(-0.3726796827030869, -0.032576674956118046, '1'),
('MOUSE', 'CYPHER'): Text(-0.11488986517371191, 0.1267906141650328, '1'),
('MOUSE', 'TRINITY'): Text(-0.062399302554665415, 0.13044975219388355, '1'),
('MOUSE', 'APOC'): Text(-0.15589147792636848, 0.17555877261092367, '1'),
('MOUSE', 'NEO'): Text(-0.07779887798430918, 0.07430795538469426, '1'),
('MOUSE', 'CABLE'): Text(-0.11668260644664852, 0.22410774225183883, '3'),
('MOUSE', 'SWITCH'): Text(-0.19648339890814348, 0.1760629139453429, '1'),
('MOUSE', 'MORPHEUS'): Text(-0.055803523462361615, 0.1607714191348062, '1'),
('MOUSE', 'TANK'): Text(-0.11234200831115715, 0.15969756629271947, '4'),
('APOC', 'CYPHER'): Text(-0.12155852760955921, 0.08897600223016178, '2'),
('APOC', 'TRINITY'): Text(-0.06906645951901957, 0.09263367603151706, '2'),
('APOC', 'MORPHEUS'): Text(-0.06247381987428757, 0.12295750666069027, '1'),
('APOC', 'NEO'): Text(-0.08446872457035393, 0.03649509520127625, '1'),
('APOC', 'CABLE'): Text(-0.12335204854735649, 0.18629214837412034, '2'),
('APOC', 'TANK'): Text(-0.11901186013577147, 0.12188292865138806, '2'),
('APOC', 'SWITCH'): Text(-0.20315421075119783, 0.138249643624074, '2'),
('COPS', 'TRINITY'): Text(0.14413503151755191, 0.17988432866921378, '1'),
('COPS', 'MORPHEUS'): Text(0.15072877406900342, 0.21020749547625917, '1'),
('COPS', 'COP'): Text(0.20740737103222384, 0.3427644276173285, '1'),
('ORACLE', 'NEO'): Text(-0.20458299156216186, -0.1780310952607721, '1'),
('AGENT SMITH',
'CYPHER'): Text(-0.11958766256987219, -0.004095444457206221, '1'),
('AGENT SMITH',
'TRINITY'): Text(-0.06709331109876693, -0.00043690203350110757, '1'),
('AGENT SMITH',
'MORPHEUS'): Text(-0.060498987543700644, 0.029887063318808305, '2'),
('AGENT SMITH', 'NEO'): Text(-0.0824955802781191, -0.05657485444584509, '5'),
('AGENT SMITH',
'AGENT BROWN'): Text(-0.09010252388707118, 0.009405653288049765, '2'),
('AGENT SMITH',
'AGENT JONES'): Text(-0.07479152482404783, -0.11437636053119116, '3'),
('DOZER', 'CYPHER'): Text(-0.0690622195985432, -0.020731294166023373, '2'),
('DOZER', 'TRINITY'): Text(-0.016569813855994875, -0.017069203839276237, '1'),
('DOZER', 'WOMAN'): Text(0.041561163236585874, -0.07545506809703684, '1'),
('DOZER', 'NEO'): Text(-0.03197279233532446, -0.07320804203494746, '1'),
('DOZER', 'TANK'): Text(-0.06651433361057513, 0.012178872503538551, '1'),
('DOZER', 'MAN'): Text(0.04830513369153833, -0.03271356727705943, '1'),
('DOZER', 'MORPHEUS'): Text(-0.009976764260199777, 0.013253154776504239, '1'),
('CABLE', 'CYPHER'): Text(-0.08234909517437361, 0.13752414448812944, '1'),
('CABLE', 'TRINITY'): Text(-0.029855804459738677, 0.14118199650712426, '1'),
('CABLE', 'NEO'): Text(-0.04525833706830673, 0.08504288529205506, '1'),
('CABLE', 'SWITCH'): Text(-0.16394167047868224, 0.18679862696955774, '1'),
('CABLE', 'MORPHEUS'): Text(-0.023261822344200045, 0.17150572215391935, '1'),
('CABLE', 'TANK'): Text(-0.07980011648710039, 0.17042811816553993, '2'),
('MOJO', 'MORPHEUS'): Text(0.053942838487697764, 0.3764066481290429, '1'),
('MOJO', 'REX'): Text(0.08018554642335096, 0.8223198606336273, '1'),
('TALL EMPLOYEE',
'MORPHEUS'): Text(0.19390856290762482, 0.0816394272297849, '1'),
('TALL EMPLOYEE',
'NEO'): Text(0.17191385417409233, -0.004821650317032411, '1'),
('WOMAN', 'CYPHER'): Text(0.030067077978150225, -0.014518686452061447, '1'),
('WOMAN', 'TRINITY'): Text(0.08256047063213834, -0.01086257145600622, '2'),
('WOMAN', 'MORPHEUS'): Text(0.08915410218017072, 0.01946092495806384, '1'),
('WOMAN', 'NEO'): Text(0.0671597333720525, -0.06700026031924788, '1'),
('WOMAN', 'MAN'): Text(0.14743466888629808, -0.02650712625120344, '2'),
('CYPHER', 'TRINITY'): Text(-0.028066765017470297, 0.043866002023858264, '5'),
('CYPHER', 'NEO'): Text(-0.04346832450158178, -0.012271492068333223, '3'),
('CYPHER', 'MAN'): Text(0.03680913687517329, 0.028222240879204774, '1'),
('CYPHER', 'SWITCH'): Text(-0.1621519205555989, 0.08948270116483659, '1'),
('CYPHER', 'MORPHEUS'): Text(-0.021470432530033112, 0.07419040788753595, '2'),
('CYPHER', 'TANK'): Text(-0.07800968074429537, 0.07311232546468094, '3'),
('SERGEANT',
'AGENT JONES'): Text(0.14188156611771718, -0.3532924765845385, '1'),
('TRINITY', 'TANK'): Text(-0.025516980601569483, 0.07677152951504229, '7'),
('TRINITY', 'OLD MAN'): Text(0.1441415457915004, -0.07158048927166205, '1'),
('TRINITY', 'COP'): Text(0.08770231013962515, 0.2104047552188283, '1'),
('TRINITY',
'AGENT JONES'): Text(0.016729417173986127, -0.0664166765514278, '1'),
('TRINITY', 'GIZMO'): Text(0.11780829756481548, 0.0993239514353711, '2'),
('TRINITY', 'MAN'): Text(0.08930103214638152, 0.031879083600820035, '3'),
('TRINITY', 'NEO'): Text(0.009023985072406537, -0.008617879572887732, '10'),
('TRINITY', 'MORPHEUS'): Text(0.031020764580458327, 0.07784543144785372, '5'),
('TRINITY', 'SWITCH'): Text(-0.10965880008282292, 0.09313882949508279, '1'),
('TRINITY', 'PILOT'): Text(-0.08907756043470116, -0.08213796381294713, '1'),
('SPOON BOY',
'PRIESTESS'): Text(-0.13134438412688643, -0.34838886365175104, '1'),
('SPOON BOY', 'NEO'): Text(-0.10352577063013191, -0.24566118112186042, '1'),
('NEO', 'TANK'): Text(-0.04092016227426887, 0.02063565645192844, '5'),
('NEO', 'OLD MAN'): Text(0.12873795885805273, -0.1277187722712657, '1'),
('NEO', 'RHINEHEART'): Text(0.20398178388060728, -0.15981988990450569, '1'),
('NEO', 'AGENT JONES'): Text(0.001326790672072864, -0.12255442683478546, '1'),
('NEO', 'GIZMO'): Text(0.10240637246125484, 0.043186907219006754, '1'),
('NEO', 'MAN'): Text(0.07389663354290577, -0.0242605448216211, '1'),
('NEO', 'SWITCH'): Text(-0.12505978261973283, 0.03700006684800161, '1'),
('NEO', 'DUJOUR'): Text(-0.02068607420428037, -0.2907303692388492, '1'),
('NEO', 'ANTHONY'): Text(0.03390473078410616, -0.2808367534112411, '1'),
('NEO', 'PRIESTESS'): Text(-0.03420178879097413, -0.1674911244705496, '2'),
('NEO', 'MORPHEUS'): Text(0.015618680197197476, 0.021709141795857367, '10'),
('NEO', 'PILOT'): Text(-0.10448145958386323, -0.13827690180448649, '1'),
('AGENT BROWN',
'AGENT JONES'): Text(-0.006280727957480692, -0.056570512514157345, '3'),
('AGENT JONES',
'PILOT'): Text(-0.09677441445166146, -0.1960774486586938, '1'),
('GIZMO', 'MORPHEUS'): Text(0.12440046718595643, 0.12964682535918648, '1'),
('MAN', 'MORPHEUS'): Text(0.0958959988506235, 0.06220216508918075, '1'),
('SWITCH', 'TANK'): Text(-0.15960360094652348, 0.1223875285442948, '2'),
('DUJOUR', 'ANTHONY'): Text(0.019594018226197663, -0.5068089248957357, '2'),
('PRIESTESS',
'MORPHEUS'): Text(-0.012204136254611253, -0.08102435718399104, '1'),
('MORPHEUS', 'TANK'): Text(-0.018923334834993866, 0.10709494033773437, '5'),
('MORPHEUS', 'COP'): Text(0.09429529000839187, 0.2407264401692698, '1')}
nx.average_shortest_path_length(matrix_graph)2.0795698924731183nx.algorithms.distance_measures.diameter(matrix_graph)5Network Diagnostics¶
To study the meso-scale properties of a network, we identify network communities. Network communities are collection of nodes that are more connected to each other than to other communities compared to a null model. Many community detection methods have been developed to identify community structure in networks. To identify network communities in the The Matrix network, we will use a technique called modularity maximisation. We will maximise modularity using the Louvain community detection heuristics.
import community as community_louvain# Detect the community structure of the graph which
# maximises the modularity using the Louvain heuristices.
partition = community_louvain.best_partition(matrix_graph, resolution=1)plt.figure(figsize=(12, 12))
nx.draw(
matrix_graph,
with_labels=True,
pos=nx.spring_layout(matrix_graph), # spring_layout is the default layout
node_color=list(partition.values()),
cmap=plt.cm.coolwarm,
node_size=1000,
)
matrix_graph_degrees = nx.degree(matrix_graph)
sorted(matrix_graph_degrees, key=lambda x: x[1], reverse=True)[('NEO', 23),
('TRINITY', 18),
('MORPHEUS', 17),
('CYPHER', 12),
('TANK', 9),
('MOUSE', 8),
('APOC', 8),
('CABLE', 8),
('AGENT SMITH', 7),
('DOZER', 7),
('SWITCH', 7),
('WOMAN', 6),
('AGENT JONES', 6),
('MAN', 6),
('COPS', 3),
('GIZMO', 3),
('PRIESTESS', 3),
('PILOT', 3),
('COP', 3),
('MOJO', 2),
('TALL EMPLOYEE', 2),
('SPOON BOY', 2),
('OLD MAN', 2),
('AGENT BROWN', 2),
('DUJOUR', 2),
('ANTHONY', 2),
('LIEUTENANT', 1),
('ORACLE', 1),
('REX', 1),
('SERGEANT', 1),
('RHINEHEART', 1)]degree_values = dict(matrix_graph_degrees).values()
plt.hist(degree_values, 6)
plt.xlabel("Degree")
plt.ylabel("Number of nodes")
plt.title("Movie: The Matrix network")
matrix_weights = nx.degree(matrix_graph, weight="weight")
sorted(matrix_weights, key=lambda x: x[1], reverse=True)[('NEO', 52),
('TRINITY', 46),
('MORPHEUS', 36),
('TANK', 31),
('CYPHER', 23),
('AGENT SMITH', 15),
('MOUSE', 13),
('APOC', 13),
('CABLE', 12),
('AGENT JONES', 10),
('MAN', 9),
('SWITCH', 9),
('DOZER', 8),
('WOMAN', 8),
('AGENT BROWN', 5),
('GIZMO', 4),
('PRIESTESS', 4),
('COPS', 3),
('DUJOUR', 3),
('ANTHONY', 3),
('PILOT', 3),
('COP', 3),
('MOJO', 2),
('TALL EMPLOYEE', 2),
('SPOON BOY', 2),
('OLD MAN', 2),
('LIEUTENANT', 1),
('ORACLE', 1),
('REX', 1),
('SERGEANT', 1),
('RHINEHEART', 1)]nx.shortest_path_length(matrix_graph, "NEO"){'NEO': 0,
'MOUSE': 1,
'APOC': 1,
'ORACLE': 1,
'AGENT SMITH': 1,
'DOZER': 1,
'CABLE': 1,
'TALL EMPLOYEE': 1,
'WOMAN': 1,
'CYPHER': 1,
'TRINITY': 1,
'SPOON BOY': 1,
'TANK': 1,
'OLD MAN': 1,
'RHINEHEART': 1,
'AGENT JONES': 1,
'GIZMO': 1,
'MAN': 1,
'SWITCH': 1,
'DUJOUR': 1,
'ANTHONY': 1,
'PRIESTESS': 1,
'MORPHEUS': 1,
'PILOT': 1,
'LIEUTENANT': 2,
'AGENT BROWN': 2,
'COPS': 2,
'COP': 2,
'SERGEANT': 2,
'MOJO': 2,
'REX': 3}Matrix Graph from moviegalaxy
- McPherson, M., Smith-Lovin, L., & Cook, J. M. (2001). Birds of a Feather: Homophily in Social Networks. Annual Review of Sociology, 27(1), 415–444. 10.1146/annurev.soc.27.1.415