import numpy as np
import igraph
= igraph.Graph()
g
0, 1, 2, 3, 4])
g.add_vertices([0, 1), (0, 2), (0, 3), (1, 3), (2, 3), (2, 4), (3, 4)])
g.add_edges([(=20, vertex_label=g.vs["name"]) igraph.plot(g, vertex_size
Random Walks in Python
1 Simulating Random Walks
We will simulate random walks on a simple graph of five nodes as follows.
A random walk is characterized by the transition probabilities between nodes.
P_{ij} = \frac{A_{ij}}{k_i}
Let us first compute the transition probabilities and store them in a matrix, \mathbf{P}.
= g.get_adjacency_sparse().toarray()
A = np.array(g.degree())
k = g.vcount()
n_nodes
# A simple but inefficient way to compute P
= np.zeros((n_nodes, n_nodes))
P for i in range(n_nodes):
for j in range(n_nodes):
if k[i] > 0:
= A[i, j] / k[i]
P[i, j] else:
= 0
P[i, j]
# Alternative, more efficient way to compute P
= A / k[:, np.newaxis]
P
# or even more efficiently
= np.einsum("ij,i->ij", A, 1 / k) P
print("Transition probability matrix:\n", P)
import matplotlib.pyplot as plt
import seaborn as sns
=True, cmap="YlGnBu")
sns.heatmap(P, annot plt.show()
Each row and column of \mathbf{P} corresponds to a node, with entries representing the transition probabilities from the row node to the column node.
Now, let us simulate a random walk on this graph. We represent a position of the walker by a vector, \mathbf{x}, with five elements, each of which represents a node. We mark the node that the walker is currently at by 1
and others as 0
.
= np.array([0, 0, 0, 0, 0])
x 0] = 1
x[print("Initial position of the walker:\n", x)
This vector representation is convenient to get the probabilities of transitions to other nodes from the current node:
\mathbf{x} \mathbf{P}
which is translated into the following code:
= x @ P
probs print("Position of the walker after one step:\n", probs)
We can then draw the next node based on the probabilities
= np.random.choice(n_nodes, p=probs)
next_node = 0 # zero out the vector
x[:] = 1 # set the next node to 1
x[next_node] print("Position of the walker after one step:\n", x)
By repeating this process, we can simulate the random walk.
Exercise 01
Write the following function to simulate the random walk for a given number of steps and return the x for each step.
def random_walk(A, n_steps):
"""
Simulate the random walk on a graph with adjacency matrix A.
Args:
A (np.ndarray): The adjacency matrix of the graph.
x (np.ndarray): The initial position of the walker.
n_steps (int): The number of steps to simulate.
Returns:
np.ndarray: The position of the walker after each step.
"""
# Your code here
pass
2 Expected behavior of random walks
What is the expected position of the walker after multiple steps? It is easy to compute the expected position of the walker after one step from initial position x(0):
\mathbb{E}[x(1)] = x(0) P
where x(t) is the probability distribution of the walker at time t. In Python, the expected position of the walker at time t=1 is given by
= np.array([1, 0, 0, 0, 0])
x_0 = x_0 @ P
x_1 print("Expected position of the walker after one step:\n", x_1)
For the second step, the expected position of the walker is given by
\mathbb{E}[x(2)] = \mathbb{E}[x(1) P] = \mathbb{E}[x(0) P] P = x(0) P^2
In other words,
= x_1 @ P
x_2 print("Expected position of the walker after two steps:\n", x_2)
Following the same argument, the expected position of the walker at time t is given by
\mathbb{E}[x(t)] = x(0) P^t
Exercise 02
Write a function to compute the expected position of the walker at time t using the above formula:
def expected_position(A, x_0, t):
"""
Compute the expected position of the walker at time t.
Args:
A (np.ndarray): The adjacency matrix of the graph.
x_0 (np.ndarray): The initial position of the walker.
t (int): The number of steps to simulate.
"""
# Your code here
pass
Exercise 03
Plot each element of x(t) as a function of t for t=0,1,2,\ldots, 1000. Try different initial positions and compare the results!
Steps: 1. Define the initial position of the walker. 2. Compute the expected position of the walker at time t using the function you wrote above. 3. Draw a line for each element of x(t), totalling 5 lines. 4. Create multiple such plots for different initial positions and compare them.
3 Community structure
Random walks can capture community structure of a network. To see this, let us consider a network of a ring of cliques.
import networkx as nx
import igraph
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
= 3
n_cliques = 5
n_nodes_per_clique
= nx.ring_of_cliques(n_cliques, n_nodes_per_clique)
G = igraph.Graph().Adjacency(nx.to_numpy_array(G).tolist()).as_undirected()
g = np.repeat(np.arange(n_cliques), n_nodes_per_clique)
membership
= [sns.color_palette()[i] for i in membership]
color_map =20, vertex_color=color_map) igraph.plot(g, vertex_size
Let us compute the expected position of the walker after 1 to 10 steps.
Compute the transition matrix:
-cell]
:tags: [hidefrom scipy import sparse
# Get the adjacency matrix and degree
= g.get_adjacency_sparse()
A = np.array(g.degree())
k
# This is an efficient way to compute the transition matrix
# using scipy.sparse
= sparse.diags(1 / k) @ A P
Compute the expected position of the walker after 1 to 300 steps:
-cell]
:tags: [hide
= np.zeros(g.vcount())
x_t 2] = 1
x_t[= [x_t]
x_list for t in range(300):
= x_t @ P
x_t
x_list.append(x_t)= np.array(x_list) x_list
Plot the expected position of the walker at each step:
-input]
:tags: [hide
= sns.color_palette("viridis", as_cmap=True)
cmap
'white')
sns.set_style(set(font_scale=1.2)
sns.'ticks')
sns.set_style(
= plt.subplots(figsize=(15,10), ncols = 3, nrows = 2)
fig, axes
= [0, 1, 3, 5, 10, 299]
t_list for i, t in enumerate(t_list):
=20, vertex_color=[cmap(x_list[t][j] / np.max(x_list[t])) for j in range(g.vcount())], target = axes[i//3][i%3])
igraph.plot(g, vertex_size//3][i%3].set_title(f"$t$ = {t}", fontsize = 25) axes[i
where the color of each node represents the probability of the walker being at that node.
An important observation is that the walker spends more time in the clique that it started from and then diffuse to others. Thus, the position of the walker before reaching the steady state tells us the community structure of the network.
4 Exercise 04
Generate a network of 100 nodes with 4 communities using a stochastic block model, with inter-community edge probability 0.05 and intra-community edge probability 0.2. Then, compute the expected position of the walker starting from node zero after x steps. Plot the results for x = 0, 5, 10, 1000.
Increase the inter-community edge probability to 0.15 and repeat the simulation. Compare the results with the previous simulation.