inst/shiny/epiicm/ui.R

##
## UI File for epiicm Shiny Application
##
## Run local: epiweb(class = "icm")
##

library(shiny)
library(bslib)
library(DT)
library(plotly)
library(EpiModel)

# -- Theme --
app_theme <- bs_theme(
  version = 5,
  bootswatch = "flatly",
  base_font = font_google("Atkinson Hyperlegible"),
  heading_font = font_google("Atkinson Hyperlegible"),
  "primary" = "#2C3E50",
  "success" = "#18BC9C",
  "info" = "#3498DB",
  "danger" = "#E74C3C"
)

# -- UI --
page_sidebar(
  title = div(
    class = "d-flex justify-content-between align-items-center w-100",
    span("EpiModel: Stochastic Individual Contact Models"),
    actionButton("runMod", "Run Model",
                 class = "btn-light btn-sm",
                 icon = icon("play"))
  ),
  theme = app_theme,

  # ===== Sidebar =====
  sidebar = sidebar(
    width = 320,
    open = "always",

    # Parameter accordion
    accordion(
      id = "params_accordion",
      open = c("Disease Type or Scenario", "Population & Time",
               "Epidemic Parameters"),

      # -- Disease Type or Scenario --
      accordion_panel(
        "Disease Type or Scenario",
        selectInput("modtype", "Disease Type",
                    choices = c("SI", "SIR", "SIS")),
        selectInput("preset", "Scenario Preset",
                    choices = c("Custom",
                                "Flu-like (SIR)",
                                "STI-like (SIS)",
                                "Measles-like (SIR)"))
      ),

      # -- Population & Time --
      accordion_panel(
        "Population & Time",
        numericInput("s.num", "Number Susceptible",
                     value = 500, min = 1, step = 50),
        numericInput("i.num", "Number Infected",
                     value = 1, min = 0, step = 1),
        conditionalPanel(
          "input.modtype == 'SIR'",
          numericInput("r.num", "Number Recovered",
                       value = 0, min = 0, step = 1)
        ),
        numericInput("nsteps", "Time Steps",
                     value = 300, min = 10, step = 50),
        numericInput("nsims", "Number of Simulations",
                     value = 5, min = 1, max = 50, step = 1),
        helpText("More simulations capture stochastic variability",
                 "but take longer to run.")
      ),

      # -- Epidemic Parameters --
      accordion_panel(
        "Epidemic Parameters",
        sliderInput("inf.prob", "Transmission Probability per Act",
                    min = 0, max = 1, value = 0.1, step = 0.01),
        sliderInput("act.rate", "Act Rate",
                    min = 0, max = 20, value = 0.5, step = 0.1),
        conditionalPanel(
          "input.modtype != 'SI'",
          sliderInput("rec.rate", "Recovery Rate",
                      min = 0, max = 1, value = 0.05, step = 0.01)
        )
      ),

      # -- Intervention --
      accordion_panel(
        "Intervention",
        checkboxInput("enable_intervention", "Enable Intervention",
                      value = FALSE),
        conditionalPanel(
          "input.enable_intervention",
          sliderInput("inter.eff", "Intervention Efficacy",
                      min = 0, max = 1, value = 0.5, step = 0.01),
          helpText("Proportional reduction in transmission probability."),
          numericInput("inter.start", "Intervention Start (time step)",
                       value = 100, min = 1, step = 10)
        )
      ),

      # -- Vital Dynamics --
      accordion_panel(
        "Vital Dynamics",
        checkboxInput("enable_vital", "Include Births & Deaths",
                      value = FALSE),
        conditionalPanel(
          "input.enable_vital",
          numericInput("a.rate", "Birth Rate (per person per time step)",
                       value = 0.001, min = 0, step = 0.001),
          numericInput("ds.rate", "Death Rate",
                       value = 0.001, min = 0, step = 0.001),
          checkboxInput("diff_death_rates",
                        "Different death rates by compartment",
                        value = FALSE),
          conditionalPanel(
            "input.diff_death_rates",
            numericInput("di.rate", "Death Rate, Infected",
                         value = 0.001, min = 0, step = 0.001),
            conditionalPanel(
              "input.modtype == 'SIR'",
              numericInput("dr.rate", "Death Rate, Recovered",
                           value = 0.001, min = 0, step = 0.001)
            )
          )
        )
      )
    ) # end accordion
  ), # end sidebar


  # ===== Main Panel =====

  # -- Main Plot Card --
  card(
    full_screen = TRUE,
    card_header(
      class = "d-flex justify-content-between align-items-center",
      "Epidemic Trajectory",
      div(
        class = "d-flex align-items-center gap-2",
        selectInput("compsel", NULL,
                    choices = c("Compartment Prevalence",
                                "Compartment Size",
                                "Disease Incidence"),
                    width = "330px"),
        downloadButton("dlMainPlot", "PDF",
                       class = "btn-sm btn-outline-secondary")
      )
    ),
    card_body(
      plotlyOutput("MainPlotly", height = "500px")
    )
  ),

  # Bottom row: Summary / Data / Guide tabs
  navset_card_tab(
    id = "main_tabs",
    height = "500px",

    nav_panel(
      "Summary",
      div(class = "p-3", style = "overflow-y: auto; height: 100%;",
          uiOutput("outSummary"))
    ),

    nav_panel(
      "Data",
      div(style = "overflow-y: auto; height: 100%;",
        div(
          class = "mb-3",
          fluidRow(
            column(4,
              selectInput("datasel", "Data View",
                          choices = c("Means", "Standard Deviations",
                                      "Individual Simulations"))
            ),
            column(4,
              conditionalPanel(
                "input.datasel == 'Individual Simulations'",
                uiOutput("simnoControl")
              )
            )
          )
        ),
        DTOutput("outData"),
        div(
          class = "mt-2",
          downloadButton("dlData", "Download CSV",
                         class = "btn-sm btn-outline-secondary")
        )
      )
    ),

    nav_panel(
      "Guide",
      div(
        class = "p-3",
        style = "max-width: 900px; overflow-y: auto; height: 100%;",

        # --- Overview ---
        h4("User Guide"),
        p("This application solves and visualizes",
          tags$strong("stochastic individual contact models (ICMs)"),
          "of infectious disease transmission using the",
          tags$a("EpiModel", href = "https://www.epimodel.org/",
                 target = "_blank"),
          "package for R. ICMs are agent-based microsimulation analogs to
          deterministic compartmental models. Each person in the population
          is represented as a discrete agent, and disease transmission is a
          stochastic (random) process that depends on the contact patterns
          and transmission probability per contact event."),

        p("Because ICMs are stochastic, running the model multiple times
          with the same parameters produces different outcomes. The
          application runs multiple simulations and displays their mean
          trajectory along with the interquartile range (IQR) to show the
          distribution of possible outcomes."),

        # --- Disease Types ---
        hr(),
        h5("Disease Types"),
        p("Three compartmental structures are available, each representing
          a different natural history of infection:"),
        tags$ul(
          tags$li(tags$strong("SI (Susceptible-Infected):"),
                  "Individuals move from susceptible to infected and remain
                  infected and infectious permanently. There is no recovery.
                  This is appropriate for chronic infections like HIV (without
                  treatment) or herpes."),
          tags$li(tags$strong("SIR (Susceptible-Infected-Recovered):"),
                  "Infected individuals recover and gain lasting immunity.
                  Once recovered, they cannot be re-infected. This applies to
                  many acute viral infections like measles, influenza, and
                  SARS-CoV-2 (as a simplification)."),
          tags$li(tags$strong("SIS (Susceptible-Infected-Susceptible):"),
                  "Infected individuals recover but return to the susceptible
                  state with no lasting immunity. They can be re-infected.
                  This is typical for many bacterial STIs like gonorrhea
                  and chlamydia.")
        ),

        # --- Epidemic Parameters ---
        hr(),
        h5("Epidemic Parameters"),
        tags$ul(
          tags$li(tags$strong("Transmission Probability per Act:"),
                  "The probability that an infection is transmitted during a
                  single contact (act) between an infected and a susceptible
                  individual. Ranges from 0 to 1."),
          tags$li(tags$strong("Act Rate:"),
                  "The average number of contact events (acts) per person
                  per unit time. Higher act rates mean more opportunities for
                  transmission."),
          tags$li(tags$strong("Recovery Rate:"),
                  "(SIR and SIS only.) The per-capita rate at which infected
                  individuals recover per time step. In a closed population,
                  the average duration of infection is 1 / recovery rate. With
                  vital dynamics enabled, the average duration in the infected
                  state is 1 / (recovery rate + departure rate for infecteds).")
        ),
        p("Together, these parameters determine the",
          tags$strong("basic reproduction number"),
          HTML("(R<sub>0</sub>),"),
          "the average number of secondary infections caused by one infected
          individual in a fully susceptible population:"),
        p(class = "text-center",
          style = "font-size: 1.1rem;",
          HTML("R<sub>0</sub> = (transmission probability &times;
                act rate) / recovery rate")),
        p(HTML("When R<sub>0</sub> > 1, the epidemic is expected to grow.
          When R<sub>0</sub> < 1, the infection is expected to die out.
          Because ICMs are stochastic, individual simulations may deviate
          from this expectation, especially with small populations.")),

        # --- Stochastic Variation ---
        hr(),
        h5("Stochastic Variation and Simulations"),
        p("Unlike deterministic compartmental models (DCMs), ICMs are
          stochastic. Each simulation may produce a different epidemic
          trajectory even with identical parameters, because individual
          transmission events are governed by random draws. This means
          outcomes like peak timing, peak size, and final epidemic size
          will vary across simulations."),
        p("The", tags$strong("Number of Simulations"), "control sets how
          many independent runs are performed. More simulations give a
          better picture of the range of possible outcomes. The plot shows:"),
        tags$ul(
          tags$li(tags$strong("Mean line:"), "The average trajectory across
                  all simulations, shown as a solid line."),
          tags$li(tags$strong("IQR band:"), "The interquartile range (25th
                  to 75th percentile) shown as a shaded ribbon. This captures
                  the middle 50% of simulation outcomes at each time step.")
        ),
        p("With only one simulation, only the individual trajectory is shown."),

        # --- Scenario Presets ---
        hr(),
        h5("Scenario Presets"),
        p("Three built-in presets configure all parameters to reasonable
          values for common infectious diseases. In all presets, each time
          step represents one day:"),
        tags$ul(
          tags$li(tags$strong("Flu-like (SIR):"),
                  "Low per-act transmission probability (0.03) with a high
                  contact rate (10 acts/day), reflecting airborne spread.
                  Average duration of infection of about 7 days (recovery
                  rate = 0.15). Initial population of 501."),
          tags$li(tags$strong("STI-like (SIS):"),
                  "Moderate transmission probability (0.2) with a low contact
                  rate (0.5 acts/day), reflecting sexual transmission.
                  Average duration of infection of about 100 days (recovery
                  rate = 0.01). Initial population of 510."),
          tags$li(tags$strong("Measles-like (SIR):"),
                  "High transmission probability (0.5) and moderate contact
                  rate (3 acts/day), producing a high",
                  HTML("R<sub>0</sub>"),
                  "of 15. Average duration of infection of about 10 days
                  (recovery rate = 0.1). Initial population of 501.")
        ),
        p("Select", tags$em("Custom"), "to set parameters manually."),

        # --- Interventions ---
        hr(),
        h5("Interventions"),
        p("When enabled, an intervention reduces the transmission probability
          by a proportional amount starting at a specified time step. For
          example, an efficacy of 0.5 cuts the transmission probability in
          half. This can represent public health measures such as vaccination
          campaigns, mask mandates, contact tracing, or behavioral
          interventions."),
        p("The intervention effect is shown as a vertical dashed line on the
          plot. Compare epidemic trajectories with and without the intervention
          to assess its impact."),

        # --- Vital Dynamics ---
        hr(),
        h5("Vital Dynamics"),
        p("By default, the population is closed (no births or deaths). Enabling
          vital dynamics adds a constant per-capita birth rate (new susceptibles
          entering the population) and per-capita death rates. By default, a
          single death rate is applied uniformly across all compartments.
          Checking", tags$em("Different death rates by compartment"),
          "allows setting separate death rates for susceptible, infected,
          and (for SIR models) recovered individuals. This is useful when
          disease-induced mortality differs from background mortality. Vital
          dynamics are important for modeling endemic equilibria over longer
          time horizons, where demographic turnover replenishes the susceptible
          pool."),

        # --- Reading the Output ---
        hr(),
        h5("Reading the Output"),
        p("The application provides three output views:"),
        tags$ul(
          tags$li(tags$strong("Plot:"),
                  "Interactive time-series plots (powered by plotly) showing
                  compartment sizes, prevalence, or disease incidence.
                  Mean lines and IQR bands summarize the stochastic variation
                  across simulations. Hover over any point to see exact values.
                  Use the plotly toolbar to zoom, pan, or download the plot
                  as a PNG."),
          tags$li(tags$strong("Summary:"),
                  HTML("Key epidemic metrics including R<sub>0</sub>, peak
                  infected count and timing, cumulative infections, incidence
                  rate, and (when applicable) attack rate, plus stochastic
                  variation statistics. Additional sections appear when
                  interventions or vital dynamics are enabled.")),
          tags$li(tags$strong("Data:"),
                  "The full simulation output as a searchable, sortable table.
                  Choose to view means across simulations, standard deviations,
                  or individual simulation values. Download the raw data as a
                  CSV file for further analysis.")
        ),

        tags$h6(class = "fw-semibold mt-3", "Incidence Metrics"),
        p("The summary tab reports two measures of disease incidence:"),
        tags$ul(
          tags$li(tags$strong("Incidence Rate:"),
                  "Total new infections divided by total susceptible
                  person-time (reported per 1,000 person-timesteps). This
                  measure is always shown and is valid for all model types,
                  including SIS models with re-infection and models with vital
                  dynamics, because it accounts for the changing size of the
                  susceptible population over time."),
          tags$li(tags$strong("Attack Rate:"),
                  "Total new infections divided by the initial number of
                  susceptibles. This is a simple proportion representing the
                  fraction of the original susceptible population that became
                  infected. It is only displayed for SI and SIR models without
                  vital dynamics, because it requires a closed population with
                  no re-infection. In SIS models (where individuals can be
                  re-infected) or models with births and deaths (where the
                  susceptible pool changes), the attack rate is not
                  well-defined and is therefore hidden.")
        ),

        # --- ICM vs DCM ---
        hr(),
        h5("ICM vs. DCM"),
        p("Individual contact models (ICMs) and deterministic compartmental
          models (DCMs) both track the flow of individuals between disease
          states, but they differ in key ways:"),
        tags$ul(
          tags$li(tags$strong("Stochasticity:"), "ICMs model each individual
                  agent and each contact event stochastically, while DCMs solve
                  a system of ordinary differential equations (ODEs)
                  deterministically."),
          tags$li(tags$strong("Population representation:"), "ICMs track
                  discrete individuals; DCMs track aggregate compartment sizes
                  that can take fractional values."),
          tags$li(tags$strong("Small populations:"), "ICMs naturally handle
                  small populations where integer effects and random extinction
                  matter. DCMs assume large, well-mixed populations."),
          tags$li(tags$strong("Computational cost:"), "ICMs are slower because
                  each agent is simulated individually. Use the Run Model
                  button to control when the simulation executes.")
        ),

        # --- Further Resources ---
        hr(),
        h5("Further Resources"),
        p("This application uses a subset of EpiModel's capabilities. The
          full R package supports deterministic compartmental models (DCMs)
          and stochastic network models built on exponential-family random
          graph models (ERGMs). For tutorials and documentation, visit the",
          tags$a("EpiModel website.", href = "https://www.epimodel.org/",
                 target = "_blank")),
        tags$ul(
          tags$li("Source code and bug reports:",
                  tags$a("GitHub",
                         href = "https://github.com/EpiModel/EpiModel",
                         target = "_blank")),
          tags$li("R package on CRAN:",
                  tags$a("EpiModel",
                         href = "https://cran.r-project.org/package=EpiModel",
                         target = "_blank")),
          tags$li("Course materials:",
                  tags$a("SISMID EpiModel Workshop",
                         href = "https://epimodel.github.io/sismid/",
                         target = "_blank"))
        ),

        # --- Citation ---
        hr(),
        h5("Citation"),
        p("Jenness SM, Goodreau SM, Morris M (2018).",
          tags$em("EpiModel: An R Package for Mathematical Modeling of
                   Infectious Disease over Networks."),
          "Journal of Statistical Software, 84(8), 1-47.",
          tags$a("doi:10.18637/jss.v084.i08",
                 href = "https://doi.org/10.18637/jss.v084.i08",
                 target = "_blank")),

        # --- Authors ---
        hr(),
        h5("Authors"),
        p("Samuel M. Jenness, Department of Epidemiology, Emory University"),
        p("Steven M. Goodreau, Department of Anthropology, University of Washington"),
        p("Martina Morris, Departments of Statistics and Sociology, University of Washington")
      )
    )
  ) # end navset_card_tab
) # end page_sidebar

Try the EpiModel package in your browser

Any scripts or data that you put into this service are public.

EpiModel documentation built on March 19, 2026, 9:08 a.m.