0% found this document useful (0 votes)
8 views

Epidemics On Networks Python Package

Uploaded by

alex
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

Epidemics On Networks Python Package

Uploaded by

alex
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 28

EoN (Epidemics on Networks): a fast, flexible Python

package for simulation, analytic approximation, and


analysis of epidemics on networks
arXiv:2001.02436v2 [q-bio.QM] 18 Jan 2020

Joel C. Miller∗† Tony Ting†

Abstract
We provide a description of the Epidemics on Networks (EoN) python package
designed for studying disease spread in static networks. The package consists of over
100 methods available for users to perform stochastic simulation of a range of different
processes including SIS and SIR disease, and generic simple or comlex contagions.

This paper is available in a shorter form without examples at


https://joss.theoj.org/papers/10.21105/joss.01731.pdf

Introduction
EoN (EpidemicsOnNetworks) is a pure-python package designed to assist studies of infectious
processes spreading through networks. It originally rose out of the book Mathematics of
Epidemics on Networks [10], and now consists of over 100 user-functions.
EoN provides a set of tools for

• Susceptible-Infected-Susceptible (SIS) and Susceptible-Infected-Recovered (SIR) dis-


ease

– Stochastic simulation of disease spread in networkx graphs


∗ continuous time Markovian
∗ continuous time nonMarkovian
∗ discrete time
– Numerical solution of over 20 differential equation models, including
∗ individual and pair-based models
∗ pairwise models

La Trobe University

Institute for Disease Modeling

1
∗ edge-based compartmental models

• Stochastic simulation of a wide range of Simple and Complex contagions

• Visualization and analysis of stochastic simulations

These algorithms are built on the networkx package [7]. EoN’s documentation is main-
tained at

https://epidemicsonnetworks.readthedocs.io/en/latest/

including numerous examples at

https://epidemicsonnetworks.readthedocs.io/en/latest/Examples.html.

In this paper we provide brief descriptions with examples of a few of EoN’s tools. The exam-
ples shown are intended to demonstrate the ability of the tools. The online documentation
gives more detail about how to use them.
We model spreading processes on a contact network. In this context, many mathemati-
cians and physicists are accustomed to thinking of individuals as nodes with their potentially
infectious partnerships as edges. However, for those who come from other backgrounds this
abstraction may be less familiar.
Therefore, we will describe a contact network along which an infections process spreads
as consisting of “individuals” and “partnerships” rather than “nodes” and “edges”. This
has an additional benefit because in the simple contagion algorithm (described later), we
need to define some other networks whose nodes represent possible statuses and whose edges
represent transitions that can occur. Referring to “individuals” and “partnerships” when
discussing the process spreading on the contact network makes it easier to avoid confusion
between the different networks.
We start this paper by describing the tools for studying SIS and SIR disease through
stochastic simulation and differential equations models. Then we discuss the simple and
complex contagions, including examples showing how the simple contagion can be used to
capture a range of standard disease models. Finally we demonstrate the visualization tools.

SIR and SIS disease


Stochastic simulation
The stochastic SIR and SIS simulation tools allow the user to investigate standard SIS and
SIR dynamics (SEIR/SIRS and other processes are addressed within the simple contagion
model):

• Markovian SIS and SIR simulations (fast SIS, Gillespie SIS, fast SIR, and Gillespie SIR).

• non-Markovian SIS and SIR simulations (fast nonMarkovian SIS and fast nonMarkovian SIR).

2
• discrete time SIS and SIR simulations where infections last a single time step (basic discrete SIS,
basic discrete SIR, and discrete SIR).

For both Markovian and non-Markovian methods it is possible for the transition rates to
depend on intrinsic properties of individuals and of partnerships.
The continuous-time stochastic simulations have two different implementations: a Gille-
spie implementation [5, 4] and an Event-driven implementation. Both approaches are effi-
cient. They have similar speed if the dynamics are Markovian (depending on the network
and disease parameters either may be faster than the other), but the event-driven imple-
mentation can also handle non-Markovian dynamics. In earlier versions, the event-driven
simulations were consistently faster than the Gillespie simulations, and thus they are named
fast SIR and fast SIS. The Gillespie simulations have since reached comparable speed
using ideas from [8] and [2].
The algorithms can typically handle an SIR epidemic spreading on hundreds of thousands
of individuals in well under a minute on a laptop. The SIS versions are slower because the
number of events that happen is often much larger in an SIS simulation.

Examples
To demonstrate these, we begin with SIR simulations on an Erdős–Rényi network having a
million individuals (in an Erdős–Rényi network each individual has identical probability of
independently partnering with any other individual in the population).

import networkx as nx
import EoN
import matplotlib.pyplot as plt

N = 10**6 #number of individuals


kave = 5 #expected number of partners
print(’generating graph G with {} nodes’.format(N))
G = nx.fast_gnp_random_graph(N, kave/(N-1)) #Erdo’’s-Re’nyi graph

rho = 0.005 #initial fraction infected


tau = 0.3 #transmission rate
gamma = 1.0 #recovery rate

print(’doing event-based simulation’)


t1, S1, I1, R1 = EoN.fast_SIR(G, tau, gamma, rho=rho)
#instead of rho, we could specify a list of nodes as initial_infecteds, or
#specify neither and a single random node would be chosen as the index case.

print(’doing Gillespie simulation’)

3
t2, S2, I2, R2 = EoN.Gillespie_SIR(G, tau, gamma, rho=rho)

print(’done with simulations, now plotting’)


plt.plot(t1, I1, label = ’fast_SIR’)
plt.plot(t2, I2, label = ’Gillespie_SIR’)
plt.xlabel(’$t$’)
plt.ylabel(’Number infected’)
plt.legend()
plt.show()

This produces a (stochastic) figure like

The run-times of fast SIR and Gillespie SIR are both comparable to the time taken
to generate the million individual network G. The epidemics affect around 28 percent of the
population. The differences between the simulations are entirely due to stochasticity.
We can perform similar simulations with an SIS epidemic. Because SIS epidemics take
longer to simulate, we use a smaller network and specify the optional tmax argument defining
the maximum stop time (by default tmax=100).

import networkx as nx
import EoN
import matplotlib.pyplot as plt

4
N = 10**5 #number of individuals
kave = 5 #expected number of partners
print(’generating graph G with {} nodes’.format(N))
G = nx.fast_gnp_random_graph(N, kave/(N-1)) #Erdo’’s-Re’nyi graph

rho = 0.005 #initial fraction infected


tau = 0.3 #transmission rate
gamma = 1.0 #recovery rate
print(’doing Event-driven simulation’)
t1, S1, I1 = EoN.fast_SIS(G, tau, gamma, rho=rho, tmax = 30)
print(’doing Gillespie simulation’)
t2, S2, I2 = EoN.Gillespie_SIS(G, tau, gamma, rho=rho, tmax = 30)

print(’done with simulations, now plotting’)


plt.plot(t1, I1, label = ’fast_SIS’)
plt.plot(t2, I2, label = ’Gillespie_SIS’)
plt.xlabel(’$t$’)
plt.ylabel(’Number infected’)
plt.legend()
plt.show()

This produces a (stochastic) figure like

We now consider an SIR disease spreading with non-Markovian dynamics. We assume

5
that the infection duration is gamma distributed, but the transmission rate is constant
(yielding an exponential distribution of time to transmission).
This follows [22].

import networkx as nx
import EoN
import matplotlib.pyplot as plt
import numpy as np

def rec_time_fxn_gamma(u):
return np.random.gamma(3,0.5) #gamma distributed random number

def trans_time_fxn(u, v, tau):


if tau >0:
return np.random.exponential(1./tau)
else:
return float(’Inf’)

N = 10**6 #number of individuals


kave = 5 #expected number of partners
print(’generating graph G with {} nodes’.format(N))
G = nx.fast_gnp_random_graph(N, kave/(N-1)) #Erdo’’s-Re’nyi graph
tau = 0.3

for cntr in range(10):


print(cntr)
print(’doing Event-driven simulation’)
t, S, I, R = EoN.fast_nonMarkov_SIR(G, trans_time_fxn = trans_time_fxn,
rec_time_fxn = rec_time_fxn_gamma,
trans_time_args = (tau,))

#To reduce file size and make plotting faster, we’ll just plot 1000
#data points. It’s not really needed here, but this demonstrates
#one of the available tools in EoN.

subsampled_ts = np.linspace(t[0], t[-1], 1000)


subI, subR = EoN.subsample(subsampled_ts, t, I, R)
print(’done with simulation, now plotting’)
plt.plot(subsampled_ts, subI+subR)

plt.xlabel(’$t$’)
plt.ylabel(’Number infected or recovered’)
plt.show()

6
This produces a (stochastic) figure like

Differential Equations Models


EoN also provides a set of tools to numerically solve approximately 20 differential equations
models for SIS or SIR disease spread in networks. The various models use different infor-
mation about the network to make deterministic predictions about the number infected at
different times. These use the Scipy integration tools. The derivations of the models and
explanations of their simplifying assumptions are described in [10].
Depending on the model, we need different information about the network structure. The
algorithms allow us to provide the information as inputs. However, there is also a version
of each model which takes a network as an input instead and then measures the network
properties.

Examples
We demonstrate an SIS pairwise model and an SIR edge-based compartmental model.
Our first example uses an SIS homogeneous pairwise model (section 4.3.3 of [10]). This
model uses the average degree of the population and then attempts to track the number of
[SI] and [SS] pairs. We assume a network with an average degree of 20. The initial condition
is that a fraction ρ (rho) of the population is infected at random.

import networkx as nx
import EoN

7
import matplotlib.pyplot as plt

N=10000
gamma = 1
rho = 0.05
kave = 20
tau = 2*gamma/ kave
S0 = (1-rho)*N
I0 = rho*N
SI0 = (1-rho)*kave*rho*N
SS0 = (1-rho)*kave*(1-rho)*N
t, S, I = EoN.SIS_homogeneous_pairwise(S0, I0, SI0, SS0, kave, tau, gamma,
tmax=10)
plt.plot(t, S, label = ’S’)
plt.plot(t, I, label = ’I’)
plt.xlabel(’$t$’)
plt.ylabel(’Predicted numbers’)
plt.legend()
plt.show()

This produces

For ease of comparison with simulation and consistency with existing literature, the
output of the model should be interpreted in terms of an expected number of individuals in

8
each status, which requires that our values scale with N. So all of the initial conditions have
a factor of N. If we are interested in proportion, we could arbitrarily set N=1, and then our
solutions would give us the proportion of the population in each status.
Our second example uses an Edge-based compartmental model for an SIR disease (section
6.5.2 of [10] and also [17, 14]). This model incorporates information about the degree distri-
bution (i.e., how the number of partners is distributed), but assumes that the partnerships
are selected as randomly as possible given this distribution. The model requires we define
a “generating function” ψ(x) which is equal to the sum ∞ k
P
k=0 Sk (0)x where Sk (0) is the
proportion of all individuals in the population who both have k partners and are susceptible
at t = 0. It also requires the derivative ψ 0 (x) as well as φS (0), the probability an edge from
a susceptible node connects to another susceptible node at time 0. By default, it assumes
there are no recovered individuals at time 0.
If the population has a Poisson degree distribution with mean kave and the infection is
introduced by randomly infecting a proportion ρ of the population at time 0, then ψ(x) =
(1 − ρ)e−hki(1−x) , ψ 0 (x) = (1 − ρ)hkie−hki(1−x) and φS (0) = 1 − ρ where hki denotes kave. So
we have

import networkx as nx
import EoN
import matplotlib.pyplot as plt
import numpy as np

gamma = 1
tau = 1.5
kave = 3
rho = 0.01
phiS0 = 1-rho
def psi(x):
return (1-rho)* np.exp(-kave*(1-x))
def psiPrime(x):
return (1-rho)*kave*np.exp(-kave*(1-x))

N=1

t, S, I, R = EoN.EBCM(N, psi, psiPrime, tau, gamma, phiS0, tmax = 10)

plt.plot(t, S, label = ’S’)


plt.plot(t, I, label = ’I’)
plt.plot(t, R, label = ’R’)
plt.xlabel(’$t$’)
plt.ylabel(’Predicted proportions’)
plt.legend()
plt.show()

9
This produces

.
To be consistent with the other differential equations models, this EBCM implementation
returns the expected number in each status, rather than the expected proportion. Most of
the literature on the EBCM approach [17] focuses on expected proportion. By setting N=1,
we have found the proporitons of the population in each status.

Simple and Complex Contagions


There are other contagious processes in networks which have received attention. Many of
these fall into one of two types, “simple contagions” and “complex contagions”.
In a “simple contagion” an individual u may be induced to change status by an interaction
with its partner v. This status change occurs with the same rate regardless of the statuses
of other partners of u (although the other partners may cause u to change to another status
first). SIS and SIR diseases are special cases of simple contagions.
In a “complex contagion” however, we permit the rate at which u changes from one
status to another to depend on the statuses of others in some more complicated way. Two
infected individuals may cause a susceptible individual to become infected at some higher
rate than would result from them acting independently. This is frequently thought to model
social contagions where an individual may only believe something if multiple partners believe
it [1].
The simple and complex contagions are currently implemented only in a Gillespie setting,
and so they require Markovian assumptions. Although they are reasonably fast, it would

10
typically be feasible to make a bespoke algorithm that runs significantly faster.

Simple contagions
EoN provides a function Gillespie simple contagion which allows a user to specify the
rules governing an arbitrary simple contagion.
Examples are provided in the online documentation, including

• SEIR disease (there is an exposed state before becoming infectious)

• SIRS disease (recovered individuals eventually become susceptible again)

• SIRV disease (individuals may get vaccinated)

• Competing SIR diseases (there is cross immunity)

• Cooperative SIR diseases (infection with one disease helps spread the other)

The implementation requires the user to separate out two distinct ways that transitions
occur: those that are intrinsic to an individual’s current state and those that are induced
by a partner. To help demonstrate, consider an “SEIR” epidemic, where individuals begin
susceptible, but when they interact with infectious partners they may enter an exposed
state. They remain in that exposed state for some period of time before transitioning into
the infectious state independently of the status of any partner. They remain infectious
and eventually transition into the recovered state, again independently of the status of any
partner. Here the “E” to “I” and “I” to “R” transitions are intrinsic to the individual’s
state, while the “S” to “E” transition is induced by a partner.
To formalize this, we identify two broad types of transitions:

• Spontaneous Transitions: Sometimes individuals change status without influence


from any other individual. For example, an infected individual may recover, or an
exposed individual may move into the infectious class. These transitions between
statuses can be represented by a directed graph H where the nodes are not the original
individuals of the contact network G, but rather the potential statuses individuals can
take. The edges represent transitions that can occur, and we weight the edges by
the rate. In the SEIR case we would need the graph H to have edges ’E’→’I’ and
’I’→’R’. The edges would be weighted by the transition rates. Note H need not have
a node ’S’ because susceptible nodes do not change status on their own.

• Induced Transitions: Sometimes individuals change status due to the influence of


a single partner. For example in an SEIR model an infected individual may transmit
to a susceptible partner. So an (’I’, ’S’) pair may become (’I’, ’E’). We can
represent these transitions with a directed graph J. Here the nodes of J are pairs
(tuples) of statuses, representing potential statuses of individuals in a partnership. An

11
edge represents a possible partner-induced transition. In the SEIR case, there is only
a single such transition, represented by the edge (’I’, ’S’) → (’I’, ’E’) with a
weight representing the transmission rate. No other nodes are required in J. An edge
always represents the possibility that a node in the first state can cause the other node
to change state. So the first state in the pair remains the same. The current version
does not allow for both nodes to simultaneously change states.

Examples
We first demonstrate a stochastic simulation of a simple contagion with an SEIR example.
To demonstrate additional flexibility we allow some individuals to have a higher rate of
transitioning from ’E’ to ’I’ and some partnerships to have a higher transmission rate. This
is done by adding weights to the contact network G which scale the rates for those individuals
or partnerships. The documentation discusses other ways we can allow for heterogeneity in
transition rates.
Note that this process is guaranteed to terminate, so we can set tmax to be infinite.
Processes which may not terminate will require a finite value. The default is 100.

import EoN
import networkx as nx
from collections import defaultdict
import matplotlib.pyplot as plt
import random

N = 100000
print(’generating graph G with {} nodes’.format(N))
G = nx.fast_gnp_random_graph(N, 5./(N-1))

#We add random variation in the rate of leaving exposed class


#and in the partnership transmission rate.
#There is no variation in recovery rate.

node_attribute_dict = {node: 0.5+random.random() for node in G.nodes()}


edge_attribute_dict = {edge: 0.5+random.random() for edge in G.edges()}

nx.set_node_attributes(G, values=node_attribute_dict,
name=’expose2infect_weight’)
nx.set_edge_attributes(G, values=edge_attribute_dict,
name=’transmission_weight’)
#
#These individual and partnership attributes will be used to scale
#the transition rates. When we define \texttt{H} and \texttt{J}, we provide the name
#of these attributes.

12
#More advanced techniques to scale the transmission rates are shown in
#the online documentation

H = nx.DiGraph() #For the spontaneous transitions


H.add_node(’S’) #This line is actually unnecessary.
H.add_edge(’E’, ’I’, rate = 0.6, weight_label=’expose2infect_weight’)
H.add_edge(’I’, ’R’, rate = 0.1)

J = nx.DiGraph() #for the induced transitions


J.add_edge((’I’, ’S’), (’I’, ’E’), rate = 0.1,
weight_label=’transmission_weight’)
IC = defaultdict(lambda: ’S’)
for node in range(200):
IC[node] = ’I’

return_statuses = (’S’, ’E’, ’I’, ’R’)

print(’doing Gillespie simulation’)


t, S, E, I, R = EoN.Gillespie_simple_contagion(G, H, J, IC, return_statuses,
tmax = float(’Inf’))

print(’done with simulation, now plotting’)


plt.plot(t, S, label = ’Susceptible’)
plt.plot(t, E, label = ’Exposed’)
plt.plot(t, I, label = ’Infected’)
plt.plot(t, R, label = ’Recovered’)
plt.xlabel(’$t$’)
plt.ylabel(’Simulated numbers’)
plt.legend()
plt.show()

This produces a (stochastic) figure like

13
Interaction between diseases can lead to interesting effects [6, 3, 11, 13]. Now we show
two cooperative SIR diseases. In isolation, each of these diseases would fail to start an
epidemic. However, together they can, and sometimes they exhibit interesting osillatory
behavior. To help stimulate the oscillations, we start with an asymmetric initial condition,
though oscillations can be induced purely by stochastic effects for smaller initial conditions.
To the best of our knowledge, this oscillatory behavior has not been studied previously.

import EoN
import networkx as nx
from collections import defaultdict
import matplotlib.pyplot as plt

N = 300000
print(’generating graph G with {} nodes’.format(N))
G = nx.fast_gnp_random_graph(N, 5./(N-1))

#In the below:


#’SS’ means an individual susceptible to both diseases
#’SI’ means susceptible to disease 1 and infected with disease 2
#’RS’ means recovered from disease 1 and susceptible to disease 2.
#etc.

H = nx.DiGraph() #DiGraph showing spontaneous transitions


#(no interactions between indivdiuals required)

14
H.add_node(’SS’) #we actually don’t need to include the ’SS’ node in H.
H.add_edge(’SI’, ’SR’, rate = 1) #An individual who is susceptible to disease
#1 and infected with disease 2 will recover
#from disease 2 with rate 1.
H.add_edge(’IS’, ’RS’, rate = 1)
H.add_edge(’II’, ’IR’, rate = 0.5)
H.add_edge(’II’, ’RI’, rate = 0.5)
H.add_edge(’IR’, ’RR’, rate = 0.5)
H.add_edge(’RI’, ’RR’, rate = 0.5)

#In the below the edge ((’SI’, ’SS’), (’SI’, ’SI’)) means an
#’SI’ individual connected to an ’SS’ individual can lead to a transition in
#which the ’SS’ individual becomes ’SI’. The rate of this transition is 0.18.
#
#Note that \texttt{IR} and \texttt{RI} individuals are more infectious than other
#individuals.
#
J = nx.DiGraph() #DiGraph showing induced transitions (require interaction).
J.add_edge((’SI’, ’SS’), (’SI’, ’SI’), rate = 0.18)
J.add_edge((’SI’, ’IS’), (’SI’, ’II’), rate = 0.18)
J.add_edge((’SI’, ’RS’), (’SI’, ’RI’), rate = 0.18)
J.add_edge((’II’, ’SS’), (’II’, ’SI’), rate = 0.18)
J.add_edge((’II’, ’IS’), (’II’, ’II’), rate = 0.18)
J.add_edge((’II’, ’RS’), (’II’, ’RI’), rate = 0.18)
J.add_edge((’RI’, ’SS’), (’RI’, ’SI’), rate = 1)
J.add_edge((’RI’, ’IS’), (’RI’, ’II’), rate = 1)
J.add_edge((’RI’, ’RS’), (’RI’, ’RI’), rate = 1)
J.add_edge((’IS’, ’SS’), (’IS’, ’IS’), rate = 0.18)
J.add_edge((’IS’, ’SI’), (’IS’, ’II’), rate = 0.18)
J.add_edge((’IS’, ’SR’), (’IS’, ’IR’), rate = 0.18)
J.add_edge((’II’, ’SS’), (’II’, ’IS’), rate = 0.18)
J.add_edge((’II’, ’SI’), (’II’, ’II’), rate = 0.18)
J.add_edge((’II’, ’SR’), (’II’, ’IR’), rate = 0.18)
J.add_edge((’IR’, ’SS’), (’IR’, ’IS’), rate = 1)
J.add_edge((’IR’, ’SI’), (’IR’, ’II’), rate = 1)
J.add_edge((’IR’, ’SR’), (’IR’, ’IR’), rate = 1)

return_statuses = (’SS’, ’SI’, ’SR’, ’IS’, ’II’, ’IR’, ’RS’, ’RI’, ’RR’)

initial_size = 250
IC = defaultdict(lambda: ’SS’)

15
for individual in range(initial_size): #start with some people having both
IC[individual] = ’II’
for individual in range(initial_size, 5*initial_size): #and more with only
#the 2nd disease
IC[individual] = ’SI’

print(’doing Gillespie simulation’)


t, SS, SI, SR, IS, II, IR, RS, RI, RR = EoN.Gillespie_simple_contagion(G, H,
J, IC, return_statuses,
tmax = float(’Inf’))

plt.semilogy(t, IS+II+IR, ’-.’, label = ’Infected with disease 1’)


plt.semilogy(t, SI+II+RI, ’-.’, label = ’Infected with disease 2’)

plt.xlabel(’$t$’)
plt.ylabel(’Number infected’)
plt.legend()
plt.show()

This produces a (stochastic) figure like

16
Complex contagions
Complex contagions are implemented through Gillespie complex contagion which allows
a user to specify the rules governing a relatively arbitrary complex contagion. The one
criteria we note is that there is no memory - an individual will change from one status to
another based on the current statuses of its neighbors, and not based on previous interactions
with some neighbors who may have since changed status.
In the Gillespie implementation, we need a user-defined function which calculates the
rate at which u will change status (given knowledge about the current state of the system)
and another user-defined function which chooses the new status of u given that it is changing
status. We finally need a user-defined function that will determine which other nodes have
their rate change due to u’s transition. By knowing the rates of all nodes the Gillespie
algorithm can choose the time of the next transition and which node transitions. Then
it finds the new state, and finally it calculates the new rates for all nodes affected by the
change.
Once these functions are defined, the Gillespie algorithm is able to perform the complex
contagion simulation.

Example
Previous work [16] considered a dynamic version of the Watts Threshold Model [23] spreading
through clustered and unclustered networks. The Watts Threshold Model is like an SI
model, except that nodes have a threshold and must have more than some threshold number
of infected partners before becoming infected. The dynamic model in [16] assumed that
nodes transmit independently of one another, and a recipient accumulates transmissions
until reaching a threshold and then switches status. An individual v can only transmit once
to a partner u. Because individuals cannot transmit to the same partner more than once it
becomes nontrivial to implement this in a way consistent with the memoryless criterion.
Here we use another dynamic model that yields the same final state. Once a node has
reached its threshold number of infected partners, it transitions at rate 1 to the infected state.
The dynamics are different, but it can be proven that the final states in both models are
identical and follow deterministically from the initial condition. The following will produce
the equivalent of Fig. 2a of [16] for our new dynamic model. In that Figure, the threshold
was 2.

import networkx as nx
import EoN
import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict

def transition_rate(G, node, status, parameters):


’’’This function needs to return the rate at which \texttt{node} changes status.
For the model we are assuming, it should return 1 if \texttt{node} has at least

17
2 infected partners and 0 otherwise. The information about the threshold
is provided in the tuple \texttt{parameters}.
’’’

r = parameters[0] #the threshold

#if susceptible and at least \texttt{r} infected partners, then rate is 1

if status[node] == ’S’ and len([nbr for nbr in G.neighbors(node) if


status[nbr] == ’I’])>=r:
return 1
else:
return 0

def transition_choice(G, node, status, parameters):


’’’this function needs to return the new status of node. We assume going
in that we have already calculated it is changing status.

this function could be more elaborate if there were different


possible transitions that could happen. However, for this model,
the ’I’ nodes aren’t changing status, and the ’S’ ones are changing to
’I’. So if we’re in this function, the node must be ’S’ and becoming ’I’
’’’

return ’I’

def get_influence_set(G, node, status, parameters):


’’’this function needs to return a set containing all nodes whose rates
might change because \texttt{node} has just changed status. That is, which
nodes might \texttt{node} influence?

For our models the only nodes a node might affect are the susceptible
neighbors.
’’’

return {nbr for nbr in G.neighbors(node) if status[nbr] == ’S’}

parameters = (2,) #this is the threshold. Note the comma. It is needed


#for python to realize this is a 1-tuple, not just a
#number. \texttt{parameters} is sent as a tuple so we need
#the comma.

18
N = 600000
deg_dist = [2, 4, 6]*int(N/3)
print(’generating graph G with {} nodes’.format(N))
G = nx.configuration_model(deg_dist)

for rho in np.linspace(3./80, 7./80, 8): #8 values from 3/80 to 7/80.


print(rho)
IC = defaultdict(lambda: ’S’)
for node in G.nodes():
if np.random.random()<rho: #there are faster ways to do this random
#selection
IC[node] = ’I’

print(’doing Gillespie simulation’)


t, S, I = EoN.Gillespie_complex_contagion(G, transition_rate,
transition_choice, get_influence_set, IC,
return_statuses = (’S’, ’I’),
parameters = parameters)

print(’done with simulation, now plotting’)


plt.plot(t, I)

plt.xlabel(’$t$’)
plt.ylabel(’Number infected’)
plt.show()

This produces the (stochastic) figure

19
which shows that if the initial proportion “infected” is small enough the final size is com-
parable to the initial size. However once the initial proportion exceeds a threshold, a global
cascade occurs and infects almost every individual.
If we instead define G by

deg_dist = [(0,1), (0,2), (0,3)]*int(N/3)


print(’generating graph G with {} nodes’.format(N))
G = nx.random_clustered_graph(deg_dist)

G will be a random clustered network [12, 19], with the same degree distribution as before.
If we use a different range of values of rho, such as

for rho in np.linspace(1./80, 5./80, 8):

this will produce a figure similar to Fig. 8a of [16]. Note that the threshold initial size
required to trigger a cascade is smaller in this clustered network.

20
Visualization & Analysis
By default the simulations return numpy arrays providing the number of individuals with
each state at each time. However if we set a flag return full data=True, then the simu-
lations return a Simulation Investigation object. With the Simulation Investigation
object, there are methods which allow us to reconstruct all details of the simulation. We can
know the exact status of each individual at each time, as well as who infected whom.
There are also methods provided to produce output from the Simulation Investigation
object. These allow us to produce a snapshot of the network at a given time. By default
the visualization also includes the time series (e.g., S, I, and R) plotted beside the network
snapshot. These time series plots can be removed, or replaced by other time series, for
example we could plot multiple time series in the same axis, or time series generated by
one of the differential equations models. With appropriate additional packages needed for
matplotlib’s animation tools, the software can produce animations as well.
For SIR outbreaks, the Simulation Investigation object includes a transmission tree.
For SIS and simple contagions, it includes a directed multigraph showing the transmissions
that occurred (this may not be a tree). However for complex contagions, we cannot deter-
mine who is responsible for inducing a transition, so the implementation does not provide a
transmission tree. The transmission tree is useful for constructing synthetic phylogenies as
in [18].

21
Example - a snapshot of dynamics and a transmission tree for SIR disease.
Using the tools provided, it is possible to produce a snapshot of the spreading process at a
given time as well as an animation of the spread. We consider SIR disease spreading in the
Karate Club network [24].

import networkx as nx
import EoN
import matplotlib.pyplot as plt

G = nx.karate_club_graph()

nx_kwargs = {"with_labels":True} #optional arguments to be passed on to the


#networkx plotting command.
print(’doing Gillespie simulation’)
sim = EoN.Gillespie_SIR(G, 1, 1, return_full_data=True)
print(’done with simulation, now plotting’)

sim.display(time = 1, **nx_kwargs) #plot at time 1.


plt.show()

This produces a (stochastic) snapshot at time 1:

.
We can access the transmission tree.

T = sim.transmission_tree() #A networkx DiGraph with the transmission tree


Tpos = EoN.hierarchy_pos(T) #pos for a networkx plot

fig = plt.figure(figsize = (8,5))


ax = fig.add_subplot(111)
nx.draw(T, Tpos, ax=ax, node_size = 200, with_labels=True)
plt.show()

This plots the transmission tree:

22
.
The command hierarchy pos is based on [15].

Example - Visualizing dynamics of SIR disease with vaccination.


We finally consider an SIRV disease, that is an SIR disease with vaccination. As the disease
spreads susceptible individuals get vaccinated randomly, without regard for the status of
their neighbors.
To implement this with EoN, we must use Gillespie simple contagion.
We provide an animation showing the spread. To make it easier to visualize, we use a
lattice network.

import networkx as nx
import EoN
import matplotlib.pyplot as plt
from collections import defaultdict

print(’generating graph G’)


G = nx.grid_2d_graph(100,100) #each node is (u,v) where 0<=u,v<=99
#we’ll initially infect those near the middle
initial_infections = [(u,v) for (u,v) in G if 45<u<55 and 45<v<55]

H = nx.DiGraph() #the spontaneous transitions


H.add_edge(’Sus’, ’Vac’, rate = 0.01)
H.add_edge(’Inf’, ’Rec’, rate = 1.0)

J = nx.DiGraph() #the induced transitions


J.add_edge((’Inf’, ’Sus’), (’Inf’, ’Inf’), rate = 2.0)

23
IC = defaultdict(lambda:’Sus’) #a "dict", but by default the value is \texttt{’Sus’}.
for node in initial_infections:
IC[node] = ’Inf’

return_statuses = [’Sus’, ’Inf’, ’Rec’, ’Vac’]

color_dict = {’Sus’: ’#009a80’,’Inf’:’#ff2000’, ’Rec’:’gray’,’Vac’: ’#5AB3E6’}


pos = {node:node for node in G}
tex = False
sim_kwargs = {’color_dict’:color_dict, ’pos’:pos, ’tex’:tex}

print(’doing Gillespie simulation’)


sim = EoN.Gillespie_simple_contagion(G, H, J, IC, return_statuses, tmax=30,
return_full_data=True, sim_kwargs=sim_kwargs)

times, D = sim.summary()
#
#times is a numpy array of times. D is a dict, whose keys are the entries in
#return_statuses. The values are numpy arrays giving the number in that
#status at the corresponding time.

newD = {’Sus+Vac’:D[’Sus’]+D[’Vac’], ’Inf+Rec’ : D[’Inf’] + D[’Rec’]}


#
#newD is a new dict giving number not yet infected or the number ever infected
#Let’s add this timeseries to the simulation.
#
new_timeseries = (times, newD)
sim.add_timeseries(new_timeseries, label = ’Simulation’,
color_dict={’Sus+Vac’:’#E69A00’, ’Inf+Rec’:’#CD9AB3’})

sim.display(time=6, node_size = 4, ts_plots=[[’Inf’], [’Sus+Vac’, ’Inf+Rec’]])


plt.show()

This plots the simulation at time 6.

24
We can also animate it

ani=sim.animate(ts_plots=[[’Inf’], [’Sus+Vac’, ’Inf+Rec’]], node_size = 4)


ani.save(’SIRV_animate.mp4’, fps=5, extra_args=[’-vcodec’, ’libx264’])

This will create an mp4 file animating previous display over all calculated times. De-
pending on the computer installation, extra args will need to be modified.

Discussion
EoN provides a number of tools for studying infectious processes spreading in contact net-
works. The examples given here are intended to demonstrate the range of EoN, but they
represent only a fraction of the possibilities.
Full documentation is available at

https://epidemicsonnetworks.readthedocs.io/en/latest/.

Dependencies
scipy numpy networkx matplotlib

Related Packages
There are several alternative software packages that allow for simulation of epidemics on
networks. Here we briefly review some of these.

epydemic
Epydemic is a python package that can simulate SIS and SIR epidemics in networks. It
is also built on networkx. It can handle both discrete-time simulations or continuous-time
Markovian simulations for which it uses a Gillespie-style algorithm. It can handle more

25
processes than just SIS or SIR disease. In fact it can handle any model which can be
simulated using the EoN.simple contagion.
The documentation is available at https://pyepydemic.readthedocs.io/en/latest/.

Graph-tool
Graph-tool [20] is a python package that serves as an alternative to networkx. Many of its
underlying processes are written in C++, so it is often much faster than networkx.
Graph-tool has a number of built-in dynamic models, including the SIS, SIR, and SIRS
models. The disease models are currently available only in discrete-time versions.
The documentation for these disease models is available at

https://graph-tool.skewed.de/static/doc/dynamics.html.

EpiModel
EpiModel [9] is an R package that can handle SI, SIS, and SIR disease spread. It is possible
to extend EpiModel to other models. EpiModel is built around the StatNet package. More
details about EpiModel are available at

https://www.epimodel.org/.

Acknowledgments
The development of EoN has been supported by Global Good and by La Trobe University.
The inclusion of python code in this paper was facilitated by the package pythontex [21].

References
[1] Damon Centola, Vı́ctor M Eguı́luz, and Michael W Macy. Cascade dynamics of complex
propagation. Physica A: Statistical Mechanics and its Applications, 374(1):449–456,
2007.

[2] Wesley Cota and Silvio C Ferreira. Optimized gillespie algorithms for the simulation of
markovian epidemic processes on large and heterogeneous networks. Computer Physics
Communications, 219:303–312, 2017.

[3] Peng-Bi Cui, Francesca Colaiori, Claudio Castellano, et al. Mutually cooperative epi-
demics on power-law networks. Physical Review E, 96(2):022301, 2017.

[4] Joseph L Doob. Markoff chains–denumerable case. Transactions of the American Math-
ematical Society, 58(3):455–473, 1945.

26
[5] D. T. Gillespie. Exact stochastic simulation of coupled chemical reactions. The Journal
of Physical Chemistry, 81(25):2340–2361, 1977.
[6] Peter Grassberger, Li Chen, Fakhteh Ghanbarnejad, and Weiran Cai. Phase transitions
in cooperative coinfections: Simulation results for networks and lattices. Physical Review
E, 93(4):042316, 2016.
[7] Aric A. Hagberg, Daniel A. Schult, and Pieter J. Swart. Exploring network structure,
dynamics, and function using networkx. In Gaël Varoquaux, Travis Vaught, and Jarrod
Millman, editors, Proceedings of the 7th Python in Science Conference, pages 11–15,
2008.
[8] Petter Holme. Model versions and fast algorithms for network epidemiology. arXiv
preprint arXiv:1403.1011, 2014.
[9] S. M. Jenness, S. M. Goodreau, and M. Morris. EpiModel: An R package for math-
ematical modeling of infectious disease over networks. Journal of Statistical Software,
84(8):1–47, 2018.
[10] Istvan Z Kiss, Joel C Miller, and Péter L Simon. Mathematics of epidemics on networks:
from exact to approximate models. IAM. Springer, 2017.
[11] Quan-Hui Liu, Lin-Feng Zhong, Wei Wang, Tao Zhou, and H Eugene Stanley. Inter-
active social contagions and co-infections on complex networks. Chaos: An Interdisci-
plinary Journal of Nonlinear Science, 28(1):013120, 2018.
[12] Joel C. Miller. Percolation and epidemics in random clustered networks. Physical Review
E, 80(2):020901(R), 2009.
[13] Joel C. Miller. Cocirculation of infectious diseases on networks. Physical Review E,
87(6):060801, 2013.
[14] Joel C. Miller. Epidemics on networks with large initial conditions or changing structure.
PLoS ONE, 9(7):e101421, 2014.
[15] Joel C. Miller. Can one get hierarchical graphs from networkx with python 3? Stack
Overflow, 2015. URL:https://stackoverflow.com/revisions/29597209/14.
[16] Joel C Miller. Complex contagions and hybrid phase transitions. Journal of Complex
Networks, page cnv021, 2016.
[17] Joel C. Miller, Anja C. Slim, and Erik M. Volz. Edge-based compartmental modelling
for infectious disease spread. Journal of the Royal Society Interface, 9(70):890–906,
2012.
[18] Niema Moshiri, Manon Ragonnet-Cronin, Joel O Wertheim, and Siavash Mirarab.
Favites: simultaneous simulation of transmission networks, phylogenetic trees and se-
quences. Bioinformatics, 35(11):1852–1861, 2018.

27
[19] Mark E. J. Newman. Random graphs with clustering. Physical Review Letters,
103(5):58701, 2009.

[20] Tiago P. Peixoto. The graph-tool python library. figshare, 2014.

[21] Geoffrey M Poore. Pythontex: reproducible documents with LATEX, Python, and more.
Computational Science & Discovery, 8(1):014010, 2015.

[22] Zsolt Vizi, István Z Kiss, Joel C Miller, and Gergely Röst. A monotonic relation-
ship between the variability of the infectious period and final size in pairwise epidemic
modelling. Journal of Mathematics in Industry, 9(1):1, 2019.

[23] Duncan J. Watts. A simple model of global cascades on random networks. PNAS,
99(9):5766–5771, 2002.

[24] Wayne W Zachary. An information flow model for conflict and fission in small groups.
Journal of Anthropological Research, 33(4):452–473, 1977.

28

You might also like