An exploration of COVID-19 outcomes throuth time in Canada

health
COVID-19
Author

David Beauchesne & Robert Moriarity

Published

May 28, 2024

Code
library(vroom)
library(dplyr)
library(glue)
library(lubridate)
library(echarts4r)

Part of my postdoctoral work at the University of Toronto has put me in touch with environmental health scientists during the pandemic, one of them being an old friend, Robert Moriarity. We met during our respective master’s at Concordia University in Montreal and always thought that combining our respective expertises would be highly relevant. Rob is now an assistant professor at the School of Occupational and Public Health at the Toronto Metropolitan University.

The goal of our collaboration on my end was to begin incorporating health, social and cultural elements to cumulative effects assessments, but COVID-19 was at the forefront of many of our discussions. As such, I explored some datasets describing COVID-19 outcomes that were available in Canada and a few projects were developped from there. For this particular post, we wish to present two specific datasets and explore the variations of COVID-19 through time in Canada.


COVID-19 data

Outcomes

The Timeline of COVID-19 in Canada initiative2 provides daily COVID-19 outcome metrics (e.g. deaths, cases, hospitalizations, and tests completed) at the national, provincial/territorial, and health region scales. Here, we used the finer scale spatial data at the health region-level for which daily number of cases and deaths are available for 99 health regions across the country. We limited the temporal window of our exploration to the onset of the pandemic (2020-01-01) to the onset of the Omicron variant (2022-03-11), as it coincides with certain Canadian provinces phasing out reporting at the level of health regions and with the advent of rapid testing, which decreased case reporting throughout the country.

Let us begin by downloading and filtering the data from the GitHub repository where they are stored.

Code
# Get data
hr <- vroom::vroom("https://github.com/ccodwg/CovidTimelineCanada/raw/main/geo/hr.csv")
path <- "https://raw.githubusercontent.com/ccodwg/CovidTimelineCanada/main/data/hr/"
covid <- dplyr::bind_rows(
  vroom::vroom(glue::glue("{path}cases_hr.csv")),
  vroom::vroom(glue::glue("{path}deaths_hr.csv"))
) |>
  filter(date <= as.Date("2022-03-11")) |>
  filter(sub_region_1 != 9999)

# Create summary table
dat <- hr |>
  group_by(region) |>
  summarise(
    region_num = n(),
    population = sum(pop, na.rm = TRUE)
  )

covid |>
  group_by(name, region) |>
  summarize(value_daily = sum(value_daily)) |>
  tidyr::pivot_wider(names_from = name, values_from = value_daily) |>
  left_join(dat, by = "region") |>
  select(region, region_num, population, cases, deaths) |>
  knitr::kable(
    col.names = c("Province", "Health regions", "Population", "Cases", "Deaths"),
    row.names = FALSE,
  )
Summary table of the number of health regions, the population size and the number of COVID-19 outcomes (i.e. cases and deaths) in Canadian provinces and territories between 2020-01-01 and 2022-03-11.
Province Health regions Population Cases Deaths
AB 5 4543111 530478 4003
BC 5 5319324 352038 2958
MB 5 1409223 132724 1721
NB 7 812061 41512 317
NL 4 525972 29365 76
NS 4 1019725 47123 210
NT 1 45605 9514 20
NU 1 40526 3265 5
ON 34 15109416 1139241 12377
PE 1 170688 19703 16
QC 18 8695659 927181 13807
SK 13 1204858 127486 1147
YT 1 43789 3939 23
Code
tmp <- left_join(
  hr,
  covid |>
    group_by(name, sub_region_1) |>
    summarize(outcomes = sum(value_daily)) |>
    tidyr::pivot_wider(names_from = name, values_from = outcomes),
  by = c("hruid" = "sub_region_1")
) |>
  select(name = name_canonical, pop, cases, deaths) |>
  mutate(
    cases_per_100k = (cases / pop) * 100000,
    deaths_per_100k = (deaths / pop) * 100000
  ) |>
  rename(
    Population = pop,
    `Number of cases` = cases,
    `Number of deaths` = deaths,
    `Number of cases per 100 000 habitants` = cases_per_100k,
    `Number of deaths per 100 000 habitants` = deaths_per_100k
  )


json <- sf::st_read("https://github.com/ccodwg/CovidTimelineCanada/raw/main/geo/hr.geojson", quiet = TRUE) |>
  rename(name = name_canonical) |>
  geojsonio::geojson_list()


tmp |>
  lapply(function(x) {
    if (is.numeric(x)) {
      x / max(x)
    } else {
      x
    }
  }) |>
  as.data.frame() |>
  tidyr::pivot_longer(-name, names_to = "Outcomes", values_to = "value") |>
  group_by(Outcomes) |>
  e_charts(name, timeline = TRUE) |>
  e_map_register("Canada", json) |>
  e_map(value, map = "Canada", roam = TRUE) |>
  e_visual_map(value, color = viridis::magma(100)) |>
  echarts4r::e_timeline_serie(
    title = list(
      list(text = "Relative number of COVID-19 cases"),
      list(text = "Relative number of COVID-19 cases per 100 000 habitants"),
      list(text = "Relative number of COVID-19 deaths"),
      list(text = "Relative number of COVID-19 deaths per 100 000 habitants"),
      list(text = "Relative population size")
    )
  )

Map of health regions with relative population sizes and relative COVID-19 outcomes.

Mandates

Along with cumulative outcomes, we wish to look at the timing of certain types of regulatory interventions that were instated during the pandemic to curb the rates of outcomes (e.g. lockdowns and mask mandates). We used data from the Canadian Institute for Health Information detailing the timeline of federal, provincial and territorial government interventions during the COVID-19 pandemic1.

Code
curl::curl_download(
  "https://www.cihi.ca/sites/default/files/document/aoda-covid-19-intervention-timeline-in-canada-en.xlsx",
  "mandates.xlsx"
)
mandates <- readxl::read_xlsx("mandates.xlsx", sheet = 3, skip = 2) |>
  janitor::clean_names() |>
  filter(entry_id != "End of worksheet") |>
  mutate(intervention_category = case_when(
    intervention_category == "\r\nDistancing" ~ "Distancing",
    .default = intervention_category
  )) |>
  mutate(jurisdiction = case_when(
    jurisdiction == "Alta." ~ "AB",
    jurisdiction == "B.C." ~ "BC",
    jurisdiction == "Can." ~ "Canada",
    jurisdiction == "Man." ~ "MB",
    jurisdiction == "N.B." ~ "NB",
    jurisdiction == "N.L." ~ "NL",
    jurisdiction == "N.S." ~ "NS",
    jurisdiction == "N.W.T." ~ "NT",
    jurisdiction == "Nun." ~ "NU",
    jurisdiction == "Ont." ~ "ON",
    jurisdiction == "P.E.I." ~ "PE",
    jurisdiction == "Que." ~ "QC",
    jurisdiction == "Sask." ~ "SK",
    jurisdiction == "Y.T." ~ "YT"
  )) |>
  arrange(jurisdiction, start_date)

mandates |>
  group_by(intervention_category, jurisdiction) |>
  summarize(number = n()) |>
  tidyr::pivot_wider(names_from = jurisdiction, values_from = number) |>
  rename(`Intervention category` = intervention_category) |>
  knitr::kable()
Summary of the number of categories of interventions implemented by the Canadian and provincial/territorial governments throughout the pandemic.
Intervention category AB BC Canada MB NB NL NS NT NU ON PE QC SK YT
Case management 33 30 34 32 25 14 11 8 7 36 25 38 28 6
Closures/openings 94 89 2 115 125 113 91 28 224 187 84 208 70 54
Contextual information 3 4 NA 3 4 3 4 4 4 4 4 4 4 4
Distancing 31 32 2 54 69 50 28 12 70 77 34 80 27 20
Health services 23 23 3 21 39 36 31 12 43 33 37 59 17 14
Health workforce 45 54 41 44 19 18 38 3 NA 80 7 105 42 11
Public information 31 22 6 44 63 38 21 9 16 85 33 62 22 17
State of emergency 5 3 1 2 3 1 1 3 2 6 4 1 2 6
Travel 9 9 55 15 64 42 55 20 67 3 50 13 7 14
Vaccine 71 98 40 99 66 41 62 23 20 75 51 118 77 34

We filtered the intervention types to only consider those that have a direct influence on the behaviour of individuals, such as closures, limitations on gatherings, social distancing, mask mandates and travel restrictions. This filtering process yielded a total of 52 mandates with beginning and end dates.

Code
# label: mandates-filtering
#| tbl-cap: Filtered regulatory mandates implemented nationally and at the provincial and territorial level during the COVID-19 pandemic in Canada.
man <- vroom("mandates.csv") |>
  left_join(mandates, by = "entry_id") |>
  mutate(
    start_date = as_date(start_date),
    action = case_when(entry_id == "MB615" ~ "Eased", .default = action)
  ) |>
  tidyr::pivot_wider(
    id_cols = c(mandate_id, jurisdiction, intervention_category, intervention_type),
    names_from = action,
    values_from = start_date
  ) |>
  mutate(name = glue("{intervention_category}: {intervention_type}"))

# Table
man |>
  select(-mandate_id, -name) |>
  arrange(jurisdiction, intervention_category, intervention_type) |>
  knitr::kable(col.names = c("Jurisdiction", "Category", "Type", "New", "Eased"))
Jurisdiction Category Type New Eased
AB Closures/openings Daycares 2020-03-15 2020-05-14
AB Closures/openings Education 2020-03-15 2020-09-01
AB Distancing Gatherings 2020-03-27 2020-05-15
AB Public information Masks 2020-12-08 2021-08-16
AB State of emergency State of emergency 2020-03-17 2020-06-15
AB State of emergency State of emergency 2020-11-27 2021-02-22
BC Closures/openings Education 2020-03-17 2020-09-10
BC Distancing Gatherings 2020-03-16 2022-02-16
BC Public information Masks 2020-11-19 2022-03-11
BC State of emergency State of emergency 2020-03-18 2021-06-30
Canada Travel Restrictions 2020-03-21 2022-04-25
MB Closures/openings Daycares 2020-03-20 2020-06-01
MB Closures/openings Education 2020-03-23 2020-09-08
MB Distancing Gatherings 2020-03-13 2020-05-22
MB Public information Masks 2020-11-11 2022-03-15
MB State of emergency State of emergency 2020-03-20 2021-10-21
NB Closures/openings Daycares 2020-03-16 2020-05-08
NB Closures/openings Education 2020-03-16 2020-09-08
NB Distancing Gatherings 2020-03-12 2020-04-24
NB Public information Masks 2020-10-09 2022-03-14
NB State of emergency State of emergency 2020-03-19 2021-07-30
NL Closures/openings Daycares 2020-03-16 2020-05-11
NL Closures/openings Education 2020-03-17 2020-09-09
NL Distancing Gatherings 2020-03-18 2020-04-30
NL Public information Masks 2020-08-24 2021-08-10
NL State of emergency State of emergency 2020-03-18 NA
NS Closures/openings Daycares 2020-03-17 2020-06-15
NS Closures/openings Education 2020-03-23 2020-09-08
NS Distancing Gatherings 2020-03-15 2020-05-15
NS Public information Masks 2020-05-22 2022-03-21
NS State of emergency State of emergency 2020-03-23 NA
NT Closures/openings Education 2020-03-16 2020-08-28
NT Distancing Gatherings 2020-03-17 2020-06-12
NT Public information Masks 2021-05-03 2022-04-01
NT State of emergency State of emergency 2020-03-24 2020-07-08
NU Closures/openings Daycares 2020-03-16 2020-06-01
NU Closures/openings Education 2020-03-17 2020-08-10
NU Distancing Gatherings 2020-03-20 2020-06-01
NU Public information Masks 2021-06-14 2022-04-11
NU State of emergency State of emergency 2020-03-18 2022-04-11
ON Closures/openings Daycares 2020-03-17 2020-07-27
ON Closures/openings Education 2020-03-14 2020-09-08
ON Closures/openings Non-essential services 2020-03-25 2020-05-19
ON Distancing Gatherings 2020-03-13 2020-06-12
ON Public information Masks 2020-10-03 2022-03-21
ON State of emergency State of emergency 2020-03-17 2020-07-24
ON State of emergency State of emergency 2021-01-14 2021-02-19
ON State of emergency State of emergency 2021-04-08 2021-06-09
PE Closures/openings Daycares 2020-03-17 2020-05-22
PE Closures/openings Education 2020-03-23 2020-09-08
PE Distancing Gatherings 2020-04-03 2020-05-08
PE Public information Masks 2020-11-20 2022-05-06
PE State of emergency State of emergency 2020-04-17 2020-06-29
QC Closures/openings Daycares 2020-03-16 2020-05-11
QC Closures/openings Education 2020-03-16 2020-08-26
QC Distancing Gatherings 2020-03-20 2020-05-22
QC Public information Masks 2020-08-24 2022-05-14
QC State of emergency State of emergency 2020-03-13 NA
SK Closures/openings Daycares 2020-03-20 2020-04-30
SK Closures/openings Education 2020-03-20 2020-09-08
SK Distancing Gatherings 2020-03-16 2020-04-24
SK Public information Masks 2020-11-06 2022-02-28
SK State of emergency State of emergency 2020-03-18 2021-07-11
YT Closures/openings Daycares 2020-04-17 2020-05-22
YT Closures/openings Education 2020-03-18 2020-08-19
YT Distancing Gatherings 2020-03-16 2020-04-28
YT Public information Masks 2020-12-01 2022-03-18
YT State of emergency State of emergency 2020-03-27 2021-08-25
YT State of emergency State of emergency 2021-11-08 2022-03-17

Temporal exploration

Below, we explore the temporal trends in cumulative cases and deaths for each Canadian health region on a weekly basis. Within each region, we normalized cumulative weekly cases and deaths by the population size times 100 000 to obtain an estimate of the cumulative outcomes per 100 000 habitants for each health region. We present a plot for each province and territory, each line representing the temporal trends for each health region. Mandates are overlaid as vertical lines on the line plots. Red lines represent the implementation of new regulatory mandates, while green lines identifies the date when mandates were lifted.

Cases

Code
man <- mutate(man, Eased = case_when(is.na(Eased) ~ as.Date("2030-01-01"), .default = Eased))
dat <- covid |>
  mutate(
    week = week(date),
    year = year(date),
    date = ymd(glue("{year}0101")) + weeks(week - 1)
  ) |>
  left_join(hr[, c("hruid", "pop", "name_short")], by = c("sub_region_1" = "hruid")) |>
  group_by(date, region, sub_region_1, name_short, name, pop) |>
  summarize(value_weekly = sum(value_daily)) |>
  ungroup() |>
  mutate(outcomes_per_100000 = (value_weekly / pop) * 100000) |>
  arrange(name, region, sub_region_1, date) |>
  select(-pop, value_weekly)

# Chart
p <- dat |>
  filter(name == "cases") |>
  group_by(region) |>
  group_split() |>
  lapply(function(j) {
    j |>
      group_by(name_short) |>
      e_charts(date) |>
      e_title("Province / territory", j$region[1]) |>
      e_line(outcomes_per_100000, smooth = TRUE) |>
      e_datazoom(x_index = 0, type = "slider") |>
      e_tooltip(trigger = "axis") %>%
      purrr::reduce2(
        .x = man$New[man$jurisdiction %in% c(j$region[1], "Canada")],
        .y = man$name[man$jurisdiction %in% c(j$region[1], "Canada")],
        .f = function(x, y, z) e_mark_line(x, data = list(xAxis = y, lineStyle = list(color = "red")), title = z),
        .init = .
      ) %>%
      purrr::reduce2(
        .x = man$Eased[man$jurisdiction %in% c(j$region[1], "Canada")],
        .y = man$name[man$jurisdiction %in% c(j$region[1], "Canada")],
        .f = function(x, y, z) e_mark_line(x, data = list(xAxis = y, lineStyle = list(color = "lightgreen")), title = z),
        .init = .
      )
  })
do.call(e_arrange, p)

Deaths

Code
# Chart
x <- dat |>
  filter(name == "deaths") |>
  group_by(region) |>
  group_split() |>
  lapply(function(j) {
    j |>
      group_by(name_short) |>
      e_charts(date) |>
      e_title("Province / territory", j$region[1]) |>
      e_line(outcomes_per_100000, smooth = TRUE) |>
      e_datazoom(x_index = 0, type = "slider") |>
      e_tooltip(trigger = "axis") %>%
      purrr::reduce2(
        .x = man$New[man$jurisdiction %in% c(j$region[1], "Canada")],
        .y = man$name[man$jurisdiction %in% c(j$region[1], "Canada")],
        .f = function(x, y, z) e_mark_line(x, data = list(xAxis = y, lineStyle = list(color = "red")), title = z),
        .init = .
      ) %>%
      purrr::reduce2(
        .x = man$Eased[man$jurisdiction %in% c(j$region[1], "Canada")],
        .y = man$name[man$jurisdiction %in% c(j$region[1], "Canada")],
        .f = function(x, y, z) e_mark_line(x, data = list(xAxis = y, lineStyle = list(color = "lightgreen")), title = z),
        .init = .
      )
  })
do.call(e_arrange, x)


References

1.
Health Information, C. I. for. Canadian Data Set of COVID-19 Interventions - Data Tables. (2022).
2.
Berry, I., O’Neill, M., Sturrock, S. L., Wright, J. E., Acharya, K., Brankston, G., Harish, V., Kornas, K., Maani, N., Naganathan, T., Obress, L., Rossi, T., Simmons, A. E., Camp, M. V., Xie, X., Tuite, A. R., Greer, A. L., Fisman, D. N. & Soucy, J.-P. R. A sub-national real-time epidemiological and vaccination database for the COVID-19 pandemic in Canada. Scientific Data 8, (2021).