Maps
Overview
This notebook provides examples of creating maps to display HydroBOT data and especially aggregated outcomes. Typically, these have quantitative fill that is spatially-relevant, whether that is a point (gauge) or polygon (SDL unit, basin, etc). The numerical values can be more difficult to ascertain precisely from maps, but they are often the clearest way to present spatial data, especially when the message is one of spatial variation. Because maps lose two dimensions for data (i.e x and y become position), we often have to reduce the number of categories we look at and rely heavily on facetting and subsetting.
As with all plots in {HydroBOT}, maps have the ability to use colour and different colour palettes to include additional information, including type of response. These settings are dealt with more completely in the bar_plots, with much of the mechanics the same for maps and lines e.g. the use of colorgroups
and a list for pal_list
.
With maps, we can also plot multiple layers in the foreground overlay
and background underlay
, though these have fewer options than the ‘primary’ layer (e.g. we cannot plot data as fill in multiple polygon layers).
Demonstration setup
As usual, we need paths to the data. We use the ‘more scenarios’ examples for all the plots, with processing as in the website workflow.
Read in the data
We read in the example data we will use for all plots.
That has all the steps in the aggregation, but most of the plots here will only use a subset to demonstrate.
To make visualisation easier, the SDL units data is given a grouping column that puts the many env_obj
variables in groups defined by their first two letters, e.g. EF
for Ecosystem Function. These correspond to the ‘Target’ level, but it can be useful to have the two groupings together for some examples.
If we had used multiple aggregation functions at any step, we should filter down to the one we want here, but we only used one for this example.
For simplicity here, we will only look at a small selection of the scenarios (multiplicative changes of 0.5,1, and 2). Thus, we make two small dataframes for our primary examples here.
scenarios_to_plot <- c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0")
scenarios <- yaml::read_yaml(file.path(hydro_dir, "scenario_metadata.yml")) |>
tibble::as_tibble()
basin_to_plot <- agged_data$mdb |>
dplyr::filter(scenario %in% scenarios_to_plot) |>
dplyr::left_join(scenarios, by = "scenario")
# Create a grouping variable
obj_sdl_to_plot <- agged_data$sdl_units |>
dplyr::filter(scenario %in% scenarios_to_plot) |>
dplyr::mutate(env_group = stringr::str_extract(env_obj, "^[A-Z]+")) |>
dplyr::arrange(env_group, env_obj) |>
dplyr::left_join(scenarios, by = "scenario")
Standard scenario appearance
We will typically have a consistent look for the scenarios across the project, with a logical ordering and standard colours. Such standard colours are not included in the {HydroBOT} package because they are project/analysis- specific, but they could be set at project-level, e.g. in the .Rprofile
, if desired.
Here, we use the special arguments refvals
and refcols
to make a colour palette from a standard {paletter} option (“ggsci::nrc_npg”) while setting a specific level to a specified value. We will use the codes defining the scenarios rather than the names to make plots readable.
There is a sceneorder
argument to plot_outcomes()
that lets us explicitly set the order of the scenarios. However, it is typically easiest to simply make the scenarios a factor, though we use the sceneorder
argument here. It operates only on a column named ‘scenario’, though, so if other columns need to be ordered they should be made factors before feeding to plot_outcomes()
.
sceneorder <- forcats::fct_reorder(
basin_to_plot$scenario,
basin_to_plot$flow_multiplier
)
scene_pal <- make_pal(unique(basin_to_plot$climate_code),
palette = "ggsci::nrc_npg",
refvals = "E", refcols = "black"
)
scene_pal
<colors>
black #E64B35FF #4DBBD5FF
Make maps
We start with a simple map of polygon aggregations. Because maps lose two dimensions, we subset to the Waterbirds grouping to reduce dimensionality.
obj_sdl_to_plot |>
dplyr::filter(env_group == "WB") |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
y_lab = "Proportion EWR\nachieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "env_obj",
facet_row = "climate_code"
)
Simple background and foreground
We can add a polygon with no mapped aesthetics to the background and foreground using underlay_list
and overlay_list
. Here, we add the basin shape behind the data and the gauges used originally.
# this can take a while so use a small sheet and pre-filter to make smaller
relevant_gauges <- agged_data$ewr_code |>
filter(scenario == "climatebaseadapt0") |>
select(gauge, geometry) |>
distinct()
obj_sdl_to_plot |>
dplyr::filter(env_group == "WB") |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
y_lab = "Proportion EWR\nachieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "env_obj",
facet_row = "climate_code",
underlay_list = list(
underlay = basin,
underlay_pal = "azure"
),
overlay_list = list(
overlay = relevant_gauges,
overlay_pal = "firebrick"
)
)
Other spatial units
We used the sdl units (an intermediate aggregation level) above, but any geometric levels will work. First, we show with gauges as the focal spatial unit, e.g. before any spatial aggregation has occurred (but there has been both temporal and theme).
If we attempt to run this as-is, it shows the value of plot_outcomes()
for catching unintentional overplotting. The filtering to just NF1 is an attempt to only plot one value, but there are hidden and easily missed doubled gauges in this data. Because in the EWR tool some gauges inform several different planning units, there are more than one line for those gauges. plot_outcomes()
catches this and forces us to deal with it.
agged_data$env_obj |> # for readability
dplyr::filter(env_obj == "NF1" &
scenario %in% scenarios_to_plot) |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
y_lab = "Arithmetic Mean",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "scenario",
facet_row = "env_obj",
underlay_list = list(
underlay = "basin",
underlay_pal = "azure"
)
) +
ggplot2::theme(legend.position = "bottom")
Error in `test_overplotting()` at HydroBOT/R/plot_types.R:156:3:
! Trying to plot multiple values
(up to 4) in single polygons.
Something is duplicated-
do you need more facetting or filtering?
The ‘real’ fix of that issue would be to filter to a particular planning unit we wanted or investigate in more granular detail. But here, we just want a simple demonstration of plotting points, so we simply filter to the first record for each gauge.
env_obj_to_plot |>
plot_outcomes(
outcome_col = "ewr_achieved",
y_lab = "Arithmetic Mean",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "scenario",
facet_row = "env_obj",
underlay_list = list(
underlay = "basin",
underlay_pal = "azure"
)
) +
ggplot2::theme(legend.position = "bottom")
We can also plot the basin-scale outcomes (rather than just the shape as above).
basin_to_plot |>
plot_outcomes(
outcome_col = "ewr_achieved",
y_lab = "Proportion EWR\nachieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "Target",
facet_row = "climate_code"
)
Multiple informative layers
In their most simple sense, underlays and overlays can be used to simply add spatial information (the basin or country, the gauge locations). In more complex cases though, information can be plotted in multiple layers. The choice of a ‘primary’ layer from which other layers are underlays or overlays can be a bit arbitrary, since each layer can plot outcome data, but the ‘primary’ layer has access to a few more processing options. It typically makes the most sense to choose a primary layer to address a targeted question and choose the under/overlays to add context.
Underlays
While we can’t have informative fill in multiple layers ({ggplot2} won’t allow multiple fills), we can have informative fill in polygons underlying point (gauge) data, because the points use color
, not fill
.
First, we include a fill in the underlay for SDL unit name (e.g. not a data fill, but still informative) by simply replacing the single colour in underlay_pal
with a {paletteer} palette. It is hard to find palettes for the full set of catchments. We use a qualitative palette here. Using a continuous palette (e.g. grDevices::Purple-Yellow
) works OK as well, but the colours aren’t very well spatially-separated.
env_obj_to_plot |>
plot_outcomes(
outcome_col = "ewr_achieved",
y_lab = "All arithmetic mean",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = 1,
facet_col = "scenario",
facet_row = "env_obj",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
underlay_list = list(
underlay = sdl_units,
underlay_ycol = "SWSDLName",
underlay_pal = "palettesForR::Muted"
)
) +
ggplot2::theme(legend.position = "bottom")
We can use a continuous variable on the underlay fill, but have to be careful to choose palettes that don’t mask each other. It is very tempting to put e.g. gauges under sdl units and colour by the same palette to show the effect of the aggregation, but that is often confusing given the similarity in colour (and noting that because here we have to eliminate some planning units to plot the gauges, the full set of aggregated gauge values is not shown).
env_obj_to_plot |>
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::oslo"),
facet_col = "scenario",
facet_row = "env_obj",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
underlay_list = list(
underlay = dplyr::filter(obj_sdl_to_plot, env_obj == "NF1"),
underlay_ycol = "ewr_achieved",
underlay_pal = "scico::oslo"
)
) +
ggplot2::theme(legend.position = "bottom")
While the aggregation isn’t as obvious here, it is easier to see the different levels:
env_obj_to_plot |>
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("ggthemes::Orange-Gold"),
facet_col = "scenario",
facet_row = "env_obj",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
underlay_list = list(
underlay = dplyr::filter(obj_sdl_to_plot, env_obj == "NF1"),
underlay_ycol = "ewr_achieved",
underlay_pal = "scico::oslo"
)
) +
ggplot2::theme(legend.position = "bottom")
We can also have multiple levels of underlay polygons (e.g. if we want the basin under sdl units), and can use a fill palette in one of them (Figure 2) by using a list of lists for underlay_list
. A similar approach also works for overlays (Figure 3). And as we’ve seen above (Figure 1), we can have both underlays and overlays.
env_obj_to_plot |>
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("ggthemes::Orange-Gold"),
facet_col = "scenario",
facet_row = "env_obj",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
underlay_list = list(
list(
underlay = "basin",
underlay_pal = "cornsilk"
),
list(
underlay = dplyr::filter(obj_sdl_to_plot, env_obj == "NF1"),
underlay_ycol = "ewr_achieved",
underlay_pal = "scico::oslo"
)
)
) +
ggplot2::theme(legend.position = "bottom")
Overlays
Above, we used the gauge layer as ‘primary’, and included the sdl units or basin as underlays. We can also overlay, here with the sdl layer as ‘primary’.
We start with a single colour overlay to show where gauges are. This is the full set of BOM gauges, not just those that went into the aggregation (i.e. are in the EWR tool), which are the ones shown above (Figure 1).
We also show here the use of clip
, which auto-clips the under or overlay data to the primary layer, and the use of just an {sf} object name for underlay_list = "basin"
as shorthand when we don’t want to specify any visual properties.
obj_sdl_to_plot |>
dplyr::filter(env_obj == "NF1") |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "scenario",
facet_row = "env_obj",
underlay_list = "basin",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
overlay_list = list(
overlay = "bom_basin_gauges",
overlay_pal = "grey40",
clip = TRUE
)
) +
ggplot2::theme(legend.position = "bottom")
We can give the overlay informative colours- this outcome is similar to what we’ve done above, but the amount of control we have over scaling etc differs depending on whether a layer is ‘primary’. We also use map_outlinecolor
to adjust the colour of the primary layer outlines.
obj_sdl_to_plot |>
dplyr::filter(env_obj == "NF1") |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::lapaz"),
pal_direction = -1,
facet_col = "scenario",
facet_row = "env_obj",
underlay_list = "basin",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
map_outlinecolor = "forestgreen",
overlay_list = list(
overlay = env_obj_to_plot,
overlay_pal = "scico::oslo",
overlay_ycol = "ewr_achieved",
clip = TRUE
)
) +
ggplot2::theme(legend.position = "bottom")
Like underlays (Figure 2), we can have multiple levels of overlays (Figure 3) by using a list of lists for overlay_list
. And as we’ve seen above (Figure 1), we can have both underlays and overlays.
Here, we demonstrate a primary layer at the basin scale, overlay the sdl units with empty fill so we can see their locations, and then put gauges on with informative values.
basin_to_plot |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("scico::oslo"),
facet_col = "Target",
facet_row = "scenario",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
overlay_list = list(
list(overlay = "sdl_units"),
list(
overlay = env_obj_to_plot,
overlay_pal = "ggthemes::Orange-Gold",
overlay_ycol = "ewr_achieved"
)
)
) +
ggplot2::theme(legend.position = "bottom")
Baselining
As with the other plotting functions, we can compare to baseline using arbitrary functions, here difference
to get the arithmetic change in outcomes. For both difference
and relative
, the limits are adjusted internally to center on the reference value (0 for difference, 1 for relative), and so using a diverging palette will make that centering clear.
obj_sdl_to_plot |>
dplyr::filter(env_group == "WB") |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorset = "ewr_achieved",
pal_list = list("ggthemes::Orange-Blue-White Diverging"),
facet_col = "env_obj",
facet_row = "scenario",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
base_list = list(
base_lev = "climatebaseadapt0",
comp_fun = "difference",
group_cols = c("env_obj", "polyID")
),
underlay_list = list(underlay = basin, underlay_pal = "azure")
)
And the relative
change is likely to be the most informative and appropriate in many cases. We use the add_eps
argument to add a small amount (half the minimum value) to zeros, otherwise we end up taking the log of 0.
obj_sdl_to_plot |>
dplyr::filter(env_group == "WB") |> # Need to reduce dimensionality
plot_outcomes(
outcome_col = "ewr_achieved",
plot_type = "map",
colorgroups = NULL,
colorset = "ewr_achieved",
pal_list = list("ggthemes::Orange-Blue-White Diverging"),
facet_col = "env_obj",
facet_row = "scenario",
sceneorder = c("climatedown2adapt0", "climatebaseadapt0", "climateup2adapt0"),
base_list = list(
base_lev = "climatebaseadapt0",
comp_fun = "relative",
group_cols = c("env_obj", "polyID"),
add_eps = "auto"
),
zero_adjust = "auto",
transoutcome = "log10",
underlay_list = list(underlay = basin, underlay_pal = "azure")
)