Nothing
## ----setup, include=FALSE-----------------------------------------------------
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "reserve-design-rwanda-",
fig.width = 8,
fig.height = 5.5,
out.width = "100%",
eval = FALSE,
message = FALSE,
warning = FALSE,
error = FALSE
)
## ----load-data----------------------------------------------------------------
# library(multiscape)
# library(dplyr)
# library(ggplot2)
# library(sf)
#
# Sys.setenv(
# MAPP_RWANDA_DIRECTORY = "path/to/extracted/MaPP-Tutorial-Rwanda"
# )
#
# source("data-raw/rwanda/prepare-data.R")
# rwanda_reserve <- readRDS(
# "data-raw/rwanda/rwanda-reserve-inputs.rds"
# )
#
## ----planning-context, fig.width=8, fig.height=5.5, fig.cap="Human-modification cost and existing protected planning units in the MaPP Rwanda training landscape."----
# planning_map <- rwanda_reserve$planning_units |>
# mutate(
# protection_status = if_else(
# protected,
# "Existing protected area",
# "Available for selection"
# )
# )
#
# ggplot(planning_map) +
# geom_sf(aes(fill = cost), color = NA) +
# geom_sf(
# data = filter(planning_map, protected),
# fill = NA,
# color = "#1B7837",
# linewidth = 0.25
# ) +
# scale_fill_viridis_c(
# option = "C",
# name = "Human\nmodification"
# ) +
# coord_sf(datum = NA) +
# labs(
# title = "Planning context",
# subtitle = "Green outlines identify units with at least 75% protected-area coverage"
# ) +
# theme_minimal(base_size = 10) +
# theme(
# panel.grid = element_blank(),
# plot.title = element_text(face = "bold")
# )
## ----planning-context-static, echo=FALSE, eval=TRUE, fig.cap="Human-modification cost and existing protected planning units in the MaPP Rwanda training landscape."----
knitr::include_graphics(
"figures/rwanda/reserve-design-rwanda-planning-context-1.png"
)
## ----feature-distributions, fig.width=9, fig.height=9-------------------------
# feature_map <- rwanda_reserve$feature_distribution |>
# left_join(rwanda_reserve$features, by = c("feature" = "id")) |>
# group_by(feature) |>
# mutate(relative_amount = amount / max(amount)) |>
# ungroup() |>
# left_join(
# select(rwanda_reserve$planning_units, id, geometry),
# by = c("pu" = "id")
# ) |>
# st_as_sf()
#
# ggplot(feature_map) +
# geom_sf(aes(fill = relative_amount), color = NA) +
# facet_wrap(~name, ncol = 4) +
# scale_fill_viridis_c(
# option = "C",
# limits = c(0, 1),
# name = "Relative\namount"
# ) +
# coord_sf(datum = NA) +
# theme_void(base_size = 9) +
# theme(
# legend.position = "bottom",
# strip.text = element_text(face = "bold", size = 8)
# )
## ----feature-distributions-static, echo=FALSE, eval=TRUE, fig.cap="Spatial distributions of the thirteen conservation features. Values are normalized independently within each feature to emphasize spatial pattern rather than differences in total area."----
knitr::include_graphics(
"figures/rwanda/reserve-design-rwanda-feature-distributions-1.png"
)
## ----build-problem------------------------------------------------------------
# problem <- create_problem(
# pu = rwanda_reserve$planning_units,
# features = rwanda_reserve$features,
# dist_features = rwanda_reserve$feature_distribution,
# cost = "cost",
# pu_id_col = "id"
# ) |>
# add_actions(
# actions = rwanda_reserve$actions,
# cost = 0
# ) |>
# add_effects(
# effects = data.frame(
# action = "reserve",
# feature = rwanda_reserve$features$id,
# effect = 0
# )
# ) |>
# add_constraint_targets_relative(0.30) |>
# add_constraint_locked_planning_units(
# locked_in = rwanda_reserve$planning_units$protected
# ) |>
# add_spatial_boundary(
# name = "boundary",
# include_self = TRUE,
# edge_factor = 0.5
# ) |>
# add_objective_min_cost(alias = "cost") |>
# add_objective_min_fragmentation_planning_units(
# relation_name = "boundary",
# alias = "fragmentation"
# )
#
# problem
## ----problem-static, echo=FALSE, eval=TRUE, results='asis'--------------------
problem_output <- readLines(
"results/rwanda/problem-summary.txt",
warn = FALSE,
encoding = "UTF-8"
)
cat("```text\n", paste(problem_output, collapse = "\n"), "\n```\n", sep = "")
## ----define-blm---------------------------------------------------------------
# blm_values <- c(0, 1e-5, 1e-4, 1e-3, 1e-2, 2e-2, 5e-2, 1e-1, 1)
#
# blm_problem <- problem |>
# set_method_weighted_sum(
# aliases = c("cost", "fragmentation"),
# runs = set_runs_manual(
# data.frame(
# weight_cost = 1,
# weight_fragmentation = blm_values
# )
# ),
# normalize_weights = FALSE,
# objective_scaling = FALSE
# ) |>
# set_solver_gurobi(
# gap_limit = 0,
# verbose = TRUE
# )
## ----obtain-solutions---------------------------------------------------------
# if (!requireNamespace("gurobi", quietly = TRUE)) {
# stop("Reproducing this analysis requires Gurobi and a valid licence.")
# }
#
# blm_solutions <- solve(blm_problem)
## ----gurobi-blm-log-static, echo=FALSE, eval=TRUE, results='asis'-------------
gurobi_blm_log <- readLines(
"results/rwanda/gurobi-blm-log-excerpt.txt",
warn = FALSE
)
cat("```text\n", paste(gurobi_blm_log, collapse = "\n"), "\n```\n", sep = "")
## ----inspect-blm-runs---------------------------------------------------------
# blm_run_summary <- get_runs(blm_solutions) |>
# left_join(get_objectives(blm_solutions), by = "solution_id") |>
# mutate(blm = blm_values[run_id]) |>
# select(run_id, solution_id, blm, status, runtime, gap, cost, fragmentation)
#
# blm_run_summary
## ----inspect-blm-runs-static, echo=FALSE, eval=TRUE---------------------------
blm_run_summary_static <- read.csv(
"results/rwanda/blm-run-summary.csv",
check.names = FALSE
)
knitr::kable(
blm_run_summary_static,
digits = c(0, 0, 5, NA, 3, 4, 0, 0),
caption = "Stored run metadata and objective values for the BLM analysis."
)
## ----prepare-blm-results, include=FALSE---------------------------------------
# blm_results <- get_runs(blm_solutions) |>
# left_join(get_objectives(blm_solutions), by = "solution_id") |>
# mutate(blm = blm_values[run_id])
#
# minimum_cost <- min(blm_results$cost, na.rm = TRUE)
# minimum_cost_fragmentation <- blm_results$fragmentation[
# which.min(blm_results$cost)
# ]
#
# blm_results <- blm_results |>
# mutate(
# additional_cost = 100 * (cost - minimum_cost) / minimum_cost,
# boundary_km = fragmentation / 1000,
# blm_label = paste0("BLM = ", format(blm, scientific = TRUE))
# )
## ----plot-blm, fig.cap="Cost and spatial-configuration outcomes obtained by changing the boundary length modifier. Values are expressed relative to the minimum-cost solution (BLM = 0)."----
# ggplot(
# blm_results,
# aes(boundary_km, additional_cost)
# ) +
# geom_path(color = "grey70", linewidth = 0.5) +
# geom_point(color = "#2C7FB8", size = 2.8) +
# ggrepel::geom_text_repel(
# aes(label = blm_label),
# size = 3,
# min.segment.length = 0,
# seed = 500
# ) +
# scale_y_continuous(labels = function(x) paste0(round(x, 1), "%")) +
# labs(
# x = "Exposed boundary (km)",
# y = "Additional cost relative to the minimum-cost plan",
# title = "BLM calibration",
# subtitle = "Higher BLM values exchange additional cost for a more compact network"
# ) +
# theme_minimal(base_size = 11) +
# theme(plot.title = element_text(face = "bold"))
## ----plot-blm-static, echo=FALSE, eval=TRUE, fig.cap="Cost and spatial-configuration outcomes obtained by changing the boundary length modifier. Boundary is shown in its original spatial units (converted to kilometres), while cost is expressed relative to the minimum-cost solution (BLM = 0)."----
knitr::include_graphics(
"figures/rwanda/reserve-design-rwanda-plot-blm-1.png"
)
## ----define-epsilon-----------------------------------------------------------
# epsilon_limits <- seq(
# min(blm_results$fragmentation),
# max(blm_results$fragmentation),
# length.out = 8
# )
#
# epsilon_problem <- problem |>
# set_method_epsilon_constraint(
# primary = "cost",
# aliases = c("cost", "fragmentation"),
# runs = set_runs_manual(
# data.frame(eps_fragmentation = epsilon_limits)
# )
# ) |>
# set_solver_gurobi(
# gap_limit = 0,
# verbose = TRUE
# )
#
# epsilon_solutions <- solve(epsilon_problem)
## ----gurobi-epsilon-log-static, echo=FALSE, eval=TRUE, results='asis'---------
gurobi_log <- readLines(
"results/rwanda/gurobi-epsilon-log-excerpt.txt",
warn = FALSE
)
cat("```text\n", paste(gurobi_log, collapse = "\n"), "\n```\n", sep = "")
## ----prepare-epsilon-results, include=FALSE-----------------------------------
# epsilon_results <- get_runs(epsilon_solutions) |>
# left_join(
# get_objectives(epsilon_solutions),
# by = "solution_id"
# )
#
# epsilon_results <- epsilon_results |>
# filter(!is.na(solution_id), is.finite(cost), is.finite(fragmentation)) |>
# mutate(
# additional_cost = 100 * (cost - minimum_cost) / minimum_cost,
# boundary_km = fragmentation / 1000
# )
## ----inspect-epsilon-runs-----------------------------------------------------
# epsilon_run_summary <- get_runs(epsilon_solutions) |>
# left_join(get_objectives(epsilon_solutions), by = "solution_id") |>
# mutate(epsilon_fragmentation = epsilon_limits[run_id]) |>
# select(
# run_id,
# solution_id,
# epsilon_fragmentation,
# status,
# runtime,
# gap,
# cost,
# fragmentation
# )
#
# epsilon_run_summary
## ----inspect-epsilon-runs-static, echo=FALSE, eval=TRUE-----------------------
epsilon_run_summary_static <- read.csv(
"results/rwanda/epsilon-run-summary.csv",
check.names = FALSE
)
knitr::kable(
epsilon_run_summary_static,
digits = c(0, 0, 0, NA, 3, 4, 0, 0),
caption = paste(
"Stored run metadata, epsilon limits, and objective values for the",
"epsilon-constraint analysis."
)
)
## ----compare-methods, fig.cap="Solutions generated using fixed BLM coefficients and explicit epsilon limits. Epsilon-constraint reveals the attainable cost of progressively reducing exposed boundary."----
# comparison_data <- bind_rows(
# blm_results |>
# transmute(
# solution_id,
# additional_cost,
# boundary_km,
# method = "BLM weighted sum"
# ),
# epsilon_results |>
# transmute(
# solution_id,
# additional_cost,
# boundary_km,
# method = "Epsilon-constraint"
# )
# )
#
# ggplot(
# comparison_data,
# aes(
# boundary_km,
# additional_cost,
# color = method,
# shape = method
# )
# ) +
# geom_point(size = 3, alpha = 0.9) +
# scale_color_manual(
# values = c(
# "BLM weighted sum" = "#D95F02",
# "Epsilon-constraint" = "#1B9E77"
# )
# ) +
# scale_x_continuous(labels = function(x) paste0(round(x, 1), "%")) +
# scale_y_continuous(labels = function(x) paste0(round(x, 1), "%")) +
# labs(
# x = "Exposed boundary (km)",
# y = "Additional cost relative to the minimum-cost plan",
# color = NULL,
# shape = NULL,
# title = "Two ways to express the same planning tension"
# ) +
# theme_minimal(base_size = 11) +
# theme(
# legend.position = "bottom",
# plot.title = element_text(face = "bold")
# )
## ----compare-methods-static, echo=FALSE, eval=TRUE, fig.cap="Solutions generated using fixed BLM coefficients and explicit epsilon limits. Moving toward a lower exposed boundary requires progressively greater cost."----
knitr::include_graphics(
"figures/rwanda/reserve-design-rwanda-compare-methods-1.png"
)
## ----select-plans, include=FALSE----------------------------------------------
# cost_range <- diff(range(epsilon_results$cost))
# fragmentation_range <- diff(range(epsilon_results$fragmentation))
#
# epsilon_ranked <- epsilon_results |>
# mutate(
# normalized_cost = if (cost_range > 0) {
# (cost - min(cost)) / cost_range
# } else {
# 0
# },
# normalized_fragmentation = if (fragmentation_range > 0) {
# (fragmentation - min(fragmentation)) / fragmentation_range
# } else {
# 0
# },
# distance_to_ideal = sqrt(
# normalized_cost^2 + normalized_fragmentation^2
# )
# )
#
# representative_ids <- c(
# "Minimum cost" = epsilon_ranked$solution_id[which.min(epsilon_ranked$cost)],
# "Observed compromise" = epsilon_ranked$solution_id[
# which.min(epsilon_ranked$distance_to_ideal)
# ],
# "Minimum fragmentation" = epsilon_ranked$solution_id[
# which.min(epsilon_ranked$fragmentation)
# ]
# )
#
# representative_ids <- representative_ids[!duplicated(representative_ids)]
#
# action_results <- get_actions(epsilon_solutions)
#
# selected_units <- action_results |>
# filter(
# solution_id %in% unname(representative_ids),
# selected == 1
# ) |>
# mutate(
# plan = names(representative_ids)[
# match(solution_id, representative_ids)
# ]
# ) |>
# select(pu, solution_id, plan)
#
# representative_maps <- bind_rows(lapply(
# names(representative_ids),
# function(plan_name) {
# rwanda_reserve$planning_units |>
# select(id, protected) |>
# mutate(plan = plan_name)
# }
# )) |>
# left_join(selected_units, by = c("id" = "pu", "plan" = "plan")) |>
# mutate(
# status = case_when(
# protected ~ "Existing protected area",
# !is.na(solution_id) ~ "Additional selected unit",
# TRUE ~ "Not selected"
# ),
# plan = factor(plan, levels = names(representative_ids))
# )
## ----representative-maps, fig.width=9, fig.height=4.8, fig.cap="Representative reserve designs from the epsilon-constraint frontier. Green units were already protected; orange units are additional selections made under different spatial limits."----
# action_plot <- plot_spatial_actions(
# epsilon_solutions,
# solutions = unname(representative_ids),
# layout = "single",
# ncol = 2,
# fill_values = c(reserve = "#F39C12"),
# base_fill = "#F2F2F2",
# base_alpha = 1
# )
#
# action_plot +
# facet_wrap(
# ~solution_id,
# ncol = 2,
# labeller = as_labeller(
# setNames(names(representative_ids), representative_ids)
# )
# ) +
# geom_sf(
# data = filter(rwanda_reserve$planning_units, protected),
# fill = "#238B45",
# color = NA
# ) +
# labs(title = "Alternative reserve designs")
## ----representative-maps-static, echo=FALSE, eval=TRUE, fig.cap="Representative reserve designs from the epsilon-constraint frontier. Existing protected units occur in every plan; orange units are additions selected to meet the 30% targets under different spatial limits."----
knitr::include_graphics(
"figures/rwanda/reserve-design-rwanda-representative-maps-1.png"
)
## ----selection-frequency, fig.width=7.5, fig.height=5.2, fig.cap="Selection frequency across the epsilon-constraint solutions. Existing protected units are shown separately because they were locked into every plan."----
# frequency_data <- action_results |>
# filter(selected == 1) |>
# count(pu, name = "selected_runs") |>
# mutate(frequency = selected_runs / nrow(epsilon_results))
#
# frequency_map <- rwanda_reserve$planning_units |>
# left_join(frequency_data, by = c("id" = "pu")) |>
# mutate(
# frequency = coalesce(frequency, 0),
# frequency_available = if_else(protected, NA_real_, frequency)
# )
#
# ggplot(frequency_map) +
# geom_sf(aes(fill = frequency_available), color = NA) +
# geom_sf(
# data = filter(frequency_map, protected),
# fill = "#238B45",
# color = NA
# ) +
# scale_fill_viridis_c(
# option = "B",
# limits = c(0, 1),
# na.value = "#238B45",
# name = "Selection\nfrequency"
# ) +
# coord_sf(datum = NA) +
# labs(
# title = "Spatial stability across the frontier",
# subtitle = "Green units were already protected and locked into every solution"
# ) +
# theme_minimal(base_size = 10) +
# theme(
# panel.grid = element_blank(),
# plot.title = element_text(face = "bold")
# )
## ----selection-frequency-static, echo=FALSE, eval=TRUE, fig.cap="Selection frequency across the epsilon-constraint solutions. Existing protected units are shown separately because they were locked into every plan."----
knitr::include_graphics(
"figures/rwanda/reserve-design-rwanda-selection-frequency-1.png"
)
## ----references, results='asis', echo=FALSE, eval=TRUE------------------------
cat(paste0(
"TNC. 2024. *The Marxan Planning Platform: Tutorial (Rwanda).* ",
"Global Science, The Nature Conservancy, Arlington, Virginia, USA. ",
"<https://marxanplanning.org>.\n\n",
"Kennedy, C. M., et al. 2019. Managing the middle: a shift in conservation ",
"priorities based on the global human modification gradient. ",
"*Global Change Biology* 25: 811-826. ",
"<https://doi.org/10.1111/gcb.14549>.\n\n",
"Olson, D. M., et al. 2001. Terrestrial Ecoregions of the World: A New Map ",
"of Life on Earth. *BioScience* 51: 933-938. ",
"<https://doi.org/10.1641/0006-3568(2001)051%5B0933:TEOTWA%5D2.0.CO;2>.\n\n",
"Rondinini, C., et al. 2011. Global habitat suitability models of ",
"terrestrial mammals. *Philosophical Transactions of the Royal Society B* ",
"366: 2633-2641. <https://doi.org/10.1098/rstb.2011.0113>.\n\n",
"Theobald, D. M., et al. 2020. Earth transformed: detailed mapping of ",
"global human modification from 1990 to 2017. ",
"*Earth System Science Data* 12: 1953-1972. ",
"<https://doi.org/10.5194/essd-12-1953-2020>."
))
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.