library(ResistanceGA2)
library(terra)
surfaces <- resistance_surfaces
if (inherits(surfaces, "PackedSpatRaster")) {
surfaces <- terra::unwrap(surfaces)
}ResistanceGA2 is a major revision. If you are migrating
scripts from the original ResistanceGA package, the summary
below covers every breaking change.
All spatial inputs must now use the terra package. There
is no fallback to raster or sp classes.
| Old | New |
|---|---|
RasterLayer, RasterStack |
SpatRaster; use terra::rast() |
SpatialPoints, SpatialPointsDataFrame |
SpatVector; use terra::vect() |
raster::stack(), dropLayer() |
terra::rast(), terra::subset() |
Bundled package datasets (resistance_surfaces,
raster_orig, etc.) are stored as wrapped
SpatRaster objects and unwrapped automatically when the
package loads, so no manual conversion is needed.
| Removed | Replacement |
|---|---|
CS.prep(), Run_CS() |
jl.prep(), Run_CS.jl() for
Julia/Circuitscape workflows |
JuliaCall dependency |
JuliaConnectoR |
Standalone Circuitscape .exe |
Julia + Circuitscape.jl |
Grid.Search() |
No equivalent; use GA with restricted families |
k.smooth parameter |
Removed; use select.trans in
GA.prep() |
SS_optim.scale(), MS_optim.scale() |
Merged into SS_optim(), MS_optim() |
gdist.prep() and jl.prep() accept
covariates and formula arguments to condition
resistance search on IBD or IBE fixed effects.mlpe() and mlpe_data():
New dyad-aware interface for fitting MLPE models directly, with
lme4-style formula syntax.site_pairwise_diff() /
site_pairwise_dist(): Helper functions for
constructing pairwise IBE covariates from site-level environmental
measurements.SS_optim(),
MS_optim(), and all_comb() return objects with
print(), summary(), and plot()
methods.Run_CS.ini(): Low-level Circuitscape
.ini wrapper for advanced scenarios (source/ground mode,
mask files, included-pairs files).rga2_demo and
rga2_demo_covariates for the IBE_IBR_Framework
vignette.SpatRaster
objects are serialized via terra::wrap() before being
dispatched to parallel workers, fixing Windows-specific crashes.Landscape genetics asks how the physical environment shapes the movement of individuals; and by extension, how genes flow across the landscape. Two populations might be far apart in straight-line geographic distance, but if a highway, a mountain range, or a patch of inhospitable habitat lies between them, their genetic exchange may be far lower than geography alone would predict.
ResistanceGA2 is an R package for optimizing
the resistance values assigned to those landscape features. Rather than
deciding in advance that “forest = low resistance, pavement = high
resistance,” the package uses a genetic algorithm to
search the space of possible parameter values and find the resistance
surface that best explains the observed pattern of genetic
differentiation among sampled populations.
Three hypotheses are commonly evaluated in landscape genetics:
Isolation by Distance (IBD): Genetic differentiation increases with geographic distance because dispersal is limited. This is the null expectation under random dispersal across a homogeneous landscape.
Isolation by Resistance (IBR): Genetic differentiation is better explained by effective distance (the cost of moving through a heterogeneous landscape) than by straight-line distance. Different habitat types impede movement to different degrees, so a resistance surface is parameterized and optimized.
Isolation by Environment (IBE): Genetic differentiation reflects differences in local environmental conditions (temperature, elevation, soil type) that reduce the probability of successful migration between ecologically dissimilar sites, even if those sites are geographically close.
These three hypotheses are not mutually exclusive. In many systems,
genetic structure reflects a combination of all three.
ResistanceGA2 supports workflows that test these hypotheses
individually and in combination, including the mlpe()
formula interface and mlpe_data() dyad constructor that let
you include environmental covariates alongside resistance distances in a
single MLPE model. See the IBE_IBR_Framework vignette for a
full worked example of concurrent IBD/IBE/IBR testing.
Landscape rasters Sample locations
(categorical / continuous) (SpatVector points)
| |
v v
+----------------------------------------------+
| Resistance surface parameterization |
| (8 transformation families or class values) |
+----------------------------------------------+
|
v
+----------------------------------------------+
| Distance engine |
| gdistance (costDistance / commuteDistance) |
| OR Julia / Circuitscape |
+----------------------------------------------+
| pairwise effective distances
v
+----------------------------------------------+
| MLPE model |
| genetic distance ~ resistance distance |
| + dyadic random intercept |
+----------------------------------------------+
| log-likelihood (objective)
v
+----------------------------------------------+
| Genetic Algorithm |
| searches transformation parameter space |
| maximizes LL / minimizes AIC |
+----------------------------------------------+
|
v
Optimized resistance surface + model comparison table
The genetic algorithm (GA) is the engine that drives the search. At each generation, a population of candidate parameter sets is evaluated: each candidate defines a specific transformation of the input raster, a distance matrix is calculated, an MLPE model is fit, and the objective function (default: log-likelihood) is returned. The GA then selects, recombines, and mutates the best candidates to produce the next generation, repeating until convergence.
Inside the optimization workflow, ResistanceGA2 still
prepares the legacy pop/grp random-effect
terms needed for the package internals. For standalone analysis,
however, the recommended interface is the dyad-aware
mlpe_data()/mlpe() workflow shown in the
IBE_IBR_Framework vignette.
Install the development version from GitHub:
install.packages("devtools")
devtools::install_github("wpeterman/ResistanceGA2", build_vignettes = TRUE)If Pandoc is not available on your machine, install without vignettes first and render the vignette sources later:
The core dependencies are terra, gdistance,
lme4, GA, and ggplot2. For
Julia/Circuitscape support, you also need JuliaConnectoR
and a working Julia installation; see the Julia_Guide
vignette for setup instructions.
ResistanceGA2 ships several datasets that you can use
immediately for exploration and testing.
resistance_surfaces
#> class : SpatRaster
#> size : 50, 50, 3 (nrow, ncol, nlyr)
#> resolution : 0.5, 0.5 (x, y)
#> extent : 0, 25, 0, 25 (xmin, xmax, ymin, ymax)
#> coord. ref. :
#> source(s) : memory
#> names : categorical, continuous, feature
#> min values : 0, 6.514823, 0
#> max values : 2, 13.376758, 1resistance_surfaces is a three-layer
SpatRaster:
categorical: a categorically coded
surface with class values representing distinct habitat types (e.g.,
land cover classes). The GA assigns a separate resistance value to each
class.continuous: a continuously varying
surface (e.g., canopy cover, elevation). The GA fits a parameterized
transformation curve mapping raw values to resistance values.feature: a binary or near-binary
surface representing a linear landscape feature (e.g., a road or
river).The three bundled resistance surface layers. Categorical (left) has discrete class values. Continuous (center) varies smoothly. Feature (right) represents a linear landscape element.
# Small dataset: 25 populations
pts_small <- vect(samples[, 2:3], type = "points")
pts_small
#> class : SpatVector
#> geometry : points
#> dimensions : 25, 0 (geometries, attributes)
#> extent : 6.25, 19.25, 6.25, 19.25 (xmin, xmax, ymin, ymax)
#> coord. ref. :
# Larger dataset for full optimization runs
pts_large <- vect(sample_pops[[1]], type = "points")
pts_large
#> class : SpatVector
#> geometry : points
#> dimensions : 50, 0 (geometries, attributes)
#> extent : 3.5, 20.5, 3.5, 20.5 (xmin, xmax, ymin, ymax)
#> coord. ref. :samples contains 25 sampling locations as a matrix with
x/y coordinates. sample_pops is a list of coordinate
matrices representing multiple replicate populations;
Dc_list contains corresponding genetic distance
matrices.
plot(terra::subset(surfaces, "continuous"), main = "Sample locations")
points(pts_small, pch = 19, col = "white", cex = 0.9)
points(pts_small, pch = 1, col = "black", cex = 0.9)Sample locations overlaid on the continuous resistance surface. The 25-point small dataset (filled circles) is useful for quick examples.
raster_orig Datasetraster_orig
#> class : SpatRaster
#> size : 25, 25, 2 (nrow, ncol, nlyr)
#> resolution : 1, 1 (x, y)
#> extent : 0, 25, 0, 25 (xmin, xmax, ymin, ymax)
#> coord. ref. :
#> source(s) : memory
#> names : cat_orig, cont_orig
#> min values : 1, -3.533382
#> max values : 5, 4.03984raster_orig contains the cont_orig layer, a
continuous surface intended for use with the larger
sample_pops dataset in full optimization demonstrations.
Use this layer when you want to run SS_optim() or
MS_optim() with the larger sample sets.
Expensive computations (Circuitscape runs, full GA optimizations) are precomputed and stored as package data so that vignettes render quickly:
# Precomputed pairwise distance matrices
names(vignette_pairwise_outputs)
#> [1] "gdistance_commute" "circuitscape_matrix"
cd_matrix <- vignette_pairwise_outputs$gdistance_commute
dim(cd_matrix)
#> [1] 25 25
cd_matrix[1:4, 1:4]
#> 1 2 3 4
#> 1 0.00000 79.71312 138.35098 200.72560
#> 2 79.71312 0.00000 58.63786 121.01249
#> 3 138.35098 58.63786 0.00000 62.37462
#> 4 200.72560 121.01249 62.37462 0.00000
cs_vector <- vignette_pairwise_outputs$circuitscape_matrix
length(cs_vector)
#> [1] 300
head(cs_vector)
#> [1] 5.538005 6.486677 7.276222 7.802246 5.089549 5.828889# Precomputed Circuitscape current map (stored as a PackedSpatRaster)
vignette_current_map
#> class : SpatRaster
#> size : 50, 50, 1 (nrow, ncol, nlyr)
#> resolution : 0.5, 0.5 (x, y)
#> extent : 0, 25, 0, 25 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (CRS84) (OGC:CRS84)
#> source(s) : memory
#> varname : continuous_cum_curmap
#> name : continuous
#> min value : 0.028609
#> max value : 31.56336Every analysis in ResistanceGA2 follows the same
four-step structure, regardless of whether you are testing one surface
or many, using gdistance or Julia, or running a single
replicate or a full bootstrap.
GA.prep()GA.prep() configures the genetic algorithm and records
metadata about each resistance surface layer (type, transformation
families to consider, parameter bounds).
ga_inputs <- GA.prep(
raster = terra::subset(surfaces, c("categorical", "continuous")),
Results.dir = file.path(tempdir(), "ResistanceGA2-getting-started"),
monitor = FALSE,
quiet = TRUE
)
# Layer names and types detected automatically
ga_inputs$layer.names
#> [1] "categorical" "continuous"
ga_inputs$surface.type
#> [1] "cat" "cont"Key arguments to know:
| Argument | Default | Purpose |
|---|---|---|
raster |
required | SpatRaster with one layer per surface hypothesis |
Results.dir |
required | Directory where tables and plots will be saved |
method |
"LL" |
Objective function: "LL", "AIC", or
"R2" |
select.trans |
auto | List of transformation families per layer ("M",
"R", "A", or NA for
categorical) |
pop.size |
auto | GA population size (individuals per generation); computed from the number of parameters when not set |
maxiter |
1000 | Maximum GA generations |
run |
25 | Generations of no improvement before stopping |
parallel |
FALSE | Use parallel processing |
k.value |
2 | How to count model complexity for AIC/AICc |
Tip: For continuous surfaces, restrict
select.transto Monomolecular families ("M") unless you have a specific biological reason to expect a unimodal response. Ricker families are more flexible but increase overfitting risk. For example:select.trans = list("M", "M")for a two-layer continuous raster.
gdist.prep() sets up everything needed to compute
pairwise least-cost or commute-time distances inside R. No external
software is required.
gdist_inputs <- gdist.prep(
n.Pops = nrow(samples),
samples = pts_small,
method = "costDistance"
)
names(gdist_inputs)
#> [1] "response" "samples" "covariates"
#> [4] "formula" "transitionFunction" "directions"
#> [7] "ID" "ZZ" "keep"
#> [10] "n.Pops" "longlat" "method"
#> [13] "commute.approx" "approx.factor" "approx.scale"
#> [16] "df"The method argument selects between:
"costDistance": Dijkstra’s least-cost
path algorithm. Faster; analogous to the least-cost corridor
concept."commuteDistance": Random-walk commute
time. Slower but conceptually closer to Circuitscape’s effective
resistance.If you want a quick benchmark-based recommendation for commute
settings before you call gdist.prep(), use
recommend_commute_settings():
commute_plan <- recommend_commute_settings(
raster = terra::subset(surfaces, "continuous"),
n.Pops = nrow(samples)
)
commute_plan[c("commute.approx", "approx.factor", "benchmark_band")]
#> $commute.approx
#> [1] "aggregate"
#>
#> $approx.factor
#> [1] 2
#>
#> $benchmark_band
#> [1] "intermediate"
commute_plan$rationale
#> [1] "Raster size 50 x 50 (2,500 cells) with 25 populations maps to the 'intermediate' benchmark band. Recommendation: use commute.approx = 'aggregate' with approx.factor = 2 during optimization, then rerun the final best surface exactly once without approximation."You can then pass the recommendation directly into
gdist.prep():
For Circuitscape-based distances, use jl.prep(). This
requires a working Julia installation with Circuitscape.jl.
See the Julia_Guide vignette for full setup
instructions.
jl_inputs <- jl.prep(
n.Pops = nrow(samples),
response = lower(as.matrix(dist(samples[, 2:3]))),
CS_Point.File = pts_small,
JULIA_HOME = Sys.getenv("JULIA_BINDIR"),
run_test = TRUE,
silent = TRUE
)Note: You only need to prepare one distance engine (gdistance or Julia, not both). For iterative optimization over many surfaces,
gdistanceis usually faster. For final Circuitscape-matched outputs or current maps, use Julia.
With both ga_inputs and a distance engine object ready,
call the appropriate optimization function:
SS_optim(): optimize each surface individually
(single-surface)MS_optim(): optimize all surfaces simultaneously as a
composite (multi-surface)all_comb(): systematically test all combinations of
surfaces up to a specified size# Single-surface optimization (one run per layer)
ss_out <- SS_optim(
gdist.inputs = gdist_inputs,
GA.inputs = ga_inputs,
dist_mod = TRUE, # also fit an IBD distance model
null_mod = TRUE, # also fit a null (intercept-only) model
diagnostic_plots = FALSE
)The returned object has an S3 class with built-in
print(), summary(), and plot()
methods:
Multisurface results support the same style of inspection.
For a full worked example with precomputed results, see the
ResistanceGA vignette.
Optimization functions return structured lists. The most important components are:
$AICc: model comparison table with AIC, AICc,
R2m, R2c, LL, and k
values$ContinuousResults / $CategoricalResults:
optimized parameter estimates$cd: list of pairwise distance matrices for the winning
surfaces$MLPE.list: fitted MLPE model objects for downstream
inference# View model comparison table
knitr::kable(ss_out$AICc, digits = 2, caption = "Single-surface model comparison")
# View optimized transformation parameters
ss_out$ContinuousResultsall_comb() results also have S3 methods.
Before running a full optimization, it is useful to explore how the
transformation families reshape a resistance surface.
Plot.trans() shows the relationship between raw raster
values and transformed resistance values for a given parameter set.
Plot.trans(
PARM = c(2.5, 100),
Resistance = terra::subset(surfaces, "continuous"),
transformation = "Monomolecular",
marginal.plot = FALSE
)A Monomolecular transformation applied to the continuous resistance surface. Low values in the original raster receive low resistance; the curve saturates at high values.
You can also apply a transformation directly to obtain a new
SpatRaster:
transformed <- Resistance.tran(
transformation = "Monomolecular",
shape = 2.5,
max = 100,
r = terra::subset(surfaces, "continuous")
)
transformed
#> class : SpatRaster
#> size : 50, 50, 1 (nrow, ncol, nlyr)
#> resolution : 0.5, 0.5 (x, y)
#> extent : 0, 25, 0, 25 (xmin, xmax, ymin, ymax)
#> coord. ref. :
#> source(s) : memory
#> name : continuous
#> min value : 1
#> max value : 99.168436The two parameters that define every continuous transformation are:
shape: Controls the curvature of the
response. Small values produce gradual curves; large values produce
steep thresholds.max: The maximum resistance value
assigned to the most resistant cells. The minimum is always scaled to
1.For a complete description of all eight transformation families and
guidance on choosing among them, see the
Surface_Transformations vignette.
These objects still keep their original list structure, so existing
code using $AICc, $AICc.tab, $cd,
$k, and related components continues to work. The new S3
classes add a cleaner interactive layer on top of that structure.
For larger or slower analyses, it is often worth running multiple replicates and checking for consistency in the top-supported surfaces and parameter estimates.
library(ResistanceGA2)
library(terra)
# 1. Load bundled surfaces and sample locations.
surfaces <- resistance_surfaces
if (inherits(surfaces, "PackedSpatRaster")) {
surfaces <- terra::unwrap(surfaces)
}
pts <- terra::vect(samples[, 2:3], type = "points")
response <- lower(as.matrix(dist(samples[, 2:3])))
# 2. Prepare GA settings and the distance engine.
ga_inputs <- GA.prep(
raster = surfaces,
Results.dir = file.path(tempdir(), "rga2-results")
)
gdist_inputs <- gdist.prep(
n.Pops = nrow(samples),
samples = pts,
response = response,
method = "commuteDistance"
)
# 3. Run a small single-surface optimization for workflow testing.
ss_out <- SS_optim(
GA.inputs = ga_inputs,
gdist.inputs = gdist_inputs
)
# 4. Interpret and compare fitted surfaces.
summary(ss_out) # ranking and top-model coefficients
plot(ss_out, type = "response") # fitted response relationship
plot(ss_out, type = "ranking") # candidate model supportHow to read the workflow: GA.prep()
defines what the genetic algorithm is allowed to search.
gdist.prep() or jl.prep() defines how
resistance distances are calculated. SS_optim() fits one
surface at a time and returns a ranked model object. Start with a small
run to verify paths, data, and output interpretation, then increase
pop.size, maxiter, and the number of
replicates for production analyses.
| Function | Purpose |
|---|---|
GA.prep() |
Prepare raster layers, transformation bounds, output directories, and GA control settings |
gdist.prep() |
Prepare sample points and pairwise data for R-based distance calculations |
jl.prep() |
Prepare sample points and pairwise data for Julia/Circuitscape distance calculations |
SS_optim() |
Optimize each candidate resistance surface separately |
MS_optim() |
Optimize a composite resistance surface from multiple layers |
all_comb() |
Compare candidate surface combinations and optional replicate runs |
Resist.boot() |
Summarize model support across pseudo-bootstrap subsamples |
Resistance surface: A raster in which each cell value represents the cost of moving through that cell. High values mean high resistance (impeded movement).
Effective distance: A pairwise measure of the total movement cost between two locations, computed by accumulating resistance across the landscape path.
MLPE (Maximum Likelihood Population Effects): A mixed-effects model for pairwise distance data that accounts for the non-independence of observations sharing a common sample location (Clarke et al. 2002).
Genetic algorithm (GA): A stochastic optimization method that maintains a population of candidate solutions, applies selection, crossover, and mutation operators, and converges toward high-fitness parameter regions.
Transformation family: One of eight parameterized curve shapes (Monomolecular, Reverse Monomolecular, Inverse Monomolecular, Inverse-Reverse Monomolecular, and four Ricker equivalents) that map raw raster values to resistance values.
Log-likelihood (LL): The default objective function for optimization. The GA maximizes LL of the MLPE model, which corresponds to finding the resistance surface that best explains pairwise genetic distances.
AICc: Corrected Akaike Information Criterion. Used for model comparison after optimization. Lower values indicate better-fitting models penalized for complexity.
k.value: A user-selected rule for counting the
number of parameters in the AIC/AICc calculation. Different defensible
choices exist; the default (k.value = 2) should be reported
in publications.
IBD / IBR / IBE: Isolation by Distance / Resistance / Environment. Three competing (and complementary) hypotheses about the drivers of spatial genetic structure.
| Vignette | Description |
|---|---|
ResistanceGA |
Full workflow tutorial with single-surface, multi-surface, all-combinations, and bootstrap analyses using precomputed results |
Surface_Transformations |
Detailed explanation of all eight transformation families, when to use each, and how to constrain the GA search |
IBE_IBR_Framework |
Concurrent testing of IBD, IBE, and IBR using
mlpe()/mlpe_data() with pairwise environmental
covariates and the Idaho demo dataset |
Julia_Guide |
Installing Julia, configuring JuliaConnectoR, running
Circuitscape, and troubleshooting |
Access any vignette with:
vignette("ResistanceGA", package = "ResistanceGA2")
vignette("Surface_Transformations", package = "ResistanceGA2")
vignette("IBE_IBR_Framework", package = "ResistanceGA2")
vignette("Julia_Guide", package = "ResistanceGA2")Start small. Use resistance_surfaces
and samples for your first runs. Keep pop.size
at 25 and maxiter at 50-100 to get fast results and verify
your workflow before scaling up.
Use Monomolecular first. The Monomolecular family covers most ecologically realistic resistance–covariate relationships. It is harder to overfit and easier to interpret than Ricker families. Only include Ricker families if you have a biological hypothesis that predicts a peaked or threshold-type response.
Run replicates. Genetic algorithms are stochastic. If the same surface wins consistently across replicate runs with similar parameter estimates, that is a more trustworthy result than a single run.
Check diagnostic plots. Set
diagnostic_plots = TRUE in SS_optim() or
MS_optim(). Residual plots and the observed-vs-predicted
scatter are among the fastest ways to detect poor fits.
Report k.value. Because there is no universally
accepted way to count parameters in pairwise MLPE models, you should
always report which k.value option you used and justify the
choice in your methods.
Save your Results.dir outputs. The functions write
tables and plots to Results.dir. These are useful for
comparing runs and documenting the optimization trajectory.
Warning: Do not interpret optimization as hypothesis testing. Finding that a particular resistance surface has the lowest AICc does not mean the alternative surfaces are false. It means the data, as analyzed, are more consistent with that parameterization. Replication, cross-validation, and biological interpretation are essential complements to the optimization output.
Clarke, R. T., P. Rothery, and A. F. Raybould. 2002. Confidence limits for regression relationships between distance matrices: Estimating gene flow with distance. Journal of Agricultural, Biological, and Environmental Statistics 7:361-372. https://doi.org/10.1198/108571102320
Peterman, W. E. 2018. ResistanceGA: An R package for the optimization of resistance surfaces using genetic algorithms. Methods in Ecology and Evolution 9:1638-1647. https://doi.org/10.1111/2041-210X.12984
Peterman, W. E., G. M. Connette, R. D. Semlitsch, and L. S. Eggert. 2014. Ecological resistance surfaces predict fine-scale genetic differentiation in a terrestrial woodland salamander. Molecular Ecology 23:2402–2413.
McRae, B. H. 2006. Isolation by resistance. Evolution 60:1551-1561. https://doi.org/10.1111/j.0014-3820.2006.tb00500.x
Beninde, J., J. Wittische, and A. C. Frantz. 2024. Quantifying uncertainty in inferences of landscape genetic resistance due to choice of individual-based genetic distance metric. Molecular Ecology Resources 24:e13831.