Is ggblur *coolanduseful* for visualising uncertainty of spatial indicators?
Within the B-Cubed project, we aggregate biodiversity data in data cubes with a taxonomical dimension (e.g. species), spatial dimension (grid cell) and temporal dimension (e.g. year). From these cubes, we can calculate biodiversity indicators per grid cell (e.g. evenness, species richness). These indicators come of course with a measure of uncertainty (e.g. standard error, confidence interval width).
Blur has been proposed as a good way to visualise spatial uncertainty (see left example Fig. 6 from Kinkeldey et al., 2014; see also MacEachren et al., 2005, 2012).
I have created an example below using ggblur. Unfortunately, I still had to adjust size and blur manually for each uncertainty class (low, middle, high).
Is the idea of bluriness still in development? It would be nice to have blur and size as unlinked options.
- In my first figure, bluriness increases with uncertainty
- In my second figure, bluriness increases and size decreases with uncertainty
# Load packages
library(ggplot2) # visualisation
library(sf) # spatial objects
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
library(dplyr) # data wrangling
#>
#> 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
library(ggblur) # blurred points: https://github.com/coolbutuseless/ggblur
# Create a simple 3x3 grid using sf
grid_size <- 1 # Define cell size
grid <- expand.grid(x = 1:3, y = 1:3) %>%
st_as_sf(coords = c("x", "y"), crs = 4326) %>%
st_make_grid(cellsize = grid_size, n = c(3, 3), what = "polygons") %>%
st_sf() %>%
mutate(id = seq_len(n()))
centroids <- st_centroid(grid) %>%
st_buffer(50000)
#> Warning: st_centroid assumes attributes are constant over geometries
# Create data for estimates and uncertainty
data <- expand.grid(
x = 1:3 + 0.5, # Columns for estimate values
y = 1:3 + 0.5 # Rows for uncertainty levels
) %>%
mutate(
# Low, Medium, High estimates
estimate = rep(c(0.2, 0.5, 0.8), each = 3),
# Low, Medium, High uncertainty
uncertainty = rep(c("Low", "Medium", "High"), times = 3)
)
# Assign visual properties for uncertainty representation
data <- data %>%
mutate(
blur_size = case_when(uncertainty == "Low" ~ NA,
uncertainty == "Medium" ~ 25,
uncertainty == "High" ~ 30),
blur_size2 = case_when(uncertainty == "Low" ~ NA,
uncertainty == "Medium" ~ 15,
uncertainty == "High" ~ 30),
alpha_value = case_when(uncertainty == "Low" ~ 1,
uncertainty == "Medium" ~ 0.6,
uncertainty == "High" ~ 0.3),
size = case_when(uncertainty == "Low" ~ 30,
uncertainty == "Medium" ~ 15,
uncertainty == "High" ~ 10),
)
# Increasing blur with increasing uncertainty
p_blur <- ggplot() +
geom_sf(data = grid, color = "black", alpha = 0,
linewidth = 1) + # Grid background
geom_point_blur(
data = data %>% filter(uncertainty == "High"),
aes(x = x, y = y, colour = estimate, blur_size = blur_size),
size = 1) +
geom_point_blur(
data = data %>% filter(uncertainty == "Medium"),
aes(x = x, y = y, colour = estimate, blur_size = blur_size),
size = 15) +
geom_point(
data = data %>% filter(uncertainty == "Low"),
aes(x = x, y = y, colour = estimate),
size = 30) +
scale_colour_viridis_c(option = "D") +
scale_blur_size_continuous(range = c(20, 35)) +
theme_minimal() +
theme(panel.grid = element_line(linewidth = 1),
legend.title = element_text(face = "bold")) +
labs(title = "Blur", x = "", y = "", colour = "Estimate:") +
guides(blur_size = "none") +
annotate("segment",
x = 1.5,
y = 0.8,
xend = 3.5,
yend = 0.8,
linewidth = 1,
arrow = arrow(type = "closed", length = unit(0.02, "npc")),
colour = "black"
) +
annotate("segment",
x = 0.8,
y = 1.5,
xend = 0.8,
yend = 3.5,
linewidth = 1,
arrow = arrow(type = "closed", length = unit(0.02, "npc")),
colour = "black"
) +
annotate("text",
label = c("Higher uncertainty"),
x = 2.5,
y = 0.7,
size = 5, hjust = "center", vjust = "center", colour = "black"
) +
annotate("text",
label = c("Higher estimate"),
x = 0.7,
y = 2.5,
size = 5, hjust = "center", vjust = "bottom", colour = "black", angle = 90
)
p_blur

# Increasing blur and decreasing size with increasing uncertainty
p_blur2 <- ggplot() +
geom_sf(data = grid, color = "black", alpha = 0,
linewidth = 1) + # Grid background
geom_point_blur(
data = data %>% filter(uncertainty == "High"),
aes(x = x, y = y, colour = estimate, blur_size = blur_size2),
size = 1) +
geom_point_blur(
data = data %>% filter(uncertainty == "Medium"),
aes(x = x, y = y, colour = estimate, blur_size = blur_size2),
size = 10) +
geom_point(
data = data %>% filter(uncertainty == "Low"),
aes(x = x, y = y, colour = estimate),
size = 30) +
scale_colour_viridis_c(option = "D") +
scale_blur_size_continuous(range = c(15, 15)) +
theme_minimal() +
theme(panel.grid = element_line(linewidth = 1),
legend.title = element_text(face = "bold")) +
labs(title = "Blur + Size", x = "", y = "", colour = "Estimate:") +
guides(blur_size = "none") +
annotate("segment",
x = 1.5,
y = 0.8,
xend = 3.5,
yend = 0.8,
linewidth = 1,
arrow = arrow(type = "closed", length = unit(0.02, "npc")),
colour = "black"
) +
annotate("segment",
x = 0.8,
y = 1.5,
xend = 0.8,
yend = 3.5,
linewidth = 1,
arrow = arrow(type = "closed", length = unit(0.02, "npc")),
colour = "black"
) +
annotate("text",
label = c("Higher uncertainty"),
x = 2.5,
y = 0.7,
size = 5, hjust = "center", vjust = "center", colour = "black"
) +
annotate("text",
label = c("Higher estimate"),
x = 0.7,
y = 2.5,
size = 5, hjust = "center", vjust = "bottom", colour = "black", angle = 90
)
p_blur2

Created on 2025-02-19 with reprex v2.1.1