This vignette sketches a practical workflow for fitting isolation by
environment (IBE) and isolation by resistance
(IBR) concurrently with the new MLPE interface in
ResistanceGA2.
The central idea is simple:
IBE enters the model as one or more pairwise
endpoint-difference covariates.IBR enters the model as the optimized
resistance-distance term.This example uses the package’s Idaho demo data:
rga2_demo for sites, extracted site environments, and
the chord-distance matrixrga2_demo_covariates for the raster layers used to
generate resistance distancesThe analysis is structured so that the expensive optimization steps can be run outside the vignette, saved as package extdata, and then displayed here for later exploration.
The document is organized in two layers:
The optimization examples below use an explicit seed so the worked example is reproducible. The external builder script also supports named runtime profiles and an optional core-count override so you can choose between quick smoke tests and deeper optimization runs without editing the script itself.
if (!have_saved_outputs) {
cat(
"Saved analysis outputs were not found under ",
output_dir,
".\n\nRun `data-raw/build_ibe_ibr_vignette_outputs.R` first if you want ",
"the full set of model tables, optimization summaries, and plots to be ",
"stored beneath `inst/extdata/ibe-ibr-demo/` and appear throughout this ",
"vignette.\n",
sep = ""
)
}For a joint IBE/IBR analysis, it is
important not to blur the mechanism of endpoint mismatch with the
mechanism of landscape permeability. In this example:
IBE is represented by site-level mismatch in
cti and hliIBR is optimized over the separate raster hypotheses
err27 and ffpThis is only one defensible partition. The key point is to avoid using the same variable in both roles unless that dual interpretation is explicitly part of the biological hypothesis.
data(rga2_demo)
data(rga2_demo_covariates)
if (inherits(rga2_demo_covariates, "PackedSpatRaster")) {
rga2_demo_covariates <- unwrap(rga2_demo_covariates)
}
r_covariate <- subset(rga2_demo_covariates, c("err27", "ffp", "cti", "hli"))
sites <- rga2_demo$sites
site_env <- rga2_demo$site_environment
sites[, c("demo_id", "SiteName", "x", "y")]
#> demo_id SiteName x y
#> Airplane Airplane AirplaneLake 688816.6 5003207
#> Bachelor Bachelor BachelorMeadow 688494.4 4999093
#> BarkingFox BarkingFox BarkingFoxLake 687938.4 5000223
#> Bob Bob BobLake 690104.0 4999355
#> Cache Cache CacheLake 688742.5 4997481
#> Egg Egg EggWhiteLake 688539.3 4998146
#> Frog Frog FrogPondLake 688845.2 4998427
#> GentianL GentianL GentianLake 689781.6 5002972
#> ParagonL ParagonL ParagonLake 687421.5 4994948
#> Pothole Pothole PotholeLake 690481.4 5004352
#> ShipIsland ShipIsland ShipIslandLake 686908.5 5004064
#> Skyhigh Skyhigh SkyhighLake 688159.7 4999027
site_env
#> demo_id cti err27 ffp gsp hli nlcd
#> Airplane Airplane 6.551698 0.2211860 8.000000 286.0000 1438 42
#> Bachelor Bachelor 7.257386 0.3399709 8.000000 286.0000 1718 71
#> BarkingFox BarkingFox 7.587409 0.2636720 9.000000 283.0000 1308 42
#> Bob Bob 10.962278 0.5830295 6.000000 290.0000 1685 42
#> Cache Cache 7.817918 0.4895256 11.000000 274.0000 1277 42
#> Egg Egg 6.560551 0.4499388 10.000000 279.0000 1244 42
#> Frog Frog 6.675704 0.3340760 10.155787 276.1548 1170 42
#> GentianL GentianL 5.595447 0.2247885 6.000000 292.0000 1370 42
#> ParagonL ParagonL 9.865539 0.4723573 7.000000 285.3084 1298 11
#> Pothole Pothole 10.490610 0.4085309 6.000000 292.0000 1368 42
#> ShipIsland ShipIsland 8.181678 0.1197917 13.000000 273.0000 1300 11
#> Skyhigh Skyhigh 12.254863 0.2937623 6.829502 290.0000 1316 11
r_covariate
#> class : SpatRaster
#> size : 426, 358, 4 (nrow, ncol, nlyr)
#> resolution : 30, 30 (x, y)
#> extent : 683282.5, 694022.5, 4992833, 5005613 (xmin, xmax, ymin, ymax)
#> coord. ref. : NAD83 / UTM zone 11N (EPSG:26911)
#> source(s) : memory
#> varname : covariates
#> names : err27, ffp, cti, hli
#> min values : 0.039066, 0, 0.842985, 1014
#> max values : 0.763764, 51, 23.71476, 9263The pairwise response is the lower triangle of the 12 x 12 chord-distance matrix. We pair that response with:
IBD)cti and
hli (IBE)There are two common ways to encode IBE, and they answer
slightly different questions.
Absolute environmental differences:
|cti_i - cti_j| or |hli_i - hli_j|Multivariate environmental distance:
In practice, I would usually start with absolute differences for a small number of biologically motivated variables, then compare that model to a simpler multivariate-distance version as a sensitivity check.
That strategy is especially useful here because cti and
hli have interpretable ecological meanings, while
env_dist is intentionally more agnostic. Separate
absolute-difference terms tell us which environmental axis is carrying
signal; a multivariate distance tells us whether there is overall
environmental mismatch even when we do not want to interpret individual
axes too literally.
site_ids <- sites$demo_id
pair_index <- t(combn(site_ids, 2))
pair_index <- data.frame(
from = pair_index[, 1],
to = pair_index[, 2],
stringsAsFactors = FALSE
)
coords <- rga2_demo$sample_coords
geo <- as.numeric(scale(lower(as.matrix(dist(coords)))))
ibe_diff <- site_pairwise_diff(
site_env[, c("cti", "hli")],
scale = TRUE
)
env_dist <- site_pairwise_dist(
site_env[, c("cti", "hli")],
scale = TRUE
)
head(ibe_diff)
#> cti_diff hli_diff
#> 1 0.338346773 1.6785034
#> 2 0.496578284 0.7793052
#> 3 2.114680594 1.4806798
#> 4 0.607097178 0.9651395
#> 5 0.004244835 1.1629631
#> 6 0.059455814 1.6065676
head(env_dist)
#> env_dist
#> 1 1.7122653
#> 2 0.9240706
#> 3 2.5815280
#> 4 1.1402023
#> 5 1.1629708
#> 6 1.6076674ibe_diff and env_dist are both valid
IBE representations, but they should not usually be
included in the same model because they encode overlapping
information.
Before interpreting coefficients, it helps to inspect how strongly
the pairwise predictors overlap. If IBD, IBE,
and later IBR are all highly correlated, then model ranking
may still be informative but individual fixed-effect estimates will be
harder to interpret mechanistically.
predictor_cor <- cor(
data.frame(
geo = geo,
cti_diff = ibe_diff$cti_diff,
hli_diff = ibe_diff$hli_diff,
env_dist = env_dist$env_dist
)
)
round(predictor_cor, 3)
#> geo cti_diff hli_diff env_dist
#> geo 1.000 -0.095 -0.211 -0.269
#> cti_diff -0.095 1.000 -0.095 0.606
#> hli_diff -0.211 -0.095 1.000 0.701
#> env_dist -0.269 0.606 0.701 1.000If you have already run the external builder script, the saved output bundle stores this same matrix for later review:
Before optimizing any resistance surfaces, it is useful to ask
whether there is support for IBD, IBE, or
both.
pair_data <- mlpe_data(
response = rga2_demo$genetic_vector,
pairs = pair_index,
geo = geo,
cti_diff = ibe_diff$cti_diff,
hli_diff = ibe_diff$hli_diff,
env_dist = env_dist$env_dist
)
head(pair_data)
#> from to geo cti_diff hli_diff env_dist response
#> 1 Airplane Bachelor 0.1491438 0.338346773 1.6785034 1.7122653 0.4827800
#> 2 Airplane BarkingFox -0.3013249 0.496578284 0.7793052 0.9240706 0.4133083
#> 3 Airplane Bob 0.1204355 2.114680594 1.4806798 2.5815280 0.4093260
#> 4 Airplane Cache 0.8583245 0.607097178 0.9651395 1.1402023 0.4977017
#> 5 Airplane Egg 0.5669958 0.004244835 1.1629631 1.1629708 0.4873166
#> 6 Airplane Frog 0.4387835 0.059455814 1.6065676 1.6076674 0.4530400The examples above use one MLPE dyad term, (1 | pair),
where pair is built from the from and
to site columns. The current mlpe()
implementation can also include more than one separate dyadic
random-intercept term in the same model, as long as each term
is linked to its own pair of endpoint columns through the
pairs argument.
This can be useful when pairwise observations share endpoints at multiple sampling scales. For example, we may want one dyadic random effect for repeated use of the same sampled sites and a second dyadic random effect for repeated use of broader region labels attached to those sites.
One current constraint is that each dyadic term still expects the two
endpoint labels to differ within every row for that term. A coarse
region-level dyadic effect therefore works naturally for
between-region pairs, but not for within-region rows
where from_region == to_region. The example below keeps
only between-region observations so the two-term fit is
well-defined.
site_region <- setNames(
ifelse(sites$x <= stats::median(sites$x), "west", "east"),
as.character(sites$demo_id)
)
pair_data_region <- pair_data
pair_data_region$from_region <- site_region[as.character(pair_data_region$from)]
pair_data_region$to_region <- site_region[as.character(pair_data_region$to)]
pair_data_region <- pair_data_region[
pair_data_region$from_region != pair_data_region$to_region,
,
drop = FALSE
]
rownames(pair_data_region) <- NULL
m_site_region <- mlpe(
response ~ geo + cti_diff + hli_diff +
(1 | site_pair) + (1 | region_pair),
data = pair_data_region,
pairs = list(
site_pair = c("from", "to"),
region_pair = c("from_region", "to_region")
)
)
summary(m_site_region)
#> Linear mixed model fit by maximum likelihood ['lmerMod']
#>
#> AIC BIC logLik -2*log(L) df.resid
#> -77.7 -66.6 45.9 -91.7 29
#>
#> Scaled residuals:
#> Min 1Q Median 3Q Max
#> -2.30629 -0.53670 -0.03469 0.82154 1.99303
#>
#> Random effects:
#> Groups Name Variance Std.Dev.
#> site_pair (Intercept) 0.0002541 0.01594
#> region_pair (Intercept) 0.0000000 0.00000
#> Residual 0.0041339 0.06430
#> Number of obs: 36, groups: site_pair, 12; region_pair, 2
#>
#> Fixed effects:
#> Estimate Std. Error t value
#> (Intercept) 0.393343 0.026202 15.012
#> geo 0.073851 0.011699 6.312
#> cti_diff -0.003229 0.014014 -0.230
#> hli_diff -0.009549 0.013405 -0.712
#>
#> Correlation of Fixed Effects:
#> (Intr) geo ct_dff
#> geo -0.098
#> cti_diff -0.656 0.003
#> hli_diff -0.606 0.142 0.127The important distinction is that these are parallel dyadic
terms, not a nested dyadic syntax. Formula shorthand such as
(1 | site_pair / region_pair) is not currently translated
into a nested MLPE covariance structure, and dyadic terms with
same-label endpoints are still rejected. If you need both site-level and
region-level dyadic effects right now, write them as separate terms and
ensure the retained rows satisfy that endpoint-label constraint.
m_null <- mlpe(
response ~ 1 + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
m_ibd <- mlpe(
response ~ geo + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
m_ibe_cti <- mlpe(
response ~ cti_diff + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
m_ibe_hli <- mlpe(
response ~ hli_diff + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
m_ibe_both <- mlpe(
response ~ cti_diff + hli_diff + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
m_ibd_ibe <- mlpe(
response ~ geo + cti_diff + hli_diff + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
m_ibd_envdist <- mlpe(
response ~ geo + env_dist + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
baseline_table <- data.frame(
model = c(
"Null",
"IBD",
"IBE: cti",
"IBE: hli",
"IBE: cti + hli",
"IBD + IBE",
"IBD + env_dist"
),
AIC = c(
AIC(m_null),
AIC(m_ibd),
AIC(m_ibe_cti),
AIC(m_ibe_hli),
AIC(m_ibe_both),
AIC(m_ibd_ibe),
AIC(m_ibd_envdist)
),
logLik = c(
logLik(m_null),
logLik(m_ibd),
logLik(m_ibe_cti),
logLik(m_ibe_hli),
logLik(m_ibe_both),
logLik(m_ibd_ibe),
logLik(m_ibd_envdist)
)
)
baseline_table[order(baseline_table$AIC), ]This baseline comparison tells us whether the environmental mismatch
terms have signal before we add the path-based IBR
component.
The different baseline models answer different questions:
Null tells us how much structure is captured by the
MLPE random effect aloneIBD asks whether simple geographic separation explains
genetic distanceIBE: cti and IBE: hli isolate each
environmental mismatch axisIBE: cti + hli asks whether a small set of
interpretable mismatch variables improves fit togetherIBD + IBE tests whether environmental mismatch adds
anything after accounting for geographyIBD + env_dist is a compact alternative when you prefer
one overall environmental dissimilarity termIf saved outputs are available, the table below shows an actual model ranking from the example analysis.
if (have_saved_outputs) {
summary_bundle$baseline_table
}
#> model AIC logLik sigma deltaAIC weight
#> 2 ibd -170.8078 89.40392 0.05695819 0.000000 6.273086e-01
#> 7 ibd_envdist -169.0868 89.54341 0.05686835 1.721012 2.653190e-01
#> 6 ibd_ibe -167.2776 89.63878 0.05674323 3.530271 1.073724e-01
#> 1 null -122.7880 64.39402 0.08169552 48.019802 2.344842e-11
#> 3 ibe_cti -121.4812 64.74061 0.08123091 49.326610 1.219955e-11
#> 4 ibe_hli -121.3974 64.69872 0.08219886 49.410390 1.169907e-11
#> 5 ibe_both -120.2543 65.12715 0.08173548 50.553528 6.605749e-12This fixed-effect table is often even more informative than the rank table, because it shows how much the environmental coefficients shrink once geography enters the model.
if (have_saved_outputs) {
summary_bundle$baseline_fixef
}
#> model term estimate
#> 1 null (Intercept) 0.388676042
#> 2 ibd (Intercept) 0.388676042
#> 3 ibd geo 0.073303219
#> 4 ibe_cti (Intercept) 0.402725418
#> 5 ibe_cti cti_diff -0.012131977
#> 6 ibe_hli (Intercept) 0.401402489
#> 7 ibe_hli hli_diff -0.011979799
#> 8 ibe_both (Intercept) 0.418607528
#> 9 ibe_both cti_diff -0.013491980
#> 10 ibe_both hli_diff -0.013467778
#> 11 ibd_ibe (Intercept) 0.399096675
#> 12 ibd_ibe geo 0.072585107
#> 13 ibd_ibe cti_diff -0.003117198
#> 14 ibd_ibe hli_diff -0.006411196
#> 15 ibd_envdist (Intercept) 0.398006270
#> 16 ibd_envdist geo 0.072466306
#> 17 ibd_envdist env_dist -0.005231526For this example, the fast-run saved outputs suggest a simple but instructive pattern:
IBD is clearly supported relative to
NullIBE by itself is weaker than IBDcti_diff and hli_diff to
IBD changes the coefficients only modestly in the fast run,
suggesting that most of the baseline signal is geographic rather than
environmentalenv_dist model behaves similarly,
which makes it a useful sensitivity check rather than a replacement for
the more interpretable univariate termsOnce the fixed covariates are chosen, they can be supplied directly
to gdist.prep() so every resistance optimization is fit
conditionally on those same IBD and IBE
terms.
pts <- vect(
rga2_demo$sample_coords,
type = "points",
crs = crs(r_covariate, proj = TRUE)
)
workflow_covariates <- data.frame(
geo = geo,
cti_diff = ibe_diff$cti_diff,
hli_diff = ibe_diff$hli_diff
)
gdist_inputs <- gdist.prep(
n.Pops = nrow(rga2_demo$sample_coords),
response = rga2_demo$genetic_vector,
samples = pts,
covariates = workflow_covariates,
formula = gd ~ geo + cti_diff + hli_diff,
method = "costDistance"
)
gdist_inputs$formula
#> gd ~ geo + cti_diff + hli_diff + cd + (1 | pop)
head(gdist_inputs$df)
#> gd geo cti_diff hli_diff pop .mlpe_pop1 .mlpe_pop2
#> 1 0.4827800 0.1491438 0.338346773 1.6785034 1 1 2
#> 2 0.4133083 -0.3013249 0.496578284 0.7793052 1 1 3
#> 3 0.4093260 0.1204355 2.114680594 1.4806798 1 1 4
#> 4 0.4977017 0.8583245 0.607097178 0.9651395 1 1 5
#> 5 0.4873166 0.5669958 0.004244835 1.1629631 1 1 6
#> 6 0.4530400 0.4387835 0.059455814 1.6065676 1 1 7The resulting optimization models are of the form:
where cd is the resistance-distance term generated from
the candidate raster(s).
This conditional formulation is the key to the joint framework. We
are not asking whether IBR is better than IBE
in isolation; we are asking whether a path-based resistance hypothesis
still improves model fit after geography and endpoint environmental
mismatch have already been accounted for.
This example treats err27 and ffp as the
candidate permeability hypotheses.
ibr_layers <- subset(r_covariate, c("err27", "ffp"))
GA_inputs_ss <- GA.prep(
raster = ibr_layers,
Results.dir = file.path(tempdir(), "rga2-ibe-ibr-single"),
method = "LL",
seed = 123,
pop.size = 25,
maxiter = 15,
run = 5,
parallel = F,
keepBest = FALSE,
monitor = FALSE,
quiet = TRUE
)
ss_out <- SS_optim(
gdist.inputs = gdist_inputs,
GA.inputs = GA_inputs_ss,
dist_mod = TRUE,
null_mod = TRUE,
diagnostic_plots = FALSE
)
ss_out$AICc
summary(ss_out)That table compares:
IBR + IBD + IBE model for each candidate
rasterDistance modelNull model with no fixed predictors beyond the
interceptIf saved outputs are available, we can inspect the actual optimization ranking:
if (have_saved_outputs) {
summary_bundle$ss_aicc
}
#> Surface Fixed.Effects obj.func_LL k AIC AICc
#> 12 Null geo + cti_diff + hli_diff 89.63878 4 -171.2776 -165.5633
#> 11 Distance geo + cti_diff + hli_diff + cd 89.91863 5 -169.8373 -159.8373
#> 2 ffp geo + cti_diff + hli_diff + cd 100.62203 7 -187.2441 -159.2441
#> 1 err27 geo + cti_diff + hli_diff + cd 98.51117 7 -183.0223 -155.0223
#> R2m R2c LL
#> 12 0.5961967 0.6527893 89.63878
#> 11 0.5988908 0.6564737 89.91863
#> 2 0.7003150 0.7169291 100.62203
#> 1 0.6926207 0.7458538 98.51117The single-surface summary is also helpful because it packages the model ranking with a concise narrative about the best-supported surface.
if (have_saved_outputs) {
summary(ss_out)
}
#> ResistanceGA2 single-surface optimization
#> models evaluated: 4
#> categorical surfaces: 0
#> continuous surfaces: 2
#> best surface: Null
#> best AICc: -165.563
#> run time (min): 9.3415
#>
#> Surface Fixed.Effects obj.func_LL k AIC AICc
#> Null geo + cti_diff + hli_diff 89.63878 4 -171.2776 -165.5633
#> Distance geo + cti_diff + hli_diff + cd 89.91863 5 -169.8373 -159.8373
#> ffp geo + cti_diff + hli_diff + cd 100.62203 7 -187.2441 -159.2441
#> err27 geo + cti_diff + hli_diff + cd 98.51117 7 -183.0223 -155.0223
#> R2m R2c LL
#> 0.5961967 0.6527893 89.63878
#> 0.5988908 0.6564737 89.91863
#> 0.7003150 0.7169291 100.62203
#> 0.6926207 0.7458538 98.51117
#>
#> Fixed effects for the top model
#> term Estimate Std. Error t value
#> (Intercept) 0.399096675 0.022359888 17.8487782
#> geo 0.072585107 0.008509314 8.5300775
#> cti_diff -0.003117198 0.010086539 -0.3090454
#> hli_diff -0.006411196 0.009951220 -0.6442623And the corresponding plot provides a quick visual ranking of the candidate surfaces:
The exact top-ranked surface can vary across systems, and with
shallow search settings it can also vary across exploratory runs if no
seed is set. The key interpretive question is whether at least one
candidate resistance surface outranks the pure Distance
model while IBD + IBE are already in the fixed effects. If
it does, the sampled data support an additional conditional association
with path-based landscape distance.
If the single-surface results suggest complementary hypotheses, a
multisurface fit lets us ask whether a composite IBR term
improves support further.
GA_inputs_ms <- GA.prep(
raster = ibr_layers,
Results.dir = file.path(tempdir(), "rga2-ibe-ibr-multi"),
method = "LL",
seed = 123,
pop.size = 25,
maxiter = 15,
run = 5,
parallel = FALSE,
keepBest = FALSE,
monitor = FALSE,
quiet = TRUE
)
ms_out <- MS_optim(
gdist.inputs = gdist_inputs,
GA.inputs = GA_inputs_ms,
diagnostic_plots = FALSE
)
ms_out$AICc.tab
ms_out$percent.contribution
summary(ms_out)Multisurface optimization is especially helpful when two rasters are plausible for different ecological reasons. It lets us ask whether the best explanation is truly composite or whether one layer dominates once they are optimized together.
if (have_saved_outputs) {
summary(ms_out)
}
#> ResistanceGA2 multisurface optimization
#> best surface: err27.ffp
#>
#> Best-fit table
#> Surface Fixed.Effects obj.func_LL k AIC AICc
#> err27.ffp geo + cti_diff + hli_diff + cd 98.3008 10 -176.6016 43.39841
#> R2m R2c LL
#> 0.6934124 0.7489791 98.3008
#>
#> Percent contribution
#> surface mean
#> err27 0.98589649
#> ffp 0.01410351
#>
#> Fixed effects
#> term Estimate Std. Error t value
#> (Intercept) 0.3875158596 0.023269855 16.65312747
#> geo -0.1165464117 0.044079401 -2.64401080
#> cti_diff 0.0001726458 0.009223669 0.01871769
#> hli_diff 0.0009039138 0.009675719 0.09342084
#> cd 0.1910451025 0.043676807 4.37406289The percent-contribution table is often more informative than the raw multisurface fit by itself. It tells us whether two plausible resistance hypotheses are both contributing meaningfully or whether one layer dominates once they are optimized together. That is scientifically useful because it shows that not every plausible raster necessarily adds unique explanatory power in the joint model.
The intended workflow is to run a separate builder script once, then let this vignette pull those saved outputs back in for a fully populated worked example.
The package includes a builder script that runs the baseline MLPE
models plus single- and multi-surface optimization and saves the results
as .rds and .csv files beneath
inst/extdata/ibe-ibr-demo/ by default so the completed
worked example can ship with the package.
Run from the package root:
From an RStudio session, you can specify the parameters directly:
source("data-raw/build_ibe_ibr_vignette_outputs.R")
build_ibe_ibr_demo_outputs(
profile = "default",
cores = NULL,
seed = 123
)For a quick smoke test:
source("data-raw/build_ibe_ibr_vignette_outputs.R")
build_ibe_ibr_demo_outputs(
profile = "fast",
cores = NULL,
seed = 123
)For a deeper run:
source("data-raw/build_ibe_ibr_vignette_outputs.R")
build_ibe_ibr_demo_outputs(
profile = "full",
cores = NULL,
seed = 123
)The function arguments are:
profile = "fast" for quick smoke testsprofile = "default" for a moderate reproducible
exampleprofile = "full" for a deeper optimization runcores as an optional positive integer passed to
GA.prep(parallel = ...)seed for reproducible GA initializationThe cores argument is now passed directly through to
GA.prep(parallel = ...), including on Windows. For large
optimization runs, this is the main knob for scaling the workflow across
available hardware.
The command-line interface still works if you prefer
Rscript, but the function-based RStudio workflow is the
recommended path for interactive use.
The script writes:
pair_data.rdsworkflow_covariates.rdsbaseline_models.rdsbaseline_model_table.csvbaseline_fixed_effects.csvsingle_surface/Results/ with optimizer summaries,
rasters, and distance matricessingle_surface_optim.rdssingle_surface_aicc.csvmulti_surface/Results/ with multisurface summaries and
percent contributionmulti_surface_optim.rdsmulti_surface_aicc.csvanalysis_summary.rdsoutput_dir <- system.file("extdata", "ibe-ibr-demo", package = "ResistanceGA2")
baseline_models <- readRDS(file.path(output_dir, "baseline_models.rds"))
ss_out <- readRDS(file.path(output_dir, "single_surface_optim.rds"))
ms_out <- readRDS(file.path(output_dir, "multi_surface_optim.rds"))
summary_bundle <- readRDS(file.path(output_dir, "analysis_summary.rds"))
summary_bundle$baseline_table
summary_bundle$ss_aicc
summary_bundle$ms_aicc
plot(ss_out, type = "ranking")
plot(ms_out, type = "contribution")This framework lets us separate three related questions:
Possible outcomes are:
IBE improves fit but IBR does not:
endpoint mismatch matters more than permeability.IBR improves fit after conditioning on
IBE: there is support for a genuine landscape-connectivity
effect.The example here also shows why it is useful to explore several model classes instead of going straight to optimization:
IBE is promising
enough to include at allIBR hypotheses improve fit beyond
IBD + IBEThat sequence turns the vignette into more than a software tutorial. It becomes an analysis template for asking:
The same workflow can be extended by:
cti and hli with other
site-level environmental variablesenv_dist term instead of
several univariate mismatch termsgdistance to jl.prep() if
Circuitscape-based pair filtering or current maps are requiredlibrary(ResistanceGA2)
library(terra)
# 1. Build pairwise geographic and environmental covariates.
pts <- terra::vect(samples[, 2:3], type = "points")
pair_data <- mlpe_data(
response = lower(as.matrix(dist(samples[, 2:3]))),
distance = site_pairwise_dist(terra::crds(pts), name = "distance")$distance
)
site_env <- data.frame(
hli = sample_pops[[1]][, 1],
cti = sample_pops[[1]][, 2]
)
env_diff <- site_pairwise_diff(site_env)
pair_data <- cbind(pair_data, env_diff)
# 2. Fit baseline MLPE models before optimizing resistance.
ibd_mod <- mlpe(
response ~ distance + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
ibe_mod <- mlpe(
response ~ distance + hli_diff + cti_diff + (1 | pair),
data = pair_data,
pairs = c("from", "to")
)
# 3. Prepare a conditional resistance optimization.
gdist_inputs <- gdist.prep(
n.Pops = nrow(samples),
samples = pts,
response = pair_data$response,
covariates = pair_data[, c("distance", "hli_diff", "cti_diff")],
formula = response ~ distance + hli_diff + cti_diff,
method = "commuteDistance"
)
# 4. Optimize resistance while holding IBD and IBE terms in the model.
ss_out <- SS_optim(
GA.inputs = GA.prep(
raster = {
x <- resistance_surfaces
if (inherits(x, "PackedSpatRaster")) x <- terra::unwrap(x)
x
},
Results.dir = file.path(tempdir(), "ibe-ibr-results")
),
gdist.inputs = gdist_inputs
)How to read the output: The baseline models test
whether geographic distance and endpoint environmental mismatch explain
pairwise genetic structure before resistance is optimized. The
conditional SS_optim() call asks whether the resistance
distance term cd improves fit after those baseline terms
are already included. A stable, positive cd coefficient and
improved AICc support a conditional association between landscape
permeability and the sampled genetic distances beyond geographic and
environmental mismatch. They do not, by themselves, establish a causal
landscape effect or out-of-sample prediction.
| Function | Purpose |
|---|---|
mlpe_data() |
Build the pairwise data frame and shared-site metadata used by MLPE models |
site_pairwise_diff() |
Convert site-level environmental covariates into absolute pairwise differences |
site_pairwise_dist() |
Convert site-level covariates into multivariate pairwise environmental distances |
mlpe() |
Fit baseline MLPE models before resistance optimization |
gdist.prep() |
Prepare conditional resistance-distance inputs for optimization |
SS_optim() |
Optimize resistance surfaces while conditioning on IBD or IBE covariates |
MS_optim() |
Optimize multisurface resistance models under the same conditional framework |
vignette("GettingStarted", package = "ResistanceGA2"),
the core data and optimization workflow.vignette("ResistanceGA", package = "ResistanceGA2"),
full optimization examples and model comparison.vignette("Julia_Guide", package = "ResistanceGA2"),
using Circuitscape when current maps or Circuitscape-matched distances
are required.