import igraph
# Create the famous Zachary's karate club network
= igraph.Graph.Famous('Zachary')
g
# Visualize the network
=(300, 200), vertex_size=20, vertex_label=list(range(g.vcount()))) igraph.plot(g, bbox
Coding - Network Robustness Analysis
1 Key robustness concepts in igraph
Network robustness analysis focuses on understanding how networks behave when nodes or edges are removed, whether through random failures or targeted attacks. The Python ecosystem provides powerful tools for analyzing these phenomena:
- igraph - comprehensive network analysis with robust algorithms for connectivity analysis
- scipy.sparse.csgraph - efficient connected component algorithms for large networks
- networkx - alternative approach with different robustness metrics
Throughout this analysis, we’ll use igraph
for its reliable implementations and performance advantages when working with connectivity measures and component analysis.
Installing igraph
# Using pip (with plotting support)
pip install igraph cairocffi
# Using conda (recommended)
conda install -c conda-forge igraph cairocffi
# Alternative plotting backend
pip install igraph pycairo
Note: igraph requires compiled C libraries and plotting needs cairocffi
or pycairo
. Use conda for easier installation.
For advanced users comfortable with scipy
, the csgraph
submodule provides an excellent alternative that leverages one of Python’s most well-tested and optimized libraries. For example, csgraph.shortest_path
and csgraph.connected_components
offer high-performance implementations.
Create a robust test network
Let’s start by creating a network using the famous Zachary’s karate club, which provides an excellent testbed for robustness analysis:
About Zachary’s Karate Club
Zachary’s karate club is a famous network of 34 members of a karate club documenting friendships between members. The network is undirected and originally unweighted, making it an excellent testbed for robustness analysis.
Connected Components
Before analyzing robustness, let’s understand how to measure network connectivity. In network analysis, we often need to identify connected components:
= g.connected_components()
components print("Number of components:", len(components))
print("Component sizes:", list(components.sizes()))
print("Largest component size:", components.giant().vcount())
Number of components: 1
Component sizes: [34]
Largest component size: 34
The connectivity of a network is typically measured as the fraction of nodes in the largest connected component:
import numpy as np
def network_connectivity(graph, original_size=None):
"""Calculate network connectivity as fraction of nodes in largest component"""
if original_size is None:
= graph.vcount()
original_size
if graph.vcount() == 0:
return 0.0
= graph.connected_components()
components return max(components.sizes()) / original_size
# Test the function
= network_connectivity(g)
connectivity print(f"Current connectivity: {connectivity:.3f}")
Current connectivity: 1.000
2 Network Robustness Analysis
Now let’s explore how networks behave when nodes fail or are attacked systematically.
# If you are using Google Colab, uncomment the following line to install igraph
# !sudo apt install libcairo2-dev pkg-config python3-dev
# !pip install pycairo cairocffi
# !pip install igraph
Random Attack Simulation
Random attacks simulate random failures where nodes are removed with equal probability. Let’s implement a systematic approach:
import pandas as pd
def simulate_random_attack(graph):
"""Simulate random node removal and measure connectivity"""
= graph.copy()
g_test = g_test.vcount()
original_size = []
results
for i in range(original_size - 1): # Remove all but one node
# Randomly select and remove a node
= np.random.choice(g_test.vs.indices)
node_idx
g_test.delete_vertices(node_idx)
# Measure connectivity
= network_connectivity(g_test, original_size)
connectivity
# Store results
results.append({"connectivity": connectivity,
"frac_nodes_removed": (i + 1) / original_size,
})
return pd.DataFrame(results)
# Run the simulation
= simulate_random_attack(g) df_random
Let’s visualize the robustness profile:
import matplotlib.pyplot as plt
import seaborn as sns
set(style='white', font_scale=1.2)
sns.'ticks')
sns.set_style(
= plt.subplots(figsize=(6, 5))
fig, ax "frac_nodes_removed"],
ax.plot(df_random["connectivity"],
df_random['o-', linewidth=2, markersize=4, label="Random attack")
"Proportion of nodes removed")
ax.set_xlabel("Connectivity")
ax.set_ylabel(0, 1)
ax.set_xlim(0, 1)
ax.set_ylim(
sns.despine()
plt.tight_layout() plt.show()
Targeted Attack Simulation
Targeted attacks remove nodes based on specific criteria, such as degree centrality. High-degree nodes (hubs) often play crucial roles in network connectivity:
def simulate_targeted_attack(graph, criterion="degree"):
"""Simulate targeted node removal based on specified criterion"""
= graph.copy()
g_test = g_test.vcount()
original_size = []
results
for i in range(original_size - 1):
# Remove node with highest degree
if criterion == "degree":
= g_test.degree()
degrees = g_test.vs.indices[np.argmax(degrees)]
node_idx
g_test.delete_vertices(node_idx)
# Measure connectivity
= network_connectivity(g_test, original_size)
connectivity
# Store results
results.append({"connectivity": connectivity,
"frac_nodes_removed": (i + 1) / original_size,
})
return pd.DataFrame(results)
# Run targeted attack simulation
= simulate_targeted_attack(g) df_targeted
Now let’s compare both attack strategies:
= plt.subplots(figsize=(7, 5))
fig, ax
"frac_nodes_removed"],
ax.plot(df_random["connectivity"],
df_random['o-', linewidth=2, markersize=4, label="Random attack", alpha=0.8)
"frac_nodes_removed"],
ax.plot(df_targeted["connectivity"],
df_targeted['s-', linewidth=2, markersize=4, label="Targeted attack", alpha=0.8)
"Proportion of nodes removed")
ax.set_xlabel("Connectivity")
ax.set_ylabel(0, 1)
ax.set_xlim(0, 1)
ax.set_ylim(=False)
ax.legend(frameon
sns.despine()
plt.tight_layout() plt.show()
The comparison reveals a key insight: while networks are often robust against random failures, they can be vulnerable to targeted attacks on high-degree nodes.
Exercise 01 🏋️♀️💪🧠
Examine the degree distribution of the Zachary network and identify the nodes with highest degrees. 📊🔍
Create a visualization showing which nodes are removed first in the targeted attack. 🎯📈
Try implementing a targeted attack based on betweenness centrality instead of degree. How does it compare? 🌐⚡
3 Minimum Spanning Tree Analysis
Understanding network structure through minimum spanning trees can provide insights into network robustness. Let’s explore this concept:
import random
# Create a weighted version of our network for MST analysis
= g.copy()
g_weighted "weight"] = [random.randint(1, 10) for _ in g_weighted.es]
g_weighted.es[
# Visualize weighted network
=(300, 200),
igraph.plot(g_weighted, bbox=[w/3 for w in g_weighted.es["weight"]],
edge_width=list(range(g_weighted.vcount()))) vertex_label
Computing the Minimum Spanning Tree
The minimum spanning tree represents the most efficient way to connect all nodes:
# Find minimum spanning tree
= g_weighted.spanning_tree(weights=g_weighted.es["weight"])
mst
# Visualize the MST
=(300, 200),
igraph.plot(mst, bbox=[w/3 for w in mst.es["weight"]],
edge_width=list(range(mst.vcount()))) vertex_label
The MST identifies the most critical connections for maintaining network connectivity, which relates directly to robustness analysis.
4 Percolation Theory
Network robustness can be viewed as the inverse process of percolation. In percolation theory, we study how connectivity emerges as we randomly add nodes to a network.
def percolation_simulation(lattice_size=100, p_values=None):
"""Simulate percolation on a 2D lattice"""
if p_values is None:
= np.linspace(0, 1, 20)
p_values
# Create 2D lattice
= igraph.Graph.Lattice([lattice_size, lattice_size],
g_lattice =1, directed=False,
nei=False, circular=False)
mutual
= []
results for p in p_values:
# Randomly keep nodes with probability p
= np.where(np.random.rand(g_lattice.vcount()) < p)[0]
keep_nodes
if len(keep_nodes) > 0:
= g_lattice.subgraph(keep_nodes)
g_sub = network_connectivity(g_sub, g_lattice.vcount())
largest_size else:
= 0
largest_size
"p": p, "largest_component_fraction": largest_size})
results.append({
return pd.DataFrame(results)
# Run percolation simulation
= percolation_simulation(lattice_size=50) df_percolation
= plt.subplots(figsize=(6, 5))
fig, ax
"p"],
ax.plot(df_percolation["largest_component_fraction"],
df_percolation['o-', linewidth=2, markersize=4)
# Mark theoretical critical point for 2D lattice
= 0.593 # Theoretical value for 2D square lattice
critical_p =critical_p, color='red', linestyle='--', alpha=0.7,
ax.axvline(x=f'Critical point (p_c ≈ {critical_p})')
label
"Probability (p)")
ax.set_xlabel("Fractional largest component size")
ax.set_ylabel("Percolation on 2D Lattice")
ax.set_title(=False)
ax.legend(frameon
sns.despine()
plt.tight_layout() plt.show()
Phase Transition
The sharp transition around p_c demonstrates a phase transition - a sudden change from a disconnected to connected state as we cross the critical threshold.
Want to see this in action? 🌟 Check out this interactive simulation.
Play around with it and watch how the puddles grow and connect. 🌊
[Bernoulli Percolation Simulation 🌐](https://visualize-it.github.io/bernoulli_percolation/simulation.html) 🔗
5 Exercise 02 🏋️♀️
Let’s explore robustness in real-world networks by analyzing data from Netzschleuder.
- Select a network with fewer than 5000 nodes from Netzschleuder
- Download the CSV version and load it using
pandas
- Create the network using
igraph
- Compare random vs. targeted attack robustness profiles
- Calculate theoretical predictions using the Molloy-Reed criterion
Hint: For large networks, consider sampling node pairs to estimate average path length rather than computing all pairwise distances.
# Your implementation here
6 Real-World Case Study: Airport Network
Let’s analyze a more complex real-world network to see robustness principles in action:
# Load airport network data
= pd.read_csv("https://raw.githubusercontent.com/skojaku/core-periphery-detection/master/data/edge-table-airport.csv")
df_airports
# Process edge data
= df_airports[["source", "target"]].to_numpy()
edges = np.unique(edges.reshape(-1), return_inverse=True)[1]
edges = edges.reshape(-1, 2)
edges
# Create network
= igraph.Graph()
g_airports max(edges) + 1)
g_airports.add_vertices(np.tuple(edge) for edge in edges])
g_airports.add_edges([
print(f"Airport network: {g_airports.vcount()} nodes, {g_airports.ecount()} edges")
Airport network: 2905 nodes, 30442 edges
Theoretical Prediction using Molloy-Reed Criterion
The Molloy-Reed criterion provides a theoretical framework for predicting network robustness:
# Calculate degree statistics
= np.array(g_airports.degree())
degrees = np.mean(degrees)
k_mean = np.mean(degrees**2)
k_squared_mean
# Molloy-Reed criterion: kappa_0 > 2 for giant component
= k_squared_mean / k_mean
kappa_0 print(f"κ₀ = <k²>/<k> = {kappa_0:.3f}")
# Critical fraction for network breakdown
= 1 - 1 / (kappa_0 - 1)
f_c print(f"Predicted critical fraction f_c = {f_c:.3f}")
κ₀ = <k²>/<k> = 110.937
Predicted critical fraction f_c = 0.991
The high f_c value indicates the airport network is extremely robust to random failures, maintaining connectivity until almost all nodes are removed.
# Simulate and visualize (using subset for efficiency)
= min(500, g_airports.vcount() - 1) # Sample for computational efficiency
n_samples = np.linspace(0, g_airports.vcount() - 2, n_samples, dtype=int)
sample_indices
= simulate_random_attack(g_airports)
df_airport_robustness = df_airport_robustness.iloc[sample_indices]
df_airport_sample
= plt.subplots(figsize=(6, 5))
fig, ax "frac_nodes_removed"],
ax.plot(df_airport_sample["connectivity"],
df_airport_sample['o-', linewidth=2, markersize=3, alpha=0.8)
# Add theoretical prediction
=f_c, color='red', linestyle='--', alpha=0.7,
ax.axvline(x=f'Theoretical f_c = {f_c:.3f}')
label
# Add diagonal reference line
0, 1], [1, 0], 'gray', linestyle=':', alpha=0.5, label='Linear decline')
ax.plot([
"Proportion of nodes removed")
ax.set_xlabel("Connectivity")
ax.set_ylabel("Airport Network Robustness")
ax.set_title(=False)
ax.legend(frameon
sns.despine()
plt.tight_layout() plt.show()
Key Design Principles for Robust Networks
Based on percolation theory and empirical analysis, robust networks share several characteristics:
- Degree heterogeneity: Networks with diverse degree distributions (high κ₀) maintain connectivity better
- Distributed hubs: Resilience improves when connectivity doesn’t depend on single critical nodes
- Redundant pathways: Multiple paths between node pairs provide backup routes when primary connections fail
Understanding these principles helps in designing resilient infrastructure networks, from transportation systems to communication networks.