[This article was first published on r.iresmi.net, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
# Europe COVID-19 deaths animated map
# http://r.iresmi.net/
# data European Centre for Disease Prevention and Control
# packages ----------------------------------------------------------------
library(tidyverse)
library(httr)
library(fs)
library(sf)
library(readxl)
library(janitor)
library(glue)
library(tmap)
library(grid)
library(classInt)
library(magick)
# + btb, raster, fasterize, plyr
# sources -----------------------------------------------------------------
# https://data.europa.eu/euodp/en/data/dataset/covid-19-coronavirus-data
covid_file <- "covid_eu.csv"
covid_url <- "https://opendata.ecdc.europa.eu/covid19/casedistribution/csv"
countries_file <- "ne_50m_admin_0_countries.shp"
countries_url <- "https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries.zip"
# config ------------------------------------------------------------------
radius <- 600000 # smoothing radius (m)
pixel <- 100000 # grid resolution (m)
force_download <- FALSE # download even if already downloaded today ?
#' Kernel weighted smoothing with arbitrary bounding area
#'
#' @param df sf object (points)
#' @param field weight field in the df
#' @param bandwidth kernel bandwidth (map units)
#' @param resolution output grid resolution (map units)
#' @param zone sf study zone (polygon)
#' @param out_crs EPSG (should be an equal-area projection)
#'
#' @return a raster object
#' @import btb, raster, fasterize, dplyr, plyr, sf
lissage <- function(df, field, bandwidth, resolution, zone, out_crs = 3035) {
if (st_crs(zone)$epsg != out_crs) {
message("reprojecting data...")
zone <- st_transform(zone, out_crs)
}
if (st_crs(df)$epsg != out_crs) {
message("reprojecting study zone...")
df <- st_transform(df, out_crs)
}
zone_bbox <- st_bbox(zone)
# grid generation
message("generating reference grid...")
zone_xy <- zone %>%
dplyr::select(geometry) %>%
st_make_grid(
cellsize = resolution,
offset = c(plyr::round_any(zone_bbox[1] - bandwidth, resolution, f = floor),
plyr::round_any(zone_bbox[2] - bandwidth, resolution, f = floor)),
what = "centers") %>%
st_sf() %>%
st_join(zone, join = st_intersects, left = FALSE) %>%
st_coordinates() %>%
as_tibble() %>%
dplyr::select(x = X, y = Y)
# kernel
message("computing kernel...")
kernel <- df %>%
cbind(., st_coordinates(.)) %>%
st_set_geometry(NULL) %>%
dplyr::select(x = X, y = Y, field) %>%
btb::kernelSmoothing(
dfObservations = .,
sEPSG = out_crs,
iCellSize = resolution,
iBandwidth = bandwidth,
vQuantiles = NULL,
dfCentroids = zone_xy
)
# rasterization
message("\nrasterizing...")
raster::raster(
xmn = plyr::round_any(zone_bbox[1] - bandwidth, resolution, f = floor),
ymn = plyr::round_any(zone_bbox[2] - bandwidth, resolution, f = floor),
xmx = plyr::round_any(zone_bbox[3] + bandwidth, resolution, f = ceiling),
ymx = plyr::round_any(zone_bbox[4] + bandwidth, resolution, f = ceiling),
resolution = resolution
) %>%
fasterize::fasterize(kernel, ., field = field)
}
# download data ------------------------------------------------------------
if (!dir_exists("data")) dir_create("data")
if (!dir_exists("results")) dir_create("results")
if (!dir_exists("results/animation_eu")) dir_create("results/animation_eu")
if (!file_exists(path("data", covid_file)) |
file_info(path("data", covid_file))$modification_time < Sys.Date() |
force_download) {
GET(covid_url,
progress(),
write_disk(path("data", covid_file), overwrite = TRUE)) %>%
stop_for_status()
}
if (!file_exists(path("data", countries_file))) {
dl <- file_temp()
GET(countries_url,
progress(),
write_disk(dl)) %>%
stop_for_status()
unzip(dl, exdir = "data")
}
# data --------------------------------------------------------------------
# some countries doesn't have data for the latest days ; we fill with latest
# data
covid <- read_csv(path("data", covid_file),
col_types = cols(dateRep = col_date(format = "%d/%m/%Y"))) %>%
clean_names() %>%
complete(countryterritory_code, date_rep) %>%
replace_na(list(deaths = 0)) %>%
group_by(countryterritory_code) %>%
arrange(date_rep) %>%
mutate(deaths_cum = cumsum(deaths))
# keep only europen countries minus Russia and adding TUR and CYP
# remove overseas territories, reproject in LAEA
countries <- read_sf(path("data", countries_file)) %>%
clean_names() %>%
filter(continent == "Europe" & iso_a3_eh != "RUS" | iso_a3_eh %in% c("TUR", "CYP")) %>%
st_cast("POLYGON") %>%
st_set_crs(4326) %>%
st_join(c(xmin = -20, xmax = 35, ymin = 35, ymax = 70) %>%
st_bbox() %>%
st_as_sfc() %>%
st_as_sf() %>%
st_set_crs(4326),
left = FALSE) %>%
group_by(iso_a3_eh) %>%
summarise(geometry = st_combine(geometry)) %>%
st_transform(3035)
# pretreatment -----------------------------------------------------------
# mask to generate grid : union all countries
unioned_countries_file <- "data/eu.rds"
if (!file_exists(unioned_countries_file)) {
unioned_countries <- countries %>%
st_union() %>%
st_sf() %>%
write_rds(unioned_countries_file)
} else {
unioned_countries <- read_rds(unioned_countries_file)
}
# join countries/data for a specific date
create_df <- function(territory, date = NULL) {
covid %>%
filter(date_rep == if_else(is.null(date), max(date_rep), date)) %>%
right_join(countries,
by = c("countryterritory_code" = "iso_a3_eh")) %>%
st_as_sf() %>%
st_point_on_surface() %>%
drop_na(deaths_cum) %>%
st_as_sf()
}
covid_geo <- create_df(countries)
# smoothing for last date ---------------------------------------------------
# deaths
d <- covid_geo %>%
lissage("deaths_cum", radius, pixel, unioned_countries)
# population
p <- covid_geo %>%
lissage("pop_data2018", radius, pixel, unioned_countries)
# grid per 100000 inhab
death_pop <- d * 100000 / p
# carto -------------------------------------------------------------------
# classification for last date to be reused in animation
set.seed(1234)
classes <- classIntervals(raster::values(death_pop), n = 6, style = "kmeans", dataPrecision = 0)$brks
# animation ---------------------------------------------------------------
image_animation <- function(date) {
message(glue("\n\n{date}\n==========\n"))
m <- create_df(countries, date) %>%
lissage("deaths_cum", radius, pixel, unioned_countries) %>%
magrittr::divide_by(p) %>%
magrittr::multiply_by(100000) %>%
tm_shape() +
tm_raster(title = glue("deaths
per 100 000 inhab."),
style = "fixed",
breaks = classes,
palette = "viridis",
legend.format = list(text.separator = "to less than",
digits = 0),
legend.reverse = TRUE) +
tm_layout(title = glue("COVID-19 - Europe\ncumulative as of {date}"),
legend.position = c("right", "top"),
frame = FALSE) +
#tm_shape(countries, bbox = death_pop) +
#tm_borders() +
tm_credits(glue("http://r.iresmi.net/
bisquare kernel smoothing {radius / 1000} km on {pixel / 1000} km grid
classif. kmeans, LAEA Europe projection
data European Centre for Disease Prevention and Control / map Naturalearth"),
size = .5,
position = c(.5, .025))
message("saving map...")
tmap_save(m, glue("results/animation_eu/covid_eu_{date}.png"),
width = 800, height = 800, scale = .4,)
}
covid %>%
filter(date_rep >= "2020-03-15") %>%
pull(date_rep) %>%
unique() %>%
walk(image_animation)
animation <- glue("results/deaths_covid19_eu_{max(covid$date_rep)}.gif")
dir_ls("results/animation_eu") %>%
map(image_read) %>%
image_join() %>%
#image_scale("500x500") %>%
image_morph(frames = 1) %>%
image_animate(fps = 2, optimize = TRUE) %>%
image_write(animation)
To leave a comment for the author, please follow the link and comment on their blog: r.iresmi.net.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
