1 LOAD THE PACKAGES:

library(deSolve)
library(reshape2)
library(ggplot2)

2 MODEL INPUTS:

2.1 Vector storing the initial number of people in each compartment (at timestep 0)

initial_state_values <- c(S = 1000000-1,   # the whole population we are modelling is susceptible to infection
                          I = 1,           # the epidemic starts with a single infected person
                          R = 0)           # there is no prior immunity in the population

2.2 Vector storing the parameters describing the transition rates in units of days^-1

parameters <- c(beta = 0.4,      # the infection rate, which acts on susceptibles
                gamma = 0.1)     # the rate of recovery, which acts on those infected

2.3 Vector storing the sequence of timesteps to solve the model at

# TIMESTEPS:

# Vector storing the sequence of timesteps to solve the model at
times <- seq(from = 0, to = 100, by = 1)   # from 0 to 100 days in daily intervals

3 SIR MODEL FUNCTION:

# The model function takes as input arguments (in the following order): time, state and parameters
sir_model <- function(time, state, parameters) {  

    with(as.list(c(state, parameters)), {  # tell R to unpack variable names from the state and parameters inputs    
        
    # Calculating the total population size N (the sum of the number of people in each compartment)
      N <- S+I+R
      
    # Defining lambda as a function of beta and I:
      lambda <- beta * I/N
        
    # The differential equations
      dS <- -lambda * S               # people move out of (-) the S compartment at a rate lambda (force of infection)
      dI <- lambda * S - gamma * I    # people move into (+) the I compartment from S at a rate lambda, 
                                      # and move out of (-) the I compartment at a rate gamma (recovery)
      dR <- gamma * I                 # people move into (+) the R compartment from I at a rate gamma
      
    # Return the number of people in the S, I and R compartments at each timestep 
    # (in the same order as the input state variables)
    return(list(c(dS, dI, dR))) 
    })
  
}

3.1 MODEL OUTPUT (solving the differential equations):

# Solving the differential equations using the ode integration algorithm
output <- as.data.frame(ode(y = initial_state_values, 
                            times = times, 
                            func = sir_model,
                            parms = parameters))

3.2 Generating 2 plots:

output_long <- melt(as.data.frame(output), id = "time")       # turn output dataset into long format

3.3 Calculating the proportion in each compartment as a column in the long-format output

output_long$proportion <- output_long$value/sum(initial_state_values)

3.4 Plot the proportion of people in the S, I and R compartments over time

ggplot(data = output_long,                                               # specify object containing data to plot
       aes(x = time, y = proportion, colour = variable, group = variable)) +  # assign columns to axes and groups
  geom_line() +                                                          # represent data as lines
  xlab("Time (days)")+                                                   # add label for x axis
  ylab("Proportion of the population") +                                 # add label for y axis
  labs(colour = "Compartment",                                           # add legend title  
       title = "Proportion susceptible, infected and recovered over time") +                                                               
  theme(legend.position = "bottom")                                      # move legend to the bottom of the plot

4 Calculating the effective reproduction number in a new column

output$reff <- parameters["beta"]/parameters["gamma"] *                  # R0 = beta/gamma
                output$S/(output$S+output$I+output$R)                    # multiply R0 by the proportion susceptible
                                                                         # at each timestep/for each row
# In this calculation, the total population size (output$S+output$I+output$R) is calculated for each timestep
# so this approach would also be appropriate if the population size varies over time

4.1 Plot Reff

ggplot(data = output,                                                    # specify object containing data to plot
       aes(x = time, y = reff)) +                                        # assign columns to axes and groups
  geom_line() +                                                          # represent data as lines
  xlab("Time (days)")+                                                   # add label for x axis
  ylab("Reff") +                                                         # add label for y axis
  labs(title = "Effective reproduction number over time")                # add plot title

LS0tDQp0aXRsZTogIlNpbXVsYXRpbmcgdGhlIGVmZmVjdGl2ZSByZXByb2R1Y3Rpb24gbnVtYmVyIFJlZmYgIg0KYXV0aG9yOiAiQkluaCBUaGFuZyBUcmFuIg0KZGF0ZTogIjUvMjMvMjAyMCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQotLS0NCg0KIyBMT0FEIFRIRSBQQUNLQUdFUzoNCg0KYGBge3J9DQpsaWJyYXJ5KGRlU29sdmUpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCiMgTU9ERUwgSU5QVVRTOg0KDQojIyBWZWN0b3Igc3RvcmluZyB0aGUgaW5pdGlhbCBudW1iZXIgb2YgcGVvcGxlIGluIGVhY2ggY29tcGFydG1lbnQgKGF0IHRpbWVzdGVwIDApDQoNCmBgYHtyfQ0KDQppbml0aWFsX3N0YXRlX3ZhbHVlcyA8LSBjKFMgPSAxMDAwMDAwLTEsICAgIyB0aGUgd2hvbGUgcG9wdWxhdGlvbiB3ZSBhcmUgbW9kZWxsaW5nIGlzIHN1c2NlcHRpYmxlIHRvIGluZmVjdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBJID0gMSwgICAgICAgICAgICMgdGhlIGVwaWRlbWljIHN0YXJ0cyB3aXRoIGEgc2luZ2xlIGluZmVjdGVkIHBlcnNvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBSID0gMCkgICAgICAgICAgICMgdGhlcmUgaXMgbm8gcHJpb3IgaW1tdW5pdHkgaW4gdGhlIHBvcHVsYXRpb24NCg0KYGBgDQoNCiMjIFZlY3RvciBzdG9yaW5nIHRoZSBwYXJhbWV0ZXJzIGRlc2NyaWJpbmcgdGhlIHRyYW5zaXRpb24gcmF0ZXMgaW4gdW5pdHMgb2YgZGF5c14tMQ0KYGBge3J9DQoNCnBhcmFtZXRlcnMgPC0gYyhiZXRhID0gMC40LCAgICAgICMgdGhlIGluZmVjdGlvbiByYXRlLCB3aGljaCBhY3RzIG9uIHN1c2NlcHRpYmxlcw0KICAgICAgICAgICAgICAgIGdhbW1hID0gMC4xKSAgICAgIyB0aGUgcmF0ZSBvZiByZWNvdmVyeSwgd2hpY2ggYWN0cyBvbiB0aG9zZSBpbmZlY3RlZA0KDQoNCmBgYA0KDQojIyBWZWN0b3Igc3RvcmluZyB0aGUgc2VxdWVuY2Ugb2YgdGltZXN0ZXBzIHRvIHNvbHZlIHRoZSBtb2RlbCBhdA0KYGBge3J9DQojIFRJTUVTVEVQUzoNCg0KIyBWZWN0b3Igc3RvcmluZyB0aGUgc2VxdWVuY2Ugb2YgdGltZXN0ZXBzIHRvIHNvbHZlIHRoZSBtb2RlbCBhdA0KdGltZXMgPC0gc2VxKGZyb20gPSAwLCB0byA9IDEwMCwgYnkgPSAxKSAgICMgZnJvbSAwIHRvIDEwMCBkYXlzIGluIGRhaWx5IGludGVydmFscw0KYGBgDQoNCiMgU0lSIE1PREVMIEZVTkNUSU9OOiANCg0KYGBge3J9DQojIFRoZSBtb2RlbCBmdW5jdGlvbiB0YWtlcyBhcyBpbnB1dCBhcmd1bWVudHMgKGluIHRoZSBmb2xsb3dpbmcgb3JkZXIpOiB0aW1lLCBzdGF0ZSBhbmQgcGFyYW1ldGVycw0Kc2lyX21vZGVsIDwtIGZ1bmN0aW9uKHRpbWUsIHN0YXRlLCBwYXJhbWV0ZXJzKSB7ICANCg0KICAgIHdpdGgoYXMubGlzdChjKHN0YXRlLCBwYXJhbWV0ZXJzKSksIHsgICMgdGVsbCBSIHRvIHVucGFjayB2YXJpYWJsZSBuYW1lcyBmcm9tIHRoZSBzdGF0ZSBhbmQgcGFyYW1ldGVycyBpbnB1dHMgICAgDQogICAgICAgIA0KICAgICMgQ2FsY3VsYXRpbmcgdGhlIHRvdGFsIHBvcHVsYXRpb24gc2l6ZSBOICh0aGUgc3VtIG9mIHRoZSBudW1iZXIgb2YgcGVvcGxlIGluIGVhY2ggY29tcGFydG1lbnQpDQogICAgICBOIDwtIFMrSStSDQogICAgICANCiAgICAjIERlZmluaW5nIGxhbWJkYSBhcyBhIGZ1bmN0aW9uIG9mIGJldGEgYW5kIEk6DQogICAgICBsYW1iZGEgPC0gYmV0YSAqIEkvTg0KICAgICAgICANCiAgICAjIFRoZSBkaWZmZXJlbnRpYWwgZXF1YXRpb25zDQogICAgICBkUyA8LSAtbGFtYmRhICogUyAgICAgICAgICAgICAgICMgcGVvcGxlIG1vdmUgb3V0IG9mICgtKSB0aGUgUyBjb21wYXJ0bWVudCBhdCBhIHJhdGUgbGFtYmRhIChmb3JjZSBvZiBpbmZlY3Rpb24pDQogICAgICBkSSA8LSBsYW1iZGEgKiBTIC0gZ2FtbWEgKiBJICAgICMgcGVvcGxlIG1vdmUgaW50byAoKykgdGhlIEkgY29tcGFydG1lbnQgZnJvbSBTIGF0IGEgcmF0ZSBsYW1iZGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGFuZCBtb3ZlIG91dCBvZiAoLSkgdGhlIEkgY29tcGFydG1lbnQgYXQgYSByYXRlIGdhbW1hIChyZWNvdmVyeSkNCiAgICAgIGRSIDwtIGdhbW1hICogSSAgICAgICAgICAgICAgICAgIyBwZW9wbGUgbW92ZSBpbnRvICgrKSB0aGUgUiBjb21wYXJ0bWVudCBmcm9tIEkgYXQgYSByYXRlIGdhbW1hDQogICAgICANCiAgICAjIFJldHVybiB0aGUgbnVtYmVyIG9mIHBlb3BsZSBpbiB0aGUgUywgSSBhbmQgUiBjb21wYXJ0bWVudHMgYXQgZWFjaCB0aW1lc3RlcCANCiAgICAjIChpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgaW5wdXQgc3RhdGUgdmFyaWFibGVzKQ0KICAgIHJldHVybihsaXN0KGMoZFMsIGRJLCBkUikpKSANCiAgICB9KQ0KICANCn0NCmBgYA0KDQojIyBNT0RFTCBPVVRQVVQgKHNvbHZpbmcgdGhlIGRpZmZlcmVudGlhbCBlcXVhdGlvbnMpOg0KDQpgYGB7cn0NCiMgU29sdmluZyB0aGUgZGlmZmVyZW50aWFsIGVxdWF0aW9ucyB1c2luZyB0aGUgb2RlIGludGVncmF0aW9uIGFsZ29yaXRobQ0Kb3V0cHV0IDwtIGFzLmRhdGEuZnJhbWUob2RlKHkgPSBpbml0aWFsX3N0YXRlX3ZhbHVlcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSB0aW1lcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuYyA9IHNpcl9tb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJtcyA9IHBhcmFtZXRlcnMpKQ0KYGBgDQoNCg0KIyMgR2VuZXJhdGluZyAyIHBsb3RzOg0KDQpgYGB7cn0NCm91dHB1dF9sb25nIDwtIG1lbHQoYXMuZGF0YS5mcmFtZShvdXRwdXQpLCBpZCA9ICJ0aW1lIikgICAgICAgIyB0dXJuIG91dHB1dCBkYXRhc2V0IGludG8gbG9uZyBmb3JtYXQNCg0KYGBgDQoNCg0KIyMgQ2FsY3VsYXRpbmcgdGhlIHByb3BvcnRpb24gaW4gZWFjaCBjb21wYXJ0bWVudCBhcyBhIGNvbHVtbiBpbiB0aGUgbG9uZy1mb3JtYXQgb3V0cHV0DQoNCmBgYHtyfQ0Kb3V0cHV0X2xvbmckcHJvcG9ydGlvbiA8LSBvdXRwdXRfbG9uZyR2YWx1ZS9zdW0oaW5pdGlhbF9zdGF0ZV92YWx1ZXMpDQpgYGANCg0KDQojIyBQbG90IHRoZSBwcm9wb3J0aW9uIG9mIHBlb3BsZSBpbiB0aGUgUywgSSBhbmQgUiBjb21wYXJ0bWVudHMgb3ZlciB0aW1lDQpgYGB7cn0NCmdncGxvdChkYXRhID0gb3V0cHV0X2xvbmcsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHNwZWNpZnkgb2JqZWN0IGNvbnRhaW5pbmcgZGF0YSB0byBwbG90DQogICAgICAgYWVzKHggPSB0aW1lLCB5ID0gcHJvcG9ydGlvbiwgY29sb3VyID0gdmFyaWFibGUsIGdyb3VwID0gdmFyaWFibGUpKSArICAjIGFzc2lnbiBjb2x1bW5zIHRvIGF4ZXMgYW5kIGdyb3Vwcw0KICBnZW9tX2xpbmUoKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcmVwcmVzZW50IGRhdGEgYXMgbGluZXMNCiAgeGxhYigiVGltZSAoZGF5cykiKSsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGFkZCBsYWJlbCBmb3IgeCBheGlzDQogIHlsYWIoIlByb3BvcnRpb24gb2YgdGhlIHBvcHVsYXRpb24iKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBhZGQgbGFiZWwgZm9yIHkgYXhpcw0KICBsYWJzKGNvbG91ciA9ICJDb21wYXJ0bWVudCIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYWRkIGxlZ2VuZCB0aXRsZSAgDQogICAgICAgdGl0bGUgPSAiUHJvcG9ydGlvbiBzdXNjZXB0aWJsZSwgaW5mZWN0ZWQgYW5kIHJlY292ZXJlZCBvdmVyIHRpbWUiKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtb3ZlIGxlZ2VuZCB0byB0aGUgYm90dG9tIG9mIHRoZSBwbG90DQpgYGANCg0KIyBDYWxjdWxhdGluZyB0aGUgZWZmZWN0aXZlIHJlcHJvZHVjdGlvbiBudW1iZXIgaW4gYSBuZXcgY29sdW1uDQoNCmBgYHtyfQ0Kb3V0cHV0JHJlZmYgPC0gcGFyYW1ldGVyc1siYmV0YSJdL3BhcmFtZXRlcnNbImdhbW1hIl0gKiAgICAgICAgICAgICAgICAgICMgUjAgPSBiZXRhL2dhbW1hDQogICAgICAgICAgICAgICAgb3V0cHV0JFMvKG91dHB1dCRTK291dHB1dCRJK291dHB1dCRSKSAgICAgICAgICAgICAgICAgICAgIyBtdWx0aXBseSBSMCBieSB0aGUgcHJvcG9ydGlvbiBzdXNjZXB0aWJsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYXQgZWFjaCB0aW1lc3RlcC9mb3IgZWFjaCByb3cNCiMgSW4gdGhpcyBjYWxjdWxhdGlvbiwgdGhlIHRvdGFsIHBvcHVsYXRpb24gc2l6ZSAob3V0cHV0JFMrb3V0cHV0JEkrb3V0cHV0JFIpIGlzIGNhbGN1bGF0ZWQgZm9yIGVhY2ggdGltZXN0ZXANCiMgc28gdGhpcyBhcHByb2FjaCB3b3VsZCBhbHNvIGJlIGFwcHJvcHJpYXRlIGlmIHRoZSBwb3B1bGF0aW9uIHNpemUgdmFyaWVzIG92ZXIgdGltZQ0KDQpgYGANCg0KDQojIyBQbG90IFJlZmYNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IG91dHB1dCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzcGVjaWZ5IG9iamVjdCBjb250YWluaW5nIGRhdGEgdG8gcGxvdA0KICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHJlZmYpKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYXNzaWduIGNvbHVtbnMgdG8gYXhlcyBhbmQgZ3JvdXBzDQogIGdlb21fbGluZSgpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyByZXByZXNlbnQgZGF0YSBhcyBsaW5lcw0KICB4bGFiKCJUaW1lIChkYXlzKSIpKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYWRkIGxhYmVsIGZvciB4IGF4aXMNCiAgeWxhYigiUmVmZiIpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGFkZCBsYWJlbCBmb3IgeSBheGlzDQogIGxhYnModGl0bGUgPSAiRWZmZWN0aXZlIHJlcHJvZHVjdGlvbiBudW1iZXIgb3ZlciB0aW1lIikgICAgICAgICAgICAgICAgIyBhZGQgcGxvdCB0aXRsZQ0KYGBgDQoNCg==