I built upon this thread to plot a spatial heatmap. The difference is that I do not want to compute the density of points because I already have the value for the level of "heat". In detail I want to plot the population density of the canton of Berne (Switzerland) with colour gradients.
The population data is from the Swiss statistical office and counts the inhabitants per hectare (100m x 100m squares), downloadable here (the "STATPOP2020.csv" file). Building on jlhoward answer in thread my code so far is:
library(tidyverse)
library(rgdal)
library(GADMTools)
library(RColorBrewer)
# conversion from swiss coordinate system to wgs-84
lv03_wgs_lat <- function (y, x){
y_aux <- (y - 600000)/1000000
x_aux <- (x - 200000)/1000000
lat <- {16.9023892 +
3.238272 * x_aux -
0.270978 * (y_aux^2) -
0.002528 * (x_aux^2) -
0.0447 * (y_aux^2) * x_aux -
0.0140 * (x_aux^3)}
lat <- lat * 100/36
return(lat)
}
lv03_wgs_lon <- function (y, x){
y_aux <- (y - 600000)/1000000
x_aux <- (x - 200000)/1000000
lon <- {2.6779094 +
4.728982 * y_aux +
0.791484 * y_aux * x_aux +
0.1306 * y_aux * (x_aux^2) -
0.0436 * (y_aux^3)}
lon <- lon * 100/36
return(lon)
}
# read in data
d_pop <- read_csv2("STATPOP2020.csv") %>%
select(1:6) %>%
rename(TOT = 6) %>%
mutate(lon = lv03_wgs_lon(X_KOORD, Y_KOORD),
lat = lv03_wgs_lat(X_KOORD, Y_KOORD))
# filter swiss data to canton of berne
d_map_ch <- gadm_sf.loadCountries("CHE", level = 1)
d_map_be <- gadm_subset(d_map_ch, level = 1, regions = "Bern", usevar = NULL)
d_map_points <- st_as_sf(d_pop, coords = c("lon", "lat"), crs = 4326)
d_pop_be <- bind_cols(d_pop,
as_tibble(t(st_contains(x = d_map_be$sf, y = d_map_points, sparse = FALSE))) %>%
rename(in_be = V1)) %>%
filter(in_be)
# building on previous answer, administrative border is disregarded
ggplot(d_pop_be, aes(x = E_KOORD, y = N_KOORD)) +
stat_density2d(aes(fill = TOT), alpha = 0.4, geom = "polygon")+
scale_fill_gradientn(colours=rev(brewer.pal(7,"Spectral")))+
xlim(2555000, 2678000) +
ylim(1130000, 1245000) +
coord_fixed()
As the heatmap gets plotted, I do not get a legend for the "heat", population density, and I am not able to add colours to the gradients. Does anybody know how to add those?
The problem, as you have already established, is that you want a contour map that represents population density, not the density of measurements, which is what stat_density_2d
does. It is possible to create such an object in R, but it is difficult when the measurements are not spaced regularly on a grid (as is the case with this data). It may be best to use geom_point
here for that reason:
ggplot(d_pop_be, aes(x = E_KOORD, y = N_KOORD)) +
geom_point(aes(color = log(TOT), alpha = exp(TOT))) +
scale_colour_gradientn(colours=rev(brewer.pal(7,"Spectral")),
breaks = log(c(1, 10, 100, 1000)),
labels = c(1, 10, 100, 1000),
name = "Population density\n(People per hectare)")+
xlim(2555000, 2678000) +
ylim(1130000, 1245000) +
guides(alpha = guide_none()) +
coord_fixed()
If you want a filled contour you will have to manually create a matrix covering the area of interest, get the mean population in each bin, convert that into a data frame, then use geom_contour_filled
:
z <- tapply(d_pop_be$TOT, list(cut(d_pop_be$E_KOORD, 200),
cut(d_pop_be$N_KOORD, 200)), mean, na.rm = TRUE)
df <- expand.grid(x = seq(min(d_pop_be$E_KOORD), max(d_pop_be$E_KOORD), length = 200),
y = seq(min(d_pop_be$N_KOORD), max(d_pop_be$N_KOORD), length = 200))
df$z <- c(tapply(d_pop_be$TOT, list(cut(d_pop_be$E_KOORD, 200),
cut(d_pop_be$N_KOORD, 200)), mean, na.rm = TRUE))
df$z[is.na(df$z)] <- 0
ggplot(df, aes(x, y)) +
geom_contour_filled(aes(z = z), breaks = c(1, 5, 20, 50, 100, 1000)) +
scale_fill_manual(values = rev(brewer.pal(5, "Spectral")))