Epidemics On Networks Python Package
Epidemics On Networks Python Package
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.
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
1
∗ edge-based compartmental models
These algorithms are built on the networkx package [7]. EoN’s documentation is main-
tained at
https://epidemicsonnetworks.readthedocs.io/en/latest/
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.
• 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
3
t2, S2, I2, R2 = EoN.Gillespie_SIR(G, tau, gamma, rho=rho)
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
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
#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.
plt.xlabel(’$t$’)
plt.ylabel(’Number infected or recovered’)
plt.show()
6
This produces a (stochastic) figure like
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
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.
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
• 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:
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))
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
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))
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’
plt.xlabel(’$t$’)
plt.ylabel(’Number infected’)
plt.legend()
plt.show()
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
17
2 infected partners and 0 otherwise. The information about the threshold
is provided in the tuple \texttt{parameters}.
’’’
return ’I’
For our models the only nodes a node might affect are the susceptible
neighbors.
’’’
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)
plt.xlabel(’$t$’)
plt.ylabel(’Number infected’)
plt.show()
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
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
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()
.
We can access the transmission tree.
22
.
The command hierarchy pos is based on [15].
import networkx as nx
import EoN
import matplotlib.pyplot as plt
from collections import defaultdict
23
IC = defaultdict(lambda:’Sus’) #a "dict", but by default the value is \texttt{’Sus’}.
for node in initial_infections:
IC[node] = ’Inf’
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.
24
We can also animate it
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.
[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