NOTE: this is a practice project that may not function properly. Proceed with caution :)
COVID Vaccination Rates - Sweden
Data for the project sourced from Swedish Public Health Authority
Libraries Used
library(rvest)
library(tidyverse)
library(ggplot2)
library(rsconnect)
library(taskscheduleR)
Import Data
Using rvest, the application seeks the data from an HTML table on Folkhälsomyndigheten’s website. Whilst the agency does provide Excel data to download it is categorised weekly and not daily. Therefore, this application scrapes the daily figures from this table.
#Fetch NEW
content <- read_html('https://www.folkhalsomyndigheten.se/smittskydd-beredskap/utbrott/aktuella-utbrott/covid-19/vaccination-mot-covid-19/statistik/statistik-over-registrerade-vaccinationer-covid-19/')
#Find and Convert table to df
vac_tab <- content %>% html_table(fill = TRUE)
sv_vac <- vac_tab[[2]]
Cleaning the data
The table is labelled in Swedish and stored as characters when downloaded. Therefore, this step is used to clean up the data so it is ready to be manipulated and improve ease of use.
#Translate Column Names
colnames(sv_vac) <- c("date", "first_total", "first_percent", "second_total", "second_percent")
#Clean Data TypeS
#Date as date
sv_vac$date <- as.Date(sv_vac$date)
#First dose total as number without space separator
sv_vac$first_total <- as.numeric(gsub(" ", "", sv_vac$first_total, fixed=FALSE))
#First dose percentage as number with . separator
sv_vac$first_percent <- as.numeric(gsub(",",".", sv_vac$first_percent, fixed=TRUE))
#Second dose total as number without space separator
sv_vac$second_total <- as.numeric(gsub(" ", "", sv_vac$second_total, fixed=TRUE))
NAs introduced by coercion
#Second dose percentage as number with . separator
sv_vac$second_percent <- as.numeric(gsub(",",".", sv_vac$second_percent, fixed=TRUE))
Adding the newly scraped data to an expanding dataset
The new data is downloaded and stored into the ‘sv_vac’ data frame, this steps takes the new data and joins it to ‘sv_vac_total’ which accumulates each new day of data.
#Load existing data
sv_vac_update <- readRDS(file="sv_vac_total_all.Rda")
#Add new data to temp update df
sv_vac_update <- bind_rows(sv_vac_update, sv_vac)
#Calculate first dose ONLY
sv_vac_update <- sv_vac_update %>%
mutate(first_dose_only = first_total - second_total) %>%
mutate(first_dose_only_pc = first_percent - second_percent)
#Eliminate NAs duplicates
sv_vac_update <- sv_vac_update %>%
arrange(date) %>%
filter(!is.na(first_total)) %>%
filter(duplicated(date) == FALSE)
#Save with Total #s
saveRDS(sv_vac_update, file="sv_vac_total_all.Rda")
Plotting the vacination rates
The data from FHM includes both total number and the percentage of people who have gotten the vaccination. The percentage of population is more meaningful than the raw numbers, so the graph uses those stats. You’ll notice that FHM does not publish data on Saturdays, Sundays and Mondays, hence the gaps in data.
#Load aggregate data
sv_vac_graph_data <- readRDS(file="sv_vac_total_all.Rda")
#ggplot
sv_vac_exp_graph <- ggplot(sv_vac_graph_data, aes(x=date)) +
geom_line(aes(y= first_percent), color = "#ff6600") +
geom_line(aes(y= second_percent), color = "#f8ccab") +
geom_line(aes(y= first_dose_only_pc), color = "red") +
geom_point(aes(y=first_percent), color = "#ff6600", shape=16, size=2) +
geom_point(aes(y=second_percent), color = "#f8ccab", shape=16, size=2) +
geom_point(aes(y=first_dose_only_pc), color = "red", shape=16, size=2) +
labs(
title = "% Vaccinated",
subtitle = "First and Second Doses and Only One Dose",
caption = "Source: Folkhälsomyndigheten",
x = "Date",
y = "Percentage Vaccinated") +
# geom_text(aes(label = first_percent, y = first_percent), nudge_y = 0.5, nudge_x = 1) +
#geom_text(aes(label = second_percent, y = second_percent), nudge_y = -1) +
#geom_text(aes(label = first_dose_only_pc, y = first_dose_only_pc), color = "red", nudge_y = 1) +
scale_x_date(date_labels = "%d %b")
Graph
Note: due to the timing of this project and lack of daily dose date published in a retrievable format from FHM, the data starts from 25/02/2021. Also, colours may not be the best but were chosen to match the colours used by FHM in their official vaccination dashboard. This version [10/03/2021], also experiments with calculating the % of people who have ONLY received one dose. As compared to first dose which includes those who have received a second dose as well. The FHM dashboard is only updated once a week on Thursdays, 14:00 GMT+1. RED - ONLY 1st Dose; ORANGE - Total 1st Dose; PEACH - 2nd Dose.

LS0tDQp0aXRsZTogIlN3ZWRpc2ggVmFjY2luYXRpb25zIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgdGhlbWU6IHNpbXBsZXgNCiAgICBmaWdfd2lkdGg6IDEwDQogICAgZmlnX2hlaWdodDogNg0KICAgIGhpZ2hsaWdodDogdGFuZ28NCi0tLQ0KDQojIyMjIE5PVEU6IHRoaXMgaXMgYSBwcmFjdGljZSBwcm9qZWN0IHRoYXQgbWF5IG5vdCBmdW5jdGlvbiBwcm9wZXJseS4gUHJvY2VlZCB3aXRoIGNhdXRpb24gOikNCg0KIyMgQ09WSUQgVmFjY2luYXRpb24gUmF0ZXMgLSBTd2VkZW4NCg0KRGF0YSBmb3IgdGhlIHByb2plY3Qgc291cmNlZCBmcm9tIFtTd2VkaXNoIFB1YmxpYyBIZWFsdGggQXV0aG9yaXR5XShodHRwczovL3d3dy5mb2xraGFsc29teW5kaWdoZXRlbi5zZS8pDQoNCiMjIyBMaWJyYXJpZXMgVXNlZA0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KI3JtKGxpc3Q9IGxzKCkpDQpgYGANCg0KYGBge3IgbGlicmFyaWVzLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShyc2Nvbm5lY3QpDQpsaWJyYXJ5KHRhc2tzY2hlZHVsZVIpDQpgYGANCg0KIyMjIEltcG9ydCBEYXRhDQoNClVzaW5nIHJ2ZXN0LCB0aGUgYXBwbGljYXRpb24gc2Vla3MgdGhlIGRhdGEgZnJvbSBhbiBIVE1MIHRhYmxlIG9uIEZvbGtow6Rsc29teW5kaWdoZXRlbidzIHdlYnNpdGUuIFdoaWxzdCB0aGUgYWdlbmN5IGRvZXMgcHJvdmlkZSBFeGNlbCBkYXRhIHRvIGRvd25sb2FkIGl0IGlzIGNhdGVnb3Jpc2VkIHdlZWtseSBhbmQgbm90IGRhaWx5LiBUaGVyZWZvcmUsIHRoaXMgYXBwbGljYXRpb24gc2NyYXBlcyB0aGUgZGFpbHkgZmlndXJlcyBmcm9tIHRoaXMgdGFibGUuDQoNCmBgYHtyIHJlc3VsdHM9J2hpZGUnfQ0KI0ZldGNoIE5FVyANCmNvbnRlbnQgPC0gcmVhZF9odG1sKCdodHRwczovL3d3dy5mb2xraGFsc29teW5kaWdoZXRlbi5zZS9zbWl0dHNreWRkLWJlcmVkc2thcC91dGJyb3R0L2FrdHVlbGxhLXV0YnJvdHQvY292aWQtMTkvdmFjY2luYXRpb24tbW90LWNvdmlkLTE5L3N0YXRpc3Rpay9zdGF0aXN0aWstb3Zlci1yZWdpc3RyZXJhZGUtdmFjY2luYXRpb25lci1jb3ZpZC0xOS8nKQ0KDQojRmluZCBhbmQgQ29udmVydCB0YWJsZSB0byBkZg0KdmFjX3RhYiA8LSBjb250ZW50ICU+JSBodG1sX3RhYmxlKGZpbGwgPSBUUlVFKQ0Kc3ZfdmFjIDwtIHZhY190YWJbWzJdXQ0KYGBgDQoNCiMjIyBDbGVhbmluZyB0aGUgZGF0YQ0KDQpUaGUgdGFibGUgaXMgbGFiZWxsZWQgaW4gU3dlZGlzaCBhbmQgc3RvcmVkIGFzIGNoYXJhY3RlcnMgd2hlbiBkb3dubG9hZGVkLiBUaGVyZWZvcmUsIHRoaXMgc3RlcCBpcyB1c2VkIHRvIGNsZWFuIHVwIHRoZSBkYXRhIHNvIGl0IGlzIHJlYWR5IHRvIGJlIG1hbmlwdWxhdGVkIGFuZCBpbXByb3ZlIGVhc2Ugb2YgdXNlLg0KDQpgYGB7Un0NCiNUcmFuc2xhdGUgQ29sdW1uIE5hbWVzDQpjb2xuYW1lcyhzdl92YWMpIDwtIGMoImRhdGUiLCAiZmlyc3RfdG90YWwiLCAiZmlyc3RfcGVyY2VudCIsICJzZWNvbmRfdG90YWwiLCAic2Vjb25kX3BlcmNlbnQiKQ0KI0NsZWFuIERhdGEgVHlwZVMNCiNEYXRlIGFzIGRhdGUNCnN2X3ZhYyRkYXRlIDwtIGFzLkRhdGUoc3ZfdmFjJGRhdGUpDQojRmlyc3QgZG9zZSB0b3RhbCBhcyBudW1iZXIgd2l0aG91dCBzcGFjZSBzZXBhcmF0b3INCnN2X3ZhYyRmaXJzdF90b3RhbCA8LSBhcy5udW1lcmljKGdzdWIoIiAiLCAiIiwgc3ZfdmFjJGZpcnN0X3RvdGFsLCBmaXhlZD1GQUxTRSkpDQojRmlyc3QgZG9zZSBwZXJjZW50YWdlIGFzIG51bWJlciB3aXRoIC4gc2VwYXJhdG9yDQpzdl92YWMkZmlyc3RfcGVyY2VudCA8LSBhcy5udW1lcmljKGdzdWIoIiwiLCIuIiwgc3ZfdmFjJGZpcnN0X3BlcmNlbnQsIGZpeGVkPVRSVUUpKQ0KI1NlY29uZCBkb3NlIHRvdGFsIGFzIG51bWJlciB3aXRob3V0IHNwYWNlIHNlcGFyYXRvcg0Kc3ZfdmFjJHNlY29uZF90b3RhbCA8LSBhcy5udW1lcmljKGdzdWIoIiAiLCAiIiwgc3ZfdmFjJHNlY29uZF90b3RhbCwgZml4ZWQ9VFJVRSkpDQojU2Vjb25kIGRvc2UgcGVyY2VudGFnZSBhcyBudW1iZXIgd2l0aCAuIHNlcGFyYXRvcg0Kc3ZfdmFjJHNlY29uZF9wZXJjZW50IDwtIGFzLm51bWVyaWMoZ3N1YigiLCIsIi4iLCBzdl92YWMkc2Vjb25kX3BlcmNlbnQsIGZpeGVkPVRSVUUpKQ0KYGBgDQojIyMgQWRkaW5nIHRoZSBuZXdseSBzY3JhcGVkIGRhdGEgdG8gYW4gZXhwYW5kaW5nIGRhdGFzZXQNClRoZSBuZXcgZGF0YSBpcyBkb3dubG9hZGVkIGFuZCBzdG9yZWQgaW50byB0aGUgJ3N2X3ZhYycgZGF0YSBmcmFtZSwgdGhpcyBzdGVwcyB0YWtlcyB0aGUgbmV3IGRhdGEgYW5kIGpvaW5zIGl0IHRvICdzdl92YWNfdG90YWwnIHdoaWNoIGFjY3VtdWxhdGVzIGVhY2ggbmV3IGRheSBvZiBkYXRhLg0KYGBge1J9DQojTG9hZCBleGlzdGluZyBkYXRhDQpzdl92YWNfdXBkYXRlIDwtIHJlYWRSRFMoZmlsZT0ic3ZfdmFjX3RvdGFsX2FsbC5SZGEiKQ0KI0FkZCBuZXcgZGF0YSB0byB0ZW1wIHVwZGF0ZSBkZg0Kc3ZfdmFjX3VwZGF0ZSA8LSBiaW5kX3Jvd3Moc3ZfdmFjX3VwZGF0ZSwgc3ZfdmFjKQ0KI0NhbGN1bGF0ZSBmaXJzdCBkb3NlIE9OTFkNCnN2X3ZhY191cGRhdGUgPC0gc3ZfdmFjX3VwZGF0ZSAlPiUgDQogIG11dGF0ZShmaXJzdF9kb3NlX29ubHkgPSBmaXJzdF90b3RhbCAtIHNlY29uZF90b3RhbCkgJT4lIA0KICBtdXRhdGUoZmlyc3RfZG9zZV9vbmx5X3BjID0gZmlyc3RfcGVyY2VudCAtIHNlY29uZF9wZXJjZW50KQ0KI0VsaW1pbmF0ZSBOQXMgZHVwbGljYXRlcw0Kc3ZfdmFjX3VwZGF0ZSA8LSBzdl92YWNfdXBkYXRlICU+JSANCiAgYXJyYW5nZShkYXRlKSAlPiUgDQogIGZpbHRlcighaXMubmEoZmlyc3RfdG90YWwpKSAlPiUgDQogIGZpbHRlcihkdXBsaWNhdGVkKGRhdGUpID09IEZBTFNFKQ0KI1NhdmUgd2l0aCBUb3RhbCAjcw0Kc2F2ZVJEUyhzdl92YWNfdXBkYXRlLCBmaWxlPSJzdl92YWNfdG90YWxfYWxsLlJkYSIpDQpgYGANCiMjIyBQbG90dGluZyB0aGUgdmFjaW5hdGlvbiByYXRlcw0KVGhlIGRhdGEgZnJvbSBGSE0gaW5jbHVkZXMgYm90aCB0b3RhbCBudW1iZXIgYW5kIHRoZSBwZXJjZW50YWdlIG9mIHBlb3BsZSB3aG8gaGF2ZSBnb3R0ZW4gdGhlIHZhY2NpbmF0aW9uLiBUaGUgcGVyY2VudGFnZSBvZiBwb3B1bGF0aW9uIGlzIG1vcmUgbWVhbmluZ2Z1bCB0aGFuIHRoZSByYXcgbnVtYmVycywgc28gdGhlIGdyYXBoIHVzZXMgdGhvc2Ugc3RhdHMuIFlvdSdsbCBub3RpY2UgdGhhdCBGSE0gZG9lcyBub3QgcHVibGlzaCBkYXRhIG9uIFNhdHVyZGF5cywgU3VuZGF5cyBhbmQgTW9uZGF5cywgaGVuY2UgdGhlIGdhcHMgaW4gZGF0YS4NCmBgYHtSIGdncGxvdH0NCiNMb2FkIGFnZ3JlZ2F0ZSBkYXRhDQpzdl92YWNfZ3JhcGhfZGF0YSA8LSByZWFkUkRTKGZpbGU9InN2X3ZhY190b3RhbF9hbGwuUmRhIikNCiNnZ3Bsb3QNCnN2X3ZhY19leHBfZ3JhcGggPC0gZ2dwbG90KHN2X3ZhY19ncmFwaF9kYXRhLCBhZXMoeD1kYXRlKSkgKw0KICBnZW9tX2xpbmUoYWVzKHk9IGZpcnN0X3BlcmNlbnQpLCBjb2xvciA9ICIjZmY2NjAwIikgKw0KICBnZW9tX2xpbmUoYWVzKHk9IHNlY29uZF9wZXJjZW50KSwgY29sb3IgPSAiI2Y4Y2NhYiIpICsNCiAgZ2VvbV9saW5lKGFlcyh5PSBmaXJzdF9kb3NlX29ubHlfcGMpLCBjb2xvciA9ICJyZWQiKSArDQogIGdlb21fcG9pbnQoYWVzKHk9Zmlyc3RfcGVyY2VudCksIGNvbG9yID0gIiNmZjY2MDAiLCBzaGFwZT0xNiwgc2l6ZT0yKSArDQogIGdlb21fcG9pbnQoYWVzKHk9c2Vjb25kX3BlcmNlbnQpLCBjb2xvciA9ICIjZjhjY2FiIiwgc2hhcGU9MTYsIHNpemU9MikgKw0KICBnZW9tX3BvaW50KGFlcyh5PWZpcnN0X2Rvc2Vfb25seV9wYyksIGNvbG9yID0gInJlZCIsIHNoYXBlPTE2LCBzaXplPTIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICIlIFZhY2NpbmF0ZWQiLA0KICAgIHN1YnRpdGxlID0gIkZpcnN0IGFuZCBTZWNvbmQgRG9zZXMgYW5kIE9ubHkgT25lIERvc2UiLA0KICAgIGNhcHRpb24gPSAiU291cmNlOiBGb2xraMOkbHNvbXluZGlnaGV0ZW4iLA0KICAgIHggPSAiRGF0ZSIsDQogICAgeSA9ICJQZXJjZW50YWdlIFZhY2NpbmF0ZWQiKSArDQogIyBnZW9tX3RleHQoYWVzKGxhYmVsID0gZmlyc3RfcGVyY2VudCwgeSA9IGZpcnN0X3BlcmNlbnQpLCBudWRnZV95ID0gMC41LCBudWRnZV94ID0gMSkgKw0KICAjZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHNlY29uZF9wZXJjZW50LCB5ID0gc2Vjb25kX3BlcmNlbnQpLCBudWRnZV95ID0gLTEpICsNCiAgI2dlb21fdGV4dChhZXMobGFiZWwgPSBmaXJzdF9kb3NlX29ubHlfcGMsIHkgPSBmaXJzdF9kb3NlX29ubHlfcGMpLCBjb2xvciA9ICAicmVkIiwgbnVkZ2VfeSA9IDEpICsNCiAgc2NhbGVfeF9kYXRlKGRhdGVfbGFiZWxzID0gIiVkICViIikNCmBgYA0KIyMjIEdyYXBoDQpOb3RlOiBkdWUgdG8gdGhlIHRpbWluZyBvZiB0aGlzIHByb2plY3QgYW5kIGxhY2sgb2YgZGFpbHkgZG9zZSBkYXRlIHB1Ymxpc2hlZCBpbiBhIHJldHJpZXZhYmxlIGZvcm1hdCBmcm9tIEZITSwgdGhlIGRhdGEgc3RhcnRzIGZyb20gMjUvMDIvMjAyMS4gQWxzbywgY29sb3VycyBtYXkgbm90IGJlIHRoZSBiZXN0IGJ1dCB3ZXJlIGNob3NlbiB0byBtYXRjaCB0aGUgY29sb3VycyB1c2VkIGJ5IEZITSBpbiB0aGVpciBbb2ZmaWNpYWwgdmFjY2luYXRpb24gZGFzaGJvYXJkXSglMjJodHRwczovL2V4cGVyaWVuY2UuYXJjZ2lzLmNvbS9leHBlcmllbmNlLzZkZjU0OTFkNTY2YTQ0MzY4ZmM3MjE3MjZjMjc0MzAxJTIyKS4gVGhpcyB2ZXJzaW9uIFsxMC8wMy8yMDIxXSwgYWxzbyBleHBlcmltZW50cyB3aXRoIGNhbGN1bGF0aW5nIHRoZSAlIG9mIHBlb3BsZSB3aG8gaGF2ZSBPTkxZIHJlY2VpdmVkIG9uZSBkb3NlLiBBcyBjb21wYXJlZCB0byBmaXJzdCBkb3NlIHdoaWNoIGluY2x1ZGVzIHRob3NlIHdobyBoYXZlIHJlY2VpdmVkIGEgc2Vjb25kIGRvc2UgYXMgd2VsbC4NClRoZSBGSE0gZGFzaGJvYXJkIGlzIG9ubHkgdXBkYXRlZCBvbmNlIGEgd2VlayBvbiBUaHVyc2RheXMsIDE0OjAwIEdNVCsxLg0KUkVEIC0gT05MWSAxc3QgRG9zZTsgT1JBTkdFIC0gVG90YWwgMXN0IERvc2U7IFBFQUNIIC0gMm5kIERvc2UuDQpgYGB7ciBlY2hvPUZBTFNFfQ0Kc3ZfdmFjX2V4cF9ncmFwaA0KYGBgDQo=