An example of a simple SIR model in R

Figure 1

Figure 2

# load libraries
library(deSolve)
library(ggplot2)
library(reshape2)
# initial population values
initial_number_susceptible = 10e6 - 1
initial_number_recovered = 0
initial_number_infected = 1
initial_state_values <- c(
  S = initial_number_susceptible,
  I = initial_number_infected,
  R = initial_number_recovered
)
# model parameters
infection_rate = 1
recovery_rate = 0.1
parameters <- c(
  beta = infection_rate,
  gamma = recovery_rate
)
# time steps
times = seq(
  from = 0, 
  to = 60, 
  by = 1
)
# SIR MODEL FUNCTION 
sir_model <- function(
  time,
  state, 
  parameters
)
{
  with(as.list(c(state, parameters)), {
    # Differential equations
    N <- S+I+R
    lambda <- beta*(I/N)
    dS <- -lambda*S
    dI <- lambda*S - gamma*I
    dR <- gamma*I
    return(list(c(dS, dI, dR))) # return output
  })
}
# run the model
output <- as.data.frame(ode(y = initial_state_values,
                            times = times,
                            func = sir_model,
                            parms = parameters))
# reformat data frame
output_long <- melt(as.data.frame(output), id = "time")

# Plot the number of people in each compartment over time
gp <- ggplot(data = output_long,
             aes(x = time, y = value, colour = variable, group = variable)) +
  geom_line() +
  xlab("Time (days)")+ 
  ylab("Number of people") + 
  labs(title = paste("Susceptible, Infected and Recovered"))
gp

That’s it! As you can see, this was actually a pretty simple exercise. You can adjust the initial values and model parameters so that the model applies for your particular case of interest (for example, the COVID-19 pandemic). This is a simple implementation of the SIR model that does not accomodate population turnover, effect of vaccination or waning immunity. As you might imagine, these assumptions do not apply in many scenarios. So let’s do it!. Next, we’ll code several more complex versions of the SIR model that can accommodate the aforementioned factors.

SIR model that accomodates population turnover

Figure 3

# initial population values
initial_number_susceptible = 10e6 - 1
initial_number_recovered = 0
initial_number_infected = 1
initial_population = initial_number_infected+initial_number_recovered+initial_number_susceptible
initial_state_values <- c(
  S = initial_number_susceptible,
  I = initial_number_infected,
  R = initial_number_recovered,
  N = initial_population
)

# time steps
times <- seq(from = 0, to = 100, by = 1/365)   # from 0 to 100 YEARS in DAILY intervals

# model parameters
infection_rate = 1*365
recovery_rate = 0.1*365
death_rate = 1/(70)
birth_rate = death_rate
parameters <- c(
  beta = infection_rate,
  gamma = recovery_rate,
  b = birth_rate,
  miu = death_rate
)

# SIR MODEL FUNCTION 
sir_model <- function(
  time,
  state, 
  parameters
)
{
  with(as.list(c(state, parameters)), {
    # Differential equations
    N <- S+I+R
    lambda <- beta*(I/N)
    dS <- -lambda*S + b*N - miu*S
    dI <- lambda*S - gamma*I - miu*I
    dR <- gamma*I - R*miu
    return(list(c(dS, dI, dR, N))) # return output
  })
}

# run the model
output <- as.data.frame(ode(y = initial_state_values,
                            times = times,
                            func = sir_model,
                            parms = parameters))

# reformat data frame
plot.cols = c('time', 'S','I','R')
output_long <- melt(as.data.frame(output[,plot.cols]), id = "time")


# Plot the proportion of people in each compartment over time
gp1 <- ggplot(data = output_long,
              aes(x = time, y = value, colour = variable, group = variable)) +
  geom_line() +
  xlab("Time (years)")+ 
  ylab("Number of people") + 
  labs(title = paste("Infected over time"))
gp1

As a new surplus of susceptible individuals enter the population, oscillations in the number of infected people occur throughout history. This is represented in the model through the terms that we added to the model representing population turnover. For this example, we assumed birth and death are in equal rate. In reality populations tend to increase over time. This can, of course, be adjusted through the death and birth rate parameters (\(b\) and \(\mu\)).

One last important caveat is that the parameters in this model, for example \(\gamma\) and \(\beta\), need to be estimated from the population. Poor estimation of these values would lead to a poor performance by the SIR model. Eventually, we’ll include in this R-markdown how estimation of these parameters can be performed from a data set.