| Title: | Estimates Ecological Niche Models Using Ellipses |
|---|---|
| Description: | Ecological niche model estimation using ellipsoidal geometry under an M hypothesis. Fits nine likelihood families (presence-only, weighted, inverse-probability-weighted, skew-normal, skew-normal-weighted, skew-t, skew-t-weighted, ncst, and ncst-weighted) via multi-start optimisation with Sobol sequences. Methods for the optimisation of ellipses parameters are as described in Jimenez et al. (2022) <doi:10.1016/j.ecolmodel.2021.109823>. |
| Authors: | Angel Robles [aut, cre], Laura Jimenez [aut, ctb] (ORCID: <https://orcid.org/0000-0002-4674-4270>) |
| Maintainer: | Angel Robles <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 1.0.0 |
| Built: | 2026-09-16 12:39:50 UTC |
| Source: | https://github.com/alrobles/xnicher |
Computes , where is the un-penalised
log-likelihood (see logLik.xnicher) and is the
number of free parameters.
## S3 method for class 'xnicher' AIC(object, ..., k = 2)## S3 method for class 'xnicher' AIC(object, ..., k = 2)
object |
a fitted model object for which there exists a
|
... |
Additional fitted model objects. |
k |
Numeric, the penalty per parameter; default |
Numeric scalar, or a data.frame when multiple objects
are passed.
fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") AIC(fit)fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") AIC(fit)
Generic function for evaluating whether an optimization result meets acceptance criteria for niche model quality.
assess(x, ...)assess(x, ...)
x |
An object for which an |
... |
Additional arguments passed to methods. |
A list of diagnostics and acceptance flags. See
assess.xnicher for details.
Evaluates the quality of a multi-start optimization by comparing converged solutions. Returns one of four diagnostic flags:
"accepted_global"Multiple starts agree closely on the same high log-likelihood value — the global optimum is likely found.
"accepted_noise"Converged solutions are close but show minor numerical noise — likely a good solution.
"suggest_average"Converged solutions spread across
distinct values — consider averaging theta or increasing
num_starts.
"needs_more_starts"Too few converged solutions to draw
conclusions — increase num_starts.
## S3 method for class 'xnicher' assess(x, tol_gap = 0.01, tol_dist = 0.05, min_converged = 2L, ...)## S3 method for class 'xnicher' assess(x, tol_gap = 0.01, tol_dist = 0.05, min_converged = 2L, ...)
x |
A |
tol_gap |
Numeric. Relative tolerance used to decide between
|
tol_dist |
Numeric. Relative tolerance used to decide between
|
min_converged |
Integer. Minimum number of converged solutions
required to avoid |
... |
Ignored. |
A named list with:
flagCharacter scalar, one of the four flags above.
recommendationHuman-readable action recommendation.
gapAbsolute log-likelihood gap between the best and second-best converged solutions.
rel_gapRelative gap (gap / |best_loglik|).
n_convergedNumber of converged solutions.
best_loglikBest log-likelihood value.
result <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "ip_weighted" ) diag <- assess(result) cat("Flag :", diag$flag, "\n") cat("Recommendation:", diag$recommendation, "\n") cat("Best log-lik :", round(diag$best_loglik, 4L), "\n")result <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "ip_weighted" ) diag <- assess(result) cat("Flag :", diag$flag, "\n") cat("Recommendation:", diag$recommendation, "\n") cat("Best log-lik :", round(diag$best_loglik, 4L), "\n")
Computes , where is
nobs(object). Calls BIC.
## S3 method for class 'xnicher' BIC(object, ...)## S3 method for class 'xnicher' BIC(object, ...)
object |
a fitted model object for which there exists a
|
... |
Additional fitted model objects. |
Numeric scalar, or a data.frame when multiple objects
are passed.
fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") BIC(fit)fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") BIC(fit)
Builds a one-row-per-model summary table for a list of "xnicher"
fits, including log-likelihood, parameter count, AIC, BIC, deltas
relative to the best model, and Akaike weights.
compare_xnicher( ..., sort_by = c("AIC", "BIC", "loglik"), comparison_basis = c("unpenalised", "penalised") )compare_xnicher( ..., sort_by = c("AIC", "BIC", "loglik"), comparison_basis = c("unpenalised", "penalised") )
... |
Two or more |
sort_by |
Character. One of |
comparison_basis |
Character, one of
|
All fits MUST have been produced from the same env_occ; any
mismatch in nobs or in the variable-name ordering raises an
error, since IC values are not comparable across heterogeneous data.
A data.frame with one row per fit and columns:
modelName (or call index) of the fit.
likelihoodLikelihood family (x$likelihood).
loglikUn-penalised log-likelihood at convergence.
dfNumber of free parameters.
nobsSample size (occurrences).
AIC, BIC
Information criteria.
dAIC, dBIC
Deltas vs. the minimum.
weight_AICAkaike weights, .
convergenceucminfcpp convergence code.
AIC and BIC compare *likelihoods* on the SAME observed data. When
comparing weighted vs. presence-only families, both families evaluate
on occurrences; the weighted family
additionally normalises by an env_m integral, so its
likelihood is on a different scale. In this implementation,
logLik(weighted) returns the full weighted-likelihood value
(occurrence sum minus log-denominator), which is the right thing
for **within-family** comparison (e.g., weighted vs.
skew_normal_weighted) but should be interpreted with care
when contrasting weighted with presence-only.
fit_w <- optimize_niche(example_env_occ_2d, example_env_m_2d, num_starts = 5L, likelihood = "weighted") fit_skn <- optimize_niche(example_env_occ_2d, example_env_m_2d, num_starts = 5L, likelihood = "skew_normal_weighted") compare_xnicher(weighted = fit_w, skew_normal = fit_skn)fit_w <- optimize_niche(example_env_occ_2d, example_env_m_2d, num_starts = 5L, likelihood = "weighted") fit_skn <- optimize_niche(example_env_occ_2d, example_env_m_2d, num_starts = 5L, likelihood = "skew_normal_weighted") compare_xnicher(weighted = fit_w, skew_normal = fit_skn)
Refits the same likelihood family + same penalty knobs as the
supplied xnicher object on each train fold of env_occ,
warm-started from the full-data , then scores the
held-out fold by evaluating the un-penalised log-likelihood at the
fold's converged . Returns the **summed**
held-out log-likelihood across folds, which is the standard
out-of-sample log-likelihood used to tune ridge regularisation.
cv_xnicher( fit, env_occ, env_m = NULL, type = c("kfold", "loo"), k = 5L, seed = NULL, num_starts_cv = 1L, verbose = FALSE, ucminf_control = list(maxeval = 500L) )cv_xnicher( fit, env_occ, env_m = NULL, type = c("kfold", "loo"), k = 5L, seed = NULL, num_starts_cv = 1L, verbose = FALSE, ucminf_control = list(maxeval = 500L) )
fit |
A |
env_occ |
A |
env_m |
A |
type |
Character. |
k |
Integer. Number of folds when |
seed |
Integer or |
num_starts_cv |
Integer. Number of starts per fold. Default
|
verbose |
Logical. Print per-fold progress. |
ucminf_control |
Optional list passed to |
For penalised fits the un-penalised log-likelihood at the converged
(what logLik.xnicher returns) is monotone
in the penalty strengths – the in-sample loglik is always best at
. Cross-validation breaks that monotonicity by scoring
on data the optimiser did not see, so a properly tuned penalty
achieves higher out-of-sample loglik than the unpenalised MLE.
A list with class "xnicher_cv":
cv_loglikSummed held-out log-likelihood across folds.
cv_loglik_meanPer-occurrence average:
cv_loglik / nobs.
per_folddata.frame(fold, n_test, loglik_test,
convergence).
type"kfold" or "loo".
kNumber of folds (n_occ for LOO).
seedSeed used (or NA_integer_).
* Random k-fold CV assumes the occurrences are exchangeable. For
spatially autocorrelated data this can be optimistic; spatial-block
CV is left to a future release.
* LOO with n_occ large (e.g. Vicugna's 545) is feasible but
non-trivial: 545 ucminf refits at ~0.1-1 s each = ~1-10 minutes.
Consider type = "kfold", k = 10L as a faster proxy.
* Each fold's refit uses ucminfcpp::ucminf starting from
fit$best$theta. If the per-fold likelihood is not unimodal
(rare on dense data, common with skew families on sparse data),
bump num_starts_cv or use the original
optimize_niche() multistart machinery.
compare_xnicher for AIC/BIC-based comparisons.
fit_w <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, breadth = 0.45, likelihood = "weighted", prior_mu_lambda = 1.0 ) cv_xnicher(fit_w, example_env_occ_2d, example_env_m_2d, type = "kfold", k = 5L, seed = 42L)fit_w <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, breadth = 0.45, likelihood = "weighted", prior_mu_lambda = 1.0 ) cv_xnicher(fit_w, example_env_occ_2d, example_env_m_2d, type = "kfold", k = 5L, seed = 42L)
Constructs the lower‑triangular Cholesky factor of a
correlation matrix using the C‑vine method described in
Lewandowski, Kurowicka & Joe (2009). The factor satisfies
with .
cvine_cholesky(v, d, eta = 1)cvine_cholesky(v, d, eta = 1)
v |
Numeric vector of unconstrained reals, one for each C‑vine edge.
The length must be |
d |
Integer, dimension of the target correlation matrix ( |
eta |
Positive numeric shape parameter for the LKJ‑C‑vine prior
(default |
The algorithm proceeds in three steps:
Each element of v is mapped to the unit interval via the
logistic (sigmoid) function, then transformed to a partial correlation
on using the quantile function of a symmetric Beta
distribution with shape
(for level , 0‑indexed).
The table of partial correlations is converted to unconditional correlations using the Yule–Kendall recursion (vine recursion).
For each new row (starting from ), the algorithm
solves a triangular system to obtain the first entries of the
row, and sets the diagonal entry to maintain unit row norm.
The resulting can be used directly to construct the correlation
matrix (tcrossprod(L)), or to build a covariance matrix by scaling
with standard deviations.
A lower‑triangular matrix with
positive diagonal entries such that is a
valid correlation matrix (unit diagonal, positive definite). For ,
returns a matrix with entry 1.
Lewandowski, D., Kurowicka, D., & Joe, H. (2009). Generating random correlation matrices based on vines and extended onion method. *Journal of Multivariate Analysis*, 100(9), 1989–2001. doi:10.1016/j.jmva.2009.04.008
# For a 2x2 correlation matrix, we need one parameter v <- 0.5 L <- cvine_cholesky(v, d = 2, eta = 1) R <- tcrossprod(L) print(R) # For 3 dimensions, we need 3 parameters (d*(d-1)/2 = 3) v <- c(0.1, -0.2, 0.8) L <- cvine_cholesky(v, d = 3) R <- tcrossprod(L) all.equal(diag(R), rep(1, 3)) # Should be TRUE# For a 2x2 correlation matrix, we need one parameter v <- 0.5 L <- cvine_cholesky(v, d = 2, eta = 1) R <- tcrossprod(L) print(R) # For 3 dimensions, we need 3 parameters (d*(d-1)/2 = 3) v <- c(0.1, -0.2, 0.8) L <- cvine_cholesky(v, d = 3) R <- tcrossprod(L) all.equal(diag(R), rep(1, 3)) # Should be TRUE
Samples of environmental data from M hypothesis to estimate negative log likelihood from Abeillia abeillei presence points. This is a hummingbird example. A dataset with two variables containing points contains 2 bioclimatic variables
example_env_m_2dexample_env_m_2d
A data frame with 10000 rows and 2 variables:
temperature, in C
precipitation, in mm
<doi:10.1016/j.ecolmodel.2021.109823>
head(example_env_m_2d)head(example_env_m_2d)
Samples points from M hypothesis to estimate negative log likelihood from Abeillia abeillei presence points. This is a hummingbird example. A dataset with three columns containing extracted information from 3 bioclimatic variables
example_env_m_3dexample_env_m_3d
A data frame with 10000 rows and 3 variables:
Temperature, in C
Precipitation, in mm
Precipitation of Coldest Quarter, in mm
<doi:10.1016/j.ecolmodel.2021.109823>
head(example_env_m_3d)head(example_env_m_3d)
Species occurrence points to estimate negative log likelihood from Abeillia abeillei presence points. This is a hummingbird example. A dataset with two variables containing points contains 2 bioclimatic variables
example_env_occ_2dexample_env_occ_2d
A data frame with 73 rows and 2 variables:
temperature, in C
precipitation, in mm
doi:10.1016/j.ecolmodel.2021.109823
head(example_env_occ_2d)head(example_env_occ_2d)
Species occurrence points to estimate negative log likelihood from Abeillia abeillei presence points. This is a hummingbird example. A dataset with three variables containing points contains 3 bioclimatic variables
example_env_occ_3dexample_env_occ_3d
A data frame with 73 rows and 3 variables:
temperature, in C
precipitation, in mm
Precipitation of Coldest Quarter, in mm
<doi:10.1016/j.ecolmodel.2021.109823>
head(example_env_occ_3d)head(example_env_occ_3d)
Example of a vector of the center of an ellipsoid from two environmental variables. vector of length 2 corresponding to the centroid of an ellipsoid
example_mu_vecexample_mu_vec
An object of class numeric of length 2.
<doi:10.1016/j.ecolmodel.2021.109823>
print(example_mu_vec)print(example_mu_vec)
Species occurrence points from Abeillia abeillei presence points after download and clean from GBIF. This is a hummingbird example. A dataset with three variables. Contains scientific name, longitude and latitude.
example_occ_dfexample_occ_df
A data frame with 73 rows and 3 variables:
Species name
Decimal longitude geographical coordinate, in degrees
Decimal latitude geographical coordinate, in degrees
<doi:10.1016/j.ecolmodel.2021.109823>
head(example_occ_df)head(example_occ_df)
Example of a covariance matrix of an ellipsoid from two environmental variables. The 2 x 2 matrix corresponded to a positive semi-definite matrix. In two dimensions encodes the rotation (orientation) and scaling of an ellipse.
example_s_matexample_s_mat
An object of class matrix (inherits from array) with 2 rows and 2 columns.
<doi:10.1016/j.ecolmodel.2021.109823>
print(example_s_mat)print(example_s_mat)
Samples of environmental data from M hypothesis to estimate negative log likelihood from Vicugna vicugna.
example_vicugnaexample_vicugna
A list with 4 elements:
Data frame with two columns and 10 000 rows each representing bio1 and bio12 of the accessibility area of the species
Data frame with two columns and 545 rows each representing environmental information bio1 and bio12 associated with the occurrence the species
Data frame with geographical coordinates of the accesitibility area (M).
Data frame with geographical coordinates of the occurrence points.
head(example_vicugna$env_occ)head(example_vicugna$env_occ)
A self-contained ggplot2 layer that draws the environmental
background env_m as a faint point cloud in 2-D environmental
space. Designed to be composed with geom_xnicher_occ
and geom_xnicher_ellipse via the + operator.
geom_xnicher_background( env_m, var_names = NULL, size = 0.3, alpha = 0.25, colour = "grey60", ... )geom_xnicher_background( env_m, var_names = NULL, size = 0.3, alpha = 0.25, colour = "grey60", ... )
env_m |
Matrix or data frame with at least two columns. Only the first two columns are used (positional indexing). |
var_names |
Optional character vector of length 2 used to name the
columns of the layer's internal data frame. Defaults to
|
size, alpha, colour
|
Aesthetic parameters; defaults are tuned for a discreet background layer. |
... |
Additional fixed parameters passed to the underlying
|
A ggplot2 layer.
library(ggplot2) ggplot() + geom_xnicher_background(example_env_m_2d)library(ggplot2) ggplot() + geom_xnicher_background(example_env_m_2d)
xnicher modelBuilds a self-contained ggplot2 layer that traces one or more
iso-suitability ellipses implied by a fitted 2-D xnicher model.
The ellipse for level is the contour
, equivalently
when
level_type = "suitability", or the chi-squared confidence ellipse
when level_type = "chisq".
geom_xnicher_ellipse( model, level = c(0.95, 0.5, 0.05), level_type = c("suitability", "chisq"), n = 200L, linewidth = 0.6, colour = "firebrick", ... )geom_xnicher_ellipse( model, level = c(0.95, 0.5, 0.05), level_type = c("suitability", "chisq"), n = 200L, linewidth = 0.6, colour = "firebrick", ... )
model |
A |
level |
Numeric vector of contour levels in |
level_type |
Either |
n |
Integer number of points around each ellipse. Default 200. |
linewidth |
Path linewidth (or |
colour |
Path colour. |
... |
Additional fixed parameters passed to |
For length(level) > 1 the layer carries one column level
grouping the closed ellipse paths so a single geom_path draws
them all. Map e.g. linetype = factor(level) downstream to
distinguish them visually.
A ggplot2 layer.
library(ggplot2) fit <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L ) ggplot() + geom_xnicher_background(example_env_m_2d) + geom_xnicher_occ(example_env_occ_2d) + geom_xnicher_ellipse(fit)library(ggplot2) fit <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L ) ggplot() + geom_xnicher_background(example_env_m_2d) + geom_xnicher_occ(example_env_occ_2d) + geom_xnicher_ellipse(fit)
xnicher modelBuilds a self-contained ggplot2 layer that draws iso-suitability
contours, i.e. level sets of the fitted niche
suitability function over a 2-D environmental grid. Unlike
geom_xnicher_ellipse, which traces analytical ellipses
and is therefore tied to the Gaussian (multivariate normal) niche
geometry, this layer evaluates the model's actual suitability
function on a grid and contours the result. It works correctly for
any likelihood family supported by optimize_niche(),
including the skew-normal families ("skew_normal" and
"skew_normal_weighted") where the iso-suitability sets are
not ellipses.
geom_xnicher_isosuitability( model, level = c(0.95, 0.5, 0.05), n = 121L, expand = 0.1, linewidth = 0.6, colour = "firebrick", ... )geom_xnicher_isosuitability( model, level = c(0.95, 0.5, 0.05), n = 121L, expand = 0.1, linewidth = 0.6, colour = "firebrick", ... )
model |
A |
level |
Numeric vector of contour levels in |
n |
Integer grid resolution per axis. Default 121 (i.e. a 121 x 121 grid). Higher gives smoother contours at proportionally higher cost. |
expand |
Numeric scalar in |
linewidth |
Path linewidth (or |
colour |
Path colour. |
... |
Additional fixed parameters passed to
|
A ggplot2 layer.
For a Gaussian niche, the suitability function
has elliptical level sets; the family of contours
traces nested concentric ellipses around .
For a skew-normal niche
the factor breaks the
ellipse symmetry: contours bunch on the side that
pulls suitability toward, and stretch on the opposite side. The
contour at level is still a closed curve enclosing
high-suitability environments, but it is no longer an ellipse and
has no closed-form parameterisation. The only honest visualisation
is to evaluate on a dense 2-D grid and contour the
result – which is exactly what this layer does.
Suitability is always normalised so that at the
modal centre: for the Gaussian families that is the centre
; for the skew-normal it is the location parameter
from the SN parameterisation
(Azzalini & Capitanio 1999), which is generally not the
global suitability maximum but is a well-defined reference point.
The contour level level = 0.5 therefore always means
"regions where the niche is at least 50% as suitable as the
reference centre".
Azzalini, A. & Capitanio, A. (1999). Statistical applications of the multivariate skew normal distribution. Journal of the Royal Statistical Society, Series B, 61(3), 579–602.
library(ggplot2) fit <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "skew_normal_weighted" ) ggplot() + geom_xnicher_background(example_env_m_2d) + geom_xnicher_occ(example_env_occ_2d) + geom_xnicher_isosuitability(fit)library(ggplot2) fit <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "skew_normal_weighted" ) ggplot() + geom_xnicher_background(example_env_m_2d) + geom_xnicher_occ(example_env_occ_2d) + geom_xnicher_isosuitability(fit)
A self-contained ggplot2 layer that draws an occurrence cloud
env_occ in 2-D environmental space.
geom_xnicher_occ( env_occ, var_names = NULL, size = 1.2, alpha = 1, colour = "black", shape = 16, ... )geom_xnicher_occ( env_occ, var_names = NULL, size = 1.2, alpha = 1, colour = "black", shape = 16, ... )
env_occ |
Matrix or data frame with at least two columns. Only the first two columns are used. |
var_names |
Optional character vector of length 2 used to name the columns of the layer's internal data frame. |
size, alpha, colour, shape
|
Aesthetic parameters. |
... |
Additional fixed parameters passed to |
A ggplot2 layer.
library(ggplot2) ggplot() + geom_xnicher_background(example_env_m_2d) + geom_xnicher_occ(example_env_occ_2d)library(ggplot2) ggplot() + geom_xnicher_background(example_env_m_2d) + geom_xnicher_occ(example_env_occ_2d)
Get ellipsoid parameters. A function to compute average and the inverse of covariance matrix from environmental data
get_ellipsoid_pars(env)get_ellipsoid_pars(env)
env |
A data frame containing environmental variables |
A list with computed average of environmental variables and the covariance matrix
get_ellipsoid_pars(example_env_occ_2d)get_ellipsoid_pars(example_env_occ_2d)
Evaluates the standardized multivariate-normal density of Jimenez et
al. (2022, Eq. 2) at every cell of an environmental
SpatRaster, returning a one-layer raster of
suitability values in (or, optionally, log-suitability
in ).
habitat_suitability( param, env, output = "", overwrite = FALSE, return_log = FALSE, threads = RcppParallel::defaultNumThreads(), wopt = list() )habitat_suitability( param, env, output = "", overwrite = FALSE, return_log = FALSE, threads = RcppParallel::defaultNumThreads(), wopt = list() )
param |
Named list with components
The Cholesky factor of |
env |
A multi-layer |
output |
Character. File path for the output GeoTIFF. The empty
string |
overwrite |
Logical. If |
return_log |
Logical. If |
threads |
Integer. Number of parallel threads handed to the
inner C++ kernel. Default
|
wopt |
List. Additional write options forwarded to
|
Memory is bounded by the largest block selected by terra's memory
manager: continental-scale rasters are processed without ever
materialising the full grid in R. The per-cell computation is run by
niche_suitability_cpp, an RcppParallel kernel
that takes raw pointers into terra's column-major buffer.
The function follows the streaming I/O pattern from xsdm-devel's recipe 03:
terra::readStart on env, paired with an
on.exit(terra::readStop);
terra::writeStart on the output, paired with an
on.exit(terra::writeStop);
for each block i: read each variable's tile via
terra::readValues(..., mat = TRUE), pack into a flat
column-major numeric vector (n_tile x p), mask
NA cells, compact, call
niche_suitability_cpp, scatter the result back, and
terra::writeValues.
A one-layer SpatRaster named "suitability" (or
"log_suitability" when return_log = TRUE). Returned
invisibly when output is non-empty.
niche_suitability_cpp,
predict.xnicher.
## Hand-built (mu, Sigma): r1 <- terra::rast(nrows = 10, ncols = 10) r2 <- terra::rast(nrows = 10, ncols = 10) terra::values(r1) <- rnorm(100) terra::values(r2) <- rnorm(100) ex <- c(r1, r2) names(ex) <- c("bio1", "bio12") habitat_suitability( param = list(mu = c(0, 0), Sigma = diag(2)), env = ex )## Hand-built (mu, Sigma): r1 <- terra::rast(nrows = 10, ncols = 10) r2 <- terra::rast(nrows = 10, ncols = 10) terra::values(r1) <- rnorm(100) terra::values(r2) <- rnorm(100) ex <- c(r1, r2) names(ex) <- c("bio1", "bio12") habitat_suitability( param = list(mu = c(0, 0), Sigma = diag(2)), env = ex )
Computes a multivariate Gaussian kernel density estimate at the rows of 'x', using 'data' as the reference sample. Bandwidths are set internally via Scott's rule-of-thumb (diagonal). For 2D, a manually optimized version is used; for higher dimensions, an Eigen-based vectorized version is used.
kde_gaussian(x, data)kde_gaussian(x, data)
x |
Numeric matrix 'n_eval x p' (evaluation points). |
data |
Numeric matrix 'n_data x p' (reference sample for KDE). |
Numeric vector of length 'n_eval' with the density estimates.
x <- as.matrix(example_env_occ_2d) data <- as.matrix(example_env_m_2d) dens <- kde_gaussian(x, data) head(dens)x <- as.matrix(example_env_occ_2d) data <- as.matrix(example_env_m_2d) dens <- kde_gaussian(x, data) head(dens)
Negative log likelihood of an ellipsoid corrected with environmental combinations which come from the area of study (M)
loglik_niche(mu, s_mat, env_occ, env_m, neg = TRUE)loglik_niche(mu, s_mat, env_occ, env_m, neg = TRUE)
mu |
A vector mu of parameters |
s_mat |
The covariance matrix from environmental data frame |
env_occ |
A data.frame containing the original sample of environmental combinations that correspond to presences |
env_m |
A data.frame containing a second random sample of environmental |
neg |
Logical. Default TRUE, returns the negative of the likelihood |
A negative log likelihood value
loglik_niche( mu = example_mu_vec, s_mat = example_s_mat, env_occ = example_env_occ_2d, env_m = example_env_m_2d ) # Example with log of the likelihood loglik_niche( mu = example_mu_vec, s_mat = example_s_mat, env_occ = example_env_occ_2d, env_m = example_env_m_2d, neg = FALSE )loglik_niche( mu = example_mu_vec, s_mat = example_s_mat, env_occ = example_env_occ_2d, env_m = example_env_m_2d ) # Example with log of the likelihood loglik_niche( mu = example_mu_vec, s_mat = example_s_mat, env_occ = example_env_occ_2d, env_m = example_env_m_2d, neg = FALSE )
Computes the negative log-likelihood of the multivariate normal niche model
restricted to the set of existing environments
(Jimenez et al. 2019, Eq. 3–5). The density at an occurrence point is
normalised by the sum of the density over all background points in M:
loglik_niche_math_cpp(theta, env_occ, env_m, eta = 1, neg = TRUE, ...)loglik_niche_math_cpp(theta, env_occ, env_m, eta = 1, neg = TRUE, ...)
theta |
Numeric vector of unconstrained parameters (math scale):
|
env_occ |
Data frame or matrix ( |
env_m |
Data frame or matrix ( |
eta |
Numeric scalar, shape parameter for the LKJ C-vine prior on the correlation matrix (default 1 = uniform over correlations). |
neg |
Logical. If |
... |
Additional arguments (ignored; for compatibility). |
The factor cancels between numerator and denominator
(Eq. 3), so it does not appear in the objective. The logsumexp is computed
with the max-shift trick for numerical stability.
A scalar numeric value: the (negative) log-likelihood.
Jimenez, L., Soberon, J., Christen, J. A., & Soto, D. (2019). On the problem of modeling a fundamental niche from occurrence data. Ecological Modelling, 397, 109823.
[optimize_niche()] for multi-start fitting, [loglik_niche_math_presence_only()] for the unconstrained (no M) version.
theta <- start_theta(example_env_occ_2d) ll <- loglik_niche_math_cpp(theta, env_occ = example_env_occ_2d, env_m = example_env_m_2d, eta = 1, neg = TRUE ) print(ll)theta <- start_theta(example_env_occ_2d) ll <- loglik_niche_math_cpp(theta, env_occ = example_env_occ_2d, env_m = example_env_m_2d, eta = 1, neg = TRUE ) print(ll)
Computes the negative log-likelihood of the inverse-probability-weighted
(IPW) normal niche model. This model uses (the
inverse of the background KDE) as importance-sampling weights, applying a
Horvitz–Thompson correction so that the denominator approximates the
Lebesgue integral rather than the
-weighted integral :
loglik_niche_math_ip_weighted( theta, env_occ, env_m, eta = 1, neg = TRUE, m_subsample = NULL, m_kde_subsample = NULL, seed = NULL, den_idx = NULL, kde_idx = NULL, precomp_w_den = NULL, ... )loglik_niche_math_ip_weighted( theta, env_occ, env_m, eta = 1, neg = TRUE, m_subsample = NULL, m_kde_subsample = NULL, seed = NULL, den_idx = NULL, kde_idx = NULL, precomp_w_den = NULL, ... )
theta |
Numeric vector of unconstrained parameters (math scale):
|
env_occ |
Data frame or matrix ( |
env_m |
Data frame or matrix ( |
eta |
Numeric scalar, shape parameter for the LKJ C-vine prior on the correlation matrix (default 1 = uniform over correlations). |
neg |
Logical. If |
m_subsample |
Optional integer or fraction. If |
m_kde_subsample |
Optional integer or fraction. If |
seed |
Optional integer seed for reproducible subsampling. |
den_idx |
Optional integer vector of 1-based row indices for the denominator subset. If provided, no new denominator indices are generated. |
kde_idx |
Optional integer vector of 1-based row indices for the KDE reference subset. If provided, no new KDE indices are generated. |
precomp_w_den |
Optional numeric vector of precomputed denominator KDE weights.
Must match the size of |
... |
Additional arguments (ignored; provided for compatibility). |
where is the Mahalanobis distance at occurrence point
, and the sum over runs over background
points .
A scalar numeric value containing the (negative) log-likelihood.
The IPW model is designed for the data-generating process (DGP) of
Jimenez et al. (2019), which assumes that presence records are drawn
from the fundamental niche density
restricted to the environments that actually exist in the accessible
area :
The computational challenge is approximating the denominator
. The background sample
is drawn from geographic grid cells whose
density in environmental space is . A naive sum
therefore estimates
, not .
Dividing each term by corrects the
sampling bias via importance sampling:
This is the classical Horvitz–Thompson estimator (Horvitz & Thompson
1952) applied to niche modelling. The resulting likelihood estimates
the fundamental niche on uniform environmental space (Lebesgue measure),
removing the distortion that arises when common environments in
dominate the denominator.
"weighted" modelThe "weighted" model (Jimenez & Soberon 2022, Eq. 5/8) uses
, which is the correct likelihood under an alternative
DGP where presence intensity is proportional to (the
use-availability or resource-selection function model; Warton &
Shepherd 2010). The two models answer different ecological questions:
| Model | DGP assumption | Estimates |
"weighted" |
|
Niche under M-biased observation process |
"ip_weighted" |
restricted to |
Fundamental niche on uniform E-space |
Neither model is universally superior. "weighted" is appropriate
when observation probability correlates with environmental density (e.g.
opportunistic citizen-science records). "ip_weighted" is
appropriate when sampling effort is approximately uniform across
and the goal is to recover the species' intrinsic tolerances independent
of which environments happen to be common.
Importance-sampling variance.
When (e.g. between
disconnected environmental patches in a multimodal ),
the weights can be very large and a handful of
background points may dominate the denominator. The effective
sample size (ESS) should be monitored; low ESS indicates
unreliable estimates.
KDE bandwidth dependence.
is computed once using Scott's rule bandwidth.
Over- or under-smoothing can distort the weights. An adaptive
or cross-validated bandwidth would improve robustness.
Self-regularisation.
The IPW denominator diverges when
because rare-environment cells contribute .
This acts as a built-in penalty against unrealistically broad
niches (no explicit ridge prior is needed), but the effective
constraint depends on the tail behaviour of and
can be noisy.
Accepts explicit subsampling indices and precomputed KDE weights for
high-performance workflows. Internally delegates to
loglik_niche_math_ip_weighted_integrated.
This function was formerly called
loglik_niche_math_kde_bias_corrected.
Jimenez, L., Soberon, J., Christen, J. A., & Soto, D. (2019). On the problem of modeling a fundamental niche from occurrence data. Ecological Modelling, 397, 109823.
Jimenez, L., & Soberon, J. (2022). Weighted-normal model for the fundamental niche. Ecological Modelling, 438, 109982.
Horvitz, D. G., & Thompson, D. J. (1952). A generalization of sampling without replacement from a finite universe. J. Amer. Statist. Assoc., 47(260), 663–685.
Warton, D. I., & Shepherd, L. C. (2010). Poisson point process models solve the “pseudo-absence problem” for presence-only data in ecology. Ann. Appl. Stat., 4(3), 1383–1402.
[optimize_niche()] for multi-start fitting, [loglik_niche_math_cpp()] for the M-restricted Gaussian model.
den_idx <- sample.int(nrow(example_env_m_2d), 2000) kde_idx <- sample.int(nrow(example_env_m_2d), 5000) pre_w <- kde_gaussian( example_env_m_2d[den_idx, ], example_env_m_2d[kde_idx, ] ) loglik_niche_math_ip_weighted( theta = start_theta(example_env_occ_2d), env_occ = example_env_occ_2d, env_m = example_env_m_2d, den_idx = den_idx, kde_idx = kde_idx, precomp_w_den = pre_w )den_idx <- sample.int(nrow(example_env_m_2d), 2000) kde_idx <- sample.int(nrow(example_env_m_2d), 5000) pre_w <- kde_gaussian( example_env_m_2d[den_idx, ], example_env_m_2d[kde_idx, ] ) loglik_niche_math_ip_weighted( theta = start_theta(example_env_occ_2d), env_occ = example_env_occ_2d, env_m = example_env_m_2d, den_idx = den_idx, kde_idx = kde_idx, precomp_w_den = pre_w )
Low-level C++ bridge for the inverse-probability-weighted (IPW) normal
niche model. Computes KDE weights and Mahalanobis distances entirely
in C++ to minimise R overhead. This is the workhorse called by
loglik_niche_math_ip_weighted.
loglik_niche_math_ip_weighted_integrated( theta, env_occ, env_m, eta = 1, neg = TRUE, den_idx = NULL, kde_idx = NULL, precomp_w_den = NULL )loglik_niche_math_ip_weighted_integrated( theta, env_occ, env_m, eta = 1, neg = TRUE, den_idx = NULL, kde_idx = NULL, precomp_w_den = NULL )
theta |
Numeric vector of unconstrained parameters (math scale):
|
env_occ |
Data frame or matrix ( |
env_m |
Data frame or matrix ( |
eta |
Numeric scalar, shape parameter for the LKJ C-vine prior on the correlation matrix (default 1 = uniform over correlations). |
neg |
Logical. If |
den_idx |
Integer vector of 1-based row indices for the denominator
subsample. If |
kde_idx |
Integer vector of 1-based row indices for the KDE reference
subsample. If |
precomp_w_den |
Numeric vector of precomputed KDE weights for the
denominator points (must match |
The objective function is described in detail in
loglik_niche_math_ip_weighted.
This function was formerly called
loglik_niche_math_kde_bias_corrected_integrated.
Scalar numeric: the (negative) log-likelihood.
[loglik_niche_math_ip_weighted()] for the user-facing wrapper with automatic subsampling.
theta <- start_theta(example_env_occ_2d) ll <- loglik_niche_math_ip_weighted_integrated( theta = theta, env_occ = example_env_occ_2d, env_m = example_env_m_2d, eta = 1, neg = TRUE ) print(ll)theta <- start_theta(example_env_occ_2d) ll <- loglik_niche_math_ip_weighted_integrated( theta = theta, env_occ = example_env_occ_2d, env_m = example_env_m_2d, eta = 1, neg = TRUE ) print(ll)
Computes the negative log-likelihood of a multivariate normal niche model using only presence records, with no background correction (Jimenez et al. 2019, Eq. 2 without the M-restricted denominator).
loglik_niche_math_presence_only(theta, env_occ, eta = 1, neg = TRUE, ...)loglik_niche_math_presence_only(theta, env_occ, eta = 1, neg = TRUE, ...)
theta |
Numeric vector of unconstrained parameters (math scale):
|
env_occ |
Data frame or matrix ( |
eta |
Numeric scalar, shape parameter for the LKJ C-vine prior on the correlation matrix (default 1 = uniform over correlations). |
neg |
Logical. If |
... |
Additional arguments (ignored; for compatibility). |
The minimised objective is:
The normalisation constant is dropped (does not affect the
optimum).
Internally, is reconstructed from theta
via cvine_cholesky, and the Mahalanobis distances are
computed via a triangular solve .
Scalar numeric: the (negative) log-likelihood.
Jimenez, L., Soberon, J., Christen, J. A., & Soto, D. (2019). On the problem of modeling a fundamental niche from occurrence data. Ecological Modelling, 397, 109823.
[optimize_niche()] for multi-start fitting, [loglik_niche_math_cpp()] for the M-restricted model.
theta <- start_theta(example_env_occ_2d) ll <- loglik_niche_math_presence_only(theta, example_env_occ_2d)theta <- start_theta(example_env_occ_2d) ll <- loglik_niche_math_presence_only(theta, example_env_occ_2d)
Returns the **un-penalised** log-likelihood of a "xnicher" fit at
the converged . This is the appropriate quantity for
likelihood-based model comparison via AIC or
BIC: even when optimize_niche minimises a
ridge-penalised objective ,
the comparison metric is the bare evaluated at
the optimised .
## S3 method for class 'xnicher' logLik(object, ...)## S3 method for class 'xnicher' logLik(object, ...)
object |
A |
... |
Ignored. |
For penalised fits this means the value returned is **not** the same
as x$best$loglik: best$loglik is the negative of the
penalised objective at the optimum (and is monotone in fit quality
but not directly comparable across penalty strengths), while
logLik(x) is the bare data log-likelihood. logLik(x)
is what AIC/BIC require.
Stored on x$best$loglik_unpenalised by
optimize_niche; older fits without that field are
detected and an informative error is raised.
An object of class "logLik" with attributes
df (number of free parameters) and nobs (number of
occurrence rows used in the fit).
df is reported naively as length(theta): 2p + p(p-1)/2
for the Gaussian families, 3p + p(p-1)/2 for the skew-normal families,
3p + p(p-1)/2 + 1 for the skew-t families. This **ignores** the
shrinkage induced by the ridge penalties (prior_mu_lambda,
prior_log_sigma_lambda, prior_alpha_lambda). For weak
penalties () the bias is small; for stronger
penalties the effective df is lower than length(theta) so the
penalty ( or ) is conservative — IC values
will under-favour the more-regularised model. Quantifying effective
df via the trace of the influence matrix is left to a future release.
fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") logLik(fit) AIC(fit) BIC(fit)fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") logLik(fit) AIC(fit) BIC(fit)
Fits the inverse-probability-weighted (IPW) normal niche model using a compiled C++ backend via an external pointer for fast evaluation.
niche_ip_weighted( occ, M, den_idx, kde_idx, precomp_w_den, eta = 1, start = NULL, ... )niche_ip_weighted( occ, M, den_idx, kde_idx, precomp_w_den, eta = 1, start = NULL, ... )
occ |
Numeric matrix ( |
M |
Numeric matrix ( |
den_idx |
Integer vector of 1-based row indices selecting the
denominator subset. Must have the same length as |
kde_idx |
Integer vector of 1-based row indices selecting the KDE reference subset. |
precomp_w_den |
Numeric vector of precomputed KDE weights matching
|
eta |
Numeric scalar, shape parameter for the LKJ C-vine prior on the correlation matrix (default 1 = uniform over correlations). |
start |
A numeric vector (single-start) or list of numeric vectors (multi-start). Use [start_theta()] or [start_theta_multiple()] to generate starting values. |
... |
Ignored. Present for wrapper compatibility. |
This model assumes the data-generating process of Jimenez et al. (2019):
presence records are drawn from the fundamental niche density
restricted to the environments available
in . Because background grid cells are not uniformly distributed
in environmental space (their density is ), a naive
sum over the background would estimate
rather than the required
. The IPW correction divides each background
term by (a kernel density estimate of the environmental
density of ), yielding a Horvitz–Thompson estimator
(Horvitz & Thompson 1952) of the Lebesgue integral.
The objective is the negative log-likelihood:
The "weighted" model (Jimenez & Soberon 2022, Eq. 5/8) uses
instead, which is the correct likelihood under the
alternative use-availability DGP where presence intensity is
proportional to . See
loglik_niche_math_ip_weighted for a detailed comparison
of both DGP assumptions and known limitations.
This function was formerly called niche_kde_bias_corrected.
KDE weights must be precomputed by the user and passed via
precomp_w_den. No KDE is recomputed inside the optimiser.
Supports both single-start (start is a numeric vector) and
multi-start (start is a list of numeric vectors) optimisation.
A list with components:
thetaBest parameter vector.
valueNegative log-likelihood at the optimum.
convConvergence code (0 = success).
all_resultsData frame of all starts (multi-start only).
Jimenez, L., Soberon, J., Christen, J. A., & Soto, D. (2019). On the problem of modeling a fundamental niche from occurrence data. Ecological Modelling, 397, 109823.
Jimenez, L., & Soberon, J. (2022). Weighted-normal model for the fundamental niche. Ecological Modelling, 438, 109982.
Horvitz, D. G., & Thompson, D. J. (1952). A generalization of sampling without replacement from a finite universe. J. Amer. Statist. Assoc., 47(260), 663–685.
[optimize_niche()] for the unified fitting interface, [loglik_niche_math_ip_weighted()] for the R-level log-likelihood (includes full DGP derivation and known limitations).
occ <- as.matrix(example_env_occ_3d) M <- as.matrix(example_env_m_3d) set.seed(1) den_idx <- sample.int(nrow(M), 300L) kde_idx <- sample.int(nrow(M), 600L) w_den <- kde_gaussian(M[den_idx, ], M[kde_idx, ]) theta0 <- start_theta(example_env_occ_3d) res <- niche_ip_weighted(occ, M, den_idx, kde_idx, w_den, start = theta0) res$valueocc <- as.matrix(example_env_occ_3d) M <- as.matrix(example_env_m_3d) set.seed(1) den_idx <- sample.int(nrow(M), 300L) kde_idx <- sample.int(nrow(M), 600L) w_den <- kde_gaussian(M[den_idx, ], M[kde_idx, ]) theta0 <- start_theta(example_env_occ_3d) res <- niche_ip_weighted(occ, M, den_idx, kde_idx, w_den, start = theta0) res$value
Fits a multivariate normal niche model using only presence records, with no background correction (Jimenez et al. 2019, Eq. 2). Uses the compiled C++ backend via an external pointer for fast evaluation.
niche_presence_only(occ, eta = 1, start = NULL, ...)niche_presence_only(occ, eta = 1, start = NULL, ...)
occ |
Numeric matrix ( |
eta |
Numeric scalar, shape parameter for the LKJ C-vine prior on the correlation matrix (default 1 = uniform over correlations). |
start |
A numeric vector (single-start) or list of numeric vectors (multi-start). Use [start_theta()] or [start_theta_multiple()] to generate starting values. |
... |
Ignored. Present for wrapper compatibility. |
The minimised objective is the negative log-likelihood of the unconstrained multivariate normal:
where .
Supports both single-start (start is a numeric vector) and
multi-start (start is a list of numeric vectors) optimisation.
A list with components:
thetaBest parameter vector.
valueNegative log-likelihood at the optimum.
convConvergence code (0 = success).
all_resultsData frame of all starts (multi-start only).
Jimenez, L., Soberon, J., Christen, J. A., & Soto, D. (2019). On the problem of modeling a fundamental niche from occurrence data. Ecological Modelling, 397, 109823.
[optimize_niche()] for the unified fitting interface, [loglik_niche_math_presence_only()] for the R-level log-likelihood.
occ <- as.matrix(example_env_occ_3d) theta0 <- start_theta(example_env_occ_3d) res <- niche_presence_only(occ = occ, start = theta0) res$valueocc <- as.matrix(example_env_occ_3d) theta0 <- start_theta(example_env_occ_3d) res <- niche_presence_only(occ = occ, start = theta0) res$value
Returns the number of presence rows (nrow(env_occ)) that were
passed to optimize_niche. This is the sample size used
by BIC and is the conventional choice for the
presence-only and weighted families: even though the weighted
likelihood includes a denominator over background points, the
observed quantities being modeled are the occurrences.
## S3 method for class 'xnicher' nobs(object, ...)## S3 method for class 'xnicher' nobs(object, ...)
object |
A |
... |
Ignored. |
Integer scalar.
fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") nobs(fit)fit <- optimize_niche(env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted") nobs(fit)
Runs multi-start optimization over a Sobol low-discrepancy sequence
(via pomp) of starting points covering the parameter space implied
by env_occ and breadth.
optimize_niche( env_occ, env_m, num_starts = 100L, breadth = 0.1, likelihood = c("weighted", "ip_weighted", "presence_only", "skew_normal", "skew_normal_weighted", "skew_t", "skew_t_weighted", "ncst", "ncst_weighted"), grad = c("auto", "analytic", "central", "forward"), m_subsample = NULL, m_kde_subsample = NULL, seed = NULL, warm_start = TRUE, prior_log_sigma_lambda = 1, prior_log_sigma_center = NULL, prior_mu_lambda = 1, prior_mu_center = NULL, prior_alpha_lambda = 0.1, control = list(), verbose = FALSE, ... )optimize_niche( env_occ, env_m, num_starts = 100L, breadth = 0.1, likelihood = c("weighted", "ip_weighted", "presence_only", "skew_normal", "skew_normal_weighted", "skew_t", "skew_t_weighted", "ncst", "ncst_weighted"), grad = c("auto", "analytic", "central", "forward"), m_subsample = NULL, m_kde_subsample = NULL, seed = NULL, warm_start = TRUE, prior_log_sigma_lambda = 1, prior_log_sigma_center = NULL, prior_mu_lambda = 1, prior_mu_center = NULL, prior_alpha_lambda = 0.1, control = list(), verbose = FALSE, ... )
env_occ |
Data frame of environmental values at presence points. |
env_m |
Data frame of background environmental values. Required for
|
num_starts |
Integer. Number of Sobol starting points. |
breadth |
Numeric in (0, 0.5). Controls the quantile range used to
define starting bounds for |
likelihood |
One of |
grad |
Gradient strategy: |
m_subsample, m_kde_subsample
|
Optional integer or fraction in (0, 1].
Resolved to |
seed |
Optional integer to make subsampling deterministic. |
warm_start |
Logical. When |
prior_log_sigma_lambda |
Numeric scalar ( |
prior_log_sigma_center |
Optional numeric vector of length
|
prior_mu_lambda |
Numeric scalar ( |
prior_mu_center |
Optional numeric vector of length
|
prior_alpha_lambda |
Numeric scalar ( |
control |
Named list of control parameters for
|
verbose |
Logical. If |
... |
Additional arguments forwarded to the objective function
(e.g. |
The supported likelihood models are:
"weighted" (default): paper-faithful weighted-normal model.
Implements Eq. 5 of Jiménez & Soberón (2022, Ecological
Modelling 438:109982) – pure ML estimation of a weighted normal
density on M – plus a weakly-informative ridge prior on
that prevents the well-known Patil & Ord
(1976) drift in the un-regularised
MLE. The ridge strength is controlled by
prior_log_sigma_lambda (default 1.0, weak); set
it to 0 for pure paper Eq. 5 (not recommended on
multimodal M).
"ip_weighted": inverse-probability-weighted
(IPW) normal model. Assumes the Jimenez et al. (2019) DGP
in which presences are drawn from restricted to
. Because background grid cells sample environmental
space with density , a naive denominator
sum estimates rather than
. The IPW correction uses
(the inverse of the background KDE) as
importance-sampling weights (Eq. 8), yielding a
Horvitz–Thompson estimator of the Lebesgue integral and
recovering the fundamental niche on uniform environmental
space. Empirically stable, with built-in
self-regularisation against drift
(no ridge prior required). The "weighted" model is
preferred when observation intensity correlates with
environmental density (e.g. opportunistic records);
"ip_weighted" is preferred when sampling effort is
approximately uniform across and the goal is to
estimate intrinsic tolerances. See
loglik_niche_math_ip_weighted for a detailed
comparison and known limitations. Formerly called
"kde_bias_corrected"; the old name is accepted as a
deprecated alias.
"presence_only": model using only presence points,
no background correction. Unimodal likelihood, useful as a
sanity check or to seed the weighted multistart (see
warm_start).
"skew_normal": presence-only fit of a multivariate
skew-normal niche
(Azzalini & Capitanio 1999, J. R. Stat. Soc. Ser. B 61(3):
579-602):
Adds a length-p skewness vector to the
existing parameters.
recovers the symmetric Gaussian niche exactly.
Sobol-start machinery samples in
(Azzalini & Capitanio 1999, Sec. 5).
"skew_normal_weighted": paper Eq. 5 with the SN
density in place of the Gaussian, plus the same ridge
prior on used by "weighted".
Defaults inherit from "weighted".
"skew_t": presence-only fit of the multivariate
non-central skew-t (NCST) density (Branco & Dey 2001, J.
Multivariate Analysis 79(1):99-113):
Adds a single degrees-of-freedom parameter log_r
on top of the skew-normal layout. Smaller = heavier
tails; recovers "skew_normal".
The marginal density has no closed form, so it is computed
by 32-node Gauss-Laguerre quadrature on the
mixing variable. Sobol-start machinery samples log_r
in .
"skew_t_weighted": paper Eq. 5 with the NCST
density in place of the Gaussian, plus the same ridge prior
on used by "weighted". Defaults
inherit from "weighted".
For multimodal or long-tailed env_m, consider z-scoring
env_occ and env_m so all variables have comparable
spread (e.g. z-score or quantile-rank); the ridge prior is then
uniform across axes.
An object of class "xnicher" (see new_xnicher).
optimize_niche() optimizes via ucminfcpp::ucminf_xptr() with
a compiled C++ objective built by create_niche_obj_ptr(). The full
(theta -> mu, log sigma, v) unpacking, cvine_cholesky,
log-likelihood, and gradient are evaluated in pure C++ with no R-callback
overhead. For the weighted likelihood, gradient mode "analytic" uses
closed-form derivatives over mu and log sigma and central
finite differences over the C-vine partial-correlation block.
The KDE that weights the M-background depends only on the environmental
data (not on theta), so the weights are computed once before
optimization. By default, the KDE reference (m_kde_subsample) and
the denominator subset (m_subsample) are both capped at
10\,000 distinct background combinations – the maximum the model can
usefully exploit even for very large rasters. A floor of
max(500, 50 * 2^p) rows is used as a heuristic minimum
representative sample (Silverman 1986; Wand & Jones 1995); below this
floor a warning() is emitted.
Jimenez, L., Soberon, J., Christen, J. A., & Soto, D. (2019). On the problem of modeling a fundamental niche from occurrence data. Ecological Modelling, 397, 109823.
Jimenez, L., & Soberon, J. (2022). Weighted-normal model for the fundamental niche. Ecological Modelling, 438, 109982.
Azzalini, A., & Capitanio, A. (1999). Statistical applications of the multivariate skew normal distribution. J. R. Stat. Soc. B, 61(3), 579–602.
Branco, M. D., & Dey, D. K. (2001). A general class of multivariate skew-elliptical distributions. J. Multivariate Anal., 79(1), 99–113.
[loglik_niche_math_cpp()], [loglik_niche_math_presence_only()], [loglik_niche_math_ip_weighted()].
result <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, breadth = 0.1, likelihood = "weighted" ) print(result) assess(result)result <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, breadth = 0.1, likelihood = "weighted" ) print(result) assess(result)
Runs ucminfcpp::ucminf_xptr() over a compiled C++ objective function created by create_niche_obj_ptr(). Supports single-start and multi-start optimization. Ensures that all starting vectors are numeric doubles and finite.
optimize_niche_xptr( start = NULL, xptr, control = ucminfcpp::ucminf_control(grad = "central", gradstep = c(1e-06, 1e-08), maxeval = 200), multi_start = FALSE )optimize_niche_xptr( start = NULL, xptr, control = ucminfcpp::ucminf_control(grad = "central", gradstep = c(1e-06, 1e-08), maxeval = 200), multi_start = FALSE )
start |
Numeric vector (single start) or list of numeric vectors (multi-start). |
xptr |
External pointer created by create_niche_obj_ptr(). |
control |
List of control parameters for ucminfcpp::ucminf_control(). |
multi_start |
Logical; TRUE if multiple starts are supplied. |
A list with:
par — best parameter vector
value — negative log-likelihood
conv — convergence code
all_results — (multi-start only) table of all runs
## optimize_niche() uses optimize_niche_xptr() internally; ## call it directly for full control over each optimization run: fit <- optimize_niche( env_occ = example_env_occ_3d, env_m = example_env_m_3d, num_starts = 3L, likelihood = "ip_weighted" ) fit$best$value## optimize_niche() uses optimize_niche_xptr() internally; ## call it directly for full control over each optimization run: fit <- optimize_niche( env_occ = example_env_occ_3d, env_m = example_env_m_3d, num_starts = 3L, likelihood = "ip_weighted" ) fit$best$value
xnicher objectpredict method for objects of class "xnicher" returned
by optimize_niche. Reconstructs the niche centroid
and covariance from the optimizer's
best$theta (mu, log_sigma, v) parameterization, then evaluates
the standardized Gaussian suitability map of Jimenez et al. (2022,
Eq. 2) over an environmental SpatRaster.
## S3 method for class 'xnicher' predict( object, env, ..., return_log = FALSE, threads = RcppParallel::defaultNumThreads(), output = "", overwrite = FALSE, wopt = list() )## S3 method for class 'xnicher' predict( object, env, ..., return_log = FALSE, threads = RcppParallel::defaultNumThreads(), output = "", overwrite = FALSE, wopt = list() )
object |
A |
env |
A multi-layer |
... |
Currently ignored. |
return_log |
Logical. |
threads |
Integer. Threads for the inner C++ kernel. Default
|
output |
Character. File path for the output GeoTIFF. The
empty string |
overwrite |
Logical. Forwarded to |
wopt |
List. Forwarded to |
The same Gaussian suitability surface is used for every fitted family:
"weighted", "ip_weighted", "presence_only",
and the skew-normal / skew-t variants all project the fitted
geometry. For skew fits, the skewness and tail
parameters affect fitting but are not used by this Gaussian projection.
Internally:
.
.
C-vine partial-correlation block
is mapped to a
correlation Cholesky factor via
cvine_cholesky.
,
.
A one-layer SpatRaster named "suitability"
(or "log_suitability"). Returned invisibly when
output is non-empty.
habitat_suitability,
niche_suitability_cpp,
cvine_cholesky.
fit <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted" ) r1 <- terra::rast(nrows = 10, ncols = 10) r2 <- terra::rast(nrows = 10, ncols = 10) terra::values(r1) <- rnorm(100) terra::values(r2) <- rnorm(100) env <- c(r1, r2) names(env) <- colnames(example_env_occ_2d) suit <- predict(fit, env)fit <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "weighted" ) r1 <- terra::rast(nrows = 10, ncols = 10) r2 <- terra::rast(nrows = 10, ncols = 10) terra::values(r1) <- rnorm(100) terra::values(r2) <- rnorm(100) env <- c(r1, r2) names(env) <- colnames(example_env_occ_2d) suit <- predict(fit, env)
Displays a concise summary of a "xnicher" optimization result,
including the likelihood type, number of starts, convergence count,
and the best log-likelihood achieved.
## S3 method for class 'xnicher' print(x, ...)## S3 method for class 'xnicher' print(x, ...)
x |
A |
... |
Ignored. |
Invisibly returns x.
result <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "ip_weighted" ) print(result)result <- optimize_niche( env_occ = example_env_occ_2d, env_m = example_env_m_2d, num_starts = 5L, likelihood = "ip_weighted" ) print(result)
Example dataset for *Rhipicephalus microplus* (cattle tick) presence points and a random background sample inside the accessible area (M).
rhipicephalusrhipicephalus
A list with four data frames:
Geographic coordinates (lon, lat) of presence points.
Environmental values of bio1 and rh at presence points.
Geographic coordinates (lon, lat) of background points in M.
Environmental values of bio1 and rh at background points.
Occurrence and background data prepared from the Garrapatas dataset; raster layers are annual mean temperature (bio1, in °C) and relative humidity (rh, in percent).
str(rhipicephalus) head(rhipicephalus$env_occ)str(rhipicephalus) head(rhipicephalus$env_occ)
Presence coordinates for *Rhipicephalus microplus*.
rhipicephalus_occrhipicephalus_occ
A data frame with 334 rows and 3 variables:
Species scientific name
Decimal longitude, in degrees
Decimal latitude, in degrees
Garrapatas dataset.
head(rhipicephalus_occ)head(rhipicephalus_occ)
Starting values for niche model on math scale
start_theta(env_occ, skew = FALSE, skew_t = FALSE)start_theta(env_occ, skew = FALSE, skew_t = FALSE)
env_occ |
Data frame with environmental values at presence points. |
skew |
Logical. If |
skew_t |
Logical. If |
Numeric vector of starting values for 'theta'.
start_theta(example_env_occ_2d) start_theta(example_env_occ_2d, skew = TRUE) start_theta(example_env_occ_2d, skew_t = TRUE)start_theta(example_env_occ_2d) start_theta(example_env_occ_2d, skew = TRUE) start_theta(example_env_occ_2d, skew_t = TRUE)
Creates a set of starting parameter vectors on the math scale for use with the log-likelihood functions. Ensures all starting values are strictly numeric and finite, even if env_data is supplied as a data frame.
start_theta_multiple( env_data, num_starts = 100, quant_vec = c(0.1, 0.5, 0.9), method = "sobol", skew = FALSE, skew_t = FALSE )start_theta_multiple( env_data, num_starts = 100, quant_vec = c(0.1, 0.5, 0.9), method = "sobol", skew = FALSE, skew_t = FALSE )
env_data |
Environmental data (matrix or data frame). Must be numeric. |
num_starts |
Integer, number of starting points. |
quant_vec |
Quantiles for mu ranges. |
method |
"sobol" (Sobol design) or "uniform". |
skew |
Logical. If |
skew_t |
Logical. If |
A data frame of dimension num_starts × num_parameters.
starts <- start_theta_multiple( env_data = example_env_occ_2d, num_starts = 5L, method = "uniform" ) dim(starts)starts <- start_theta_multiple( env_data = example_env_occ_2d, num_starts = 5L, method = "uniform" ) dim(starts)