Simulating Deterministic SIR Models in a Closed and Open Population Playground

Below, you will find code for creating a shiny flex-dashboard for simple SIR open and closed population models.

  ---
  title: "Simulating SIR Models in a Closed and Open Population"
  date: "`r format(Sys.time(), '%B %d, %Y')`"
  editor_options:
    chunk_output_type: console
  output: flexdashboard::flex_dashboard
  runtime: shiny
  ---


  {r setup, include=FALSE}
  knitr::opts_chunk$set(echo = F, warning = F, message = F, fig.align = 'center', results = 'hide', fig.keep = 'all')
  library(tidyverse)
  library(cowplot)
  # https://rmarkdown.rstudio.com/flexdashboard/layouts.html#multiple_pages




  {r}
  #..............................................................
  # Open Model
  #..............................................................
  # stochastic simulator for the unobserved state process --> how cases are generated
  open_sir_step <- function(S, I, R, N, Beta, mu_IR, mu_L, delta.t){
    SI_events <- rbinom(n = 1, size = S, prob = 1 - exp((-(mu_L+Beta*(I/N)))*delta.t))
    SI_deaths <- rbinom(n = 1, size = SI_events, prob = mu_L/(mu_L + Beta*(I/N)))
    infected <- SI_events - SI_deaths
    IR_events <- rbinom(n = 1, size = I, prob = 1 - exp(-(mu_L+mu_IR)*delta.t))
    IR_deaths <- rbinom(n = 1, size = IR_events, prob = mu_L/(mu_L + mu_IR))
    recovered <- IR_events - IR_deaths
    recovered_deaths <- rbinom(n = 1, size = R, prob = 1 - exp(-mu_L))

    S <- S - infected + IR_deaths + recovered_deaths
    I <- I + infected - recovered - IR_deaths
    R <- R + recovered - recovered_deaths
    N <- S + I + R
    return(c("N" = N, "S" = S, "I" = I, "R" = R))
  }
  # recursively just step through time now
  open_sir_wrapper <-  function(N, I0, R0, Beta, mu_IR, mu_L, time, ...){
    # set up
    S0 <- N - I0  - R0
    time.steps <- (time - dplyr::lag(time))
    # init
    ret <- matrix(NA, nrow = length(time.steps) , ncol = 4)
    ret[1,] <- open_sir_step(S = S0,
                              I = I0, R = R0, N = N,
                              Beta = Beta,
                              mu_IR = mu_IR,
                              mu_L = mu_L,
                              delta.t = 0)

    for (t in 2:length(time.steps)) {
      ret[t,] <-  open_sir_step(S = ret[t-1, 2],
                                 I = ret[t-1, 3],
                                 R = ret[t-1, 4],
                                 N = ret[t-1, 1],
                                 Beta = Beta,
                                 mu_IR = mu_IR,
                                 mu_L = mu_L,
                                 delta.t = time.steps[t]
      )
    }
    # out
    ret <- cbind.data.frame(time, ret)
    colnames(ret) <- c("time", "N", "S", "I", "R")
    return(ret)

  }

  #..............................................................
  # Closed Model
  #..............................................................
  closed_sir_step <- function(S,I, R, N, Beta, mu_IR, delta.t){
    dN_SI <- rbinom(n = 1, size = S, prob = 1 - exp(-Beta*(I/N)*delta.t))
    dN_IR <- rbinom(n = 1, size = I, prob = 1 - exp(-mu_IR*delta.t))
    S <- S - dN_SI
    I <- I + dN_SI - dN_IR
    R <- R + dN_IR
    return(c("S" = S, "I" = I, "R" = R))
  }

  # recursively just step through time now
  closed_sir_wrapper <-  function(N, I0, R0, Beta, mu_IR, time, ...){
    # set up
    S0 <- N - I0 - R0
    time.steps <- (time - dplyr::lag(time))
    # init
    ret <- matrix(NA, nrow = length(time.steps) , ncol = 3)
    ret[1,] <- closed_sir_step(S = S0,
                                I = I0, R = R0, N = N,
                                Beta = Beta,
                                mu_IR = mu_IR, delta.t = 0)

    for (t in 2:length(time.steps)) {
      ret[t,] <-  closed_sir_step(S = ret[t-1, 1],
                                   I = ret[t-1, 2],
                                   R = ret[t-1, 3],
                                   N = N, # closed population
                                   Beta = Beta,
                                   mu_IR = mu_IR,
                                   delta.t = time.steps[t]
      )
    }
    # out
    ret <- cbind.data.frame(time, ret)
    colnames(ret) <- c("time", "S", "I", "R")
    return(ret)

  }




  ### Sidebar {.sidebar}
  {r, results='asis'}
  #..............................................................
  # Sliding Panel for Different Parameters we are estimating
  #..............................................................

  inputPanel(
    sliderInput("Time", label = "Time Observed Since Start of Epidemic",
                min = 0, max = 1e3, value = 500, step = 100),

    sliderInput("mu_L", label = "Birth & Death Rate",
                min = 0.01, max = 0.1, value = 0.01, step = 0.005),

    sliderInput("beta", label = "Beta (Effective Contact Rate)",
                min = 0, max = 1, value = 0.2, step = 0.001),

    sliderInput("mu_IR", label = "Gamma (Rate of Recovery)",
                min = 0, max = 0.5, value = 0.1, step = 0.005),

    actionLink("button", "Ruh Roh!", icon("bug"),
               style="color: #fff; background-color: #337ab7; border-color: #2e6da4")

  )





  ### SIR Closed vs. Open

  {r, results='asis', fig.align='center', fig.height=11, fig.width=8}

  renderPlot({ withProgress(message = 'Making plot', {
    # re-render button
    input$button
    #..............................................................
    # plot closed
    #..............................................................
    closed_truth <- closed_sir_wrapper(N = 1e5,
                                        I0 = 1,
                                        R0 = 0,
                                        Beta = input$beta,
                                        mu_IR = input$mu_IR,
                                        time = 1:input$Time)

    closed_sir_plot <- closed_truth %>%
      tidyr::gather(., key = "compartment", "count", 2:ncol(.)) %>%
      dplyr::mutate(Compartment = factor(compartment, levels = c("S", "I", "R"))) %>%
      ggplot() +
      geom_line(aes(x=time, y=count, color = Compartment), size = 2, alpha = 0.9) +
      scale_color_manual("", values = c("#2171b5", "#e41a1c", "#6a51a3")) +
      xlab("Time") + ylab("N") + ggtitle("SIR Closed Population") +
      scale_y_continuous(label = scales::unit_format(big.mark = ",", scale = 1, unit = "")) +
      theme_bw() +
      theme(
        plot.title = element_text(family = "Helvetica", face = "bold", vjust = 0.5,  hjust = 0.5, size = 18),
        axis.text.x = element_text(family = "Helvetica", face = "bold", hjust = 0.5, vjust = 0.5, size = 14),
        axis.text.y = element_text(family = "Helvetica", hjust = 0.5, vjust = 0.5, size = 14, angle = 45),
        legend.title = element_text(family = "Helvetica", face = "bold", vjust = 0.5, size = 15),
        legend.text = element_text(family = "Helvetica", hjust = 0.5, vjust = 0.5, size = 14),
        panel.background = element_rect(fill = "transparent"),
        plot.background = element_rect(fill = "transparent"),
        legend.background = element_rect(fill = "transparent"))



    #..............................................................
    # plot OPEN MODEL
    #..............................................................
    open_truth <- open_sir_wrapper(N = 1e5,
                                    I0 = 1,
                                    R0 = 0,
                                    Beta = input$beta,
                                    mu_IR = input$mu_IR,
                                    mu_L = input$mu_L,
                                    time = 1:input$Time)



    open_truthPlot <- open_truth %>%
      dplyr::select(-c("N")) %>%
      tidyr::gather(., key = "compartment", "count", 2:ncol(.)) %>%
      dplyr::mutate(Compartment = factor(compartment, levels = c("S", "I", "R"))) %>%
      ggplot() +
      geom_line(aes(x=time, y=count, color = Compartment), size = 2, alpha = 0.9) +
      scale_color_manual("", values = c("#2171b5", "#e41a1c", "#6a51a3")) +
      xlab("Time") + ylab("N") + ggtitle("SIR Open Population") +
      scale_y_continuous(label = scales::unit_format(big.mark = ",", scale = 1, unit = "")) +
      theme_bw() +
      theme(
        plot.title = element_text(family = "Helvetica", face = "bold", vjust = 0.5,  hjust = 0.5, size = 18),
        axis.title = element_text(family = "Helvetica", face = "bold", vjust = 0.5, size = 15),
        axis.text.x = element_text(family = "Helvetica", face = "bold", hjust = 0.5, vjust = 0.5, size = 14),
        axis.text.y = element_text(family = "Helvetica", hjust = 0.5, vjust = 0.5, size = 14, angle = 45),
        legend.title = element_text(family = "Helvetica", face = "bold", vjust = 0.5, size = 15),
        legend.text = element_text(family = "Helvetica", hjust = 0.5, vjust = 0.5, size = 14),
        panel.background = element_rect(fill = "transparent"),
        plot.background = element_rect(fill = "transparent"),
        legend.background = element_rect(fill = "transparent"))

  }) # end of progress bar

    #..............................................................
    # plot out
    #..............................................................
    toprow <- cowplot::plot_grid(closed_sir_plot, open_truthPlot, nrow = 1)


    # R0
    R0 <- round(input$beta / (input$mu_IR + input$mu_L), 2)
    bottomrow <- ggplot() + geom_text(aes(0,0,
                                          label= paste0("R[0] %~~% rho*c*D %~~% beta / (gamma + mu[L]) %~~%", R0)), parse=TRUE, size = 10, fontface = "bold") +
      theme_minimal() +
      theme(
        panel.grid = element_blank(),
        axis.text = element_blank(),
        axis.title = element_blank()
      ) + xlim(-0.1, 0.1) + ylim(-0.1, 0.1)


    cowplot::plot_grid(toprow, bottomrow, nrow = 2, rel_heights = c(4, 1))
  })
Avatar
Nicholas Brazeau
Resident Physician

Related