Figure 1 (Participant and case numbers)

Preparations

library(ggplot2)
library(cowplot)
library(magrittr)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
here_r = function (...) here::here("Statistics", "R", ...)
here_koco = function (...) here::here("KoCo19_Datasets", ...)
here_out = function (...) here::here("Epi_out", "epi_figures_yannik_DZ", ...)

dir.create(here_out(), showWarnings = FALSE)

# Setup
source(here_r("setup.R"))
source(here_r("functions.R"))

koco_data = read.csv(here_koco("Analysis Data Sets", "Koco_baseline.csv"))
muc_data = read.csv(here_koco(
  "Analysis Data Sets", "muc_data_cases_04_11.csv"), sep=',')

# Definitions
col_red = "#bf635d"
col_blue = "#59a3b2"
col_green = "#59ab7c"
col_yellow = "#efae59"

col_koco = col_blue
col_muc = col_red
col_koco2 = "#01579B"
col_muc2 = "#B00020"
col_books = col_yellow

date_range = c(as.Date("2020-03-01"), as.Date("2020-06-08"))
date_breaks = as.Date(
  c("2020-03-01", "2020-04-01", "2020-05-01", "2020-06-01"))
date_break_labels =
  c("01.03.", "01.04.", "01.05.", "01.06.")

###############################################################################
# Set up Munich data

muc_data = dplyr::rename(
  muc_data,
  "Date"="Datum",
  "Deceased"="Verstorben",
  "Recovered"="Genesen",
  "Total"="Gesamt",
  "Infected"="Aktuell.infiziert"
)

# Fix funny data entries
muc_data[is.na(muc_data)] = 0

# Clear up Date
muc_data$Date = paste(muc_data$Date,"-2020", sep="")
muc_data$Date = as.Date(as.character(muc_data$Date), format = c("%d-%b-%Y"))
# Restrict to < 2020-06-06 as there was a change of data thereafter
muc_data = muc_data[which(muc_data$Date < "2020-06-13"),]
# Get week of year (as by ISO 8601, starting on Monday)
muc_data$Week = lubridate::isoweek(muc_data$Date)

# Check totals
muc_data$Total_check =
  muc_data$Deceased + muc_data$Recovered + muc_data$Infected
if (sum(muc_data$Total-muc_data$Total_check) != 0) {
  stop("Totals do not fit")
}

muc_date = muc_data

# Get table with the first entry per week
muc_week = muc_data[!duplicated(muc_data$Week),]

# Get table with the last entry per week
#muc_week = muc_data[!rev(duplicated(rev(muc_data$Week))),]

# Count new cases
muc_date$New_cases = muc_date$Total -
  c(0, muc_date[1:nrow(muc_date)-1,"Total"])
muc_week$New_cases = muc_week$Total -
  c(0, muc_week[1:nrow(muc_week)-1,"Total"])
# Count new deaths
muc_week$New_deceased = muc_week$Deceased -
  c(0, muc_week[1:nrow(muc_week)-1,"Deceased"])

###############################################################################
# Set up KoCo data

koco_data$Result_bin = ifelse(koco_data$R_quant >= cutoff$new$Roche, 1, 0)
koco_data$Date = as.Date(koco_data$VisitDate, format = c("%Y-%m-%d"))
koco_data$Week = lubridate::isoweek(koco_data$Date)
# Sort by date
koco_data = koco_data[order(koco_data$Date),]

# Attach all week 24 to week 23 due to low case numbers thereafter and
#  change in Munich data
koco_data[koco_data$Week>23, "Week"] = 23

# Group per date
koco_date = koco_data %>% group_by(Date) %>%
  summarise(Recruits=n(), Cases=sum(Result_bin))
## `summarise()` ungrouping output (override with `.groups` argument)
koco_date$Recruits_cumsum = cumsum(koco_date$Recruits)
koco_date$Cases_cumsum = cumsum(koco_date$Cases)

# Group per week
koco_week =
  koco_data %>% group_by(Week) %>%
  summarise(Recruits=n(), Cases=sum(Result_bin), Date=first(Date))
## `summarise()` ungrouping output (override with `.groups` argument)
koco_week$Recruits_cumsum = cumsum(koco_week$Recruits)
koco_week$Cases_cumsum = cumsum(koco_week$Cases)

# Normalize
koco_date$Cases_rel = koco_date$Cases / koco_date$Recruits
koco_week$Cases_rel = koco_week$Cases / koco_week$Recruits

# Uncertainties
koco_week[c("Cases_rel_lci", "Cases_rel_uci")] =
  epitools::pois.exact(koco_week$Cases, koco_week$Recruits,
                       conf.level = 0.95)[c("lower", "upper")]

roche_sero_estimates = read.csv(
  here_koco("Analysis Data Sets", "Roche_sero_estimates.csv"))
rownames(roche_sero_estimates) =
  paste(roche_sero_estimates[,"group1"],
        roche_sero_estimates[,"group2"], sep=" & ")
adjusted_weighted_estimate =
  roche_sero_estimates["Sampling Weighted & Adjusted", "ppest"] / 100
adjusted_weighted_lci =
  roche_sero_estimates["Sampling Weighted & Adjusted", "pplci"] / 100
adjusted_weighted_uci =
  roche_sero_estimates["Sampling Weighted & Adjusted", "ppuci"] / 100

# Icons
here_images = function (...) here::here("Icons", ...)
icon_antibody = png::readPNG(here_images("icon_antibody.png"))
icon_coronavirus = png::readPNG(here_images("icon_coronavirus.png"))
icon_book = png::readPNG(here_images("icon_book.png"))
g_antibody = grid::rasterGrob(icon_antibody)
g_coronavirus = grid::rasterGrob(icon_coronavirus)
g_book = grid::rasterGrob(icon_book)
ggplot() + annotation_custom(g_antibody)

ggplot() + annotation_custom(g_coronavirus)

ggplot() + annotation_custom(g_book)

Cumulatives

#ggplot(data=muc_date, aes(x=Date, y=Total), col=col_muc) +
#  geom_line(col=col_muc) + geom_point(col=col_muc)
plt_muc_weeklycumu = ggplot(data=muc_week, aes(x=Date, y=Total)) +
  geom_line(col=col_muc) + geom_point(col=col_muc) +
  #theme(axis.text.x = element_text(angle = 90)) +
  #scale_x_date(limits = date_range, labels = scales::date_format("%b %d"))
  scale_x_date(
    limits = date_range, breaks=date_breaks, labels = date_break_labels) +
  theme(axis.title.x = element_blank()) +
  labs(y="Kumulative # \n PCR-positiver Tests", x="")
plt_muc_weeklycumu
## Warning: Removed 3 row(s) containing missing values (geom_path).
## Warning: Removed 3 rows containing missing values (geom_point).

ggsave(here_out("DZ_muc_weeklycumu.png"), width=3.5, height=2.5, dpi=720)
## Warning: Removed 3 row(s) containing missing values (geom_path).

## Warning: Removed 3 rows containing missing values (geom_point).
ggsave(here_out("DZ_muc_weeklycumu.pdf"), width=3.5, height=2.5, dpi=720)
## Warning: Removed 3 row(s) containing missing values (geom_path).

## Warning: Removed 3 rows containing missing values (geom_point).
#ggplot(data=koco_date, aes(x=Date, y=Recruits_cumsum)) +
#  geom_line(col=col_koco) + geom_point(col=col_koco)
ggplot(data=koco_week, aes(x=Date, y=Recruits_cumsum)) +
  geom_line(col=col_koco) + geom_point(col=col_koco)

#ggplot(data=koco_date, aes(x=Date, y=Cases_cumsum)) +
#  geom_line(col=col_koco) + geom_point(col=col_koco)
ggplot(data=koco_week, aes(x=Date, y=Cases_cumsum)) +
  geom_line(col=col_koco) + geom_point(col=col_koco)

Relative case numbers

ggplot(data=koco_date, aes(x=Date, y=Cases_rel)) +
  geom_line(col=col_koco) + geom_point(col=col_koco)

plt_koco_weeklyfrac = ggplot(data=koco_week, aes(x=Date, y=Cases_rel)) +
  geom_rect(
    ymin=adjusted_weighted_lci, ymax=adjusted_weighted_uci,
    xmin=as.Date("2020-04-06"), xmax=Inf, fill="grey30", alpha=0.01) +
  geom_segment(
    y = adjusted_weighted_estimate, yend = adjusted_weighted_estimate,
    x=as.Date("2020-04-06"), xend=Inf, col="grey30") +
  geom_line(col=col_koco) + geom_point(col=col_koco) +
  geom_line(aes(y=Cases_rel_lci), col=col_koco, linetype="dashed") +
  geom_line(aes(y=Cases_rel_uci), col=col_koco, linetype="dashed") +
  scale_y_continuous(
    limits = c(0, max(koco_week$Cases_rel_uci)), labels = scales::percent) +
  #scale_x_date(limits = date_range, labels = scales::date_format("%b %d"))
  scale_x_date(
    limits = date_range, breaks=date_breaks, labels = date_break_labels) +
  theme(axis.title.x = element_blank()) +
  labs(y="Prozentsatz positiver\nAntikörper-Tests", x="")
plt_koco_weeklyfrac

ggsave(here_out("DZ_koco_weeklyfrac.png"), width=3.5, height=2.5, dpi=720)
ggsave(here_out("DZ_koco_weeklyfrac.pdf"), width=3.5, height=2.5, dpi=720)

New cases

ggplot(data=muc_date, aes(x=Date, y=New_cases)) +
  geom_line(col=col_muc) + geom_point(col=col_muc)

plt_muc_weeklycases = ggplot(data=muc_week, aes(x=Date, y=New_cases)) +
  geom_line(col=col_muc) + geom_point(col=col_muc) +
  #scale_x_date(limits = date_range, labels = scales::date_format("%b %d"))
  scale_x_date(limits = date_range, breaks=date_breaks, labels = date_break_labels) +
  theme(axis.title.x = element_blank()) +
  labs(y="# Offiziell registrierter\nPCR-positiver Tests", x="")
plt_muc_weeklycases
## Warning: Removed 3 row(s) containing missing values (geom_path).
## Warning: Removed 3 rows containing missing values (geom_point).

ggsave(here_out("DZ_muc_weeklycases.png"), width=3.5, height=2.5, dpi=720)
## Warning: Removed 3 row(s) containing missing values (geom_path).

## Warning: Removed 3 rows containing missing values (geom_point).
ggsave(here_out("DZ_muc_weeklycases.pdf"), width=3.5, height=2.5, dpi=720)
## Warning: Removed 3 row(s) containing missing values (geom_path).

## Warning: Removed 3 rows containing missing values (geom_point).
ggplot(data=koco_date, aes(x=Date, y=Recruits)) +
  geom_line(col=col_koco) + geom_point(col=col_koco)

plt_koco_weeklyrecruits = ggplot(data=koco_week, aes(x=Date, y=Recruits)) +
  geom_line(col=col_koco) + geom_point(col=col_koco) +
  #scale_x_date(limits = date_range, labels = scales::date_format("%b %d"))
  scale_x_date(limits = date_range, breaks=date_breaks, labels = date_break_labels) +
  scale_y_continuous(limits = c(0, max(koco_week$Recruits))) +
  theme(axis.title.x = element_blank()) +
  labs(y="# KoCo19 Teilnehmer\npro Woche", x="")
plt_koco_weeklyrecruits

ggsave(here_out("DZ_koco_weeklyrecruits.png"), width=3.5, height=2.5, dpi=720)
ggsave(here_out("DZ_koco_weeklyrecruits.pdf"), width=3.5, height=2.5, dpi=720)

ggplot(data=koco_date, aes(x=Date, y=Cases)) +
  geom_line(col=col_koco) + geom_point(col=col_koco)

ggplot(data=koco_week, aes(x=Date, y=Cases)) +
  geom_line(col=col_koco) + geom_point(col=col_koco)

Prevalence

# Munich population size >= 14 years
muc_pop_size = 1561720 # 1369444
muc_estimate = muc_date[nrow(muc_date), "Total"] / muc_pop_size

rounded_labels <- function(x) sprintf("%.1f%%", x*100)

plt_prevalence = ggplot(
  data.frame(Source=factor(c("Offiziell", "KoCo19"),
                           levels=c("Offiziell", "KoCo19")),
             Prevalence=c(muc_estimate, adjusted_weighted_estimate),
             Image=c(here_images("icon_coronavirus.png"),
                     here_images("icon_antibody.png"))), 
       aes(x=Source, y=Prevalence, col=Source, fill=Source)) +
  geom_bar(stat="identity", alpha=0.5) +
  scale_color_manual(values=c(col_muc, col_koco)) +
  scale_fill_manual(values=c(col_muc, col_koco)) +
  theme(legend.position = "none", axis.title.x=element_blank()) +
  annotation_custom(g_coronavirus, xmin=0.8, xmax=1.2, ymin=0.018, ymax=Inf) +
  annotation_custom(g_antibody, xmin=1.8, xmax=2.2, ymin=0.018, ymax=Inf) +
  geom_segment(
    x = 1.1, y = muc_estimate+0.0003,
    xend = 1.1, yend = adjusted_weighted_estimate-0.0003,
    lineend = "round", linejoin = "round",
    arrow = arrow(length = unit(0.08, "npc")),
    colour = "grey40") +
  geom_segment(
    xend = 1.1, yend = muc_estimate+0.0003,
    x = 1.1, y = adjusted_weighted_estimate-0.0003,
    lineend = "round", linejoin = "round",
    arrow = arrow(length = unit(0.08, "npc")),
    colour = "grey40") +
  annotate(geom="text", x= 0.9, y=muc_estimate + (adjusted_weighted_estimate - muc_estimate) / 2,
           label=sprintf("≈%.0f-fach", adjusted_weighted_estimate / muc_estimate),
           angle=90) +
  scale_y_continuous(limits=c(0,0.02), labels=rounded_labels) +
  labs(x="", y="Prävalenz-Schätzer")
plt_prevalence

ggsave(here_out("DZ_prevalence.png"), width=2.5, height=3.5, dpi=720)
ggsave(here_out("DZ_prevalence.pdf"), width=2.5, height=3.5, dpi=720, device=cairo_pdf)

Combined

# Margin
mg=0.3

plt_1 = plot_grid(
  plot_grid(
    plot_grid(
      ggplot() + annotation_custom(g_coronavirus) + theme_minimal(),
      title_plot("A", plt_muc_weeklycases) +
        theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
      title_plot("B", plt_muc_weeklycumu) +
        theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
      nrow = 1, rel_widths = c(1.2, 5, 5)),
    plot_grid(
      ggplot() + annotation_custom(g_antibody) + theme_minimal(),
      title_plot("C", plt_koco_weeklyrecruits) +
        theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
      title_plot("D", plt_koco_weeklyfrac) +
        theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
      nrow = 1, rel_widths = c(1.2, 5, 5)),
    nrow=2),
  plot_grid(
    NULL,
    title_plot("E", plt_prevalence) +
      theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
    NULL, rel_heights = c(1, 5, 1), ncol = 1),
  nrow=1, rel_widths = c(4, 1.2)
)
## Warning: Removed 3 row(s) containing missing values (geom_path).
## Warning: Removed 3 rows containing missing values (geom_point).
## Warning: Removed 3 row(s) containing missing values (geom_path).
## Warning: Removed 3 rows containing missing values (geom_point).
plt_1

ggsave(here_out("DZ_fig1.png"), width=10, height=5, dpi=720)
ggsave(here_out("DZ_fig1.pdf"), width=10, height=5, dpi=720, device=cairo_pdf)

Figure 2 (Mortality)

Overall mortality

mort_data = read.csv(here_koco("Analysis Data Sets", "mor_muc.csv"))
mort_data$Mean = rowMeans(mort_data[,c("Y2016", "Y2017", "Y2018", "Y2019")])
mort_data = mort_data %>% dplyr::rename(
  "Week"="X", "2016"="Y2016", "2017"="Y2017", "2018"="Y2018", "2019"="Y2019",
  "2020"="Y2020")
mort_data$Mean_2016_20 = NULL

# Restrict to week < 24
# Exclude week 24, as the death data must have been corrected there
mort_data = mort_data[which(mort_data$Week < 24),]

# Add dates to mort_data
mort_data = inner_join(
  mort_data, muc_week[,c("Date" ,"Week", "New_deceased")], by = "Week")

# Calculate excess mortalities
mort_data$Excess2016 = mort_data$`2016`- mort_data$`Mean`
mort_data$Excess2017 = mort_data$`2017`- mort_data$`Mean`
mort_data$Excess2018 = mort_data$`2018`- mort_data$`Mean`
mort_data$Excess2019 = mort_data$`2019`- mort_data$`Mean`
mort_data$Excess2020 = mort_data$`2020`- mort_data$`Mean`

# To long format for figure
mort_data_long = mort_data %>% 
  dplyr::rename("Mittelwert"="Mean") %>%
  reshape2::melt(
   id=c("Week", "Date", "Excess2016", "Excess2017", "Excess2018",
        "Excess2019", "Excess2020", "New_deceased")) %>%
  dplyr::rename("Year"="variable")

# For correct order
mort_data_long$Year = factor(
  mort_data_long$Year,
  levels = c("2016", "2017", "2018", "2019", "Mittelwert", "2020"))

plt_deaths = ggplot(mort_data_long,
                    aes(x=Date, col=`Year`, fill=`Year`, y=value)) +
  geom_line() + geom_point() +
  scale_y_continuous(limits=c(0, max(mort_data_long$value)+2)) +
  scale_x_date(limits = date_range, breaks=date_breaks,
               labels = date_break_labels) +
  scale_color_manual(
    values = c("grey65", "grey50", "grey35", "grey20",
               col_green, col_yellow)) +
  labs(x="", y="Wöchentliche Todesfälle") +
  theme(legend.title=element_blank(),
        legend.position=c(0.5, 0.2),
        legend.direction = "horizontal",
        axis.title.x = element_blank())
plt_deaths
## Warning: Removed 18 row(s) containing missing values (geom_path).
## Warning: Removed 18 rows containing missing values (geom_point).

ggsave(here_out("DZ_deaths.png"), width=3.5, height=3, dpi=720)
## Warning: Removed 18 row(s) containing missing values (geom_path).

## Warning: Removed 18 rows containing missing values (geom_point).
ggsave(here_out("DZ_deaths.pdf"), width=3.5, height=3, dpi=720)
## Warning: Removed 18 row(s) containing missing values (geom_path).

## Warning: Removed 18 rows containing missing values (geom_point).

Excess mortality

# To long format for figure
mort_data_long = mort_data[,c("Date", "Excess2020", "New_deceased")] %>%
  dplyr::rename("Übersterblichkeit\nMärz-Juni 2020"="Excess2020",
                "SARS-CoV-2 assozi-\nierte Todesfälle"="New_deceased") %>%
  reshape2::melt(id="Date") %>%
  dplyr::rename("Year"="variable")

plt_excess = ggplot(mort_data_long, aes(x=Date, col=`Year`, fill=`Year`,
                                        y=value)) +
  geom_line() + geom_point() +
  geom_area(
    #data=mort_data_long[mort_data_long$Year=="Übersterblichkeit\nMärz-Juni 2020",],
    alpha=0.3, position="identity", show.legend = FALSE) +
  geom_line() + geom_point() +
  scale_x_date(limits = date_range, breaks=date_breaks,
               labels = date_break_labels) +
  scale_color_manual(values = c(col_books, col_muc)) +
  scale_fill_manual(values = c(col_books, col_muc)) +
  geom_hline(yintercept=0, linetype="dotted") +
  scale_y_continuous(limits=c(-50,max(mort_data_long$value))) +
  labs(x="", y="Abweichungen in der Anzahl\nwöchentlicher Todesfälle") +
  theme(legend.title=element_blank(),
        legend.position=c(0.5, 0.2),
        legend.background = element_blank(),
        # No other way to increase the spacing
        legend.key.height=unit(0.8, 'cm'),
        #legend.direction="vertical",
        axis.title.x = element_blank())
plt_excess
## Warning: Removed 6 row(s) containing missing values (geom_path).
## Warning: Removed 6 rows containing missing values (geom_point).
## Warning: Removed 6 row(s) containing missing values (geom_path).
## Warning: Removed 6 rows containing missing values (geom_point).

ggsave(here_out("DZ_excess.png"), width=3.5, height=3, dpi=720)
## Warning: Removed 6 row(s) containing missing values (geom_path).

## Warning: Removed 6 rows containing missing values (geom_point).
## Warning: Removed 6 row(s) containing missing values (geom_path).
## Warning: Removed 6 rows containing missing values (geom_point).
ggsave(here_out("DZ_excess.pdf"), width=3.5, height=3, dpi=720)
## Warning: Removed 6 row(s) containing missing values (geom_path).

## Warning: Removed 6 rows containing missing values (geom_point).
## Warning: Removed 6 row(s) containing missing values (geom_path).
## Warning: Removed 6 rows containing missing values (geom_point).

Totals

# Total excess mortalitiy in the period of consideration
total_excess_mortality = sum(mort_data$Excess2020)
# Total Covid related deaths in the period of consideration
total_covid_deaths = muc_week[nrow(muc_week), "Deceased"]
# Category labels
labels = c("Übersterblichkeit\nMärz-Juni 2020",
           "SARS-CoV-2 assozi-\nierte Todesfälle")

plt_total_deaths = ggplot(
  data.frame(Source=factor(labels, levels=labels),
             Total=c(total_excess_mortality, total_covid_deaths),
             Image=c(here_images("icon_book.png"),
                     here_images("icon_coronavirus.png"))),
       aes(x=Source, y=Total, col=Source, fill=Source)) +
  geom_bar(stat="identity", alpha=0.5) +
  scale_color_manual(values=c(col_books, col_muc)) +
  scale_fill_manual(values=c(col_books, col_muc)) +
  scale_y_continuous(limits=c(0,240)) +
  annotation_custom(g_book, xmin=0.8, xmax=1.2, ymin=220, ymax=Inf) +
  annotation_custom(g_coronavirus, xmin=1.8, xmax=2.2, ymin=220, ymax=Inf) +
  theme(legend.position = "none", axis.title.x=element_blank()) +
  labs(x="", y="Gesamtzahl von Todesfällen")
plt_total_deaths

ggsave(here_out("DZ_total_deaths.png"), width=3.5, height=3, dpi=720)
ggsave(here_out("DZ_total_deaths.pdf"), width=3.5, height=3, dpi=720)

Combined

plt_2= plot_grid(
  title_plot("A", plt_deaths) +
    theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
  title_plot("B", plt_excess) +
    theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
  title_plot("C", plt_total_deaths) +
    theme(plot.margin = unit(c(mg,mg,mg,mg), "cm")),
  nrow = 1, rel_widths = c(4, 4, 4)
)
## Warning: Removed 18 row(s) containing missing values (geom_path).
## Warning: Removed 18 rows containing missing values (geom_point).
## Warning: Removed 6 row(s) containing missing values (geom_path).
## Warning: Removed 6 rows containing missing values (geom_point).
## Warning: Removed 6 row(s) containing missing values (geom_path).
## Warning: Removed 6 rows containing missing values (geom_point).
plt_2

ggsave(here_out("DZ_fig2.png"), width=10, height=3, dpi=720)
ggsave(here_out("DZ_fig2.pdf"), width=10, height=3, dpi=720, device=cairo_pdf)