10-09-2026            >eR-BioStat and STAEM-3D
Modeling infectious diseases using R - the SIR model
Ziv Shkedy and Rashider Aloni based on Chapter 2-4 in the book Modeling infectious diseases parameters based on serological and social contact data (https://www.openintro.org/book/biostat/)
## load/install libraries
.libPaths(c("./Rpackages",.libPaths()))
library(knitr)
library(tidyverse)
library(deSolve)
library(minpack.lm)
library(ggpubr)
library(readxl)
library(gamlss)
library(data.table)
library(grid)
library(png)
library(nlme)
library(gridExtra)
library(mvtnorm)
library(e1071)
library(lattice)
library(ggplot2)
library(dslabs)
library(NHANES)
library(plyr)
library(dplyr)
library(nasaweather)
library(ggplot2)
library(gganimate)
library(av)
library(gifski)
library(foreach)
library("DAAG")
library(DT)
library(TeachingDemos)
library(gridExtra)
The starting point in this chapter is the SIR model in endemic equilibrium. In particular, we assume that all parameters are constant over time and the focus goes to the age of the individual as the time scale of primary interest. In Section XXX equilibrium values for the SIR-model with vital dynamics and a constant population were derived in case of homogeneous mixing. In practice however age is a predominant factor in the way an infection spreads through a population given that children have had fewer years of exposure than adults.
Extending the SIR model in terms of changes over age and time yields the following system of partial differential equations: \[ \begin{eqnarray} \frac{\partial S(a,t)}{\partial a}+\frac{\partial S(a,t)}{\partial t}&=&N \mu(a)-(\lambda(a,t)+\mu(a))S(a,t),\nonumber\\ \frac{\partial I(a,t)}{\partial a}+\frac{\partial I(a,t)}{\partial t}&=&\lambda(a,t)S(a,t)-(\nu+\mu(a))I(a,t),\nonumber\\ \frac{\partial R(a,t)}{\partial a}+\frac{\partial R(a,t)}{\partial t}&=&\nu I(a,t)-\mu(a)R(a,t), \end{eqnarray} \]
Here, \(S(a,t)\) is the number of individual in the population that are susceptible at age \(a\) and time \(t\) and \(I(a,t)\) and \(R(a.t)\) are the number of individual in the population that are infected and immune at age \(a\) and time \(t\), respectivly.
Under endemic equilibrium, \[ \frac{\partial S(a,t)}{\partial t}=\frac{\partial I(a,t)}{\partial t}=\frac{\partial R(a,t)}{\partial t}=0, \]
the SIR model is simplified
\[ \begin{eqnarray} \frac{d S(a)}{d a}&=&N \mu(a)-(\lambda(a)+\mu(a))S(a),\nonumber\\ \frac{d I(a)}{d a}&=&\lambda(a)S(a)-(\nu+\mu(a))I(a),\nonumber\\ \frac{d R(a)}{d a}&=&\nu I(a)-\mu(a)R(a), \end{eqnarray} \]
We consider a closed population, i.e., individuals do not enter or exit to/from the population, that is \(\mu(a)=0\). In this case the SIR model is given by
\[ \begin{eqnarray} \frac{d S(a)}{d a}&=&-\lambda(a)S(a),\nonumber\\ \frac{d I(a)}{d a}&=&\lambda(a)S(a)-\nu I(a),\nonumber\\ \frac{d R(a)}{d a}&=&\nu I(a). \end{eqnarray} \]
There are two parameters in the model, the force of infection \(\lambda\) and the recovery rate \(\nu\).
library(deSolve)
For a population in which the average age at infection is 5 years (i.e., the average time that is spent in the susceptible class is 5 years) we have \[ \lambda=\frac{1}{5}=0.2. \]
For a recovery rate of 10 days,
\[ \nu= \left(\frac{10}{365} \right )^{-1}=36.5 \]
In R, the object parameters is used to store the parameter values.
parameters <- c(lambda = 0.2, v=36.5)
parameters
## lambda v
## 0.2 36.5
We assume a population size of \(N=5000\) with one infected individual at age=0, \(I(0)=1\). This implies that \(S(0)=N-1=4999\) and \(R(0)=0\). The state vector is given by
state <- c(X=4999,Y=1,Z=0)
state
## X Y Z
## 4999 1 0
Note that the R objects X, Y and Z correspond to the compartments S, I and R, respectively.
The SIR model
\[ \begin{eqnarray} \frac{d S(a)}{d a}&=&-\lambda(a)S(a),\nonumber\\ \frac{d I(a)}{d a}&=&\lambda(a)S(a)-\nu I(a),\nonumber\\ \frac{d R(a)}{d a}&=&\nu I(a), \end{eqnarray} \]
can be defined in R using theSIR function in the following way
SIR<-function(t,state,parameters)
{
with(as.list(c(state, parameters)),
{
dX <- -lambda*X
dY <- lambda*X - v*Y
dZ <- v*Y
list(c(dX, dY, dZ))
})
}
The object times is the ages that we use to integrate the equation system in order to produce the output. In our example, the age ranges from 0 to 40 years old.
times<-seq(0,40,by=0.01)
times[1:20]
## [1] 0.00 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.10 0.11 0.12 0.13 0.14
## [16] 0.15 0.16 0.17 0.18 0.19
We solve the equations system and produce the model’s solution using the function >tt>ode of the R package deSolve. In the panel below, \[ S(0)=4999, I(0)=1,R(0)=0, \] and \[ S(0.05)=4949.259, I(0.05)=22.989501,R(0.05)=27.751380. \]
The R object out contain the numerical solution of the model. The objects X, Y and Z are the number of susceptible, infected and immune individuals at age a (the R object times).
require(deSolve)
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
head(out)
## time X Y Z
## 1 0.00 4999.000 1.000000 0.000000
## 2 0.01 4989.012 9.061818 1.926190
## 3 0.02 4979.044 14.641580 6.314481
## 4 0.03 4969.096 18.498345 12.405853
## 5 0.04 4959.168 21.159066 19.673391
## 6 0.05 4949.259 22.989501 27.751380
Figure 1 shows the numerical solution for the example. We notice that, the fraction infected individuals is relatively small compared to the fraction susceptible and immune. This is due to the different time spent in each compartment. The average duration in the susceptible class is 5 year and after recovery individuals gain life long immunity against reinfection.
par(mfrow=c(1,2), oma=c(0,0,3,0))
plot (times,out$X ,type="l",main="S and R", xlab="age", ylab="-",lwd=2)
lines(times,out$Z,col=3,lwd=2)
legend(20,4000,c("S","R"),lty=c(1,1),col=c(1,3))
plot (times,out$Y ,type="l",main="I", xlab="time", ylab="-",lwd=2)
mtext(outer=TRUE,side=3,"SIR model, D=10 days",cex=1.5)
Figure 1: Solution for a SIR model with lambda=0,2 and D=10 days.
In this example we change the recovery rate from 10 days to two months (60 days) so individuals stay in the infected class a longer time compare to the first example. In this case \[ \nu= \left(\frac{60}{365} \right )^{-1}=6.08 \]
60/365
## [1] 0.1643836
1/(60/365)
## [1] 6.083333
The new parameters vector is given by
parameters <- c(lambda = 0.2, v=6.083333)
parameters
## lambda v
## 0.200000 6.083333
state <- c(X=4999,Y=1,Z=0)
#state
Note that compared to an average duration of 10 days in the infected class (Example 1), we expect to observed a high proportion of infected individuals at any age which can be clearly seen in Figure 2.
require(deSolve)
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
head(out)
## time X Y Z
## 1 0.00 4999.000 1.00000 0.0000000
## 2 0.01 4989.012 10.63116 0.3568532
## 3 0.02 4979.044 19.67452 1.2815405
## 4 0.03 4969.096 28.16483 2.7393722
## 5 0.04 4959.168 36.13475 4.6977098
## 6 0.05 4949.259 43.61504 7.1258399
par(mfrow=c(1,2), oma=c(0,0,3,0))
plot (times,out$X ,type="l",main="S and R", xlab="age", ylab="-",lwd=2)
lines(times,out$Z,col=3,lwd=2)
legend(20,4000,c("S","R"),lty=c(1,1),col=c(1,3))
plot (times,out$Y ,type="l",main="Y", xlab="time", ylab="-",lwd=2)
mtext(outer=TRUE,side=3,"SIR model D=two months", cex=1.5)
Figure 2: Solutions for a SIR model D=60 days.
In this example we change the formulation of the force of infection and assume that \(\lambda(a)=\beta times I(a)\). Here \(\beta\) is the transmission rate and \(I(a)\) is the number of infected individuals at age a.
For a recovery rate of 10 days, the new parameters vector is given by
parameters <- c(beta=0.0085, v=36.5)
state <- c(X=4999,Y=1,Z=0)
parameters
## beta v
## 0.0085 36.5000
#state
times<-seq(0,10,by=0.01)
#times[1:20]
For the SIR model, we use beta Y X instead of lambdaYX
SIR<-function(t,state,parameters)
{
with(as.list(c(state, parameters)),
{
dX <- -beta*Y*X
dY <- beta*Y*X - v*Y
dZ <- v*Y
list(c(dX, dY, dZ))
})
}
The solution of the model is presnted in Figure XXX. Note that the force of infection in this model is not constant but it is proportional for the number of individuals in the infected compartment presented in Figure 3.
require(deSolve)
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
head(out)
## time X Y Z
## 1 0.00 4999.000 1.000000 0.0000000
## 2 0.01 4998.562 1.061727 0.3761535
## 3 0.02 4998.097 1.127220 0.7755187
## 4 0.03 4997.604 1.196705 1.1995104
## 5 0.04 4997.080 1.270419 1.6496279
## 6 0.05 4996.524 1.348611 2.1274607
par(mfrow=c(2,2), oma=c(0,0,3,0))
plot (times,out$X ,type="l",main="X", xlab="time", ylab="-")
plot (times,out$Y ,type="l",main="Y", xlab="time", ylab="-")
plot (times,out$Z ,type="l",main="Y", xlab="time", ylab="-")
mtext(outer=TRUE,side=3,"SIR model, lambda=beta*I",cex=1.5)
Figure 3: Solution for a SIR model with an age dependent force of infection.
We consider an open population model in which the birth rate is equal to the death rate, \(\mu(a,t)\). We assume life long immunity and that there is no access mortality related to disease. The model formulated below follows individuals over time. This implies that the time unit of interest is the calender time \(t\).
\[ \begin{eqnarray} \frac{\partial S(t)}{\partial t}&=&N \mu(t)-(\lambda(t)+\mu(t))S(t),\nonumber\\ \frac{\partial I(t)}{\partial t}&=&\lambda(t)S(t)-(\nu+\mu(t))I(t),\nonumber\\ \frac{\partial R(t)}{\partial t}&=&\nu I(t)-\mu(a)R(t), \end{eqnarray} \]
The above model yields a constant population because of equal birth and death rates: \[ \frac{\partial S(t)}{\partial t}+\frac{\partial I(t)}{\partial t}+\frac{\partial R(t)}{\partial t}= \mu (N-S(t)-I(t)-R(t))=0. \]
The equilibrium values, \(S_{\infty}, I_{\infty}, R_{\infty}\) are the number of indivuduals in each compartment when \(t \longrightarrow \infty\) and given by
See Section 4 below for a numerical example.
For a population with life expectancy of 75 year, \(\beta=0.001\) and \(v=1\), the parameter and state vectors are given by
library(deSolve)
parameters <- c(mu=1/75,beta=0.001,v=1)
print(parameters)
## mu beta v
## 0.01333333 0.00100000 1.00000000
state <- c(X=4999,Y=1,Z=0)
print(state)
## X Y Z
## 4999 1 0
times<-seq(0,400,by=0.01)
#print(times[1:20])
Since we assume that at \(t=0\) only one individual is infected, \(I(0)=1\) and the test are susceptible, we can define \(S(0)=N-1\).
p<-0.0
N<-5000
N
## [1] 5000
state <- c(X=N-1,Y=1,Z=0)
print(state)
## X Y Z
## 4999 1 0
SIR<-function(t,state,parameters)
{
with(as.list(c(state, parameters)),
{
dX <- N*mu*(1-p)-beta*Y*X - mu*X
dY <- beta*Y*X - v*Y - mu*Y
dZ <- v*Y -mu*Z+N*mu*p
list(c(dX, dY, dZ))
})
}
require(deSolve)
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
head(out)
## time X Y Z
## 1 0.00 4999.000 1.000000 0.00000000
## 2 0.01 4998.949 1.040663 0.01020166
## 3 0.02 4998.896 1.082977 0.02081645
## 4 0.03 4998.841 1.127011 0.03186124
## 5 0.04 4998.784 1.172835 0.04335397
## 6 0.05 4998.724 1.220521 0.05531262
out[40001,]
## time X Y Z
## 40001 400 1013.333 52.45686 3934.21
outp02<-out #beta=0.001#
For \(\mu=\frac{1}{75}\), \(\nu=1\), \(\beta=0.001\) and \(N=5000\), the equilibrium values, the equilibrium values are given by
outp02[40001,]
## time X Y Z
## 40001 400 1013.333 52.45686 3934.21
Figure 4 shows the fractions of susceptible and infected individuals at each time point together with how the numbers of susceptible and infected individuals in the population reach their equilibrium values. Notice that indeed the equilibrium values for S are I reached the values of 1013.333 and 52.45686, respectively (also shown in Figure 5). The damping effects in \(S(t)\) and \(I(t)\) over time have a slightly different pattern. \(S(t)\) oscillates around the equilibrium value and as time passes the magnitude of the oscillations decrease up to the point at which \(S(t)\) reaches the endemic equilibrium fraction. For \(I(t)\) the oscillations exhibit a different pattern with recurrent epidemics for which the peaks decrease over time. Note that the (time dependent) force of infection is proportional to \(I(t)\) since \(\lambda(t)=\beta \times I(t)\).
par(mfrow=c(2,1))
plot(times,outp02$X,type="l",main="S", xlab="time", ylab="Number of susceptible")
plot(times,outp02$Y,type="l",main="I", xlab="time", ylab="Number of infected")
Figure 4: Susceptible and infected individuals.
#times<-seq(0,400,by=0.01)
#require(deSolve)
#out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
#head(out)
#par(mfrow=c(2,2))
#plot (times,out$X ,type="l",main="S", xlab="time", ylab="-")
#plot (times,out$Y ,type="l",main="I", xlab="time", ylab="-")
#plot (times,out$Z ,type="l",main="R", xlab="time", ylab="-")
#plot(out$X,out$Y,type="l")
#mtext(outer=TRUE,side=3,"SIR model",cex=1.5)
#par(mfrow=c(2,1))
#plot (times,out$X ,type="l",main="S", xlab="time", ylab="-")
#plot (times,out$Y ,type="l",main="I", xlab="time", ylab="-")
#mtext(outer=TRUE,side=3,"SIR model",cex=1.5)
par(mfrow=c(1,1))
plot(out$X,out$Y,type="l",xlab="S",ylab="I")
Figure 5: Equilibrium plot.
In this example we assume that \(\beta=0.001/2=0.0005\). In R, the new parameter vector is given by
parameters <- c(mu=1/75,beta=0.001,v=1)
parameters <- c(mu=1/75,beta=0.0005,v=1)
parameters
## mu beta v
## 0.01333333 0.00050000 1.00000000
For \(\mu=\frac{1}{75}\), \(\nu=1\), \(\beta=0.0005\) and \(N=5000\), the equilibrium values, the equilibrium values are given by
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
outp04<-out #beta=0.0005#
outp04[40001,]
## time X Y Z
## 40001 400 2024.805 39.54206 2935.653
Figure 6 and 7 show the solution for a second scenario in which \(\beta = 0.0005\). We notice that in addition to the new equilibrium values, the time at which the susceptible class builds up is longer and as a result the inter-epidemic period is longer.
par(mfrow=c(1,1))
plot(times,outp02$X,type="l",main="S", xlab="time", ylab="Number of susceptible")
lines(times,outp04$X,col=2)
legend(100,4000,c("beta=0.001","beta=0.0005"),lty=c(1,1),col=c(1,2))
Figure 6: Susceptible individuals for a SIR model with beta=0.001 and beta=0.0005 (red line).
par(mfrow=c(1,1))
plot(times,outp02$Y,type="l",main="I", xlab="time", ylab="Number of infected")
lines(times,outp04$Y,col=2)
legend(100,2000,c("beta=0.001","beta=0.0005"),lty=c(1,1),col=c(1,2))
Figure 7: Infected individual for a SIR model with beta=0.001 and beta=0.0005 (red line).
plot(outp02$X,outp02$Y,type="l")
lines(outp04$X,outp04$Y,col=2)
legend(3500,2000,c("beta=0.001","beta=0.0005"),lty=c(1,1),col=c(1,2))
Figure 8: Equilibrium plot for beta=0.001 and beta=0.0005 (red line)
Since the number of new infection in the population is equal to \(\beta \ times I(t) \times S(t)\) a reduction in the population size \(N\) implies that at any time, there are less susceptible in the population and the number of new infection is reduced as well. Our straining point is a SIR model for a population with \(N=5000\).
parameters <- c(mu=1/75,beta=0.001,v=1)
print(parameters)
## mu beta v
## 0.01333333 0.00100000 1.00000000
state <- c(X=4999,Y=1,Z=0)
times<-seq(0,400,by=0.01)
p<-0.0
N<-5000
parameters <- c(mu=1/75,beta=0.001,v=1)
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
outp02<-out #N=5000#
We run the same model (with the same parameter setting) for a population with \(N=2500\).
N<-2500
parameters <- c(mu=1/75,beta=0.001,v=1)
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
outp04<-out # N=2500
Figure 9X and 10 shows that although the equilibrium values are only slightly different, it take a linger time to build up the susceptible class after an outbreak and as a result, for the scenario with smaller population, we expect to see less outbreaks over time.
par(mfrow=c(1,1))
plot(times,outp02$X,type="l",main="S", xlab="time", ylab="Number of susceptible")
lines(times,outp04$X,col=2)
legend(100,4000,c("N=5000","N=2500"),lty=c(1,1),col=c(1,2))
Figure 9: SIR model for population size 2500 (red line) and 5000.
par(mfrow=c(1,1))
plot(times,outp02$Y,type="l",main="I", xlab="time", ylab="Number of infected")
lines(times,outp04$Y,col=2)
legend(100,2000,c("N=5000","N=2500"),lty=c(1,1),col=c(1,2))
Figure 10: SIR model for population size 2500 (red line) and 5000
The equilibriums values for the two senarios are shown in Figure 11.
plot(outp02$X,outp02$Y,type="l")
lines(outp04$X,outp04$Y,col=2)
legend(1000,1500,c("N=5000","N=2500"),lty=c(1,1),col=c(1,2))
Figure 11: Equilibrium plot for population size 2500 (red line) and 5000
The basic reproductive number, \(R_{0}\), represents the number of new infections introduced by one infectious individual in a completely susceptible population. Note that in case the model is formulated in terms of the total number of individuals in each compartment (and not fractions)
\[ R_{0}=\frac{N \times \beta}{\nu+\mu}. \]
The number of individual in the susceptible compartment
\[ S(\infty)=\frac{1}{R_{0}} \times N. \]
The number of infected individuals
\[ I(\infty)=\frac{\mu}{\beta}(R_{0}-1). \]
We consider the setting of Example 2, a population of size \(N=5000\), \(\mu=1/75\), \(\beta=0.0005\) and \(\nu=1\). In this case for \(R_{0}, S_{\infty}\) and \(I_{\infty}\) we have
N=5000
mu=1/75
beta=0.0005
v=1
R0<-N*beta/(v+mu)
R0
## [1] 2.467105
Sinf<-1/R0*N
Sinf
## [1] 2026.667
Iinf<-mu/beta*(R0-1)
Iinf
## [1] 39.12281
From the model solutions we have
parameters <- c(mu=1/75,beta=0.0005,v=1)
print(parameters)
## mu beta v
## 0.01333333 0.00050000 1.00000000
state <- c(X=4999,Y=1,Z=0)
times<-seq(0,800,by=0.01)
lastt<-length(times)
p<-0.0
N<-5000
out <- as.data.frame(ode(y=state,times=times,func=SIR,parms=parameters))
out[lastt,]
## time X Y Z
## 80001 800 2026.667 39.12345 2934.21
Figure 12 shows the evolution of \(S(t)\) and \(I(t)\) over time.
par(mfrow=c(1,2))
plot(times,out$X,type="l",main="S", xlab="time", ylab="Number of susceptible")
plot(times,out$Y,type="l",main="I", xlab="time", ylab="Number of infected")
Figure 12: Susceptible an infected individuals.