Introduction
The aim of this tutorial is to provide a short introduction to the prioritizr R package. It is also intended to help conservation planners familiar the Marxan decision support tool (Ball et al. 2009) start using the package for their work.
Data
Let’s load the packages and data used in this tutorial. Since this tutorial uses data from the prioritizrdata R package, please ensure that it is installed. The data used in this tutorial were obtained from the Introduction to Marxan course and the Australian Government’s National Vegetation Information System.
# load packages
library(prioritizrdata)
library(prioritizr)
library(sf)
library(terra)
library(vegan)
library(cluster)
# set seed for reproducibility
set.seed(500)
# load planning unit data
tas_pu <- get_tas_pu()
# load feature data
tas_features <- get_tas_features()
Let’s have a look at the planning unit data. The tas_pu
object contains planning units represented as spatial polygons (i.e., a
sf::st_sf()
object). This object has three columns that
denote the following information for each planning unit: a unique
identifier (id
), unimproved land value (cost
),
and current conservation status (locked_in
). Planning units
that have at least half of their area overlapping with existing
protected areas are denoted with a locked in TRUE
value,
otherwise they are denoted with a value of FALSE
.
# print planning unit data
print(tas_pu)
## Simple feature collection with 1130 features and 4 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: 298809.6 ymin: 5167775 xmax: 613818.8 ymax: 5502544
## Projected CRS: WGS 84 / UTM zone 55S
## # A tibble: 1,130 × 5
## id cost locked_in locked_out geom
## <int> <dbl> <lgl> <lgl> <MULTIPOLYGON [m]>
## 1 1 60.2 FALSE TRUE (((328497 5497704, 326783.8 5500050, 326775…
## 2 2 19.9 FALSE FALSE (((307121.6 5490487, 305344.4 5492917, 3053…
## 3 3 59.7 FALSE TRUE (((321726.1 5492382, 320111 5494593, 320127…
## 4 4 32.4 FALSE FALSE (((304314.5 5494324, 304342.2 5494287, 3043…
## 5 5 26.2 FALSE FALSE (((314958.5 5487057, 312336 5490646, 312339…
## 6 6 51.3 FALSE FALSE (((327904.3 5491218, 326594.6 5493012, 3284…
## 7 7 32.3 FALSE FALSE (((308194.1 5481729, 306601.2 5483908, 3066…
## 8 8 38.4 FALSE FALSE (((322792.7 5483624, 319965.3 5487497, 3199…
## 9 9 3.55 FALSE FALSE (((334896.6 5490731, 335610.4 5492490, 3357…
## 10 10 1.83 FALSE FALSE (((356377.1 5487952, 353903.1 5487635, 3538…
## # ℹ 1,120 more rows
# plot map of planning unit coverage by protected areas
plot(st_as_sf(tas_pu[, "locked_in"]), main = "Protected area coverage")
Now, let’s look at the conservation feature data. The
tas_features
object describes the spatial distribution of
the features. Specifically, the feature data are a multi-layer raster
(i.e., a terra::rast()
object). Each layer corresponds to a
different vegetation community. Within each layer, cells values denote
the presence (using value of 1) or absence (using value of 0) of the
vegetation community across the study area.
# print planning unit data
print(tas_features)
## class : SpatRaster
## dimensions : 398, 359, 33 (nrow, ncol, nlyr)
## resolution : 1000, 1000 (x, y)
## extent : 288801.7, 647801.7, 5142976, 5540976 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 55S (EPSG:32755)
## source : tas_features.tif
## names : Banks~lands, Bould~marks, Calli~lands, Cool ~orest, Eucal~hyll), Eucal~torey, ...
## min values : 0, 0, 0, 0, 0, 0, ...
## max values : 1, 1, 1, 1, 1, 1, ...
# plot map of the first four vegetation classes
plot(tas_features[[1:4]])
Problem formulation
Now we will formulate a conservation planing problem. To achieve
this, we first specify which objects contain the planning unit and
feature data (using the problem()
function). Next, we
specify that we want to use the minimum set objective function (using
the add_min_set_objective()
function). This objective
function indicates that we wish to minimize the total cost of planning
units selected by the prioritization. We then specify boundary penalties
to reduce spatial fragmentation in the resulting prioritization (using
the add_boundary_penalties()
function; see the Calibrating
trade-offs vignette for details on calibrating the penalty
value). We also specify representation targets to ensure the resulting
prioritization provides adequate coverage of each vegetation community
(using the add_relative_targets()
function). Specifically,
we specify targets to ensure at least 17% of the spatial extent of each
vegetation community (based on the Aichi Target 11).
Additionally, we set constraints to ensure that planning units
predominately covered by existing protected areas are selected by the
prioritization (using the add_locked_in_constraints()
function). Finally, we specify that the prioritization should either
select – or not select – planning units for prioritization (using the
add_binary_decisions()
function).
# build problem
p1 <-
problem(tas_pu, tas_features, cost_column = "cost") %>%
add_min_set_objective() %>%
add_boundary_penalties(penalty = 0.005) %>%
add_relative_targets(0.17) %>%
add_locked_in_constraints("locked_in") %>%
add_binary_decisions()
# print problem
print(p1)
## A conservation problem (<ConservationProblem>)
## ├•data
## │├•features: "Banksia woodlands", … (33 total)
## │└•planning units:
## │ ├•data: <sftbl_dftbldata.frame> (1130 total)
## │ ├•costs: continuous values (between 0.1925 and 61.9273)
## │ ├•extent: 298809.5764, 5167774.5993, 613818.7743, 5502543.7119 (xmin, ymin, xmax, ymax)
## │ └•CRS: WGS 84 / UTM zone 55S (projected)
## ├•formulation
## │├•objective: minimum set objective
## │├•penalties:
## ││└•1: boundary penalties (`penalty` = 0.005, `edge_factor` = 0.5, …)
## │├•targets: relative targets (between 0.17 and 0.17)
## │├•constraints:
## ││└•1: locked in constraints (257 planning units)
## │└•decisions: binary decision
## └•optimization
## ├•portfolio: default portfolio
## └•solver: gurobi solver (`gap` = 0.1, `time_limit` = 2147483647, `first_feasible` = FALSE, …)
## # ℹ Use `summary(...)` to see complete formulation.
Prioritization
We can now solve the problem formulation (p1
) to
generate a prioritization (using the solve()
function). The
prioritizr R package supports a range of different exact
algorithm solvers, including Gurobi, IBM CPLEX,
CBC, HiGHS, Rsymphony, and
lpsymphony. Although there are benefits and limitations
associated with each of these different solvers, they should return
similar results. Note that you will need at least one solver installed
on your system to generate prioritizations. Since we did not specify a
solver when building the problem, the prioritizr R package will
automatically select the best available solver installed. We recommend
using the Gurobi solver if possible, and have used it for this
tutorial (see the Gurobi Installation Guide vignette for
installation instructions). After solving the problem, the
prioritization will be stored in the solution_1
column of
the s1
object.
# solve problem
s1 <- solve(p1)
## Set parameter Username
## Set parameter TimeLimit to value 2147483647
## Set parameter MIPGap to value 0.1
## Set parameter Presolve to value 2
## Set parameter Threads to value 1
## Academic license - for non-commercial use only - expires 2025-04-21
## Gurobi Optimizer version 11.0.3 build v11.0.3rc0 (linux64 - "Ubuntu 22.04.4 LTS")
##
## CPU model: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz, instruction set [SSE2|AVX|AVX2|AVX512]
## Thread count: 4 physical cores, 8 logical processors, using up to 1 threads
##
## Optimize a model with 6329 rows, 4278 columns and 20749 nonzeros
## Model fingerprint: 0xf830e2c0
## Variable types: 0 continuous, 4278 integer (4278 binary)
## Coefficient statistics:
## Matrix range [2e-06, 6e+01]
## Objective range [5e-01, 2e+02]
## Bounds range [1e+00, 1e+00]
## RHS range [2e-01, 2e+03]
## Found heuristic solution: objective 36789.525254
## Found heuristic solution: objective 26818.653495
## Presolve removed 1909 rows and 1249 columns
## Presolve time: 0.04s
## Presolved: 4420 rows, 3029 columns, 11081 nonzeros
## Found heuristic solution: objective 24077.930942
## Variable types: 0 continuous, 3029 integer (3029 binary)
## Found heuristic solution: objective 22854.296392
## Root relaxation presolve removed 8 rows and 6 columns
## Root relaxation presolved: 4412 rows, 3023 columns, 11060 nonzeros
##
##
## Root relaxation: objective 1.799626e+04, 1296 iterations, 0.05 seconds (0.09 work units)
##
## Nodes | Current Node | Objective Bounds | Work
## Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
##
## 0 0 17996.2587 0 1115 22854.2964 17996.2587 21.3% - 0s
## H 0 0 21408.703461 17996.2587 15.9% - 0s
## H 0 0 20673.221523 17996.2587 12.9% - 0s
## H 0 0 19891.369195 17996.2587 9.53% - 0s
##
## Explored 1 nodes (1296 simplex iterations) in 0.11 seconds (0.18 work units)
## Thread count was 1 (of 8 available processors)
##
## Solution count 7: 19891.4 20673.2 21408.7 ... 36789.5
##
## Optimal solution found (tolerance 1.00e-01)
## Best objective 1.989136919462e+04, best bound 1.799625865394e+04, gap 9.5273%
Feature representation
Let’s examine how well the vegetation communities are represented by existing protected areas and the prioritization.
# create column with existing protected areas
tas_pu$pa <- round(tas_pu$locked_in)
# calculate feature representation statistics based on existing protected areas
tc_pa <- eval_target_coverage_summary(p1, tas_pu[, "pa"])
print(tc_pa)
## # A tibble: 33 × 9
## feature met total_amount absolute_target absolute_held absolute_shortfall
## <chr> <lgl> <dbl> <dbl> <dbl> <dbl>
## 1 Banksia … TRUE 2.00 0.340 0.367 0
## 2 Boulders… TRUE 140. 23.9 65.5 0
## 3 Callitri… FALSE 6.00 1.02 0.487 0.533
## 4 Cool tem… TRUE 7257. 1234. 2992. 0
## 5 Eucalypt… TRUE 5699. 969. 1398. 0
## 6 Eucalypt… FALSE 9180. 1561. 1030. 531.
## 7 Eucalypt… TRUE 38.0 6.46 15.1 0
## 8 Eucalypt… FALSE 1908. 324. 189. 135.
## 9 Eucalypt… FALSE 388. 65.9 27.4 38.6
## 10 Eucalypt… TRUE 6145. 1045. 1449. 0
## # ℹ 23 more rows
## # ℹ 3 more variables: relative_target <dbl>, relative_held <dbl>,
## # relative_shortfall <dbl>
# calculate feature representation statistics based on the prioritization
tc_s1 <- eval_target_coverage_summary(p1, s1[, "solution_1"])
print(tc_s1)
## # A tibble: 33 × 9
## feature met total_amount absolute_target absolute_held absolute_shortfall
## <chr> <lgl> <dbl> <dbl> <dbl> <dbl>
## 1 Banksia … TRUE 2.00 0.340 0.367 0
## 2 Boulders… TRUE 140. 23.9 71.8 0
## 3 Callitri… TRUE 6.00 1.02 1.49 0
## 4 Cool tem… TRUE 7257. 1234. 3300. 0
## 5 Eucalypt… TRUE 5699. 969. 1786. 0
## 6 Eucalypt… TRUE 9180. 1561. 1775. 0
## 7 Eucalypt… TRUE 38.0 6.46 18.0 0
## 8 Eucalypt… TRUE 1908. 324. 325. 0
## 9 Eucalypt… TRUE 388. 65.9 89.9 0
## 10 Eucalypt… TRUE 6145. 1045. 2081. 0
## # ℹ 23 more rows
## # ℹ 3 more variables: relative_target <dbl>, relative_held <dbl>,
## # relative_shortfall <dbl>
# explore representation by existing protected areas
## calculate number of features adequately represented by existing protected
## areas
sum(tc_pa$met)
## [1] 18
## summarize representation (values show percent coverage)
summary(tc_pa$relative_held * 100)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000 3.163 18.363 23.827 39.649 93.002
## visualize representation (values show percent coverage)
hist(tc_pa$relative_held * 100,
main = "Feature representation by existing protected areas",
xlim = c(0, 100),
xlab = "Percent coverage of features (%)")
# explore representation by prioritization
## summarize representation (values show percent coverage)
summary(tc_s1$relative_held * 100)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 17.01 18.80 30.43 37.82 49.43 100.00
## calculate number of features adequately represented by the prioritization
sum(tc_s1$met)
## [1] 33
## visualize representation (values show percent coverage)
hist(
tc_s1$relative_held * 100,
main = "Feature representation by prioritization",
xlim = c(0, 100),
xlab = "Percent coverage of features (%)"
)
We can see that representation of the vegetation communities by existing protected areas is remarkably poor. For example, many of the vegetation communities have nearly zero coverage by existing protected areas. In other words, are almost entirely absent from existing protected areas. We can also see that all vegetation communities have at least 17% coverage by the prioritization – meaning that it meets the representation targets for all of the features.
Irreplaceability
After generating the prioritization, we can examine the relative importance of planning units selected by the prioritization. This can be useful to identify critically important planning units for conservation – in other words, places that contain biodiversity features which cannot be represented anywhere else – and schedule implementation of the prioritization. To achieve this, we will use the Ferrier metric (Ferrier et al. 2000).
# calculate irreplaceability
irrep_s1 <- eval_ferrier_importance(p1, s1["solution_1"])
print(irrep_s1)
## Simple feature collection with 1130 features and 34 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: 298809.6 ymin: 5167775 xmax: 613818.8 ymax: 5502544
## Projected CRS: WGS 84 / UTM zone 55S
## # A tibble: 1,130 × 35
## `Banksia woodlands` Boulders/rock with algae, lichen…¹ Callitris forests an…²
## * <dbl> <dbl> <dbl>
## 1 0 0 0
## 2 0 0 0
## 3 0 0 0
## 4 0 0 0
## 5 0 0 0
## 6 0 0 0
## 7 0 0 0
## 8 0 0 0
## 9 0 0 0
## 10 0 0 0
## # ℹ 1,120 more rows
## # ℹ abbreviated names:
## # ¹`Boulders/rock with algae, lichen or scattered plants, or alpine fjaeldmarks`,
## # ²`Callitris forests and woodlands`
## # ℹ 32 more variables: `Cool temperate rainforest` <dbl>,
## # `Eucalyptus (+/- tall) open forest with a dense broad-leaved and/or tree-fern understorey (wet sclerophyll)` <dbl>,
## # `Eucalyptus open forests with a shrubby understorey` <dbl>, …
# manually coerce values for planning units not selected in prioritization
# to NA, so that they are shown in white
irrep_s1$plot_total <- irrep_s1$total
irrep_s1$plot_total[s1$solution_1 < 0.5] <- NA_real_
# plot map of overall importance scores
plot(st_as_sf(irrep_s1[, "plot_total"]), main = "Overall importance")
Portfolios
Conservation planning exercises often involve generating multiple
different prioritizations. This can help decision makers consider
different options, and provide starting points for building consensus
among stakeholders. To generate a range of different prioritizations
given the same problem formulation, we can use portfolio functions. Here
we will use the gap portfolio to generate 1000 solutions that are within
20% of optimality. Please note that you will need to have the
Gurobi solver installed to use this specific portfolio. If you
don’t have access to Gurobi, you could try using the shuffle
portfolio instead (using the add_shuffle_portfolio()
function).
# create new problem with a portfolio added to it
p2 <-
p1 %>%
add_gap_portfolio(number_solutions = 1000, pool_gap = 0.2)
# print problem
print(p2)
## A conservation problem (<ConservationProblem>)
## ├•data
## │├•features: "Banksia woodlands", … (33 total)
## │└•planning units:
## │ ├•data: <sftbl_dftbldata.frame> (1130 total)
## │ ├•costs: continuous values (between 0.1925 and 61.9273)
## │ ├•extent: 298809.5764, 5167774.5993, 613818.7743, 5502543.7119 (xmin, ymin, xmax, ymax)
## │ └•CRS: WGS 84 / UTM zone 55S (projected)
## ├•formulation
## │├•objective: minimum set objective
## │├•penalties:
## ││└•1: boundary penalties (`penalty` = 0.005, `edge_factor` = 0.5, …)
## │├•targets: relative targets (between 0.17 and 0.17)
## │├•constraints:
## ││└•1: locked in constraints (257 planning units)
## │└•decisions: binary decision
## └•optimization
## ├•portfolio: gap portfolio (`number_solutions` = 1000, `pool_gap` = 0.2)
## └•solver: gurobi solver (`gap` = 0.1, `time_limit` = 2147483647, `first_feasible` = FALSE, …)
## # ℹ Use `summary(...)` to see complete formulation.
# generate prioritizations
prt <- solve(p2)
## Set parameter Username
## Set parameter TimeLimit to value 2147483647
## Set parameter MIPGap to value 0.1
## Set parameter Presolve to value 2
## Set parameter Threads to value 1
## Set parameter PoolSolutions to value 1000
## Set parameter PoolSearchMode to value 2
## Set parameter PoolGap to value 0.2
## Academic license - for non-commercial use only - expires 2025-04-21
## Gurobi Optimizer version 11.0.3 build v11.0.3rc0 (linux64 - "Ubuntu 22.04.4 LTS")
##
## CPU model: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz, instruction set [SSE2|AVX|AVX2|AVX512]
## Thread count: 4 physical cores, 8 logical processors, using up to 1 threads
##
## Optimize a model with 6329 rows, 4278 columns and 20749 nonzeros
## Model fingerprint: 0xf830e2c0
## Variable types: 0 continuous, 4278 integer (4278 binary)
## Coefficient statistics:
## Matrix range [2e-06, 6e+01]
## Objective range [5e-01, 2e+02]
## Bounds range [1e+00, 1e+00]
## RHS range [2e-01, 2e+03]
## Found heuristic solution: objective 36789.525254
## Found heuristic solution: objective 26818.653495
## Presolve removed 1434 rows and 258 columns
## Presolve time: 0.02s
## Presolved: 4895 rows, 4020 columns, 12064 nonzeros
## Variable types: 0 continuous, 4020 integer (4020 binary)
## Found heuristic solution: objective 25741.299203
## Root relaxation presolved: 4895 rows, 4020 columns, 12064 nonzeros
##
##
## Root relaxation: objective 1.799626e+04, 1440 iterations, 0.06 seconds (0.10 work units)
##
## Nodes | Current Node | Objective Bounds | Work
## Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
##
## 0 0 17996.2587 0 1216 25741.2992 17996.2587 30.1% - 0s
## H 0 0 22020.805500 17996.2587 18.3% - 0s
## H 0 0 20460.109776 17996.2587 12.0% - 0s
## 0 0 18090.6275 0 1238 20460.1098 18090.6275 11.6% - 0s
## H 0 0 19408.211150 18090.6275 6.79% - 0s
## 0 0 18106.4253 0 1203 19408.2111 18106.4253 6.71% - 0s
## 0 0 18106.5097 0 1128 19408.2111 18106.5097 6.71% - 0s
## 0 0 18108.9518 0 1121 19408.2111 18108.9518 6.69% - 0s
## 0 0 18108.9518 0 1121 19408.2111 18108.9518 6.69% - 1s
## 0 2 18109.9351 0 1121 19408.2111 18109.9351 6.69% - 1s
## H 27 27 19182.229771 18109.9351 5.59% 115 1s
## H 187 187 19131.891551 18109.9351 5.34% 42.8 2s
## H 472 455 18712.916589 18109.9351 3.22% 21.4 2s
## H 494 474 18560.845115 18121.8415 2.37% 24.8 2s
## H 512 493 18558.685470 18121.8415 2.35% 25.8 2s
## H 519 3 18395.725721 18121.8415 1.49% 25.5 4s
## 674 156 20259.5779 155 1 18395.7257 18175.1726 1.20% 31.6 5s
##
## Cutting planes:
## Cover: 1
## MIR: 2
## GUB cover: 1
## RLT: 7
##
## Explored 2914 nodes (53672 simplex iterations) in 8.81 seconds (12.35 work units)
## Thread count was 1 (of 8 available processors)
##
## Solution count 1000: 18395.7 18443.6 18450.4 ... 20239.8
## No other solutions better than 20239.8
##
## Optimal solution found (tolerance 1.00e-01)
## Best objective 1.839572572128e+04, best bound 1.823236110920e+04, gap 0.8881%
print(prt)
## Simple feature collection with 1130 features and 1004 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: 298809.6 ymin: 5167775 xmax: 613818.8 ymax: 5502544
## Projected CRS: WGS 84 / UTM zone 55S
## # A tibble: 1,130 × 1,005
## id cost locked_in locked_out solution_1 solution_2 solution_3 solution_4
## <int> <dbl> <lgl> <lgl> <dbl> <dbl> <dbl> <dbl>
## 1 1 60.2 FALSE TRUE 0 0 0 0
## 2 2 19.9 FALSE FALSE 0 0 0 0
## 3 3 59.7 FALSE TRUE 0 0 0 0
## 4 4 32.4 FALSE FALSE 0 0 0 0
## 5 5 26.2 FALSE FALSE 0 0 0 0
## 6 6 51.3 FALSE FALSE 0 0 0 0
## 7 7 32.3 FALSE FALSE 0 0 0 0
## 8 8 38.4 FALSE FALSE 0 0 0 0
## 9 9 3.55 FALSE FALSE 0 0 0 0
## 10 10 1.83 FALSE FALSE 0 0 0 0
## # ℹ 1,120 more rows
## # ℹ 997 more variables: solution_5 <dbl>, solution_6 <dbl>, solution_7 <dbl>,
## # solution_8 <dbl>, solution_9 <dbl>, solution_10 <dbl>, solution_11 <dbl>,
## # solution_12 <dbl>, solution_13 <dbl>, solution_14 <dbl>, solution_15 <dbl>,
## # solution_16 <dbl>, solution_17 <dbl>, solution_18 <dbl>, solution_19 <dbl>,
## # solution_20 <dbl>, solution_21 <dbl>, solution_22 <dbl>, solution_23 <dbl>,
## # solution_24 <dbl>, solution_25 <dbl>, solution_26 <dbl>, …
After generating all these prioritizations, we now want some way to visualize them. Because it would be onerous to look at each and every prioritization individually, we will use statistical analyses to help us. We can visualize the differences between these different prioritizations – based on which planning units they selected – using a hierarchical cluster analysis (Harris et al. 2014).
# extract solutions
prt_results <- sf::st_drop_geometry(prt)
prt_results <- prt_results[, startsWith(names(prt_results), "solution_")]
# calculate pair-wise distances between different prioritizations for analysis
prt_dists <- vegan::vegdist(t(prt_results), method = "jaccard", binary = TRUE)
# run cluster analysis
prt_clust <- hclust(as.dist(prt_dists), method = "average")
# visualize clusters
opar <- par()
par(oma = c(0, 0, 0, 0), mar= c(0, 4.1, 1.5, 2.1))
plot(
prt_clust, labels = FALSE, sub = NA, xlab = "",
main = "Different prioritizations in portfolio"
)
suppressWarnings(par(opar))
We can see that there are approximately six main groups of prioritizations in the portfolio. To explore these different groups, let’s conduct another cluster analysis (i.e., a k-medoids analysis) to extract the most representative prioritization from each of these groups. In other words, we will run another statistical analysis to find the most central prioritization within each group.
# run k-medoids analysis
prt_med <- pam(prt_dists, k = 6)
# extract names of prioritizations that are most central for each group.
prt_med_names <- prt_med$medoids
print(prt_med_names)
## [1] "solution_443" "solution_48" "solution_368" "solution_663" "solution_738"
## [6] "solution_813"
# create a copy of prt and set values for locked in planning units to -1
# so we can easily visualize differences between prioritizations
prt2 <- prt[, prt_med_names]
prt2[which(tas_pu$locked_in > 0.5), prt_med_names] <- -1
# plot a map showing main different prioritizations
# dark grey: locked in planning units
# grey: planning units not selected
# green: selected planning units
plot(st_as_sf(prt2), pal = c("grey60", "grey90", "darkgreen"))
Marxan compatibility
The prioritizr R package provides functionality to help Marxan users generate prioritizations. Specifically, it can import conservation planning data prepared for Marxan, and can generate prioritizations using a similar problem formulation as Marxan (based on Beyer et al. 2016). Indeed, the problem formulation presented earlier in this vignette is very similar to that used by Marxan. The key difference is that the problem formulation we specified earlier uses “hard constraints” for feature representation, and Marxan uses “soft constraints” for feature representation. This means that prioritization we generated earlier was mathematically guaranteed to reach the targets for all features. However, if we used Marxan to generate the prioritization, then we could have produced a prioritization that would fail to reach targets (depending the Species Penalty Factors used to generate the prioritization). In addition to these differences in terms problem formulation, the prioritizr R package uses exact algorithms – instead of the simulated annealing algorithm – which ensures that we obtain prioritizations that are near optimal.
Here we will show the prioritizr R package can import Marxan data and generate a prioritization. To begin with, let’s import a conservation planning data prepared for Marxan.
# import data
## planning unit data
pu_path <- system.file("extdata/marxan/input/pu.dat", package = "prioritizr")
pu_data <- read.csv(pu_path, header = TRUE, stringsAsFactors = FALSE)
print(head(pu_data))
## id cost status xloc yloc
## 1 3 0.000 0 1116623 -4493479
## 2 30 7527.275 3 1110623 -4496943
## 3 56 37349.075 0 1092623 -4500408
## 4 58 16959.021 0 1116623 -4500408
## 5 84 34220.256 0 1098623 -4503872
## 6 85 178907.584 0 1110623 -4503872
## feature data
spec_path <- system.file(
"extdata/marxan/input/spec.dat", package = "prioritizr"
)
spec_data <- read.csv(spec_path, header = TRUE, stringsAsFactors = FALSE)
print(head(spec_data))
## id prop spf name
## 1 10 0.3 1 bird1
## 2 11 0.3 1 nvis2
## 3 12 0.3 1 nvis8
## 4 13 0.3 1 nvis9
## 5 14 0.3 1 nvis14
## 6 15 0.3 1 nvis20
## amount of each feature within each planning unit data
puvspr_path <- system.file(
"extdata/marxan/input/puvspr.dat", package = "prioritizr"
)
puvspr_data <- read.csv(puvspr_path, header = TRUE, stringsAsFactors = FALSE)
print(head(puvspr_data))
## species pu amount
## 1 26 56 120.344884
## 2 26 58 45.167010
## 3 26 84 68.047375
## 4 26 85 9.735624
## 5 26 86 7.803476
## 6 26 111 478.327417
## boundary data
bound_path <- system.file(
"extdata/marxan/input/bound.dat", package = "prioritizr"
)
bound_data <- read.table(bound_path, header = TRUE, stringsAsFactors = FALSE)
print(head(bound_data))
## id1 id2 boundary
## 1 3 3 16000
## 2 3 30 4000
## 3 3 58 4000
## 4 30 30 12000
## 5 30 58 4000
## 6 30 85 4000
After importing the data, we can now generate a prioritization based
on the Marxan problem formulation (using the
marxan_problem()
function). Please note that this
function does not generate prioritizations using
Marxan. Instead, it uses the data to create an
optimization problem formulation similar to Marxan – using hard
constraints instead of soft constraints – and uses an exact algorithm
solver to generate a prioritization.
# create problem
p3 <- marxan_problem(
pu_data, spec_data, puvspr_data, bound_data, blm = 0.0005
)
# print problem
print(p3)
## A conservation problem (<ConservationProblem>)
## ├•data
## │├•features: "bird1", "nvis2", "nvis8", "nvis9", "nvis14", "nvis20", … (17 total)
## │└•planning units:
## │ ├•data: <data.frame> (1751 total)
## │ ├•costs: continuous values (between 0 and 415692.1938)
## │ ├•extent: NA
## │ └•CRS: NA
## ├•formulation
## │├•objective: minimum set objective
## │├•penalties:
## ││└•1: boundary penalties (`penalty` = 5e-04, `edge_factor` = 1, …)
## │├•targets: relative targets (between 0.3 and 0.3)
## │├•constraints:
## ││├•1: locked in constraints (317 planning units)
## ││└•2: locked out constraints (1 planning units)
## │└•decisions: binary decision
## └•optimization
## ├•portfolio: default portfolio
## └•solver: gurobi solver (`gap` = 0.1, `time_limit` = 2147483647, `first_feasible` = FALSE, …)
## # ℹ Use `summary(...)` to see complete formulation.
# solve problem
s3 <- solve(p3)
## Set parameter Username
## Set parameter TimeLimit to value 2147483647
## Set parameter MIPGap to value 0.1
## Set parameter Presolve to value 2
## Set parameter Threads to value 1
## Academic license - for non-commercial use only - expires 2025-04-21
## Gurobi Optimizer version 11.0.3 build v11.0.3rc0 (linux64 - "Ubuntu 22.04.4 LTS")
##
## CPU model: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz, instruction set [SSE2|AVX|AVX2|AVX512]
## Thread count: 4 physical cores, 8 logical processors, using up to 1 threads
##
## Optimize a model with 10075 rows, 6780 columns and 24778 nonzeros
## Model fingerprint: 0x37d7a0a1
## Variable types: 0 continuous, 6780 integer (6780 binary)
## Coefficient statistics:
## Matrix range [5e-05, 4e+03]
## Objective range [4e+00, 4e+05]
## Bounds range [1e+00, 1e+00]
## RHS range [5e+03, 3e+05]
## Found heuristic solution: objective 1.221202e+08
## Presolve removed 4707 rows and 3103 columns
## Presolve time: 0.05s
## Presolved: 5368 rows, 3677 columns, 12704 nonzeros
## Variable types: 0 continuous, 3677 integer (3677 binary)
## Found heuristic solution: objective 1.009944e+08
## Root relaxation presolved: 5368 rows, 3677 columns, 12704 nonzeros
##
##
## Root relaxation: interrupted, 2 iterations, 0.01 seconds (0.01 work units)
##
## Nodes | Current Node | Objective Bounds | Work
## Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
##
## 0 0 - 0 1.0099e+08 9.1033e+07 9.86% - 0s
##
## Explored 1 nodes (2 simplex iterations) in 0.06 seconds (0.09 work units)
## Thread count was 1 (of 8 available processors)
##
## Solution count 2: 1.00994e+08 1.2212e+08
##
## Optimal solution found (tolerance 1.00e-01)
## Best objective 1.009944489031e+08, best bound 9.103307331724e+07, gap 9.8633%
## id cost status xloc yloc locked_in locked_out solution_1
## 1 3 0.000 0 1116623 -4493479 FALSE FALSE 0
## 2 30 7527.275 3 1110623 -4496943 FALSE TRUE 0
## 3 56 37349.075 0 1092623 -4500408 FALSE FALSE 0
## 4 58 16959.021 0 1116623 -4500408 FALSE FALSE 0
## 5 84 34220.256 0 1098623 -4503872 FALSE FALSE 0
## 6 85 178907.584 0 1110623 -4503872 FALSE FALSE 0
Conclusion
This tutorial shows how the prioritizr R package can be used to build a conservation problem, generate a prioritization, and evaluate it. Although we explored just a few functions, the package provides many different functions so that you can build and custom-tailor conservation planning problems to suit your needs. To learn more about the package, please see the package vignettes for an overview of the package, instructions for installing the Gurobi optimization suite, benchmarks comparing the performance of different solvers, and a record of publications that have cited the package. In addition to this tutorial, the package also provides tutorials on incorporating connectivity into prioritizations, calibrating trade-offs between different criteria (e.g., total cost and spatial fragmentation), and creating prioritizations that have multiple management zones or management actions.