Skip to content

Instantly share code, notes, and snippets.

@timchurches
Last active May 15, 2020 08:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timchurches/95204f0565b0311ec32408a7e27c0f7f to your computer and use it in GitHub Desktop.
Save timchurches/95204f0565b0311ec32408a7e27c0f7f to your computer and use it in GitHub Desktop.
Source code for JMIR paper: Churches T & Jorm L. "COVOID: A flexible, freely available stochastic individual contact model for exploring COVID-19 intervention and control strategies
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only" page-range-format="expanded" default-locale="en-US">
<info>
<title>American Medical Association 11th edition</title>
<title-short>AMA (11th ed.)</title-short>
<id>http://www.zotero.org/styles/american-medical-association</id>
<link href="http://www.zotero.org/styles/american-medical-association" rel="self"/>
<link href="http://www.zotero.org/styles/american-medical-association-10th-edition" rel="template"/>
<link href="https://westlibrary.txwes.edu/sites/default/files/pdf/AMACitationStyle.pdf" rel="documentation"/>
<link href="https://www.amamanualofstyle.com/fileasset/AMAMOS/aaaAMWA%20presentation%20Nov%202019%20FULL.pdf" rel="documentation"/>
<author>
<name>Julian Onions</name>
<email>julian.onions@gmail.com</email>
</author>
<contributor>
<name>Christian Pietsch</name>
<uri>http://purl.org/net/pietsch</uri>
</contributor>
<contributor>
<name>Daniel W Chan</name>
<email>danwchan@protonmail.com</email>
</contributor>
<contributor>
<name>Patrick O'Brien</name>
<email>obrienpat86@gmail.com</email>
</contributor>
<category citation-format="numeric"/>
<category field="medicine"/>
<summary>The American Medical Association style as used in JAMA. Version 11 as per November-2019.</summary>
<updated>2020-04-23T13:38:04+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
</info>
<locale xml:lang="en">
<terms>
<term name="page-range-delimiter">-</term>
</terms>
</locale>
<macro name="editor">
<names variable="editor">
<name name-as-sort-order="all" sort-separator=" " initialize-with="" delimiter=", " delimiter-precedes-last="always"/>
<label form="short" prefix=", "/>
</names>
</macro>
<macro name="author">
<group suffix=".">
<names variable="author">
<name name-as-sort-order="all" sort-separator=" " initialize-with="" delimiter=", " delimiter-precedes-last="always"/>
<label form="short" prefix=", "/>
<substitute>
<names variable="editor"/>
<text macro="title"/>
</substitute>
</names>
</group>
</macro>
<macro name="access">
<choose>
<if type="article-newspaper" match="none">
<choose>
<if variable="DOI">
<text value="doi:"/>
<text variable="DOI"/>
</if>
<else-if variable="URL">
<group delimiter=". ">
<choose>
<if type="webpage post post-weblog" match="any">
<date variable="issued" prefix="Published " form="text"/>
</if>
</choose>
<group>
<text term="accessed" text-case="capitalize-first" suffix=" "/>
<date variable="accessed">
<date-part name="month" suffix=" "/>
<date-part name="day" suffix=", "/>
<date-part name="year"/>
</date>
</group>
<text variable="URL"/>
</group>
</else-if>
</choose>
</if>
</choose>
</macro>
<macro name="title">
<choose>
<if type="bill book graphic legal_case legislation motion_picture report song" match="any">
<text variable="title" font-style="italic" text-case="title"/>
</if>
<else>
<text variable="title"/>
</else>
</choose>
</macro>
<macro name="publisher">
<text variable="publisher"/>
</macro>
<macro name="edition">
<choose>
<if is-numeric="edition">
<group delimiter=" ">
<number variable="edition" form="ordinal"/>
<text term="edition" form="short"/>
</group>
</if>
<else>
<text variable="edition" suffix="."/>
</else>
</choose>
</macro>
<citation collapse="citation-number">
<sort>
<key variable="citation-number"/>
</sort>
<layout delimiter="," vertical-align="sup">
<text variable="citation-number"/>
<group prefix="(" suffix=")">
<label variable="locator" form="short" strip-periods="true"/>
<text variable="locator"/>
</group>
</layout>
</citation>
<bibliography hanging-indent="false" et-al-min="7" et-al-use-first="3" second-field-align="flush">
<layout>
<text variable="citation-number" suffix=". "/>
<text macro="author"/>
<text macro="title" prefix=" " suffix="."/>
<choose>
<if type="bill book graphic legislation motion_picture report song" match="any">
<group suffix="." prefix=" " delimiter=" ">
<group delimiter=" ">
<text term="volume" form="short" text-case="capitalize-first" strip-periods="true"/>
<text variable="volume" suffix="."/>
</group>
<text macro="edition"/>
<text macro="editor" prefix="(" suffix=")"/>
</group>
<text macro="publisher" prefix=" "/>
<group suffix="." prefix="; ">
<date variable="issued">
<date-part name="year"/>
</date>
<text variable="page" prefix=":"/>
</group>
</if>
<else-if type="chapter paper-conference entry-dictionary entry-encyclopedia" match="any">
<group prefix=" " delimiter=" ">
<text term="in" text-case="capitalize-first" suffix=":"/>
<text macro="editor"/>
<text variable="container-title" font-style="italic" suffix="." text-case="title"/>
<group delimiter=" ">
<text term="volume" form="short" text-case="capitalize-first" strip-periods="true"/>
<text variable="volume" suffix="."/>
</group>
<text macro="edition"/>
<text variable="collection-title" suffix="."/>
<group suffix=".">
<text macro="publisher"/>
<group suffix="." prefix="; ">
<date variable="issued">
<date-part name="year"/>
</date>
<text variable="page" prefix=":"/>
</group>
</group>
</group>
</else-if>
<else-if type="article-newspaper">
<text variable="container-title" font-style="italic" prefix=" " suffix=". "/>
<choose>
<if variable="URL">
<group delimiter=". " suffix=".">
<text variable="URL"/>
<group prefix="Published ">
<date variable="issued">
<date-part name="month" suffix=" "/>
<date-part name="day" suffix=", "/>
<date-part name="year"/>
</date>
</group>
<group>
<text term="accessed" text-case="capitalize-first" suffix=" "/>
<date variable="accessed">
<date-part name="month" suffix=" "/>
<date-part name="day" suffix=", "/>
<date-part name="year"/>
</date>
</group>
</group>
</if>
<else>
<group delimiter=":" suffix=".">
<group>
<date variable="issued">
<date-part name="month" suffix=" "/>
<date-part name="day" suffix=", "/>
<date-part name="year"/>
</date>
</group>
<text variable="page"/>
</group>
</else>
</choose>
</else-if>
<else-if type="legal_case">
<group suffix="," prefix=" " delimiter=" ">
<text macro="editor" prefix="(" suffix=")"/>
</group>
<group prefix=" " delimiter=" ">
<text variable="container-title"/>
<text variable="volume"/>
</group>
<text variable="page" prefix=", " suffix=" "/>
<group prefix="(" suffix=")." delimiter=" ">
<text variable="authority"/>
<date variable="issued">
<date-part name="year"/>
</date>
</group>
</else-if>
<else-if type="webpage post post-weblog" match="any">
<text variable="container-title" prefix=" " suffix="."/>
</else-if>
<else-if type="speech">
<group prefix=" " suffix=":">
<choose>
<if variable="genre">
<text variable="genre" suffix=" "/>
<text term="presented at"/>
</if>
<else>
<text term="presented at" text-case="capitalize-first"/>
</else>
</choose>
</group>
<group delimiter="; " prefix=" " suffix=".">
<text variable="event"/>
<group>
<date delimiter=" " variable="issued">
<date-part name="month"/>
<date-part name="day" suffix=","/>
<date-part name="year"/>
</date>
</group>
<text variable="event-place"/>
</group>
</else-if>
<else>
<text macro="editor" prefix=" " suffix="."/>
<group prefix=" " suffix=".">
<text variable="container-title" font-style="italic" form="short" strip-periods="true" suffix="."/>
<group delimiter=";" prefix=" ">
<choose>
<if variable="issue volume" match="any">
<date variable="issued">
<date-part name="year"/>
</date>
</if>
<else>
<group delimiter=" ">
<text value="Published online"/>
<date form="text" date-parts="year-month-day" variable="issued"/>
</group>
</else>
</choose>
<group>
<text variable="volume"/>
<text variable="issue" prefix="(" suffix=")"/>
</group>
</group>
<text variable="page" prefix=":"/>
</group>
</else>
</choose>
<text prefix=" " macro="access"/>
</layout>
</bibliography>
</style>
---
title: "“COVOID”: A flexible, freely available stochastic individual contact model for exploring COVID-19 intervention and control strategies"
author: "Tim Churches & Louisa Jorm"
date: 2020-03-30
output:
html_document:
self_contained: true
references:
- id: wiles2020
title: The three phases of Covid-19 – and how we can make it manageable
author:
- family: Wiles
given: S
- family: Morris
given: T
container-title: Wikipedia
URL: 'https://upload.wikimedia.org/wikipedia/commons/c/c5/Covid-19-curves-graphic-social-v3.gif'
accessed:
year: 2020
month: 3
day: 29
- id: cdc2007
title: 'Interim pre-pandemic planning guidance: community strategy for pandemic influenza mitigation in the United States: early, targeted, layered use of nonpharmaceutical interventions.'
author:
- family: Centers for Disease Control (CDC)
URL: 'https://stacks.cdc.gov/view/cdc/11425'
publisher: CDC, Atlanta, USA
type: article-journal
issued:
year: 2007
accessed:
year: 2020
month: 3
day: 29
- id: oneill2010
title: Relating infectious disease transmission models to data
author:
- family: O’Neill
given: PD
container-title: Statistics in Medicine
volume: 29
page: 2069-2077
type: article-journal
issued:
year: 2010
- id: ferguson2020
title: Impact of non-pharmaceutical interventions (NPIs) to reduce COVID- 19 mortality and healthcare demand
author:
- family: Ferguson
given: NM
- family: Laydon
given: D
- family: Nedjati-Gilani
given: G
- family: Imai
given: N
- family: Ainslie
given: K
- family: Baguelin
given: M
- family: Bhatia
given: S
- family: Boonyasiri
given: A
- family: Cucunubá
given: Z
- family: Cuomo-Dannenburg
given: G
- family: Dighe
given: A
- family: Dorigatti
given: I
- family: Fu
given: H
- family: Gaythorpe
given: K
- family: Green
given: W
- family: Hamlet
given: A
- family: Hinsley
given: W
- family: Okell
given: LC
- family: van Elsland
given: S
- family: Thompson
given: H
- family: Verity
given: R
- family: Volz
given: E
- family: Wang
given: H
- family: Wang
given: Y
- family: Walker
given: PGT
- family: Walters
given: C
- family: Winskill
given: P
- family: Whittaker
given: C
- family: Donnelly
given: CA
- family: Riley
given: S
- family: Ghani
given: AC
URL: 'https://www.imperial.ac.uk/media/imperial-college/medicine/sph/ide/gida-fellowships/Imperial-College-COVID19-NPI-modelling-16-03-2020.pdf'
publisher: Imperial College London
issued:
year: 2012
month: 3
day: 16
accessed:
year: 2020
month: 3
day: 29
- id: chang2020
title: Modelling transmission and control of the COVID-19 pandemic in Australia [preprint]
author:
- family: Chang
given: SL
- family: Harding
given: N
- family: Zachreson
given: C
- family: Cliff
given: OM
- family: Prokopenko
given: M
container-title: arXiv
volume: 2003.10218v3
URL: 'http://dx.doi.org/10.1038/nmat3283'
issued:
year: 2020
month: 3
accessed:
year: 2020
month: 3
day: 29
- id: reuters2020
title: The projection that changed Britain's coronavirus policy
author:
- family: Reuters World News
URL: 'https://www.reuters.com/article/us-health-coronavirus-britain-research-f/factbox-the-projection-that-changed-britains-coronavirus-policy-idUSKBN21415L'
publisher: Reuters
accessed:
year: 2020
month: 3
day: 29
- id: abcnews2020
title: Data shows coronavirus can only be controlled if 8 out of 10 Australians stay home
author:
- family: ABC News
URL: 'https://www.abc.net.au/news/2020-03-25/coronavirus-covid-19-modelling-stay-home-chart/12084144'
publisher: Australian Broadcasting Corporation (ABC)
accessed:
year: 2020
month: 3
day: 29
- id: jenness2018
title: 'EpiModel: An R Package for Mathematical Modeling of Infectious Disease over Networks'
author:
- family: Jenness
given: SM
- family: Goodreau
given: SM
- family: Morris
given: M
container-title: J Statistical Software
volume: 84
URL: 'http://dx.doi.org/10.18637/jss.v084.i08'
DOI: 10.18637/jss.v084.i08
issue: 8
page: 1-47
type: article-journal
issued:
year: 2018
month: 3
- id: epimodel2020
title: EpiModel [software]
URL: 'https://www.epimodel.org'
accessed:
year: 2020
month: 3
day: 29
- id: rproject2020
title: The R Project for Statistical Computing [software]
URL: 'https://www.r-project.org'
accessed:
year: 2020
month: 3
day: 29
- id: aihw2019
title: 'Hospital resources 2017–18: Australian hospital statistics'
author:
- family: Australian Institute of Health and Welfare
URL: 'https://www.aihw.gov.au/reports/hospitals/hospital-resources-2017-18-ahs/contents/hospitals-and-average-available-beds'
publisher: Australian Institute of Health and Welfare, Canberra, Australia
type: report
issued:
year: 2019
accessed:
year: 2020
month: 3
day: 29
- id: abs2020
title: '3218.0 - Regional Population Growth, Australia, 2018-19'
author:
- family: Australian Bureau of Statistics (ABS)
URL: 'https://www.abs.gov.au/AUSSTATS/abs@.nsf/mf/3218.0'
publisher: Australian Bureau of Statistics (ABS), Canberra, Australia
type: report
issued:
year: 2020
accessed:
year: 2020
month: 3
day: 29
- id: mizumoto2020
title: Estimating the asymptomatic proportion of coronavirus disease 2019 (COVID-19) cases on board the Diamond Princess cruise ship, Yokohama, Japan, 2020
author:
- family: Mizumoto
given: K
- family: Kagaya
given: K
- family: Zarebski
given: A
- family: Chowell
given: G
container-title: Euro Surveill.
volume: 25
URL: 'https://doi.org/10.2807/1560-7917.ES.2020.25.10.2000180'
DOI: 10.2807/1560-7917.ES.2020.25.10.2000180
issue: 10
accessed:
year: 2020
month: 3
day: 29
- id: constantino2020
title: The effectiveness of full and partial travel bans against COVID-19 spread in Australia for travellers from China, 18 March 2020, PREPRINT (Version 1)
author:
- family: Costantino
given: V
- family: Heslop
given: D
- family: Macintyre
given: R
container-title: Research Square
URL: 'https://doi.org/10.21203/rs.3.rs-17714/v1'
DOI: 10.21203/rs.3.rs-17714/v1
publisher: Research Square
accessed:
year: 2020
month: 3
day: 29
- id: eames2012
title: Measured Dynamic Social Contact Patterns Explain the Spread of H1N1v Influenza.
author:
- family: Eames
given: KTD
- family: Tilston
given: NL
- family: Brooks-Pollock
given: E
- family: Edmunds
given: WJ
container-title: PLoS Computational Biology
URL: 'https://doi.org/10.1371/journal.pcbi.1002425'
DOI: 10.1371/journal.pcbi.1002425
accessed:
year: 2020
month: 3
day: 29
- id: datansw2020
title: 'NSW COVID-19 cases data'
author:
- family: data.NSW
URL: 'https://data.nsw.gov.au/nsw-covid-19-data/cases'
publisher: New South Wales Government, Sydney, Australia
type: report
issued:
year: 2020
accessed:
year: 2020
month: 5
day: 12
- id: churches2020
title: 'Tim Churches Health Data Science Blog: Modelling the effects of public health interventions on COVID-19 transmission using R - part 2'
author:
- family: Churches
given: T
url: 'https://timchurches.github.io/blog/posts/2020-03-18-modelling-the-effects-of-public-health-interventions-on-covid-19-transmission-part-2/'
issued:
year: 2020
month: 3
accessed:
year: 2020
month: 3
day: 30
- id: turk2020
title: Modeling COVID-19 latent prevalence to assess a public health intervention at a state and regional scale
author:
- family: Turk
given: PJ
- family: Chou
given: SH
- family: Kowalkowski
given: MA
- family: Palmer
given: PP
- family: Priem
given: JS
- family: Spencer
given: MD
- family: Taylor
given: YJ
- family: McWilliams
given: AD
container-title: medRxiv
URL: 'http://medrxiv.org/content/early/2020/04/18/2020.04.14.20063420.abstract'
DOI: 10.1101/2020.04.14.20063420
accessed:
year: 2020
month: 4
day: 28
- id: barton2020
title: Call for transparency of COVID-19 models
container-title: Science
volume: 368
number: 6490
pages: 482-483
year: 2020
doi: 10.1126/science.abb8637
publisher: American Association for the Advancement of Science
URL: 'https://science.sciencemag.org/content/368/6490/482.2'
author:
- family: Barton
given: CM
- family: Alberti
given: M
- family: Ames
given: D
- family: Atkinson
given: JA
- family: Bales
given: J
- family: Burke
given: E
- family: Chen
given: M
- family: Diallo
given: SY
- family: Earn
given: DJD
- family: Fath
given: B
- family: Feng
given: Z
- family: Gibbons
given: C
- family: Hammond
given: R
- family: Heffernan
given: J
- family: Houser
given: H
- family: Hovmand
given: PS
- family: Kopainsky
given: B
- family: Mabry
given: PL
- family: Mair
given: C
- family: Meier
given: P
- family: Niles
given: R
- family: Nosek
given: B
- family: Osgood
given: N
- family: Pierce
given: S
- family: Polhill
given: JG
- family: Prosser
given: L
- family: Robinson
given: E
- family: Rosenzweig
given: C
- family: Sankaran
given: S
- family: Stange
given: K
- family: Tucker
given: G
- id: taylor2020
title: 'Covidsafe app: how Australia’s coronavirus contact tracing app works, what it does, downloads and problems'
container-title: The Guardian
year: 2020
month: 5
day: 13
publisher: Guardian News & Media Limited, Manchester, Great Britain
URL: 'https://www.theguardian.com/australia-news/2020/may/13/covid-19-safe-app-australia-how-download-does-it-work-australian-government-covidsafe-tracking-downloads'
author:
- family: Taylor
given: J
accessed:
year: 2020
month: 5
day: 13
- id: prem2017
title: Projecting social contact matrices in 152 countries using contact surveys and demographic data
container-title: PLoS Computational Biology
year: 2017
month: 9
day: 12
doi: 10.1371/journal.pcbi.1005697
URL: 'https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1005697'
author:
- family: Prem
given: K
- family: Cook
given: AR
- family: Jit
given: M
accessed:
year: 2020
month: 5
day: 13
csl: ama.csl
---
```{r installation, include=FALSE, eval=TRUE}
for (pkg in c("tidyverse", "magrittr", "lubridate", "knitr",
"gt", "devtools", "DiagrammeR", "EpiModel",
"parallel", "foreach", "tictoc", "patchwork"))
if (!requireNamespace(pkg)) install.packages(pkg)
if (!requireNamespace("gt")) install_github("gt")
```
```{r setup, include=FALSE, eval=TRUE}
knitr::opts_chunk$set(echo = FALSE, cache=TRUE,
tidy.opts=list(width.cutoff=60),
tidy=TRUE,
fig.height=6.25,
fig.width=6.25)
library(tidyverse)
library(patchwork)
library(magrittr)
library(lubridate)
library(knitr)
library(gt)
library(tictoc)
suppressMessages(library(EpiModel))
library(DiagrammeR)
library(devtools)
library(parallel)
library(foreach)
library(ggeasy)
# devtools::github_install("CBDRH/covidrecon")
library(covidrecon)
tic()
```
```{r get-nsw-incidence, echo=FALSE, eval=TRUE, warning=FALSE, message=FALSE}
# returns NSW incidence by geography tibble
get_raw_nsw_geo_data <- function() {
nsw_incidence_by_geo_url <- paste0("https://data.nsw.gov.au/data/dataset/",
"aefcde60-3b0c-4bc0-9af1-6fe652944ec2/resource/",
"21304414-1ff1-4243-a5d2-f52778048b29/download/",
"covid-19-cases-by-notification-date-and",
"-postcode-local-health-district-and-",
"local-government-area.csv")
colspec <- cols(
notification_date = col_date(format = ""),
postcode = col_character(),
lhd_2010_code = col_character(),
lhd_2010_name = col_character(),
lga_code19 = col_character(),
lga_name19 = col_character())
read_csv(nsw_incidence_by_geo_url, col_types = colspec)
}
get_nsw_lga_incidence <- function(df) {
df %>%
group_by(notification_date, lga_name19) %>%
summarise(n=n()) %>%
ungroup() %>%
mutate(provenance = "data.nsw.gov.au")
}
nsw_lga_incidence <- get_nsw_lga_incidence(get_raw_nsw_geo_data())
se_sydney_incidence <- nsw_lga_incidence %>%
filter(notification_date >= ymd("2020-03-01"),
lga_name19 %in% c("Randwick (C)",
"Woollahra (A)",
"Waverley (A)")) %>%
mutate(count=n,
NDate=notification_date,
compartment="obs.incidence",
experiment="Scenario 06") %>%
select(NDate, count, compartment, experiment)
se_sydney_cum_incidence_30apr2020 <- se_sydney_incidence %>%
arrange(NDate) %>%
mutate(cum_incidence = cumsum(count)) %>%
filter(NDate == ymd("2020-04-30")) %>%
pull(cum_incidence)
se_sydney_incidence <- se_sydney_incidence %>%
bind_rows(se_sydney_incidence %>% mutate(experiment="Scenario 07")) %>%
bind_rows(se_sydney_incidence %>% mutate(experiment="Scenario 08")) %>%
bind_rows(se_sydney_incidence %>% mutate(experiment="Scenario 09")) %>%
bind_rows(se_sydney_incidence %>% mutate(experiment="Scenario 10"))
```
```{r get-australia-incidence, echo=FALSE, eval=TRUE, warning=FALSE, message=FALSE}
australia_cum_incidence_30apr2020 <- covidrecon::covid_latest() %>%
filter(date == ymd("2020-04-30")) %>%
filter(country_region == "Australia") %>%
pull(cumulative_cases)
```
## Abstract
**Background**: Throughout March 2020, leaders in countries across the globe were making crucial decisions about how and when to implement public health interventions to combat COVID-19. They urgently needed tools to help them to explore what will work best in their specific circumstances of epidemic size and spread and feasible intervention scenarios.
**Objectives**: We sought to rapidly develop a flexible, freely available simulation model for use by modellers and researchers to allow investigation of how various public health interventions implemented at various time points might change the shape of the COVID-19 epidemic curve.
**Methods**: “COVOID” (COVID-19 Open-source Infection Dynamics) is a stochastic individual contact model (ICM), which extends the ICMs provided by the open-source EpiModel package for the R statistical computing environment. To demonstrate its use and inform urgent decisions as at 30 March 2020, we modelled similar intervention scenarios to those reported by other investigators using various model types, as well as novel scenarios. The scenarios involved isolation of cases, moderate social distancing and stricter population “lock-downs” enacted over varying time periods, in a hypothetical population of 100,000 people. On 30 April 2020, we simulated the epidemic curve for the three contiguous local areas (population 287,344) in eastern Sydney, Australia, that recorded 5.3% of Australian cases of COVID-19 through to 30 April 2020, under five different intervention scenarios, and compared the modelled predictions with the observed epidemic curve for these areas.
**Results**: COVOID allocates each member of a population to one of seven compartments. The number of times individuals in the various compartments interact with each other and their probability of transmitting infection at each interaction can be varied to simulate the effects of interventions. Using COVOID on 30 March 2020, we were able to replicate the epidemic response patterns to specific social distancing intervention scenarios reported by others. The simulated curve for three local areas of Sydney from 1 March to 30 April 2020 was similar to the observed epidemic curve, in terms of peak numbers of cases, total numbers of cases and duration, under a scenario representing the public health measures that were actually enacted, including case isolation and ramp-up of testing and social distancing measures.
**Conclusions**: COVOID allows rapid modelling of many potential intervention scenarios, can be tailored to diverse settings and requires only standard computing infrastructure. It replicates the epidemic curves produced by other models that require highly detailed population-level data, and its predicted epidemic curve, using parameters simulating the public health measures that were enacted, was similar in form to that actually observed in Sydney, Australia. Our team and collaborators are currently developing an extended open-source COVOID package comprising a suite of tools to explore intervention scenarios using several categories of model.
**Keywords**: COVID-19; epidemic curve; infection dynamics; public health interventions
## Introduction
March 2020 was a critical time in the global COVID-19 pandemic, when political leaders and policymakers were making crucial decisions that would shape the lives and futures of people and communities. “Flattening the curve” had become a rallying cry in the fight against COVID-19, popularised by media outlets and leaders worldwide. However, the ubiquitous COVID-19 “flattening the curve” infographic [@wiles2020] can be traced back to a purely conceptual diagram in a 2007 US Centres for Disease Control report recommending strategies for pandemic influenza mitigation [@cdc2007]. It was essential that political leaders and their advisers had ready access to more sophisticated mathematical and computational tools to allow them to explore quickly and iteratively how implementing various public health interventions would potentially change the shape of the COVID-19 epidemic curve in their settings.
Stochastic individual contact models (ICMs), also known as individual-based or agent-based models, are increasingly used for epidemic simulation modelling. These models represent individual units in the population and the contacts between them as discrete events and capture the stochasticity seen in real-world disease outbreaks. Compared with more traditional deterministic compartmental models (DCMs), which are based on systems of differential equations for the movement of the population through discrete states at specified rates, they may produce more realistic results, especially in situations where micro-epidemics emerge at city and community levels [@oneill2010].
As at 30 March 2020, ICMs for COVID-19 had recently been reported for the United Kingdom (UK) and United States (US) [@ferguson2020] and Australia [@chang2020], adapted from existing models for pandemic influenza. These use whole-of-population census data and model contacts between individuals in the population within households, schools, workplaces and in the wider community. The UK model appears to have been influential in driving a turnaround in the COVID-19 response strategy in that nation [@reuters2020]. The Australian model highlighted the potential for the virus to spread virtually unchecked unless there were high levels of compliance with social distancing measures [@abcnews2020].
Given the enormous consequences of decisions about public health interventions that were being made at that time, it was highly desirable to independently assess the robustness of these (not yet peer-reviewed) ICMs. However, the software code for these models has not been made publicly available, limiting scrutiny of their underlying structure, and making it impossible to replicate exactly their findings or test sensitivity to alternative assumptions.
Furthermore, the ICMs reported as at 30 March 2020 reflected the circumstances of high-income western nations. Their findings may not be applicable in countries and communities that have substantially different demography, social network structures, education and health systems, workplaces and community resources. Replicating them rapidly in other settings is very challenging, because they require the ready availability of detailed population-level data. Furthermore, running them requires access to high-performance computing, which is not feasible in many settings.
Our objective was to develop a flexible, freely available COVID-19 ICM simulation model for use by modellers and researchers that can be tailored to diverse settings and run using standard desktop or laptop computing hardware. Importantly, given the quickly evolving situation worldwide, we sought to build a model that permitted highly flexible definitions of intervention strategies which more closely reflect the real world, in which epidemic control measures tend to take time to implement, often less completely than hoped, and which cannot be and are not sustained indefinitely.
## Methods
### Model building
“COVOID” (COVID-19 Open-source Infection Dynamics) is a stochastic ICM which we constructed by extending the peer-reviewed [@jenness2018] open-source EpiModel package [@epimodel2020] for the widely-used, open-source R statistical computing environment [@rproject2020]. Our model extensions allocate each member of a hypothetical population to one of seven compartments (Figure 1). We have replaced the traditional E (exposed) compartment as used in Susceptible-Exposed-Infectious-Recovered (SEIR) models, with an A compartment, representing infected, asymptomatic individuals who are nonetheless potentially infectious. Addition compartments, representing symptomatic and/or test-positive infected individuals in (self-)isolation (Q) and infected individuals requiring hospital care (H), were also added, as well as a compartment for COVID-19 case fatalities (F), as distinct from deaths due to other causes which together with emigration are handled by a separate demographic removal process.
At each one-day time step of the simulation, individuals randomly encounter and are exposed to other individuals in the population. The intensity of this population mixing is controlled by an _act rate_ parameter specific to each of the infectious compartments (A, I and Q), with each "act" representing an opportunity for disease transmission, or at least those "acts" between susceptible individuals and infectious individuals. Recovered individuals are no longer infectious and are assumed to be immune from further re-infection, thus their interactions do not result in infections, nor do interactions between pairs of susceptible individuals nor pairs of infectious individuals; only the interactions between susceptible and infectious individuals may give rise to new infections. However, not every such opportunity for disease transmission will result in actual disease transmission. The probability of transmission at each interaction is controlled by an _infection probability_ parameter, also specific to each of the infectious compartments (A, I and Q).
Thus, the interventions are simulated by varying the _act rate_ parameter (equivalent to social distancing in the population), and the _infection probability_ parameter (equivalent to increased practice of hygiene measures such as hand washing, use of hand sanitisers, not touching one's face, and mask wearing by the infectious). The _act rate_ and _infection probability_ for the isolated compartment (Q) are set to lower levels than for the asymptomatic infected/infectious (A) and symptomatic/test-positive infected/infectious (I) compartments. Other parameters can also be changed, as a function of time (so they can be ramped up and ramped down, or pulsed, as required) in order to simulate public health interventions, such as changes to the rate at which individuals in the symptomatic/test-positive I compartment enter the isolation Q compartment.
```{r figure1-diagram, echo=FALSE, eval=TRUE, message=FALSE, layout="l-page", fig.cap="Figure 1: Structure of the COVOID stochastic individual contact model. The dashed arrows represent interpersonal interactions through which transmission of infection may occur. The solid arrows include possible transitions between compartments."}
grViz("
digraph SEIQHRF {
# a 'graph' statement
graph [overlap = false, fontsize = 10] #, rankdir = LR]
# several 'node' statements
node [shape = box,
fontname = Helvetica]
S[label='S=Susceptible'];
A[label='A=Exposed and infected,\nasymptomatic,\npotentially infectious'];
I[label='I=Symptomatic and/or test-positive,\ninfected and infectious'];
Q[label='Q=Case isolated\n(infectious)'];
H[label='H=Requires\nhospitalisation\n(hospitalised if\ncapacity not exceeded'];
R[label='R=Recovered/immune'];
F[label='F=Case fatality']
# several 'edge' statements
S->A
I->S[style='dashed']
A->I
A->S[style='dashed']
I->Q
Q->S[style='dashed']
I->R
I->H
H->F
H->R
Q->R
Q->H
}
")
```
## Intervention scenarios modelled as at 30 March 2020
We used COVOID to model intervention scenarios in a hypothetical population of 100,000 people. A baseline case assuming no interventions was established using parameters based on values in the literature. Interventions were then simulated by varying the number of times individuals in the various compartments interact with each other (the _act rate_ for each of the infectious compartments A, I and Q).
The baseline case assumes three symptomatic infected individuals (compartment I) at day 1, plus four asymptomatic but infected individuals (compartment A). The initial value for the I compartment was chosen heuristically, and we assumed that 60% of infected individuals were asymptomatic, based on the findings in Japanese citizens repatriated from Wuhan, as reported by Mizumoto _et al._ [@mizumoto2020], which were the best estimates available at the time. Other parameters were based on those used by Constantino, Heslop and Macintyre [@constantino2020] which were in turn based on the best estimates available in the preprint literature at the time. We specified an average of 8.5 interpersonal interactions per day, 5% probability of infection following interactions with symptomatic infectious individuals (I compartment), 2% probability of infection following interactions with asymptomatic infectious individuals (A compartment), and that just 3% of symptomatic individuals (I compartment) self-isolate on each day of illness, with subsequently 2.5 personal interactions per day while in self-isolation. Hospital capacity is set at 1,148 beds, approximating the Australian average of 3.8 beds per 1,000 population [@aihw2019] and the rate of fatalities in those requiring hospitalisation (H compartment) is doubled for the prevalent cases above this capacity limit who require hospitalisation.
The parameters for both the initial and subsequent baseline models are shown in Box 1.
Chang _et al_. [@chang2020] used a highly detailed agent-based model for the entire Australian population, originally developed to investigate influenza transmission, to investigate the effect of 90-day periods of reduced social mixing (social distancing) in which 90, 80 and 70 % of the population were assumed to be instantaneously compliant, compared to their baseline model.
Using the baseline parameters shown in Box 1, we investigated the same intervention scenarios, as well as 60 and 50 per cent compliance levels, by using weighted means of compliant and non-compliant act rate parameters. The scenarios are listed in Box 2. Because we were simulating in a hypothetical population of only 100,000, resulting in faster spread than would occur in the full Australian population of 25 million, we initiated the social distancing interventions at 15 days, rather than at 45 days as done by Chang _et al_. [@chang2020].
Box 1: Parameters used for baseline models
| Parameter | Value | Description | Rationale |
|-----------------------|---------|-------------------------|-------------------------|
| Start date | 1st March, 2020 | Day 1 of simulation | Beginning of sustained community transmission in NSW, Australia |
| Initial S compartment (30 March models) | 100,000 | Susceptible population at day 1 | Hypothetical population used for initial 30 March 2020 models |
| Initial S compartment (30 April models) | 287,337 | Susceptible population at day 1 | Population of Waverley, Woolahra and Randwick local government areas in eastern Sydney [@abs2020] |
| Initial A compartment | 4 | Infected but asymptomatic persons at day 1 | Assuming 60% of infected persons are asymptomatic based on Mizumoto _et al._ [@mizumoto2020] |
| Initial I compartment | 3 | Infected but symptomatic and/or test-positive persons at day 1 | Number of detected cases in modelled population in three weeks prior to start date |
| Q, R, H & F compartments | 0 | Other compartments at day 1 | Assumed empty at start |
| _act rate_ (social contact rate) per day for A and I compartments | 8.5 | Number of social contacts with potential for infection per day per individual. | Based on average daily contact rates given in Table 1 of Eames _et al._ [@eames2012] |
| _act rate_ (social contact rate) per day for Q compartment | 1.5 | As above | Adapted from reduction in transmission for those in isolation or quarantine used by by Constantino _et al_. [@constantino2020] |
| _infection probability_ | 0.05 for I compartment, 0.02 for A and Q compartments | Probability of transmitting infection at each encounter as defined by _act rate_ | No published values for COVID-19 found in literature, heuristic values based on discussions with subject matter experts |
| _isolation rate_ per day | 0.033 | Proportion of symptomatic people putting themselves into self-isolation per day of symptoms, in absence of public health information encouraging then to do so. | No values found in literature, heuristic value based on discussions with subject matter experts |
| _progression rate_ | Discrete Weibull distribution, mean 5, shape 1.5 | Distribution of time in A compartment, equivalent to the incubation time | Adapted from values used by Constantino _et al_. [@constantino2020] |
| _hospitalisation rate_ per day | 0.01 | Crude (non-age-specific) proportion of people in I compartment that require hospitalisation per day in compartment | Adapted from values used by Constantino _et al_. [@constantino2020] |
| _discharge rate_ per day | 0.05 | Proportion of persons in H compartment who are discharged from needing hospital care each day | Reciprocal of mean length of stay, based on values used by Constantino _et al_. [@constantino2020] |
| _recovery rate_ per day | 0.05 | Proportion recovering each day, based on reciprocal of mean duration of illness of 20 days. | Based on value used by Constantino _et al._ [@constantino2020] |
| _fatality base rate_ per day | 0.02 | Proportion of persons in H compartment if number is less than or equal to hospital capacity who die each day | Based on mean death rates used by Constantino _et al._ [@constantino2020] |
| _fatality above capacity rate_ per day | 0.04 | Proportion of persons in H compartment in excess of hospital capacity who die each day | Heuristic value, no relevant COVID-19 data relating to this found in literature |
Box 2: Scenarios modelled as at 30 March 2020
| Scenario | Description |
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Scenario 01 | Starting at day 15 (15 March 2020), instantaneous imposition of 90% social distancing for 90 days, then instantaneous reversion to baseline social contact rate. |
| Scenario 02 | Starting at day 15 (15 March 2020), instantaneous imposition of 80% social distancing for 90 days, then instantaneous reversion to baseline social contact rate. |
| Scenario 03 | Starting at day 15 (15 March 2020), instantaneous imposition of 70% social distancing for 90 days, then instantaneous reversion to baseline social contact rate. |
| Scenario 04 | Starting at day 15 (15 March 2020), instantaneous imposition of 60% social distancing for 90 days, then instantaneous reversion to baseline social contact rate. |
| Scenario 05 | Starting at day 15 (15 March 2020), instantaneous imposition of 50% social distancing for 90 days, then instantaneous reversion to baseline social contact rate. |
### Comparison of modelled versus observed epidemic curves in Sydney, Australia as at 30 April 2020
The first cases of COVID-19 were reported in Australia on 24th January 2020. The island of Australia has a vast geography and sparse population and has limited border entry points. The city of Sydney, capital of the state of New South Wales (NSW), is the major entry point for international travellers. As at 30 April 2020, `r se_sydney_cum_incidence_30apr2020` / `r australia_cum_incidence_30apr2020` (`r format(100*se_sydney_cum_incidence_30apr2020/australia_cum_incidence_30apr2020, digits=2)`%) of Australia’s recorded locally-acquired cases of COVID-19 were among residents of three contiguous local government areas of Sydney: Randwick, Waverley and Woollahra, with a combined population of 287,344 [@abs2020]. In order to compare scenarios modelled using COVOID with observed Australian data from the COVID-19 epidemic, we ran simulations for incident cases in the combined population of these three local areas, where it could be assumed that the population had ample opportunities for mixing and exposure to the virus.
A staged series of public health measures were enacted in Australia from 1 February 2020, summarised as they applied in the state of NSW in Box 3.
In order to compare our simulations with observed incidence data, we chose a starting date for our simulations of 1 March 2020, 15 days prior to the gradual ramp-up of social distancing measures in NSW. At that date, 3 cases had been recorded in the three eastern Sydney local government areas used for our model, thus we initialised the model with 3 persons in the I compartment. As noted above, we assumed approximately 60% of infections were asymptomatic and thus also initialised the model with 4 persons in the A compartment. Other parameters were also as per the baseline model described above.
Box 3: COVID-19 public health measures enacted in the state of New South Wales (NSW), Australia, 1 February to 30 April 2020
| Date (2020) | Public health measures enacted |
|-------------|--------------------------------|
| 1 February | Borders closed to all non-residents and non-Australian citizens who had left or transited through mainland China |
| 16 March | Outdoor events with more than 500 attendees banned |
| 17 March | Self-quarantine (14 days) for all arriving overseas travellers |
| 20 March | Borders closed to all non-residents and non-Australian citizens |
| 21 March | Social distancing rule of 4 square metres per person in any enclosed space |
| 23 March | Pubs, clubs, gyms, indoor sporting venues, entertainment venues closed, food outlets restricted to takeaway or delivery |
| 26 March | Closures extended to include e.g. personal services, arcades, brothels, galleries, museums, swimming pools, community facilities, libraries, gambling venues and markets |
| 29 March | Public gatherings limited to two people. People only to leave their houses for: shopping for essentials; medical or compassionate needs; exercise in compliance with the public gathering restriction; or work or education purposes. |
| 30 March | Mandatory isolation in hotels for travellers |
| 28 April | Gradual easing of restrictions commences |
Using this baseline model for eastern Sydney, we then modelled several scenarios to explore the effect of various intervention strategies on the fit of our baseline model to the observed data. The scenarios are described in Box 4. In particular, scenarios 08 and 10 were intended to mimic the actual interventions that had occurred in Sydney as at 30 April 2020.
We compared the epidemic curves simulated by COVOID with reported data for locally acquired new cases for Randwick, Waverley and Woollahra for the period 1 March 2020 to 30 April 2020 [@datansw2020] by examining modelled and observed daily peak and total numbers of incident cases.
Box 4: Scenarios modelled as at 30 April 2020
| Scenario | Description |
|--------------|--------------------|
| Scenario 06 | Starting at day 1 (1 March 2020), linear ramp up of self-isolation rate (per day) from 3.3% to 33% over a 15 day period, then hold at 33% indefinitely. |
| Scenario 07 | Isolation rates s per scenario 06, plus a moderate increase in social distancing to 50% starting at day 15 (15 March 2020) by linearly ramping the _act rate_ per day down from 8.5 to 4.75 over a 15 day period (through to 30 March 2020), then maintaining social distancing at 50% (_act rate_ = 4.65) for a further 45 days, then reverting immediately to no social distancing (_act rate_ = 8.5 per day) |
| Scenario 08 | Isolation rates as per scenario 06, plus a substantial increase in social distancing to 80% starting at day 15 (15 March 2020) by linearly ramping the _act rate_ per day down from 8.5 to 2.5 over a 15 day period (through to 30 March 2020), then maintaining social distancing at 80% (_act rate_ = 2.5) for a further 30 days, then reverting immediately to 50% social distancing (_act rate_ = 4.75 per day) on an ongoing basis |
| Scenario 09 | Isolation rates as per scenario 06 plus a substantial increase in social distancing to 80% starting at day 15 (15 March 2020) by linearly ramping the _act rate_ per day down from 8.5 to 2.5 over a 15 day period (through to 30 March 2020), then maintaining social distancing at 80% (_act rate_ = 2.5) for a further 30 days, then slowly reverting to no social distancing (_act rate_ = 8.5 per day) over the subsequent 90 day period |
| Scenario 10 | As per scenario 09 but, immediately following the full "lock-down" period between 30 March and 30 April 2020, there is a linear increase of the isolation rate (per day) from 33% to 66% over a 30 day period through to 28 May 2020, with subsequent maintenance of self-isolation with high compliance (66% per day) on an ongoing basis. |
### Software and code
COVOID is implemented on top of EpiModel v1.8 [@epimodel2020] running on R version 3.6.1 [@rproject2020]. The COVOID model is described in more detail in the technical blog of the first author [@churches2020], and all the code used for the simulations reported here is available at https://gist.github.com/timchurches/ce8858ae1e572153a54271bd52deb9c3 and https://gist.github.com/timchurches/95204f0565b0311ec32408a7e27c0f7f.
## Results
### Computing resources
The twelve simulations reported in this paper were each run eight times and the results averaged, taking approximately 60 minutes to complete when running in parallel on an eight-core Intel CPU. The same set of simulations for a population of 1,000,000 were also run successfully on the same hardware, taking approximately 3 hours and utilising less than 16GB of RAM, suggesting that run times scale as a low-order power of the population size. Running on one, two, four or eight CPU cores resulted in near-linear reductions in total run times, which is expected given that each simulation run is independent. Scaling to use more CPU cores is automatic, and near real-time response would be possible on suitably-sized cloud computing infrastructure, if required.
### Baseline model
The results of the baseline model simulated for a hypothetical population of 100,000 people, without any public health interventions, is shown in Figure 2. Unsurprisingly, nearly 90% of the population are infected within two months, with several thousand projected deaths due to COVID-19 infection. These projections are unrealistic, because a complete lack of public health intervention, or equivalent spontaneous behaviour modification in the population) has not occurred anywhere, but they serve to show that the baseline model produces the expected results.
```{r load-covoid-code, echo=FALSE, eval=TRUE, message=FALSE}
source_files <- c("_icm.mod.init.seiqhrf.R",
"_icm.mod.status.seiqhrf.R",
"_icm.mod.vital.seiqhrf.R",
"_icm.control.seiqhrf.R",
"_icm.utils.seiqhrf.R",
"_icm.saveout.seiqhrf.R",
"_icm.icm.seiqhrf.R")
src_path <- paste0("./_posts/2020-03-18-modelling-the-effects-of-public-health-",
"interventions-on-covid-19-transmission-part-2/")
gist_url <- "https://gist.github.com/timchurches/92073d0ea75cfbd387f91f7c6e624bd7"
local_source <- FALSE
for (source_file in source_files) {
if (local_source) {
source(paste(src_path, source_file, sep=""))
} else {
source_gist(gist_url, filename=source_file)
}
}
```
```{r baseline-100000-sim-setup, echo=FALSE, eval=TRUE}
# function to set-up and run the baseline simulations
hc_scaler <- 10 # 1 for 10,000 pop, 10 for 100,000 pop, 100 for 1,000,000 pop, 480 for 4.8M pop
baseline_act_rate_ei <- 8.5
simulate <- function(# control.icm params
type = "SEIQHRF",
nsteps = 366,
nsims = 8,
ncores = 8,
prog.rand = FALSE,
rec.rand = FALSE,
fat.rand = FALSE, # TRUE,
quar.rand = FALSE,
hosp.rand = FALSE,
disch.rand = FALSE,
infection.FUN = infection.seiqhrf.icm,
recovery.FUN = progress.seiqhrf.icm,
departures.FUN = departures.seiqhrf.icm,
arrivals.FUN = arrivals.icm,
get_prev.FUN = get_prev.seiqhrf.icm,
# init.icm params
s.num = 100000,
e.num=4, # 60% asymptomatic ratio
i.num = 3, # 3 cases in Sydney eastern suburbs region in 21 days through and including 10th March
q.num=0,
h.num=0,
r.num = 0,
f.num = 0,
# param.icm params
inf.prob.e = 0.02,
act.rate.e = baseline_act_rate_ei, # 10,
inf.prob.i = 0.05,
act.rate.i = baseline_act_rate_ei, # 10,
inf.prob.q = 0.02,
act.rate.q = 1.5,
quar.rate = 1/30,
hosp.rate = 1/100,
disch.rate = 1/20,
prog.rate = 1/10,
prog.dist.scale = 5,
prog.dist.shape = 1.5,
rec.rate = 1/20,
rec.dist.scale = 35,
rec.dist.shape = 1.5,
fat.rate.base = 1/50,
hosp.cap = 40*hc_scaler,
fat.rate.overcap = 1/25,
fat.tcoeff = 0.5,
vital = TRUE,
a.rate = (10.5/365)/1000,
a.prop.e = 0.01,
a.prop.i = 0.001,
a.prop.q = 0.01,
ds.rate = (7/365)/1000,
de.rate = (7/365)/1000,
di.rate = (7/365)/1000,
dq.rate = (7/365)/1000,
dh.rate = (20/365)/1000,
dr.rate = (7/365)/1000,
out="mean"
) {
control <- control.icm(type = type,
nsteps = nsteps,
nsims = nsims,
ncores = ncores,
prog.rand = prog.rand,
rec.rand = rec.rand,
infection.FUN = infection.FUN,
recovery.FUN = recovery.FUN,
arrivals.FUN = arrivals.FUN,
departures.FUN = departures.FUN,
get_prev.FUN = get_prev.FUN)
init <- init.icm(s.num = s.num,
e.num = e.num,
i.num = i.num,
q.num = q.num,
h.num = h.num,
r.num = r.num,
f.num = f.num)
param <- param.icm(inf.prob.e = inf.prob.e,
act.rate.e = act.rate.e,
inf.prob.i = inf.prob.i,
act.rate.i = act.rate.i,
inf.prob.q = inf.prob.q,
act.rate.q = act.rate.q,
quar.rate = quar.rate,
hosp.rate = hosp.rate,
disch.rate = disch.rate,
prog.rate = prog.rate,
prog.dist.scale = prog.dist.scale,
prog.dist.shape = prog.dist.shape,
rec.rate = rec.rate,
rec.dist.scale = rec.dist.scale,
rec.dist.shape = rec.dist.shape,
fat.rate.base = fat.rate.base,
hosp.cap = hosp.cap,
fat.rate.overcap = fat.rate.overcap,
fat.tcoeff = fat.tcoeff,
vital = vital,
a.rate = a.rate,
a.prop.e = a.prop.e,
a.prop.i = a.prop.i,
a.prop.q = a.prop.q,
ds.rate = ds.rate,
de.rate = de.rate,
di.rate = di.rate,
dq.rate = dq.rate,
dh.rate = dh.rate,
dr.rate = dr.rate)
sim <- icm.seiqhrf(param, init, control)
sim_df <- as.data.frame(sim, out=out)
return(list(sim=sim, df=sim_df))
}
```
```{r baseline-sim, echo=FALSE, eval=TRUE}
baseline_sim <- simulate(ncores=8)
```
```{r baseline-sim-figure-2a, echo=FALSE, eval=TRUE, message=FALSE, warning=FALSE, fig.cap="Figure 2: baseline simulation with hypothetical 100,000 population"}
baseline_plot_df <- baseline_sim$df %>%
# use only the prevalence columns
select(time, s.num, e.num, i.num, se.flow, q.num,
h.num, r.num, f.num) %>%
pivot_longer(-c(time),
names_to="compartment",
values_to="count") %>%
filter(time <= 250) %>%
mutate(Date = ymd("2020-03-01") + days(time))
# define a standard set of colours to represent compartments
compcols <- c("s.num" = "orange", "e.num" = "pink", "i.num" = "red", "se.flow" = "purple", "obs.incidence" = "blue",
"q.num" = "cyan", "h.num" = "magenta", "r.num" = "lightgreen",
"f.num" = "black")
complabels <- c("s.num" = "S (susceptible)", "e.num" = "A (infected/asymptomatic)",
"i.num" = "I (infected/infectious)", "se.flow" = "Predicted incidence",
"obs.incidence" = "Observed incidence", "q.num" = "Q (isolated)",
"h.num" = "H (requires hospitalisation)", "r.num" = "R (recovered)",
"f.num" = "F (deaths due to COVID-19)")
complabels_wrapped <- c("s.num" = "S (susceptible)", "e.num" = "A (infected/asymptomatic)",
"i.num" = "I (infected/infectious)", "se.flow" = "Predicted incidence",
"obs.incidence" = "Observed incidence", "q.num" = "Q (isolated)",
"h.num" = "H (requires\nhospitalisation)", "r.num" = "R (recovered)",
"f.num" = "F (deaths due\nto COVID-19)")
baseline_plot_df %>%
# examine only the first 150 days since it
# is all over by then using the default parameters
filter(time <= 150) %>%
ggplot(aes(x=Date, y=count, colour=compartment)) +
geom_line(size=2, alpha=0.5) +
scale_colour_manual(values = compcols, labels=complabels, name="Compartment") +
scale_y_continuous(labels = scales::comma) +
theme_minimal() +
labs(x="Date (simulated)",
y="Prevalence (persons)")
```
```{r baseline-sim-figure-2b, echo=FALSE, eval=FALSE, message=FALSE, warning=FALSE, fig.cap="Figure 2b: baseline simulation"}
baseline_plot_df %>%
filter(compartment %in% c("e.num","i.num", "se.flow",
"q.num","h.num",
"f.num")) %>%
filter(time <= 150) %>%
ggplot(aes(x=Date, y=count, colour=compartment)) +
geom_line(size=2, alpha=0.5) +
scale_colour_manual(values = compcols, labels=complabels, name="Compartment") +
scale_y_continuous(labels = scales::comma) +
theme_minimal() +
labs(x="Date (simulated)",
y="Prevalence (persons)")
```
```{r extract-timings-setup, echo=FALSE, eval=TRUE}
# define a function to extract timings and assemble a data frame
get_times <- function(simulate_results) {
sim <- simulate_results$sim
for (s in 1:sim$control$nsims) {
if (s == 1) {
times <- sim$times[[paste("sim",s,sep="")]]
times <- times %>% mutate(s=s)
} else {
times <- times %>%
bind_rows(sim$times[[paste("sim",s,sep="")]] %>%
mutate(s=s))
}
}
times <- times %>%
mutate(infTime=ifelse(infTime <0, -5, infTime),
expTime=ifelse(expTime <0, -5, expTime)) %>%
mutate(incubation_period = infTime - expTime,
illness_duration = recovTime - expTime,
illness_duration_hosp = dischTime - expTime,
hosp_los = dischTime - hospTime,
quarantine_delay = quarTime - infTime,
survival_time = fatTime - infTime) %>%
select(s,
incubation_period,
quarantine_delay,
illness_duration,
illness_duration_hosp,
hosp_los,
survival_time) %>%
pivot_longer(-s, names_to="period_type",
values_to="duration") %>%
mutate(period_type = factor(period_type, levels=c("incubation_period",
"quarantine_delay",
"illness_duration",
"illness_duration_hosp",
"hosp_los",
"survival_time"),
labels=c("Incubation period",
"Delay entering isolation",
"Illness duration",
"Illness duration (hosp)",
"Hospital care required duration",
"Survival time of case fatalities"),
ordered = TRUE))
return(times)
}
```
```{r extract-timings, echo=FALSE, eval=TRUE}
times <- get_times(baseline_sim)
```
An important but rarely reported aspect of simulation models is the distribution of (simulated) persons in each compartment of the model. This provides additional assurance that flows between compartments reflect known or expected distributions of real-life times in various disease states corresponding to the compartments. The distribution of durations in key model compartments for the baseline model are shown in Figure 3.
```{r fig-3-visualise-timings, echo=FALSE, eval=TRUE, fig.cap="Figure 3: Distributions of time in each compartment in baseline model"}
times %>%
filter(duration <= 30,
period_type != "Hospital care required duration") %>%
ggplot(aes(x=duration, fill=period_type)) +
geom_bar() +
facet_grid(period_type~., scales="free_y", labeller = labeller(period_type = label_wrap_gen(15))) +
scale_y_continuous(labels = scales::comma) +
labs(y="Frequency",
x="Days in compartment") +
theme_minimal() +
easy_remove_legend()
```
### Social distancing scenarios with varying compliance modelled at 30 March 2020
The results of COVOID modelling of 90-day periods of social distancing with instantaneous effect and varying levels of compliance, based on interventions modelled by Chang _et al_. [@chang2020] are shown in Figure 4. Social distancing with at least 80% compliance completely suppresses the epidemic for the duration of the intervention, while compliance of 70% still substantially reduced cases and deaths. In each of these scenarios, cases rebounded dramatically once social distancing is relaxed, demonstrating that ongoing control measures will be required. These findings are very similar overall to those reported by Chang _et al._ [@chang2020], noting the differences in time frames due to the different population sizes being modelled.
We modelled two additional scenarios, of 60% and 50% compliance with social distancing, and found that although these flatten the epidemic curve compared to the baseline scenario, transmission is not halted, and substantial numbers of cases and deaths occur during the intervention period. In the 50% compliance scenario, hospital capacity is overwhelmed during the intervention period. However, sufficient herd immunity is attained in the 50% social distancing scenario to prevent any second wave of infection after social distancing is relaxed, at the expense of considerable morbidity and mortality and an overwhelmed hospital system while social distancing is in place.
```{r elongate-function-definition, echo=FALSE, eval=TRUE, warning=FALSE}
elongate <- function(sim) {
sim_df <- sim$df %>%
# use only the prevalence columns
select(time, s.num, e.num, i.num, se.flow, q.num,
h.num, r.num, f.num) %>%
filter(time <= 250) %>%
pivot_longer(-c(time),
names_to="compartment",
values_to="count")
return(sim_df)
}
```
```{r prokopenko_scenario_1, echo=FALSE, eval=TRUE}
# Baseline plus Social distancing 90% compliance
ld_act_rate <- 0.1*baseline_act_rate_ei + 0.9*1
ninety_day_lockdown_90percent_vector <-c(rep(baseline_act_rate_ei, 17), rep(ld_act_rate, 90), rep(baseline_act_rate_ei, 259))
exp1_ninety_day_lockdown_90percent_sim <- simulate(act.rate.i = ninety_day_lockdown_90percent_vector,
act.rate.e = ninety_day_lockdown_90percent_vector)
```
```{r prokopenko_scenario_2, echo=FALSE, eval=TRUE}
# Baseline plus Social distancing 80% compliance
ld_act_rate <- 0.2*baseline_act_rate_ei + 0.8*1
ninety_day_lockdown_80percent_vector <-c(rep(baseline_act_rate_ei, 17), rep(ld_act_rate, 90), rep(baseline_act_rate_ei, 259))
exp2_ninety_day_lockdown_80percent_sim <- simulate(act.rate.i = ninety_day_lockdown_80percent_vector,
act.rate.e = ninety_day_lockdown_80percent_vector)
```
```{r prokopenko_scenario_3, echo=FALSE, eval=TRUE}
# Baseline plus Social distancing 70% compliance
ld_act_rate <- 0.3*baseline_act_rate_ei + 0.7*1
ninety_day_lockdown_70percent_vector <-c(rep(baseline_act_rate_ei, 17), rep(ld_act_rate, 90), rep(baseline_act_rate_ei, 259))
exp3_ninety_day_lockdown_70percent_sim <- simulate(act.rate.i = ninety_day_lockdown_70percent_vector,
act.rate.e = ninety_day_lockdown_70percent_vector)
```
```{r prokopenko_scenario_4, echo=FALSE, eval=TRUE}
# Baseline plus Social distancing 60% compliance
ld_act_rate <- 0.4*baseline_act_rate_ei + 0.6*1
ninety_day_lockdown_60percent_vector <-c(rep(baseline_act_rate_ei, 17), rep(ld_act_rate, 90), rep(baseline_act_rate_ei, 259))
exp4_ninety_day_lockdown_60percent_sim <- simulate(act.rate.i = ninety_day_lockdown_60percent_vector,
act.rate.e = ninety_day_lockdown_60percent_vector)
```
```{r prokopenko_scenario_5, echo=FALSE, eval=TRUE}
# Baseline plus Social distancing 50% compliance
ld_act_rate <- 0.5*baseline_act_rate_ei + 0.5*1
ninety_day_lockdown_50percent_vector <-c(rep(baseline_act_rate_ei, 17), rep(ld_act_rate, 90), rep(baseline_act_rate_ei, 259))
exp5_ninety_day_lockdown_50percent_sim <- simulate(act.rate.i = ninety_day_lockdown_50percent_vector,
act.rate.e = ninety_day_lockdown_50percent_vector)
```
```{r fig-4-sims-post-processing, echo=FALSE, eval=TRUE, warning=FALSE, cache=FALSE}
# Now let's examine the results.
exp1_ninety_day_lockdown_90percent_sim_df <- elongate(exp1_ninety_day_lockdown_90percent_sim)
exp2_ninety_day_lockdown_80percent_sim_df <- elongate(exp2_ninety_day_lockdown_80percent_sim)
exp3_ninety_day_lockdown_70percent_sim_df <- elongate(exp3_ninety_day_lockdown_70percent_sim)
exp4_ninety_day_lockdown_60percent_sim_df <- elongate(exp4_ninety_day_lockdown_60percent_sim)
exp5_ninety_day_lockdown_50percent_sim_df <- elongate(exp5_ninety_day_lockdown_50percent_sim)
baseline_scenarios_01_05_df <- baseline_plot_df %>%
mutate(experiment="Baseline") %>%
bind_rows(exp1_ninety_day_lockdown_90percent_sim_df %>%
mutate(experiment="Scenario 01 - 90% social distancing")) %>%
bind_rows(exp2_ninety_day_lockdown_80percent_sim_df %>%
mutate(experiment="Scenario 02 - 80% social distancing")) %>%
bind_rows(exp3_ninety_day_lockdown_70percent_sim_df %>%
mutate(experiment="Scenario 03 - 70% social distancing")) %>%
bind_rows(exp4_ninety_day_lockdown_60percent_sim_df %>%
mutate(experiment="Scenario 04 - 60% social distancing")) %>%
bind_rows(exp5_ninety_day_lockdown_50percent_sim_df %>%
mutate(experiment="Scenario 05 - 50% social distancing")) %>%
mutate(NDate = ymd("2020-03-01") + days(time))
combined_plot_df <- baseline_scenarios_01_05_df %>%
mutate(xmin=if_else(experiment != "Baseline", ymd("2020-03-01") + days(17), NA_Date_),
ymin=if_else(experiment != "Baseline", -Inf, NA_real_),
xmax=if_else(experiment != "Baseline", ymd("2020-03-01") + days(17+90), NA_Date_),
ymax=if_else(experiment != "Baseline", Inf, NA_real_),
greylabel = case_when(stringr::str_starts(experiment,
"Scenario 01") ~ "90% social\ndistancing\nfor 90 days",
stringr::str_starts(experiment,
"Scenario 02") ~ "80% social\ndistancing\nfor 90 days",
stringr::str_starts(experiment,
"Scenario 03") ~ "70% social\ndistancing\nfor 90 days",
stringr::str_starts(experiment,
"Scenario 04") ~ "60% social\ndistancing\nfor 90 days",
stringr::str_starts(experiment,
"Scenario 05") ~ "50% social\ndistancing\nfor 90 days",
TRUE ~ NA_character_))
fiftyshades <- c("Baseline" = "white",
"Scenario 01 - 90% social distancing" = "grey22",
"Scenario 02 - 80% social distancing" = "grey28",
"Scenario 03 - 70% social distancing" = "grey34",
"Scenario 04 - 60% social distancing" = "grey40",
"Scenario 05 - 50% social distancing" = "grey46")
scalecols <- c(compcols,fiftyshades)
scalelabs <- c(complabels,fiftyshades)
```
```{r figure-4-plot, echo=FALSE, eval=TRUE, warning=FALSE, fig.cap="Figure 4: Social distancing scenarios with varying compliance modelled at 30 March 2020"}
p1 <- combined_plot_df %>%
filter(compartment %in% c("e.num","i.num",
"q.num")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_rect(aes(xmin=xmin, xmax=xmax,
ymin=ymin, ymax=ymax),
alpha=0.2,
fill="grey88",
colour="grey88") +
geom_line(size=1, alpha=0.5) +
geom_label(aes(x=ymd("2020-03-01") + days(17+90/2), y=22000,label=greylabel),
size=1.5,colour="grey40", alpha=0.5) +
facet_grid(experiment ~ .) +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="date (simulated)",
y="Prevalence (persons)") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank(),
strip.text.y = element_blank()) +
easy_plot_legend_size(size=7)
p2 <- combined_plot_df %>%
filter(compartment %in% c("s.num", "r.num")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_rect(aes(xmin=xmin, xmax=xmax,
ymin=ymin, ymax=ymax),
alpha=0.2,
fill="grey88",
colour="grey88") +
geom_line(size=1, alpha=0.5) +
facet_grid(experiment ~ .) +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="Date (simulated)",
y="") +
easy_all_text_size(size=6) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_blank())
p3 <- combined_plot_df %>%
filter(compartment %in% c("h.num",
"f.num")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_rect(aes(xmin=xmin, xmax=xmax,
ymin=ymin, ymax=ymax),
alpha=0.2,
fill="grey88",
colour="grey88") +
geom_line(size=1, alpha=0.5) +
geom_hline(yintercept=hc_scaler*40, colour="red") +
annotate("text", x = ymd("2020-03-01") + days(220), y = hc_scaler*(40 - 0),
label = "Hospital\ncapacity", size=1.7) +
facet_grid(experiment ~ ., labeller = labeller(experiment = label_wrap_gen(15))) +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) +
labs(x="Date (simulated)",
y="") +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
easy_all_text_size(size=6) +
easy_plot_legend_size(size=7) +
theme(strip.text.y = element_text(size = 6))
p1 + p2 + p3
```
### Comparison of modelled interventions versus observed epidemic curves in Sydney, Australia as at 30 April 2020
The results of COVOID modelling of the eastern Sydney population, using the same parameters as the baseline model shown above, are shown in Figure 5. Unsurprisingly, hospital capacity is quickly exceeded, resulting in a large number of deaths as people die without receiving adequate medical care. However, as can be seen in teh left-hand panel of Figure 5, in retrospect this scenario is also completely unrealistic. The results using various scenarios that approximate public health interventions as they occurred in NSW, Australia during March and April 2020 are shown in Figure 6 and Figure 7. The actual, observed incidence of confirmed COVID-19 infections in the same eastern Sydney population is similarly shown in the left two columns in those figures. Under all scenarios, compared to the baseline simulation, the COVID-19 epidemic curve is substantially flattened and “shrunk” due to case-based interventions, specifically isolation and self-isolation of all symptomatic and/or test-positive cases with moderate alacrity (33% of cases entering isolation each day post symptom onset or test result). Under none of the modelled intervention scenarios does the the number of cases requiring hospitalisation overwhelm assumed hospital capacity, but a significant number of deaths nevertheless occur in several of the scenarios.
Scenario 06 demonstrates that moderate compliance with self-isolation, with no increase in social distancing, substantially dampens the epidemic and reduces deaths by 50%. Scenario 07, which adds one month of moderate social distancing (at considerable social and economic cost), shows that the epidemic is merely delayed by the social distancing, and the final result is almost identical to the case where no social distancing was attempted.
Scenario 08, in which substantial social distancing, effectively "lockdown" (80% reduction in average contacts), is implemented for one month, followed by a relaxation of social distancing to approximately 50% of baseline levels results in only a small initial epidemic, which closely resembles the observed data in both magnitude and duration, with ongoing suppression, but not complete elimination, of cases following the relaxation of the lock-down period.
Scenario 09, which is the same as scenario 08 except that social distancing slowly relaxes all the way back to baseline levels, results in a "second wave" which is much better than the first, but still only one-tenth the size of the no-intervention model epidemic.
Scenario 10 is the same as scenario 09 except that the isolation rate is increased, post-lockdown, to double the level in the other scenarios. This simulates very high testing rates and very efficient case-based interventions. The result is almost complete suppression of any second or subsequent waves, despite social distancing slowly being relaxed to baseline levels.
```{r baseline-se-sydney-sim-setup, echo=FALSE, eval=TRUE}
# function to set-up and run the baseline simulations
hc_scaler <- 29 # 1 for 10,000 pop, 10 for 100,000 pop, 100 for 1,000,000 pop, 480 for 4.8M pop
baseline_act_rate_ei <- 8.5
simulate <- function(# control.icm params
type = "SEIQHRF",
nsteps = 366,
nsims = 8,
ncores = 8,
prog.rand = FALSE,
rec.rand = FALSE,
fat.rand = FALSE, # TRUE,
quar.rand = FALSE,
hosp.rand = FALSE,
disch.rand = FALSE,
infection.FUN = infection.seiqhrf.icm,
recovery.FUN = progress.seiqhrf.icm,
departures.FUN = departures.seiqhrf.icm,
arrivals.FUN = arrivals.icm,
get_prev.FUN = get_prev.seiqhrf.icm,
# init.icm params
s.num = 287337, # Waverly, Woolahra and Randwick LGAs
e.num=4, # 60% asymptomatic ratio
i.num = 3, # 3 cases in Sydney eastern suburbs region in 21 days through and including 10th March
q.num=0,
h.num=0,
r.num = 0,
f.num = 0,
# param.icm params
inf.prob.e = 0.02,
act.rate.e = baseline_act_rate_ei, # 10,
inf.prob.i = 0.05,
act.rate.i = baseline_act_rate_ei, # 10,
inf.prob.q = 0.02,
act.rate.q = 1.5,
quar.rate = 1/30,
hosp.rate = 1/100,
disch.rate = 1/20,
prog.rate = 1/10,
prog.dist.scale = 5,
prog.dist.shape = 1.5,
rec.rate = 1/20,
rec.dist.scale = 35,
rec.dist.shape = 1.5,
fat.rate.base = 1/50,
hosp.cap = 40*hc_scaler,
fat.rate.overcap = 1/25,
fat.tcoeff = 0.5,
vital = TRUE,
a.rate = (10.5/365)/1000,
a.prop.e = 0.01,
a.prop.i = 0.001,
a.prop.q = 0.01,
ds.rate = (7/365)/1000,
de.rate = (7/365)/1000,
di.rate = (7/365)/1000,
dq.rate = (7/365)/1000,
dh.rate = (20/365)/1000,
dr.rate = (7/365)/1000,
out="mean"
) {
control <- control.icm(type = type,
nsteps = nsteps,
nsims = nsims,
ncores = ncores,
prog.rand = prog.rand,
rec.rand = rec.rand,
infection.FUN = infection.FUN,
recovery.FUN = recovery.FUN,
arrivals.FUN = arrivals.FUN,
departures.FUN = departures.FUN,
get_prev.FUN = get_prev.FUN)
init <- init.icm(s.num = s.num,
e.num = e.num,
i.num = i.num,
q.num = q.num,
h.num = h.num,
r.num = r.num,
f.num = f.num)
param <- param.icm(inf.prob.e = inf.prob.e,
act.rate.e = act.rate.e,
inf.prob.i = inf.prob.i,
act.rate.i = act.rate.i,
inf.prob.q = inf.prob.q,
act.rate.q = act.rate.q,
quar.rate = quar.rate,
hosp.rate = hosp.rate,
disch.rate = disch.rate,
prog.rate = prog.rate,
prog.dist.scale = prog.dist.scale,
prog.dist.shape = prog.dist.shape,
rec.rate = rec.rate,
rec.dist.scale = rec.dist.scale,
rec.dist.shape = rec.dist.shape,
fat.rate.base = fat.rate.base,
hosp.cap = hosp.cap,
fat.rate.overcap = fat.rate.overcap,
fat.tcoeff = fat.tcoeff,
vital = vital,
a.rate = a.rate,
a.prop.e = a.prop.e,
a.prop.i = a.prop.i,
a.prop.q = a.prop.q,
ds.rate = ds.rate,
de.rate = de.rate,
di.rate = di.rate,
dq.rate = dq.rate,
dh.rate = dh.rate,
dr.rate = dr.rate)
sim <- icm.seiqhrf(param, init, control)
sim_df <- as.data.frame(sim, out=out)
return(list(sim=sim, df=sim_df))
}
```
```{r eastern-sydney-baseline-sim, echo=FALSE, eval=TRUE}
eastern_sydney_baseline_sim <- simulate()
```
```{r scenario-6-sim, echo=FALSE, eval=TRUE}
# Ramp up self-isolation rate, starting at day 1, ramp up to 0.3333, over 15 day period, then ongoing.
isolation_ramp <- function(t) {
ifelse(t <= 2, 0.0333, ifelse(t <= 17, 0.0333 + (t-3)*(0.3333 - 0.03333)/15, 0.3333))
}
exp6_sim <- simulate(quar.rate = isolation_ramp(1:366))
```
```{r scenario-7-sim, echo=FALSE, eval=TRUE}
# As per scenario 6 plus a moderate increase in social distancing to 50% (4.75) for everyone (reducing the `act.rate` from `r baseline_act_rate_ei`), again ramping it down between days 15 and 30, then maintain at 4.75 for a further 45 days, then revert to `r baseline_act_rate_ei`.
social_distance_ramp <- function(t) {
ifelse(t <= 17, baseline_act_rate_ei, ifelse(t <= 32, baseline_act_rate_ei - (t-17)*(baseline_act_rate_ei - 4.75)/15, ifelse(t <= 77, 4.75, baseline_act_rate_ei)))
}
exp7_sim <- simulate(act.rate.i = social_distance_ramp(1:366),
act.rate.e = social_distance_ramp(1:366),
quar.rate = isolation_ramp(1:366))
```
```{r scenario-8-sim, echo=FALSE, eval=TRUE}
# As per scenario 7 but four week instantaneous lockdown (act.rate = 2.5) starting at day 30, then reversion to baseline act.rate of baseline_act_rate_ei.
social_distance_ramp8 <- function(t) {
ifelse(t <= 17, baseline_act_rate_ei,ifelse(t <= 32, baseline_act_rate_ei - (t-17)*(baseline_act_rate_ei - 2.5)/15, ifelse(t <= 62, 2.5, 4.75)))
}
exp8_sim <- simulate(act.rate.i = social_distance_ramp8(1:366),
act.rate.e = social_distance_ramp8(1:366),
quar.rate = isolation_ramp(1:366))
```
```{r scenario-9-sim, echo=FALSE, eval=TRUE}
# As per scenario 8 but eight week lockdown starting at day 30.
social_distance_ramp9 <- function(t) {
ifelse(t <= 17,
baseline_act_rate_ei,
ifelse(t <= 32,
baseline_act_rate_ei - (t-17)*(baseline_act_rate_ei - 2.5)/15,
ifelse(t <= 62,
2.5,
ifelse(t <=152,
2.5 + ((t-62)*(baseline_act_rate_ei - 2.5)/90),
baseline_act_rate_ei)
)
)
)
}
exp9_sim <- simulate(act.rate.i = social_distance_ramp9(1:366),
act.rate.e = social_distance_ramp9(1:366),
quar.rate = isolation_ramp(1:366))
```
```{r scenario-10-sim, echo=FALSE, eval=TRUE}
# As per scenario 9 but eight week lockdown starting at day 30, followed by self-isolation with high compliance (66% per day) on an ongoing basis.
post_lockdown_isolation_ramp <- function(t) {
ifelse(t <= 2, 0.0333,
ifelse(t <= 17,
(0.0333 + (t-3)*(0.3333 - 0.03333)/15),
ifelse(t <= 62,
0.3333,
ifelse(t <=92,
(0.3333 + (t-62)*(0.6666 - 0.3333)/30),
0.6666)
)
)
)
}
exp10_sim <- simulate(act.rate.i = social_distance_ramp9(1:366),
act.rate.e = social_distance_ramp9(1:366),
quar.rate = post_lockdown_isolation_ramp(1:366))
```
```{r assemble-sim-results, echo=FALSE, eval=TRUE, warning=FALSE, cache=FALSE}
eastern_sydney_baseline_sim_df <- elongate(eastern_sydney_baseline_sim)
exp6_sim_df <- elongate(exp6_sim)
exp7_sim_df <- elongate(exp7_sim)
exp8_sim_df <- elongate(exp8_sim)
exp9_sim_df <- elongate(exp9_sim)
exp10_sim_df <- elongate(exp10_sim)
eastern_sydney_baseline_plot_df <- eastern_sydney_baseline_sim_df %>%
mutate(experiment="Baseline") %>%
mutate(line_start = NA_Date_,
line_end = NA_Date_,
line_y = NA_real_,
line_label = NA_character_) %>%
mutate(xmin= NA_Date_,
ymin= NA_real_,
xmax= NA_Date_,
ymax= NA_real_,
greylabel = NA_character_) %>%
mutate(NDate = ymd("2020-03-01") + days(time))
scenarios_06_10_df <- exp6_sim_df %>%
mutate(experiment="Scenario 06") %>%
bind_rows(exp7_sim_df %>%
mutate(experiment="Scenario 07")) %>%
bind_rows(exp8_sim_df %>%
mutate(experiment="Scenario 08")) %>%
bind_rows(exp9_sim_df %>%
mutate(experiment="Scenario 09")) %>%
bind_rows(exp10_sim_df %>%
mutate(experiment="Scenario 10")) %>%
mutate(NDate = ymd("2020-03-01") + days(time))
baseline_scenarios_06_10_df <- eastern_sydney_baseline_sim_df %>%
mutate(experiment="Baseline") %>%
mutate(NDate = ymd("2020-03-01") + days(time)) %>%
bind_rows(scenarios_06_10_df)
novel_combined_plot_df <- scenarios_06_10_df %>%
mutate(line_start = case_when(experiment == "Scenario 06" ~ ymd("2020-03-01") + days(17),
experiment == "Scenario 07" ~ ymd("2020-03-01") + days(17),
experiment == "Scenario 08" ~ ymd("2020-03-01") + days(17),
experiment == "Scenario 09" ~ ymd("2020-03-01") + days(17),
experiment == "Scenario 10" ~ ymd("2020-03-01") + days(17),
TRUE ~ NA_Date_),
line_end = case_when(experiment == "Scenario 06" ~ ymd("2020-03-01") + days(250),
experiment == "Scenario 07" ~ ymd("2020-03-01") + days(250),
experiment == "Scenario 08" ~ ymd("2020-03-01") + days(250),
experiment == "Scenario 09" ~ ymd("2020-03-01") + days(250),
experiment == "Scenario 10" ~ ymd("2020-03-01") + days(250),
TRUE ~ NA_Date_),
line_y = case_when(experiment == "Scenario 06" ~ 1000, # was Inf
experiment == "Scenario 07" ~ 1000,
experiment == "Scenario 08" ~ 1000,
experiment == "Scenario 09" ~ 1000,
experiment == "Scenario 10" ~ 1000,
TRUE ~ NA_real_),
line_label = case_when(experiment == "Scenario 06" ~ "Ramp up isolation rate per day\nto 33% by day 15",
experiment == "Scenario 07" ~ "Ramp up isolation rate per day\nto 33% by day 15",
experiment == "Scenario 08" ~ "Ramp up isolation rate per day\nto 33% by day 15",
experiment == "Scenario 09" ~ "Ramp up isolation rate per day\nto 33% by day 15",
experiment == "Scenario 10" ~ "Ramp up isolation rate per day\nto 33% by day 15 then\n66% isolation rate following relaxation\n",
TRUE ~ NA_character_)) %>%
mutate(xmin=case_when(stringr::str_starts(experiment, "Scenario 06") ~ NA_Date_,
stringr::str_starts(experiment, "Scenario 07") ~ NA_Date_,
stringr::str_starts(experiment, "Scenario 08") ~ ymd("2020-03-01") + days(17),
stringr::str_starts(experiment, "Scenario 09") ~ ymd("2020-03-01") + days(32),
stringr::str_starts(experiment, "Scenario 10") ~ ymd("2020-03-01") + days(32),
TRUE ~ NA_Date_),
ymin=case_when(stringr::str_starts(experiment, "Scenario 06") ~ NA_real_,
stringr::str_starts(experiment, "Scenario 07") ~ NA_real_,
stringr::str_starts(experiment, "Scenario 08") ~ -Inf,
stringr::str_starts(experiment, "Scenario 09") ~ -Inf,
stringr::str_starts(experiment, "Scenario 10") ~ -Inf,
TRUE ~ NA_real_),
xmax=case_when(stringr::str_starts(experiment, "Scenario 06") ~ NA_Date_,
stringr::str_starts(experiment, "Scenario 07") ~ NA_Date_,
stringr::str_starts(experiment, "Scenario 08") ~ ymd("2020-03-01") + days(17 + 60),
stringr::str_starts(experiment, "Scenario 09") ~ ymd("2020-03-01") + days(32 + 30),
stringr::str_starts(experiment, "Scenario 10") ~ ymd("2020-03-01") + days(32 + 60),
TRUE ~ NA_Date_),
ymax=case_when(stringr::str_starts(experiment, "Scenario 06") ~ NA_real_,
stringr::str_starts(experiment, "Scenario 07") ~ NA_real_,
stringr::str_starts(experiment, "Scenario 08") ~ 40, # was Inf
stringr::str_starts(experiment, "Scenario 09") ~ 40,
stringr::str_starts(experiment, "Scenario 10") ~ 40,
TRUE ~ NA_real_),
greylabel = case_when(stringr::str_starts(experiment, "Scenario 06") ~ NA_character_,
stringr::str_starts(experiment, "Scenario 07") ~ NA_character_,
stringr::str_starts(experiment,
"Scenario 08") ~ "Ramp to 50%\nsocial\ndistancing\nfor 60 days",
stringr::str_starts(experiment,
"Scenario 09") ~ "90% social\ndistancing\nfor 30 days",
stringr::str_starts(experiment,
"Scenario 10") ~ "90% social\ndistancing\nfor 60 days",
stringr::str_starts(experiment,
"Scenario 11") ~ "90% social\ndistancing\nfor 60 days",
TRUE ~ NA_character_))
```
```{r fig-5-visualise-eastern-sydney-baseline-sim-results, echo=FALSE, eval=TRUE, warning=FALSE, fig.height=2.8333, fig.width=6.25, fig.cap="Figure 5: Eastern Sydney baseline simulation, no interventions"}
p2a <- eastern_sydney_baseline_plot_df %>%
bind_rows(se_sydney_incidence) %>%
filter(compartment %in% c("se.flow", "obs.incidence")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=0.5, alpha=0.5) +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) + # , limits = c(NA, 50)
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="Date (simulated and real)",
y="Incidence (persons)") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank(),
strip.text.y = element_blank()) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p3 <- eastern_sydney_baseline_plot_df %>%
filter(compartment %in% c("h.num",
"f.num")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=1, alpha=0.7) +
geom_hline(yintercept=hc_scaler*40, colour="red") +
annotate("text", x = ymd("2020-03-01") + days(210), y = hc_scaler*(40 - 0),
label = "Hospital\ncapacity", size=1.7) +
scale_colour_manual(values = compcols,
labels=complabels_wrapped,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) +
labs(x="Date (simulated)",
y="Prevalence (persons)") +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
easy_all_text_size(size=6) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p2a + p3 + plot_layout(ncol=2, nrow=1)
```
```{r figs-6-and-7-prep-intervention-data, echo=FALSE, eval=TRUE}
sim6_interventions <- tibble(time=1:366,
isolation_rate=isolation_ramp(1:366),
act_rate=8.5,
experiment="Scenario 06")
sim7_interventions <- tibble(time=1:366,
isolation_rate=isolation_ramp(1:366),
act_rate=social_distance_ramp(1:366),
experiment="Scenario 07")
sim8_interventions <- tibble(time=1:366,
isolation_rate=isolation_ramp(1:366),
act_rate=social_distance_ramp8(1:366),
experiment="Scenario 08")
sim9_interventions <- tibble(time=1:366,
isolation_rate=isolation_ramp(1:366),
act_rate=social_distance_ramp9(1:366),
experiment="Scenario 09")
sim10_interventions <- tibble(time=1:366,
isolation_rate=post_lockdown_isolation_ramp(1:366),
act_rate=social_distance_ramp9(1:366),
experiment="Scenario 10")
interventions <- sim6_interventions %>%
bind_rows(sim7_interventions) %>%
bind_rows(sim8_interventions) %>%
bind_rows(sim9_interventions) %>%
bind_rows(sim10_interventions) %>%
mutate(NDate = ymd("2020-03-01") + days(time)) %>%
filter(time <= 250) %>%
pivot_longer(cols=c(isolation_rate, act_rate), names_to="Intervention", values_to="intervention_value") %>%
mutate(Intervention = case_when(Intervention == "isolation_rate" ~ "Isolation rate (per day)",
Intervention == "act_rate" ~ "Social contact rate (per day)",
TRUE ~ NA_character_)) %>%
mutate(y_range = case_when(Intervention == "Isolation rate (per day)" ~ 1.0,
Intervention == "Social contact rate (per day)" ~ 10,
TRUE ~ NA_real_))
```
```{r fig-6-visualise-sim-results, echo=FALSE, eval=TRUE, warning=FALSE, fig.height=6.25, fig.width=6.25, fig.cap="Figure 6: Comparison of modelled versus observed epidemic curves in Sydney, Australia as at 30 April 2020"}
null_labeller <- function(string) {
return("")
}
p1 <- interventions %>%
mutate(Intervention = case_when(Intervention == "Isolation rate (per day)" ~ "Isolation rate\n(per day)",
Intervention == "Social contact rate (per day)" ~ "Social contact rate\n(per day)",
TRUE ~ NA_character_)) %>%
ggplot(aes(x=NDate, y=intervention_value, fill=Intervention)) +
geom_area(stat="identity", size=1, alpha=0.5) +
geom_blank(aes(y=y_range)) +
scale_fill_hue(guide = guide_legend(nrow=3)) +
facet_grid(rows=vars(experiment, Intervention), scales="free_y") +
labs(x="Date (simulated)",
y="Rate per day") +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank()) +
theme(strip.text = element_text(colour = 'white')) +
easy_plot_legend_size(size=7)
p2a <- novel_combined_plot_df %>%
bind_rows(se_sydney_incidence) %>%
filter(compartment %in% c("se.flow", "obs.incidence")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=0.5, alpha=0.5) +
facet_grid(experiment ~ .) +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) + # , limits = c(NA, 50)
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="Date (simulated and real)",
y="Incidence (persons) - fixed axis scales") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank(),
strip.text.y = element_blank()) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p2b <- novel_combined_plot_df %>%
bind_rows(se_sydney_incidence) %>%
filter(compartment %in% c("se.flow", "obs.incidence")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=0.5, alpha=0.5) +
facet_grid(experiment ~ ., scales="free_y") +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) + # , limits = c(NA, 50)
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="Date (simulated and real)",
y="Incidence (persons) - variable axis scales") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank(),
strip.text.y = element_blank()) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p3 <- novel_combined_plot_df %>%
filter(compartment %in% c("h.num",
"f.num")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=1, alpha=0.7) +
geom_hline(yintercept=hc_scaler*40, colour="red") +
annotate("text", x = ymd("2020-03-01") + days(210), y = hc_scaler*(40 - 0),
label = "Hospital\ncapacity", size=1.7) +
facet_grid(experiment ~ .) +
scale_colour_manual(values = compcols,
labels=complabels_wrapped,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) +
labs(x="Date (simulated)",
y="Prevalence (persons)") +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
easy_all_text_size(size=6) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p2a + p2b + p3 + p1 + plot_layout(ncol=4, nrow=1)
```
```{r fig-7-visualise-sim-results, echo=FALSE, eval=TRUE, warning=FALSE, fig.height=6.25, fig.width=6.25, fig.cap="Figure 7: Detail, scenarios 08 and 10"}
p1 <- interventions %>%
filter(experiment %in% c("Scenario 08", "Scenario 10")) %>%
ggplot(aes(x=NDate, y=intervention_value, fill=Intervention)) +
geom_area(stat="identity", size=1, alpha=0.5) +
geom_blank(aes(y=y_range)) +
scale_fill_hue(guide = guide_legend(nrow=3)) +
facet_grid(rows=vars(experiment, Intervention), scales="free_y") +
labs(x="Date (simulated)",
y="Rate per day") +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank()) +
theme(strip.text = element_text(colour = 'white')) +
easy_plot_legend_size(size=7)
p2a <- novel_combined_plot_df %>%
bind_rows(se_sydney_incidence) %>%
filter(compartment %in% c("se.flow", "obs.incidence")) %>%
filter(experiment %in% c("Scenario 08", "Scenario 10")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=0.5, alpha=0.5) +
facet_grid(experiment ~ .) +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) + # , limits = c(NA, 50)
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="Date (simulated and real)",
y="Incidence (persons) - fixed axis scales") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank(),
strip.text.y = element_blank()) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p2b <- novel_combined_plot_df %>%
bind_rows(se_sydney_incidence) %>%
filter(compartment %in% c("se.flow", "obs.incidence")) %>%
filter(experiment %in% c("Scenario 08", "Scenario 10")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=0.5, alpha=0.5) +
facet_grid(experiment ~ ., scales="free_y") +
scale_colour_manual(values = compcols,
labels=complabels,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) + # , limits = c(NA, 50)
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
labs(x="Date (simulated and real)",
y="Incidence (persons) - variable axis scales") +
easy_all_text_size(size=6) +
theme(strip.background = element_blank(),
strip.text.y = element_blank()) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p3 <- novel_combined_plot_df %>%
filter(compartment %in% c("h.num",
"f.num")) %>%
filter(experiment %in% c("Scenario 08", "Scenario 10")) %>%
ggplot(aes(x=NDate, y=count, colour=compartment)) +
geom_line(size=1, alpha=0.7) +
geom_hline(yintercept=hc_scaler*40, colour="red") +
annotate("text", x = ymd("2020-03-01") + days(210), y = hc_scaler*(40 - 0),
label = "Hospital\ncapacity", size=1.7) +
facet_grid(experiment ~ .) +
scale_colour_manual(values = compcols,
labels=complabels_wrapped,
guide = guide_legend(nrow=3)) +
scale_y_continuous(labels = scales::comma) +
labs(x="Date (simulated)",
y="") +
theme_minimal() +
theme(legend.title = element_blank(),
legend.position = "top",
legend.direction = "horizontal") +
easy_all_text_size(size=6) +
easy_plot_legend_size(size=7) +
theme(strip.background = element_blank(),
strip.text.y = element_text(size=6, angle=-90))
p2b + p1 + plot_layout(ncol=2, nrow=1, widths=c(2,1))
```
#### Supplementary files
The outputs of all of the simulations reported here are provided as supplementary files in CSV format.
```{r write-supp-files, echo=FALSE, eval=TRUE, message=FALSE}
write_csv(path = "baseline_scenarios_01_05.csv", x = baseline_scenarios_01_05_df)
write_csv(path = "baseline_scenarios_06_10.csv", x = baseline_scenarios_06_10_df)
```
## Discussion
### Principal results
COVOID allocates each member of its hypothetical population to one of seven compartments. The number of times individuals in the various compartments interact with each other and their probability of transmitting infection at each interaction can be varied to simulate the effects of interventions.
Using COVOID on 30 March 2020, we were able to replicate the epidemic response patterns to specific social distancing intervention scenarios reported by other investigators at that time, and to further investigate emergence of herd immunity effects with even lower levels of social distancing. Importantly, we confirmed "second wave" rebound behaviours of the epidemic after the higher levels of social distancing were relaxed, a phenomenon that was not remarked upon in the study which motivated the COVOID model [@chang2020].
Using COVOID on 30 April 2020, the simulated incidence for three local areas of Sydney from 1 March to 30 April 2020 was similar to the actual, observed epidemic curve in two of the intervention scenarios which were modelled. These two scenarios (08 and 10) are also arguably closest to the interventions that took place in Sydney during the months of March and April 2020. At the time of writing (early May 2020), these two scenarios also point to possible post-lockdown "exit strategy" futures in which social distancing is gradually relaxed over several months, either to intermediate levels compared to pre-COVID-19, or completely, but in the latter case, allied with much expanded testing to detect cases as early as possible, and extremely efficient and swift isolation of cases and associated contact tracing and quarantining. At this stage, both Australian and NSW governments appear to be contemplating a path similar to scenarion 10, and have invested heavily in both testing capacity case-based intervention capacity, including deployment of a smartphone contact tracing "app" nationwide [@taylor2020].
### Limitations
COVOID was developed very quickly in a rapidly evolving environment in terms of our understanding of the infection dynamics of COVID-19, and thus several key parameters had to be informed by expert opinion from colleagues, and other heuristics. In addition, we could not test the effects of closures of schools or universities because COVOID is a global mixing model that does not reflect mixing in specific settings such as schools or workplaces.
The absence of age-specific parameters is another key limitation of the current model, although in the absence of detailed data on age differences in COVID-19 disease progression at the time of writing, with the exception of death rates, the added complication of age-specificity may not add very much accuracy. Future versions of COVOID, which will leverage the widely-used POLYMOD age-specific contact matrices [@prem2017], will use age as an attribute of each person in the simulation.
Agent-based models are notoriously computationally-intensive, and the COVOID model is no exception, although it does take advantage of parallel computation available on almost all computers these days. However, computational burden means that it is impractical to simulate very large populations, although the model was successfully trialled with populations of 1 million. Further work is underway to improve the processing efficiency by rewriting critical sections of the R code as C++.
Due to time constraints in the rapidly-evolving situation in March 2020, the initial COVOID model was released as a set of R scripts rather than as a software package with detailed documentation or simple user interface, and hence its potential user base was limited to modellers and researchers with relevant technical expertise. Our team and collaborators are currently developing an extended open-source COVOID package for R comprising a suite of tools to explore intervention scenarios using several categories of model.
### Comparison with prior work
In our initial simulations as at 30 March 2020, we explicitly sought to test the simulations produced by COVOID with those reported by Chang _et al_. based on a highly detailed agent-based model for the entire Australian population [@chang2020]. Our findings regarding social distancing interventions with varying degrees of compliance are very similar to theirs [@chang2020] and also broadly consistent with those for social distancing interventions produced by the UK Imperial College agent-based model [@ferguson2020]. Importantly, COVOID and the other agent-based models all highlight the potential for resurgence of cases once social distancing measures are relaxed. This indicates that these measures may “buy time” in which to put in place comprehensive measures for testing, case finding, isolation and quarantine, rather than being sufficient in themselves to halt the epidemic.
It is encouraging that results produced by COVOID are similar to those so far reported from the more complex agent-based models that require highly detailed population data and high-performance computing.
As at 30 April 2020, we could locate only one other study that compared modelled predictions with observed data for COVID-19 incidence for a specific population. Turk _et al_. [@turk2020] compared DCM Susceptible-Infected-Removed (SIR) model predictions to observed prevalence data for North Carolina, USA, and used EpiModel to simulate interventions by altering the probability of infection. They reported that a model incorporating parameters that simulated a stay-at-home intervention increasingly produced a better fit to the observed data as the epidemic progressed, and emphasised the value of flexible, continuously iterated models for informing local responses.
## Conclusions
COVOID allows rapid modelling of many potential intervention scenarios, can be tailored to diverse settings and requires only standard computing infrastructure. It replicates the epidemic response patterns produced by other models that require highly detailed population-level data, and its predicted epidemic curve was similar in form to that actually observed in Sydney, Australia. In answer to the call for transparency and reproducibility in COVID-19 models [@barton2020], it is freely available as a tool to support public health decision makers in the current COVID-19 crisis. Our team and collaborators are currently developing an extended open-source COVOID package comprising a suite of tools to explore intervention scenarios using several categories of model.
## Acknowledgements
TC conceived the idea of COVOID and wrote all code. Both authors participated in the design and implementation of the intervention scenarios and visualisations and drafted the article. Both authors approved the final version.
## Conflicts of Interest
None to declare.
## Abbreviations
COVID-19: Corona Virus Disease 2019<br>
COVOID: COVID-19 Open-source Infection Dynamics<br>
ICM: Individual contact model<br>
UK: United Kingdom<br>
USA: United States of America<br>
SEIR: Susceptible-Exposed-Infected-Recovered<br>
CSV: comma-separated values
```{r, echo=FALSE, eval=TRUE, warning=FALSE}
toc()
```
## References
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment