############################
##### GLOBAL VARIABLES #####
############################
source("scripts/utils.R")
#Date of the VirusSeq release, load from params passed in during CLI command
VirusSeq_release=format(as.Date(params$datestamp),"%B %d, %Y")
Variants_Canada_over_time_Start_Date=as.Date('2021-01-01')
pangoversion="4.2 (Viral AI)" #this should probably be passed in via params
# load variant designations and colour palette from file
VOCVOI <- read.csv("resources/vocvoi.csv")
#define canadian provinces that are available and can be shown.
all.regions = data.frame(name=c("Canada","British Columbia", "Alberta",
"Saskatchawan", "Manitoba", "Ontario",
"Quebec", "Nova Scotia", "New Brunswick",
"Newfoundland and Labrador"),
shortname=c("Canada","BC", "AB",
"SK", "MB", "ON",
"QC", "NS", "NB","NL"))
#define a color palatte for variants used throughout the RMD
pal <- VOCVOI$color
names(pal) <- VOCVOI$name
pal["other"] <- 'grey'
## 1. LOAD processed metadata of Canadian sequences (with latest pangolin, division, and full seq IDs)
#Download metadata from VirusSeq, put the date here:
meta <- read.csv(gzfile(paste0(params$datadir, "virusseq.metadata.csv.gz")), sep="\t")
meta$province <- meta$geo_loc_name_state_province_territory
# Select only the column we want to use later
columnlist=c("fasta_header_name", "province", "host_gender", "host_age_bin",
"sample_collection_date", "sample_collected_by",
"purpose_of_sampling", "purpose_of_sequencing","lineage",
"raw_lineage", "gisaid_accession", "isolate")
meta <- meta[ , columnlist]
### metadata cleaning
unknown.str <- c("Undeclared", "Not Provided", "Restricted Access", "Missing",
"Not Applicable","","NA","unknow")
meta <- as.data.frame(apply(meta, 2, function(x) {
x[is.element(x, unknown.str)] <- "Unknown"
x
}))
meta$sample_collection_date <- as.Date(meta$sample_collection_date)
meta$week <- cut(meta$sample_collection_date, 'week')
meta$month <- gsub("-..$","",as.character(cut(meta$sample_collection_date, 'month')))
startdate <- max(meta$sample_collection_date) - days(120)
## parse PANGO lineages
source("scripts/scanlineages.R")
meta$pango_group <- create.pango.group(VOCVOI, meta)
meta$pango_group <- as.factor(meta$pango_group)
meta <- meta %>% mutate(gisaid_accession = str_replace(gisaid_accession, "EPI_ISL_", "")) %>% rename(GID=gisaid_accession)
fullpangotree <- makepangotree(unique(meta$raw_lineage))
latelineage_pangotree <- makepangotree(unique(
meta$raw_lineage[
meta$sample_collection_date > max(meta$sample_collection_date) - days(60)
]
))
reference <- getStrictoSubLineages("BQ.1", meta)
## 2. LOAD epidemiological data (PHAC)
#from: https://health-infobase.canada.ca/covid-19/epidemiological-summary-covid-19-cases.html?stat=num&measure=total&map=pt#a2
epidataCANall <- read.csv(paste0(params$datadir, "/CanadianEpiData.csv"))
epidataCANall$date <- as.Date(epidataCANall$date)
epidataCANall$prname <- gsub('_', ' ', epidataCANall$prname)
epidate <- tail(epidataCANall,1)$date #download date
epidataCANall$previousvalue <- 0
#small loop to get the numtoday column from previous versions of this file from the cumulative cases
for(row in 1:nrow(epidataCANall)) {
p <- epidataCANall[row, "prname"]
subdf <- epidataCANall[which(
(epidataCANall$date > epidataCANall[row, "date"] & epidataCANall$prname==p)
), ]
if(nrow(subdf) != 0) {
nextrow <- which( (epidataCANall$date == min(subdf$date) & epidataCANall$prname==p))
epidataCANall[nextrow, "previousvalue"] <- epidataCANall[row, "totalcases"]
}
}
epidataCANall$numtoday <- epidataCANall$totalcases - epidataCANall$previousvalue
#load the pango_lineage notes file and extracts parental data using it's alias as a separate column
lineageDescriptions <- read.table(paste0(params$datadir,"lineageNotes.tsv"), sep = "\t", header=T, as.is=TRUE, quote = "", stringsAsFactors = F) %>%
mutate(alias=str_extract(Description, "(?<=Alias of )(.*?)(?=(,| ))")) %>%
mutate(parentAlias = str_extract(alias, ".*(?=\\.)")) %>% rowwise() %>% mutate(Parent = rawtoreallineage(parentAlias)) %>%
dplyr::select(Lineage, Parent, Description)
colnames(lineageDescriptions) <- c("Lineage", "Ancestor", "Description")
This notebook is built to explore Canadian SARS-CoV-2 genomic and epidemiological data with the aim of investigating viral evolution and spread. It is for discussion with pillar 6’s team and for sharing with collaborators, e.g. public health labs. These analyses can spur further research within or across pillars, be used for reports (or data dashboards), support discussions with the science communication pillar for public dissemination, and enable code reuse by public health authorities/laboratories for their internal use.
Canadian genomic and epidemiological data will be regularly pulled from various public sources (see list below) to keep these analyses up-to-date. Only representations of aggregate data will be posted here.
Important caveats and disclaimers:
These analyses represent only a snapshot of SARS-CoV-2 evolution in Canada. Only some infections are detected by PCR testing, only some of those are sent for whole-genome sequencing, and not all sequences are posted to public facing reposittories. Sequencing volumes and priorities have changed during the pandemic, and the sequencing strategy is typically a combination of prioritizing outbreaks, travellers, public health investigations, and random sampling for genomic surveillance.
For example, specific variants or populations might be preferentially sequenced at certain times in certain jurisdictions. When possible, these differences in sampling strategies are mentioned but they are not always known. With the arrival of the Omicron wave, many jurisdictions across Canada reached testing and sequencing capacity mid-late December 2021 and thus switched to targeted testing of priority groups (e.g., hospitalized patients, health care workers, and people in high-risk settings). Therefore, from this time onward, case counts are likely underestimated and the sequenced virus diversity is not necessarily representative of the virus circulating in the overall population.
Thus, interpretation of these plots and comparisons between health regions should be made with caution, considering that the data may not be fully representative. These analyses are subject to frequent change given new data and updated lineage designations.
The last sample collection date is 16 February, 2023
[NOTE: Barplots for lineage frequencies are now interactive, and new tables were added. All tables are now searchable, and the data downloadable.]
XBB.1.5, combined with its subvariants and other variants with the mutation S:F486P, are still collectively growing in all regions of Canada. This collective set includes XBB.1.9.1, and XBF subvariants. Some BQ.1 subvariants are increasing in some regions of Canada along with CH.1.1. Differences between some provinces continue - see the evolving plots below of “Fastest growing lineages in Canada”, and “case count trends by variant”, per province.
Variants of current interest, due to their current/potential growth advantage, mutations of potential functional significance, or spread in other countries (note that some of these are not yet detected in Canada):
Here we take a look, sub-dividing the major sub-lineages currently circulating in Canada.
source("scripts/subtype_plotter.R")
sublineagestoplot = data.frame(name=c("BA.1*","BA.2*","BA.2*",
"BA.4*","BA.5.1*","BA.5.2*",
"BQ*","BA.5*","B*"),
tabname=c("BA.1","early BA.2","late BA.2",
"BA.4","BA.5.1","BA.5.2",
"BQ","Other BA.5","Divergent lineages"),
mindate=c("","","2022-08-01",
"","","",
"","","2022-03-01"),
maxdate=c("2022-06-01","2022-08-01","",
"","","",
"","",""),
exclude_previously_plotted=c(FALSE,FALSE,FALSE,
FALSE,FALSE,FALSE,
FALSE,TRUE,TRUE))
plotted=c()
frequencyTable <- data.frame()
for (i in 1:nrow(sublineagestoplot)) {
mindate=NA
maxdate=NA
if(sublineagestoplot[[i,"mindate"]]!=""){
mindate=as.Date(sublineagestoplot[[i,"mindate"]])
}
if(sublineagestoplot[[i,"maxdate"]]!=""){
maxdate=as.Date(sublineagestoplot[[i,"maxdate"]])
}
set=getStrictoSubLineages(sublineagestoplot[[i,"name"]],meta)
if(sublineagestoplot[[i,"exclude_previously_plotted"]]){
set=set[!set %in% plotted]
}
subVariantData=plot.subvariants.ggplot(sublineage=set,mindate=mindate,maxdate=maxdate)
#outputs the text/plots into RMD
cat("###", sublineagestoplot[[i,"tabname"]], "\n")
cat("####", sublineagestoplot[[i,"tabname"]],"sublineages")
if (!is.na(mindate)){cat(" From",sublineagestoplot[[i,"mindate"]])}
if (!is.na(maxdate)){cat(" Until",sublineagestoplot[[i,"maxdate"]])}
cat("[ (Frequency Table Download)](", DisplayHTMLTableDownloadLink(subVariantData$data %>% dplyr::select(Date, Lineage, Frequency, `% Frequency`),sublineagestoplot[[i,"tabname"]]),")")
cat("\n\n")
# ay <- list(
# tickfont = list(size=11.7),
# titlefont=list(size=14.6),
# overlaying = "y",
# nticks = 5,
# side = "right",
# title = "Second y axis"
# )
print(htmltools::tagList(ggplotly(subVariantData$absolute, height = 400)))
#%>% add_lines(x=~Date, y=~`TotalCases(*10e2)`, colors="grey", yaxis="y2", data=subVariantData$data, showlegend=F, inherit=F) %>% layout(yaxis2=ay)))
print(htmltools::tagList(ggplotly(subVariantData$relative, height = 400)))
cat("\n\n")
if (nrow(frequencyTable) == 0){
frequencyTable <- subVariantData$data %>% dplyr::select(Date, Lineage, Frequency,`% Frequency`)
}else{
frequencyTable <- rbind(frequencyTable, (subVariantData$data %>% dplyr::select(Date, Lineage, Frequency,`% Frequency`)) )
}
#cat("\n**Lineage frequencies table for the above chart:**\n\n")
#printing the datatable here works but for some reason the download buttons gets dropped and search dont work properly.
#To get it working, we need to print a table of everything after this loop. see below
#print(htmltools::tagList(DisplayHTMLTable(subVariantData$data %>% dplyr::select(Date, Lineage, Frequency, `% Frequency`), width="100%")))
plotted <<- unique(c(plotted,set))
}
Here we show the selection estimates and their 95% confidence intervals for Pango lineages with more than 10 sequences in Canada since 2022-10-19 and with enough data to estimate the confidence interval. Each selection estimate measures the growth rate relative to BQ.1 stricto (i.e., sequences designated as BQ.1 and not its descendants, like BQ.1.1). Plots showing the change in variant frequency over time in Canada are given below for lineages with more than 50 sequences.
Growth advantage of 0-5% corresponds to doubling times of more than two weeks, with 5-10% reflecting one to two week doubling times and over 10% representing significant growth of less than one week doubling time. Note that estimating selection of sub-variants with low sequence counts (less than 100 dots) is prone to error, such as mistaking one-time super spreader events or pulses of sequence data from one region as selection. Estimates with lower sequence counts in one region should be considered as very preliminary.
This plot highlights the groups of related lineages that are growing fastest (e.g., BQ.1* is the monophyletic clade that includes BQ.1.1 and all other BQ.1 sublineages.
Again, Growth advantage of 0-5% corresponds to doubling times of more than two weeks, with 5-10% reflecting one to two week doubling times and over 10% representing significant growth of less than one week doubling time. Note that estimating selection of sub-variants with low sequence counts (less than 100 dots) is prone to error, such as mistaking one-time super spreader events or pulses of sequence data from one region as selection. Estimates with lower sequence counts in one region should be considered as very preliminary.
source("scripts/plot_growing_lineages.R")
selectparam <- function(p,reg){
if(!any(is.na(p$fit))){
x=p$mut[[1]];
p$region==reg&
(p$fit)$fit[["s1"]]>0 &
sum(p$toplot$n2)>10&
substr(x, nchar(x), nchar(x))=="*"
}else{FALSE}
}
for (i in 1:length(all.regions[["name"]])) {
cat("###", all.regions[['shortname']][[i]], "\n")
cat("#### Plot single lineages in", all.regions[['name']][[i]],"\n","\n")
paramselected=allparams[sapply(allparams,function(x){selectparam(x,all.regions[['name']][[i]])})]
n=min(25,length(paramselected))
if(n!=0){
#print(plot_growing_lineage(paramselected[1:n]))
p<-plot_growing_lineage(paramselected[1:n]) #A%>% as_widget()
print(htmltools::tagList(ggplotly(p, height = 700)))
}else{
p<-(getEmptyErrorPlotWithMessage("Not enough data available.") )
print(p)
}
cat("\n\n")
}
paramselected=allparams[sapply(allparams,function(p){if(!any(is.na(p$fit))){
x=p$mut[[1]];
(p$fit)$fit[["s1"]]>0 &
sum(p$toplot$n2)>10
}else{FALSE}})]
link = DisplayHTMLTableDownloadLink(plot_growing_lineage(paramselected,makeplot=FALSE), "GrowingLineages")
#DisplayHTMLTable(plot_growing_lineage(paramselected,makeplot=FALSE))
Please click here to download table of all the selection estimates
source("scripts/plot_selection_estimator.R")
#define starting parameters (p,s) for making the selection estimates
startpar <- list(p=c(0.2, 0.05), s=c(0.05, 0.1))
#filter samples with variants of interest
setAll=getAllStrictoLineages(meta)
sublineages_BQ <- getStrictoSubLineages("BQ*",meta) #BQ variants
sublineages_XBB <- getStrictoSubLineages("XBB*",meta) #XBB variants
setAll=setAll[!setAll %in% c(sublineages_BQ, sublineages_XBB)] #the rest
#define the mutants
mutants = list(sublineages_BQ, sublineages_XBB)
mutantNames = list("BQ*", "XBB*", "the rest") #
col <- c(pal["Omicron BQ"], "XBB"=pal["XBB"]) #define custom color for XBB here because otherwise it will be black cuz recombinant.
#Set a starting date
#Note that the startdate shouldn't be too much before both alleles become common
#or rare migration events that die off could throw off the estimation procedure
#(so that the parameter estimates account for the presence of those alleles long in the past).
startdate<-max(meta$sample_collection_date)-days(120) #Using a later date with less sampling noise
#wrapper for calling plot.selection.estimate.ggplot() to avoid passing in the same local variables multiple time anjd to include a trycatch statement to show empty plots.
sub.plot.selection.estimate <- function(region,maxdate=NA){
#plot.selection.estimate.ggplot() returns a named list of data used for plotting the case count selection curves, see the function documentation for details.
obj <- tryCatch(
{
obj=plot.selection.estimate.ggplot(region=region,
startdate=startdate, startpar=startpar,
reference=c(setAll),
mutants=mutants,
names=mutantNames,
maxdate=maxdate, col=col)
},
error=function(cond){
#print(cond)
obj= (list("plot1"=NULL, "plot2"=cond))
})
return(obj)
}
#calculate estimates for each province
selectionEstimateFits <- list() #named list to store estimates for different provinces
selectionEstimateFits[["Canada"]] = sub.plot.selection.estimate(region="Canada")
maxdate = selectionEstimateFits[["Canada"]]$date
for (i in 1:length(all.regions$name)){
selectionEstimateFits[[all.regions$shortname[[i]]]] = sub.plot.selection.estimate(region=all.regions$name[[i]])
}
Here we show the trends of the various BA.2.* sublineages over time, relative to the frequency of BQ.1 by itself (shown for sublineages with at least 50 (Canada) or 20 (provinces) cases). Proportions shown here are only among BQ.1 (stricto) and the lineage illustrated. Note that these plots are not necessarily representative of trends in each province and that mixing of data from different provinces may lead to shifts in frequency that are not due to selection.
NULL
Here we show the trends of the various BA.5* sublineages over time, relative to the frequency of BQ.1 by itself (shown for sublineages with at least 50 (Canada) or 20 (provinces) cases). Proportions shown here are only among BQ.1 (stricto) and the lineage illustrated. Note that these plots are not necessarily representative of trends in each province and that mixing of data from different provinces may lead to shifts in frequency that are not due to selection.
NULL
Here we examine the relative rate of spread of the different sublineages of Omicron currently in Canada. Specifically, we determine if a new or emerging lineage has a selective advantage, s (and by how much), against a reference lineage previously common in Canada (see the methods for more details about selection and how it is estimated).
Currently, the major group of Omicron lineages rising in frequency are the XBB.* group with BQ.*,which descend from BA.5*, nearing a peak. We first show this growth of XBB* and other BQ.*, relative to the remaining strains, which consist predominantly of BA.5* (excluding BQ.*). Left plot: y-axis is the proportion of sub-lineages BQ.* and XBB.* relative to the remaining strains; right plot: y-axis describes the logit function, log(freq(XBB.* or BQ*)/freq(the rest)), which gives a straight line whose slope is the selection coefficient if selection is constant over time (see methods).
For comparison, Alpha had a selective advantage of s ~ 6%-11% per day over preexisting SARS-CoV-2 lineages, and Delta had a selective advantage of about 10% per day over Alpha.
Caveat: These selection analyses must be interpreted with caution due to the potential for non-representative sampling, lags in reporting, and spatial heterogeneity in prevalence of different sublineages across Canada. Provinces that do not have at least 20 sequences of XBB.*, BQ.*, and other lineages during this time frame are not displayed.
#define provinces to show selection plots for.
dataAvailable.regions <- all.regions %>% filter(shortname %in% c("Canada", "BC", "AB", "SK", "MA", "ON", "QC", "East"))
#display all selection plots with each province as a tab.
apply(dataAvailable.regions,1,function(reg){
cat("###", reg[["shortname"]], "\n")
n_min=20
if(reg[["name"]]=="Canada"){n_min=50}
cat("####",reg[["name"]],"\n","\n")
if (is.null(selectionEstimateFits[[reg[["shortname"]]]]$plot1))
{print(getEmptyErrorPlotWithMessage("Not enough Data Available"))
print(getEmptyErrorPlotWithMessage("Not enough Data Available"))}
else {
print(selectionEstimateFits[[reg[["shortname"]]]]$plot1)
print(selectionEstimateFits[[reg[["shortname"]]]]$plot2)
}
cat("\n\n")})
NULL
source("scripts/plot_case_count_selection_estimator.R")
#loads in the case count data for each province into a named list.
caseData <- parseCaseData(maxdate, params$datadir)
#loads in population data for /100000invidivudal normalization
populationData <- read.table("resources/2022Population.tsv", header=T, sep='\t', check.names = F)
#' function used to generate data required case count selection plots.
#' @param caseCountData The case counts for a specific province. i.e. one item in variable caseData
#' @param selectionEstimateObject The selection estimate object for a specific province. i.e. one item in variable selectionEstimateFits
#' @param filename String. If not NA, it will save a local copy of a plot to $PWD
plotCaseCountSelection <- function(caseCountData, selectionEstimateObject, saveToFile=F){
#caseCountData <- caseData$AB
#selectionEstimateObject<- selectionEstimateFits$AB
#filename=NA
if (selectionEstimateObject$region == "Canada"){dayToCut = 2} else { dayToCut = 5 } #define the number of days since most recent data date as "underreported"
#populate a column with reporting accuracy (Accurate | UnderReported])
caseCountData$report_type <- "Accurate"
caseCountData$report_type[(nrow(caseCountData)-dayToCut+1):nrow(caseCountData)] <- "UnderReported"
#construct a spline fit for the accurate case counts only
caseFitModel <- getCaseCountSmoothFitWithLambda(caseCountData %>% filter(report_type=="Accurate"))
#extract the y-values for the fit
caseFitLineValues <- 10^(caseFitModel$y)
#extend the previous fit for the "underreported dates" and extract the y values
caseFitLinePredictedValues <- c((10^(predict(caseFitModel, data.frame(x=(nrow(caseCountData)-dayToCut+1):nrow(caseCountData)))[[2]]))$x)
#merge the accurate and underreported case count fit lines and construct a DF with X, and metadata
caseCountFitLine<-data.frame(caseCountData$Reported_Date, c(caseFitLineValues, c(caseFitLinePredictedValues)), caseCountData$report_type)
colnames(caseCountFitLine) <- c("Reported_Date", "n", "type")
#replaces UnderReported with Projected in the "type" column
caseCountFitLine <- caseCountFitLine %>% mutate(type = ifelse(type=="UnderReported", "Projected", "Actual"))
#get the selection estimate fits, scurves and extended scurves from the selection estimate object
selectionFit <- selectionEstimateObject$fit
selectionScurves <- selectionEstimateObject$scurves
selectionScurvesExtended <- selectionEstimateObject$scurvesExtended
#since canada only have data once every 7 days, modify the estimate fit to reflect that.
if (selectionEstimateObject$region == "Canada"){
selectionScurves <- selectionEstimateObject$scurves[seq(1, nrow(selectionEstimateObject$scurves), 7), ]
selectionScurvesExtended <- selectionEstimateObject$scurvesExtended[seq(1, nrow(selectionEstimateObject$scurvesExtended), 7), ]
}
#assign names and colors
selectionNames <- selectionEstimateObject$names
selectionColors <- selectionEstimateObject$color
#now we beging constructing the object that contains data for different lines to plot.
caseSelectionLines <- list()
#build to total case line.
caseSelectionLines[[1]] <- list("line"=caseCountFitLine, "names" = "CaseCount", "color"="limegreen")
#build line for each variant
for (i in seq(from=2, to=ncol(selectionScurves))){
x = caseCountData$Reported_Date
actual = caseCountFitLine$n[1:length(selectionScurves[,i])] * selectionScurves[,i]
actual <- actual[!is.na(actual)]
if (length(actual) == length(caseCountData$Reported_Date)){
proj = NULL
} else{
proj = caseCountFitLine$n[(length(selectionScurves[,i])+1):length(caseCountFitLine$n)] * selectionScurvesExtended[,i][(length(selectionScurves[,i])+1):length(caseCountFitLine$n)]
proj <- proj[!is.na(proj)]
}
y = c(actual, proj)
#type <- c(c(rep("actual",length(selectionScurves[,i]))), c(rep("projection", length(caseFit) - length(selectionScurves[,i]))))
line <- data.frame(x,y, c(rep("Actual", length(actual)), rep("Projected", length(proj))))
colnames(line) <- c("Reported_Date", "n", "type")
caseSelectionLines[[i]] <- list("line"=line, "names" = selectionNames[[i-1]], "color"=selectionColors[i-1])
}
#build the line for "the rest"
x <- caseCountData$Reported_Date
actual = caseCountFitLine$n[1:length(selectionScurves[,1])] * selectionScurves[,1]
actual <- actual[!is.na(actual)]
if (length(actual) == length(caseCountData$Reported_Date)){
proj = NULL
} else{
proj = caseCountFitLine$n[(length(selectionScurves[,i])+1):length(caseCountFitLine$n)] * selectionScurvesExtended[,1][(length(selectionScurves[,1])+1):length(caseCountFitLine$n)]
proj <- proj[!is.na(proj)]
}
y = unique(c(actual, proj))
line <- data.frame(x,y, c(rep("Actual", length(actual)), rep("Projected", length(proj))))
colnames(line) <- c("Reported_Date", "n", "type")
caseSelectionLines[[ncol(selectionScurves) + 1]] <- list("line"=line, "names" = "The Rest", "color"="black")
#gather the population data for /100000 individual normalization
if (selectionEstimateObject$region == "Canada"){
population <- as.numeric(populationData[2,selectionEstimateObject$region]* 7)
}
else {
population <- (as.numeric(populationData[2,selectionEstimateObject$region]) )
}
#pass the lines into the plotting function
plotCaseCountByDate2(caseCountData, rev(caseSelectionLines),population,region=selectionEstimateObject$region, saveToFile=saveToFile)
}
These plots track the reported cases among people aged over 70 per 100,000 individuals (green dots). This age group is more reliably tested in Canada and is thus used to describe overall trends in COVID-19. A cubic spline is fit to the log of case counts (top curve), with the recent trends used to estimate the daily exponential growth rate r in COVID-19 cases on the last day of accurate case counts. The fit from the “Selection on Omicron” section above is used to show how each of the sub-lineages is growing or shrinking, with the corresponding exponential growth rate. The most recent case counts are generally underreported and are not used in the fits (dropping two weeks of data from Canada and five days of data from the provinces). The lambda value used for the spline fit is 0.001.
#plots the case count selection plots with each pronvince as a tab.
apply(all.regions,1,function(reg){
cat("###", reg[["shortname"]], "\n")
cat("####",reg[["name"]],"\n","\n")
if (is.null(selectionEstimateFits[[reg[["shortname"]]]]$plot1)){
print(getEmptyErrorPlotWithMessage("Not enough genomic data available."))
} else if (reg[["shortname"]] %in% names(caseData)){
p<-plotCaseCountSelection(caseData[[reg[["shortname"]]]], selectionEstimateFits[[reg[["shortname"]]]])#,reg[["shortname"]])
print(p)
} else {
print(getEmptyErrorPlotWithMessage("No case count data available."))
}
cat("\n\n")})
NULL
Tabulation of the most predominant mutational changes in Omicron, with adjacent rows comparing the composition of Canadian sublineages to that sublineage globally.
Mutational profile of Omicron and its sublineages in Canada and globally for the most prevalent (>75%) point mutations in each category (based on the 460703 genomes available on VirusSeq on March 11, 2023).
This plot shows the changing composition of sequences for all Canadian data posted to the VirusSeq Portal according to Pango lineage designation (Pango version 4.2 (Viral AI)), up to 2023-02-25. Because sampling and sequencing procedures vary by region and time, this does not necessarily reflect the true composition of SARS-CoV-2 viruses in Canada over time.
From the beginning of the pandemic to the fall of 2021, Canadian sequences were mostly of the wildtype lineages (pre-VOCs). By the beginning of summer 2021, the VOCs Alpha and Gamma were the most sequenced lineages overall in Canada. The Delta wave grew during the summer of 2021 with sublineages AY.25 and AY.27 constituting sizeable proportions of this wave. Omicron arrived in November of 2021 and spread in three main waves, first BA.1* (early 2022), then BA.2* (spring 2022), then BA.5* (summer 2022). Current, multiple sublineages of Omicron persist, with emerging sublineages spreading, such as BQ.1.1 (a BA.5 sub-lineage).
There are two Pango lineages that have a Canadian origin and that predominately spread within Canada (with some exportations internationally): B.1.438.1 and B.1.1.176. Other lineages of historical interest in Canada:
This historical analysis is not being further updated, as we focus on more interactive data plots and the “Current situation” text above.
Here we present a subsampled phylogenetic snapshot of SARS-CoV-2 genomes from Canada. The x-axis of the time tree represents the estimated number of years from today for which the root emerged. Due to the low number of XBB sequences, this estimate may not be accurate for the XBB* time tree. The x-axis of the diversity trees shows the number of mutations from the outgroup.
### metadata and trees
source("scripts/tree.r")
# load trees from files
mltree <- read.tree(paste0(params$datadir,"/aligned_nonrecombinant_sample1.rtt.nwk"))
ttree <- read.tree(paste0(params$datadir,"/aligned_nonrecombinant_sample1.timetree.nwk"))
recombTTree <- read.tree(paste0(params$datadir,"/aligned_recombinant_XBBS_sample1.timetree.nwk"))
#stopifnot(all(sort(mltree$tip.label) == sort(ttree$tip.label)))
dateseq <- seq(ymd('2019-12-01'), ymd('2022-12-01'), by='3 month')
# tips are labeled with [fasta name]_[lineage]_[coldate]
# extracting just the first part makes it easier to link to metadata
mltree$tip.label <- reduce.tipnames(mltree$tip.label)
ttree$tip.label <- reduce.tipnames(ttree$tip.label)
recombTTree$tip.label <- reduce.tipnames(recombTTree$tip.label)
fieldnames<- c("fasta_header_name", "province", "host_gender", "host_age_bin",
"sample_collected_by", "purpose_of_sampling",
"lineage", "pango_group","month","week", "GID", "isolate")
# extract rows from metadata table that correspond to ttree
metasub1 <- meta[meta$fasta_header_name%in% ttree$tip.label, fieldnames]
# sort rows to match tip labels in tree
metasub1 <- metasub1[match(ttree$tip.label, metasub1$fasta_header_name), ]
#omi tree metadata
metasub_omi <- metasub1[grepl("Omicron",metasub1$pango_group ), ]
#recomb tree metadata
mmetasub_recomb <- meta[meta$fasta_header_name%in% recombTTree$tip.label, fieldnames]
mmetasub_recomb <- mmetasub_recomb[match(recombTTree$tip.label, mmetasub_recomb$fasta_header_name), ]
#scale to number of mutations
mltree$edge.length <- mltree$edge.length*29903
mltree <- ladderize(mltree, FALSE)
###Time Tree
ttree$edge.length[ttree$edge.length == 0] <- 1e-4
#ttree <- ladderize(ttree, FALSE)
recombTTree$edge.length[recombTTree$edge.length == 0] <- 1e-4
#recombTTree <- ladderize(recombTTree, FALSE)
hab=unique(meta$host_age_bin)
hab=hab[order(hab)]
months=unique(meta$month)
months=as.character(months[order(months)])
weeks=unique(meta$week)
weeks=as.character(weeks[order(weeks)])
presetColors=data.frame(name=c("other",
VOCVOI$name,
hab,
months,
weeks),
color=c("#777777",
VOCVOI$color,
rev(hcl.colors(length(hab)-1, "Berlin")),"#777777",
hcl.colors(length(months), "Berlin"),
hcl.colors(length(weeks), "Berlin")
))
#suppressWarnings({
# res <- ace(metasub1$pango.group, ttree2, type="discrete", model="ER")
#})
#idx <- apply(res$lik.anc, 1, which.max)[2:nrow(res$lik.anc)] # exclude root edge
#anc <- levels(as.factor(metasub1$pango.group))[idx]
source("scripts/tree.r")
timeTreeJsonObj <- DrawTree(ttree, metasub1, "timetree", presetColors, fieldnames=fieldnames)
recombTimeTreeJsonObj <- DrawTree(recombTTree, mmetasub_recomb, "recombtimetree", presetColors, "lineage", fieldnames= fieldnames)
#diversity ML tree
diversityTreeJsonObj <- DrawTree(mltree, metasub1, "mltree", presetColors, fieldnames=fieldnames)
### omicron diversity tree
MLtree_omi<-keep.tip(mltree, metasub_omi$fasta_header_name)
OmicrondiversityTreeJsonObj <- DrawTree(MLtree_omi, metasub_omi, "omimltree", presetColors, fieldnames=fieldnames)
The slope of root-to-tip plots over time provide an estimate of the substitution rate. A lineage with a steeper positive slope than average for SARS-CoV-2 is accumulating mutations at a faster pace, while a lineage that exhibits a jump up (a shift in intercept but not slope) has accumulated more than expected numbers of mutations in a transient period of time (similar to what we saw with Alpha when it first appeared in the UK).
get.tipnames <- function(tip.label) {
sapply(tip.label, function(x) {
tokens <- strsplit(x, "_")[[1]]
ntok <- length(tokens)
paste(tokens[1:(ntok-2)], collapse='_')
})
}
source("scripts/fit-rtt.R")
fit1 <- fit.rtt(paste0(params$datadir, "/aligned_allSeqs_sample1.rtt.nwk"), plot=TRUE)
fit2 <- fit.rtt(paste0(params$datadir,"/aligned_allSeqs_sample2.rtt.nwk"), plot=FALSE)
fit3 <- fit.rtt(paste0(params$datadir,"/aligned_allSeqs_sample3.rtt.nwk"), plot=FALSE)
Here we show the estimate of the substitution rate for 3 independent subsamples of different variants of interest, with their 95% confidence interval.
if(!is.null(fit1)){
est1 <- get.ci(fit1); est1$rep <- 'Rep1'
est2 <- get.ci(fit2); est2$rep <- 'Rep2'
est3 <- get.ci(fit3); est3$rep <- 'Rep3'
sec.frame <- rbind(est1, est2, est3)
sec.frame$est[sec.frame$est < 0] <- 0
sec.frame$lower.95[sec.frame$lower.95 < 0] <- 0
sec.frame=sec.frame[sec.frame$Lineage != "Recombinants",]
pal <- VOCVOI$color
names(pal) <- VOCVOI$name
pal["other"] <- "white"
ggplot(sec.frame, aes(x=Lineage, y=est, group=rep)) +
geom_bar(stat="identity", color="black", aes(fill=Lineage), position='dodge') +
scale_fill_manual(values=pal) +
theme(axis.text.x = element_text(size=9, angle=45, hjust=1, vjust=0.95),
legend.position='none', panel.grid.major=element_line(colour="grey90")) +
geom_errorbar(aes(ymin=lower.95, ymax=upper.95), width=.7,
position=position_dodge(1)) +
labs(y="Substitutions / Genome / Day",
x="Lineage", fill="Subsample")
}
Here we present a searchable table that provides a short description of each lineage as well information on the ancestor of that lineage.
#show the table as paged and column searchable.
DisplayHTMLTable(lineageDescriptions)
#cat (paste0("[Please click here to download data](",DisplayHTMLTableDownloadLink(lineageDescriptions, "LineageDescriptions"),")\n\n"))
We are in the process of adding or would like to develop code for some of the following analyses:
With anonymized data on vaccination status, severity/outcome, reason for sequencing (e.g., outbreak, hospitalization, or general sampling), and setting (workplace, school, daycare, LTC, health institution, other), we could analyze genomic characteristics of the virus relative to the epidemiological and immunological conditions in which it is spreading and evolving. Studies on mutational correlations to superspreading events, vaccination status, or comparisons between variants would allow us to better understand transmission and evolution in these environments.
Genome data and metadata are sourced from the Canadian VirusSeq Data Portal. Pango lineage assignments are generated using the pangoLEARN algorithm. Source code for generating this RMarkdown notebook can be found in [https://github.com/CoVaRR-NET/duotang].
Canadian genomes were obtained from the VirusSeq data on the March 11, 2023 and down-sampled to two genomes per lineage, province and month before October 2021, and five genomes per lineage, province and month after October 2021 (about 10,000 genomes in total). We used a Python wrapper of minimap2 (version 2.17) to generate multiple sequence alignments for these genome samples. A maximum likelihood (ML) tree was reconstructed from each alignment using the COVID-19 release of IQ-TREE (version 2.2.0). Outliers were identified in by root-to-tip regression using the R package ape and removed from the dataset. TreeTime was used to reconstruct a time-scaled tree under a strict molecular clock model. The resulting trees were converted into interactive plots with ggfree and r2d3.
We extracted mutation frequencies from unaligned genomes using a custom Python wrapper of minimap2 (version 2.17). These data were supplemented with genomic data and metadata from the NCBI GenNank database, curated by the Nextstrain development team. We used these outputs to generate mutational graphs reporting mutations seen in at least 75% of sequences in the respective variants of concern in Canada. Bars are colored by substitution type, and the corresponding amino acid changes are shown. Genomic position annotations were generated in Python using SnpEFF.
To estimate selection, we used standard likelihood techniques. In brief, sublineages of interest were prespecified (e.g., BA.1, BA.1.1, BA.2) and counts by day tracked over time. If selection were constant over time, the frequency of sub-type \(i\) at time \(t\) would be expected to rise according to \[p_i(t) = \frac{p_i(0) \exp(s_i t)}{\sum_j p_j(0) \exp(s_j t)},\] where \(s_i\) is the selection coefficient favouring sub-type \(i\). A selection coefficient of \(s_i=0.1\) implies that sub-type \(i\) is expected to rise from 10% to 90% frequency in 44 days (in \(4.4./s_i\) days for other values of \(s_i\)).
At any given time \(t\), the probability of observing \(n_i\) sequences of sublineage \(i\) is multinomially distributed, given the total number of sequences from that day and the frequency of each \(p_i(t)\). Consequently, the likelihood of seeing the observed sequence data over all times \(t\) and over all sublineages \(j\) is proportional to \[L = \prod_t \prod_j p_i(t)^{n_i(t)}.\]
The BBMLE package in R was used to maximize the likelihood of the observed data (using the default optimization method, optim). For each selection coefficient, 95% confidence intervals were obtained by profile likelihood (using uniroot).
Graphs illustrating the rise in frequency of a variant over time are shown (left panels), with the area of each dot proportional to the number of sequences. 95% confidence bands were obtained by randomly drawing 10,000 sets of parameters (\(p_i\) and \(s_i\) for each sub-type) using RandomFromHessianOrMCMC
, assuming a multi-normal distribution around the maximum likelihood point (estimated from the Hessian matrix, Pawitan 2001). At each point in time, the 2.5%-97.5% range of values for \(p_i(t)\) are then shown in the confidence bands.
Logit plots (right panels) show \[ln(\frac{p_i(t)}{p_{ref}(t)})\] relative to a given reference genotype (here BA.1), which gives a line whose slope is the strength of selection \(s_i\). Changes in slope indicate changes in selection on a variant (e.g., see Otto et al.).
These estimates of selection ignore heterogeneity within provinces and may be biased by the arrival of travel-related cases while frequencies are very low. Sampling strategies that oversample clustered cases (e.g., sequencing outbreaks) will introduce additional variation beyond the multinomial expectation, but these should lead to one-time shifts in frequency rather than trends over time. Provinces with sampling strategies that are variant specific are removed, unless explicit information about the variant frequencies is available.
Maximum likelihood tree (IQ-TREE) processed with root-to-tip regression and plotting in R.
All analyses draw on the most recent publicly available viral sequence data on ViralSeq and should be interpreted with caution due to lags in reporting and sequencing priorities that can differ across provinces or territories. Note that the NCCID provides a timeline of Canadian events related to each variant: https://nccid.ca/covid-19-variants/.
Provincial sequencing strategy includes a subset of representative positive samples and prioritized cases (outbreaks, long-term care, travel-related, vaccine escape, hospitalized). Additional up-to-date covid data for this province can be found here:
http://www.bccdc.ca/health-info/diseases-conditions/covid-19/data-trends
Additional up-to-date COVID data for this province can be found here:
https://www.alberta.ca/stats/covid-19-alberta-statistics.htm#variants-of-concern
Additional up-to-date COVID data for this province can be found here:
https://www.saskatchewan.ca/government/health-care-administration-and-provider-resources/treatment-procedures-and-guidelines/emerging-public-health-issues/2019-novel-coronavirus/cases-and-risk-of-covid-19-in-saskatchewan
Additional up-to-date COVID data for this province can be found here:
https://geoportal.gov.mb.ca/apps/manitoba-covid-19/explore
Additional up-to-date COVID data for this province can be found here:
https://www.publichealthontario.ca/en/diseases-and-conditions/infectious-diseases/respiratory-diseases/novel-coronavirus/variants
Provincial random sequencing has been temporarily suspended as of Feb 8th, 2021. Quebec provides a list of updates on changes to screening and sequencing strategies, found here (in French): https://www.inspq.qc.ca/covid-19/donnees/variants#methodologie. Additiona up-to-date COVID data for this province can be found here:
https://www.inspq.qc.ca/covid-19/donnees/variants
Additional up-to-date COVID data for this province can be found here:
https://experience.arcgis.com/experience/204d6ed723244dfbb763ca3f913c5cad
Additional up-to-date COVID data for this province can be found here:
https://experience.arcgis.com/experience/8eeb9a2052d641c996dba5de8f25a8aa (NB dashboard)
Additional up-to-date COVID data for this province can be found here:
https://covid-19-newfoundland-and-labrador-gnl.hub.arcgis.com/
Collect a list of bioinformatics, phylogenetic, and modelling tools that are useful for SARS-CoV-2 analyses:
The version numbers of all packages in the current environment as well as information about the R install is reported below.
sessionInfo()
## R version 4.2.2 (2022-10-31)
## Platform: x86_64-redhat-linux-gnu (64-bit)
## Running under: Fedora Linux 37 (Server Edition)
##
## Matrix products: default
## BLAS/LAPACK: /usr/lib64/libflexiblas.so.3.3
##
## locale:
## [1] LC_CTYPE=en_CA.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_CA.UTF-8 LC_COLLATE=en_CA.UTF-8
## [5] LC_MONETARY=en_CA.UTF-8 LC_MESSAGES=en_CA.UTF-8
## [7] LC_PAPER=en_CA.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_CA.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] stats4 grid splines parallel stats graphics grDevices
## [8] utils datasets methods base
##
## other attached packages:
## [1] HelpersMG_5.8 Matrix_1.5-1 coda_0.19-4 rlang_1.0.6
## [5] MASS_7.3-58.1 bbmle_1.0.25 plotly_4.10.1 DT_0.27
## [9] reshape2_1.4.4 forcats_1.0.0 stringr_1.5.0 dplyr_1.1.0
## [13] purrr_1.0.1 readr_2.1.3 tibble_3.1.8 tidyverse_1.3.2
## [17] jsonlite_1.8.4 r2d3_0.2.6 ggfree_0.1.0 ape_5.6-2
## [21] ggplot2_3.4.0 lubridate_1.9.1 knitr_1.42 tidyr_1.3.0
##
## loaded via a namespace (and not attached):
## [1] nlme_3.1-160 fs_1.6.0 httr_1.4.4
## [4] numDeriv_2016.8-1.1 tools_4.2.2 backports_1.4.1
## [7] bslib_0.4.2 utf8_1.2.2 R6_2.5.1
## [10] DBI_1.1.3 lazyeval_0.2.2 colorspace_2.1-0
## [13] withr_2.5.0 tidyselect_1.2.0 compiler_4.2.2
## [16] cli_3.6.0 rvest_1.0.3 xml2_1.3.3
## [19] labeling_0.4.2 sass_0.4.5 scales_1.2.1
## [22] mvtnorm_1.1-3 digest_0.6.31 rmarkdown_2.20
## [25] pkgconfig_2.0.3 htmltools_0.5.4 highr_0.10
## [28] dbplyr_2.3.0 fastmap_1.1.0 htmlwidgets_1.6.1
## [31] readxl_1.4.1 rstudioapi_0.14 farver_2.1.1
## [34] jquerylib_0.1.4 generics_0.1.3 crosstalk_1.2.0
## [37] googlesheets4_1.0.1 magrittr_2.0.3 Rcpp_1.0.10
## [40] munsell_0.5.0 fansi_1.0.4 lifecycle_1.0.3
## [43] stringi_1.7.12 yaml_2.3.7 plyr_1.8.8
## [46] bdsmatrix_1.3-6 crayon_1.5.2 lattice_0.20-45
## [49] haven_2.5.1 hms_1.1.2 pillar_1.8.1
## [52] reprex_2.0.2 glue_1.6.2 evaluate_0.20
## [55] data.table_1.14.6 modelr_0.1.10 vctrs_0.5.2
## [58] tzdb_0.3.0 cellranger_1.1.0 gtable_0.3.1
## [61] assertthat_0.2.1 cachem_1.0.6 xfun_0.36
## [64] broom_1.0.3 googledrive_2.0.0 viridisLite_0.4.1
## [67] gargle_1.3.0 timechange_0.2.0 ellipsis_0.3.2
We thank all the authors, developers, and contributors to the VirusSeq database for making their SARS-CoV-2 sequences publicly available. We especially thank the Canadian Public Health Laboratory Network, academic sequencing partners, diagnostic hospital labs, and other sequencing partners for the provision of the Canadian sequence data used in this work. Genome sequencing in Canada was supported by a Genome Canada grant to the Canadian COVID-19 Genomic Network (CanCOGeN).
We gratefully acknowledge all the Authors, the Originating laboratories responsible for obtaining the specimens, and the Submitting laboratories for generating the genetic sequence and metadata and sharing via the VirusSeq database, on which this research is based.
The Canadian VirusSeq Data Portal (https://virusseq-dataportal.ca) We wish to acknowledge the following organisations/laboratories for contributing data to the Portal: Canadian Public Health Laboratory Network (CPHLN), CanCOGGeN VirusSeq, Saskatchewan - Roy Romanow Provincial Laboratory (RRPL), Nova Scotia Health Authority, Alberta ProvLab North (APLN), Queen’s University / Kingston Health Sciences Centre, National Microbiology Laboratory (NML), Institut National de Sante Publique du Quebec (INSPQ), BCCDC Public Health Laboratory, Public Health Ontario (PHO), Newfoundland and Labrador - Eastern Health, Unity Health Toronto, Ontario Institute for Cancer Research (OICR), Provincial Public Health Laboratory Network of Nova Scotia, Centre Hospitalier Universitaire Georges L. Dumont - New Brunswick, and Manitoba Cadham Provincial Laboratory. Please see the complete list of laboratories included in this repository.
Public Health Agency of Canada (PHAC) / National Microbiology Laboratory (NML) - (https://health-infobase.canada.ca/covid-19/epidemiological-summary-covid-19-cases.html)
Various provincial public health websites (e.g. INSPQ https://www.inspq.qc.ca/covid-19/donnees/)