Skip to main content

Implementations

Let's try implementing the SIR model in Python. Specifically, we will code

  • deterministic model with homogeneous mixing assumption
  • stochastic model with homogeneous mixing assumption

and observe that the two models provide approximately same result in average.

Notebook setup​

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import networkx as nx
from tqdm import tqdm, trange
import cmocean
%config InlineBackend.figure_format = 'retina'
plt.rcParams['text.usetex'] = False
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = 'Helvetica'
def show_fig_svg(fig):
import io
from IPython.display import SVG, display

# Save the figure into a string buffer as SVG
buf = io.StringIO()
fig.savefig(buf, format='svg')
buf.seek(0)

# Display the SVG image
display(SVG(buf.getvalue()))

Paremeters​

Here are the parameters that we use throughout the simulations.

params = {
"N": 50000, # Total population size
"n_steps": 1000, # Number of time steps
"beta": 0.2, # Infection rate
"gamma": 0.1, # Recovery rate
"n_stoc_sim": 20, # Number of stochastic simulations
}
params["I_initial"] = 20 # Initial number of infected individuals
params["R_initial"] = 0 # Initial number of recovered individuals
params["S_initial"] = params["N"] - (params["I_initial"] + params["R_initial"])
params
{'N': 50000,
 'n_steps': 1000,
 'beta': 0.2,
 'gamma': 0.1,
 'n_stoc_sim': 20,
 'I_initial': 20,
 'R_initial': 0,
 'S_initial': 49980}

Deterministic model​

def SIR_model(S_initial=1000, I_initial=1, R_initial=0, n_steps=1000, beta=0.8, gamma=0.4):
"""
Simulate SIR model.

Parameters
----------
S_initial : int
Initial number of Susceptible individuals.
I_initial : int
Initial number of Infected individuals.
R_initial : int
Initial number of Recovered individuals.
n_steps : int
Number of time steps to simulate.
beta : float
Infection rate (probability of infection on contact)
gamma : float
Recovery rate (probability of recovery per unit time)

Returns
-------
S_dict : dict
Dictionary with number of susceptible individuals at each time step.
{t: S_t, ... }
I_dict : dict
Dictionary with number of infected individuals at each time step.
{t: I_t, ... }
R_dict : dict
Dictionary with number of recovered individuals at each time step.
{t: R_t, ... }
"""

# Set S,I,R to initial values
S = S_initial
I = I_initial
R = R_initial

# N is always constant
N = S+I+R

# Initialize dictionaries to store values
# {t: S_t, ... } etc.
# t=0 is the initial state
S_dict = {0: S}
I_dict = {0: I}
R_dict = {0: R}

# Start simulation from t=1 to t=n_steps
for t in range(1, n_steps+1):

# Calculate number of individuals for each compartment at next time step
S_new = S - S*beta*(I/N)
I_new = I + S*beta*(I/N) - gamma*I
R_new = R + gamma*I

# Store values
S_dict[t] = S_new
I_dict[t] = I_new
R_dict[t] = R_new

# Update values for next time step
S = S_new
I = I_new
R = R_new

return S_dict, I_dict, R_dict
S_determ, I_determ, R_determ = SIR_model(
S_initial=params["S_initial"],
I_initial=params["I_initial"],
R_initial=params["R_initial"],
n_steps=params["n_steps"],
beta=params["beta"],
gamma=params["gamma"]
)
fig, ax = plt.subplots(1,1, figsize=(4,3))

# Color mapping
cmap = cmocean.cm.haline
colors = [cmap(0.3), cmap(0.5), cmap(0.75)]

compartments_to_plot = ["S", "I", "R"]

data_determ = {
"S": S_determ,
"I": I_determ,
"R": R_determ
}

for idx, compartment in enumerate(compartments_to_plot):
ax.plot(
data_determ[compartment].keys(),
data_determ[compartment].values(),
color=colors[idx],
label=f'{compartment}'
)
ax.set_title("SIR model (deterministic)", loc='left')
ax.set_xlabel("Timestep")
ax.set_ylabel("Number of individuals")

ax.set_xlim(0,200)

ax.legend()

fig.tight_layout()
# plt.savefig('fig/fig_name.pdf', bbox_inches='tight')
plt.close()
output plot

Stochastic model​

def SIR_model_stochastic(S_initial=1000, I_initial=1, R_initial=0, n_steps=1000, beta=0.8, gamma=0.4):
"""
Simulate SIR model.

Parameters
----------
S_initial : int
Initial number of Susceptible individuals.
I_initial : int
Initial number of Infected individuals.
R_initial : int
Initial number of Recovered individuals.
n_steps : int
Number of time steps to simulate.
beta : float
Infection rate (probability of infection on contact)
gamma : float
Recovery rate (probability of recovery per unit time)

Returns
-------
S_dict : dict
Dictionary with number of susceptible individuals at each time step.
{t: S_t, ... }
I_dict : dict
Dictionary with number of infected individuals at each time step.
{t: I_t, ... }
R_dict : dict
Dictionary with number of recovered individuals at each time step.
{t: R_t, ... }
"""

# Set S,I,R to initial values
S = S_initial
I = I_initial
R = R_initial

# N is always constant
N = S+I+R

# Initialize dictionaries to store values
# {t: S_t, ... } etc.
# t=0 is the initial state
S_dict = {0: S}
I_dict = {0: I}
R_dict = {0: R}

# Start simulation from t=1 to t=n_steps
for t in range(1, n_steps+1):

# Calculate number of individuals for each compartment at next time step
lambda_t = beta*(I/N)

S_diff = np.random.binomial(S, lambda_t)
I_diff = np.random.binomial(I, gamma)

S_new = S - S_diff
I_new = I + S_diff - I_diff
R_new = R + I_diff

# Store values
S_dict[t] = S_new
I_dict[t] = I_new
R_dict[t] = R_new

# Update values for next time step
S = S_new
I = I_new
R = R_new

return S_dict, I_dict, R_dict
# This is a single determination
S_stoc, I_stoc, R_stoc = SIR_model_stochastic(
S_initial=params["N"],
I_initial=params["I_initial"],
R_initial=params["R_initial"],
n_steps=params["n_steps"],
beta=params["beta"],
gamma=params["gamma"]
)
# We'll repeat the stochastic simulation for multiple times.

# Each determination will be added to the array
S_stoc_dict, I_stoc_dict, R_stoc_dict = [], [], []

for _ in trange(params["n_stoc_sim"]):
S_stoc, I_stoc, R_stoc = SIR_model_stochastic(
S_initial=params["S_initial"],
I_initial=params["I_initial"],
R_initial=params["R_initial"],
n_steps=params["n_steps"],
beta=params["beta"],
gamma=params["gamma"]
)

S_stoc_dict.append(S_stoc)
I_stoc_dict.append(I_stoc)
R_stoc_dict.append(R_stoc)

100%|██████████| 20/20 [00:00<00:00, 1020.72it/s]
# Reorganize data into a list of dictionaries, so that it can be converted into a dataframe.
data_stoc_sim = []
for id, _ in enumerate(S_stoc_dict):
for t, _ in S_stoc_dict[id].items():
data_stoc_sim.append(
{
'iter': id,
'time': t,
'S': S_stoc_dict[id][t],
'I': I_stoc_dict[id][t],
'R': R_stoc_dict[id][t]
}
)
df_stochastic = pd.DataFrame(data_stoc_sim)
df_stochastic
iter time S I R
0 0 0 49980 20 0
1 0 1 49979 19 2
2 0 2 49970 27 3
3 0 3 49967 28 5
4 0 4 49963 31 6
... ... ... ... ... ...
20015 19 996 9878 0 40122
20016 19 997 9878 0 40122
20017 19 998 9878 0 40122
20018 19 999 9878 0 40122
20019 19 1000 9878 0 40122

20020 rows × 5 columns

# Because we are dealing with multiple realizations, we'll calculate the interquantile range (25-percentile and 75-percentile)
def q25(x):
return x.quantile(0.25)

def q75(x):
return x.quantile(0.75)
# Groupby "time" and get the median and IQR
df_stochastic.groupby('time')['S'].agg(["median", q25, q75])
median q25 q75
time
0 49980.0 49980.00 49980.0
1 49977.0 49975.00 49978.0
2 49973.0 49970.00 49975.0
3 49968.0 49964.75 49971.0
4 49963.0 49958.75 49966.0
... ... ... ...
996 9851.0 9688.75 9998.5
997 9851.0 9688.75 9998.5
998 9851.0 9688.75 9998.5
999 9851.0 9688.75 9998.5
1000 9851.0 9688.75 9998.5

1001 rows × 3 columns

fig, ax = plt.subplots(1,1, figsize=(4,3))

compartments_to_plot = ["S", "I", "R"]

for idx, compartment in enumerate(compartments_to_plot):

quantiles = df_stochastic.groupby('time')[compartment].agg(["median", q25, q75])

# Median
ax.plot(
quantiles.index,
quantiles['median'],
color=colors[idx],
label=f'{compartment}'
)

# IQR
ax.fill_between(
quantiles.index,
quantiles['q25'],
quantiles['q75'],
color=colors[idx],
alpha=0.2,
)

ax.set_title("SIR model (stochastic)", loc='left')
ax.set_xlabel("Timestep")
ax.set_ylabel("Number of individuals")

ax.set_xlim(0,200)

ax.legend()


fig.tight_layout()
# plt.savefig('fig/fig_name.pdf', bbox_inches='tight')
plt.close()
output plot

Comparison​

fig, ax = plt.subplots(1,1, figsize=(4,3))

compartments_to_plot = ["S", "I", "R"]

# === Stochastic simulation ===
for idx, compartment in enumerate(compartments_to_plot):

quantiles = df_stochastic.groupby('time')[compartment].agg(["median", q25, q75])

# Median
ax.plot(
quantiles.index,
quantiles['median'],
color=colors[idx],
label=f'{compartment} (stochastic)'
)

# IQR
ax.fill_between(
quantiles.index,
quantiles['q25'],
quantiles['q75'],
color=colors[idx],
alpha=0.2,
)

# === Deterministic result ===
for idx, compartment in enumerate(compartments_to_plot):

ax.plot(
data_determ[compartment].keys(),
data_determ[compartment].values(),
linestyle=(0,(1,2)),
color="#2e2e2e",
)

ax.set_title("SIR model (comparison)", loc='left')
ax.set_xlabel("Timestep")
ax.set_ylabel("Number of individuals")

ax.set_xlim(0,200)


ax.legend()

fig.tight_layout()
# plt.savefig('fig/fig_name.pdf', bbox_inches='tight')
plt.close()
output plot