R/project_selected.R

Defines functions project_selected

Documented in project_selected

#' Project selected models to multiple sets of new data (scenarios)
#'
#' @description
#' This function performs predictions of selected models on multiple scenarios,
#' as specified in a `projection_data` object created with the
#' [prepare_projection()] function. In addition to generating predictions
#' for each replicate, the function calculates consensus measures (e.g., mean,
#' median) across replicates and models.
#'
#' @usage
#' project_selected(models, projection_data, out_dir, mask = NULL,
#'                  consensus_per_model = TRUE, consensus_general = TRUE,
#'                  consensus = c("median", "range", "mean", "stdev"),
#'                  write_replicates = FALSE, extrapolation_type = "E",
#'                  var_to_restrict = NULL, type = NULL, overwrite = FALSE,
#'                  parallel = FALSE, ncores = NULL,
#'                  progress_bar = TRUE, verbose = TRUE)
#'
#' @param models an object of class `fitted_models` returned by the
#' [fit_selected()] function.
#' @param projection_data an object of class `projection_data` returned by the
#' [prepare_projection()] function. This file contains the paths to the
#' rasters representing each scenario.
#' @param out_dir (character) a path to a root directory for saving the raster
#' file of each projection.
#' @param mask (SpatRaster, SpatVector, or SpatExtent) spatial object used to
#'        mask the variables before predict. Default is NULL.
#' @param consensus_per_model (logical) whether to calculate consensus across
#' replicates when there are more than one replicate per model. Default is TRUE.
#' @param consensus_general (logical) whether to calculate consensus across
#' models when there are more than one selected model. Default is TRUE.
#' @param consensus (character) consensus measures to calculate. Options
#' available are 'median', 'range', 'mean' and 'stdev' (standard deviation).
#' Default is c("median", "range", "mean", "stdev").
#' @param write_replicates (logical) whether to write the projections for each
#' replicate. Default is FALSE.
#' @param extrapolation_type (character) extrapolation type of model. Models can
#' be transferred with three options: free extrapolation ('E'), extrapolation
#' with clamping ('EC'), and no extrapolation ('NE'). Default = 'E'. See details.
#' @param var_to_restrict (character) vector specifying which variables to clamp
#' or not to extrapolate for. Only applicable if extrapolation_type is "EC" or "NE".
#' Default is `NULL`, clamping and no extrapolation will be done for all variables.
#' @param type (character) the format of prediction values. For `maxnet` models,
#' valid options are `"raw"`, `"cumulative"`, `"logistic"`, and `"cloglog"`. For
#' `glm` models, valid options are `"response"` and `"raw"`. If `NULL` (default),
#' the function uses `"cloglog"` for `maxnet` models and `"response"` for `glm`
#' models.
#' @param overwrite (logical) whether to overwrite SpatRaster if they already
#' exists. Only applicable if `write_files` is set to TRUE. Default is FALSE.
#' @param parallel (logical) whether to fit the candidate models in parallel.
#' Default is FALSE.
#' @param ncores (numeric) number of cores to use for parallel processing.
#' Default is NULL and uses available cores - 1. This is only applicable if
#' `parallel = TRUE`.
#' @param progress_bar (logical) whether to display a progress bar during
#' processing. Default is TRUE.
#' @param verbose (logical) whether to display messages during processing.
#' Default is TRUE.
#'
#' @export
#'
#' @importFrom parallel makeCluster stopCluster
#' @importFrom doSNOW registerDoSNOW
#' @importFrom utils setTxtProgressBar txtProgressBar
#' @importFrom terra crop wrap unwrap
#'
#' @return
#' A `model_projections` object that provides the paths to the raster
#' files with the projection results and the corresponding thresholds used to
#' binarize the predictions.
#'
#' @seealso
#' [organize_future_worldclim()], [prepare_projection()]
#'
#' @examples
#' # Step 1: Organize variables for current projection
#' ## Import current variables (used to fit models)
#' var <- terra::rast(system.file("extdata", "Current_variables.tif",
#'                                package = "kuenm2"))
#'
#' ## Create a folder in a temporary directory to copy the variables
#' out_dir_current <- file.path(tempdir(), "Current_raw_wc")
#' dir.create(out_dir_current, recursive = TRUE)
#'
#' ## Save current variables in temporary directory
#' terra::writeRaster(var, file.path(out_dir_current, "Variables.tif"))
#'
#'
#' # Step 2: Organize future climate variables (example with WorldClim)
#' ## Directory containing the downloaded future climate variables (example)
#' in_dir <- system.file("extdata", package = "kuenm2")
#'
#' ## Create a folder in a temporary directory to copy the future variables
#' out_dir_future <- file.path(tempdir(), "Future_raw_wc")
#'
#' ## Organize and rename the future climate data (structured by year and GCM)
#' ### 'SoilType' will be appended as a static variable in each scenario
#' organize_future_worldclim(input_dir = in_dir, output_dir = out_dir_future,
#'                           name_format = "bio_", static_variables = var$SoilType)
#'
#' # Step 3: Prepare data to run multiple projections
#' ## An example with maxnet models
#' ## Import example of fitted_models (output of fit_selected())
#' data(fitted_model_maxnet, package = "kuenm2")
#'
#' ## Prepare projection data using fitted models to check variables
#' pr <- prepare_projection(models = fitted_model_maxnet,
#'                          present_dir = out_dir_current,
#'                          future_dir = out_dir_future,
#'                          future_period = c("2081-2100"),
#'                          future_pscen = c("ssp126", "ssp585"),
#'                          future_gcm = c("ACCESS-CM2", "MIROC6"),
#'                          raster_pattern = ".tif*")
#'
#' # Step 4: Run multiple model projections
#' ## A folder to save projection results
#' out_dir <- file.path(tempdir(), "Projection_results/maxnet_projections")
#' dir.create(out_dir, recursive = TRUE)
#'
#' ## Project selected models to multiple scenarios
#' p <- project_selected(models = fitted_model_maxnet, projection_data = pr,
#'                       out_dir = out_dir)

project_selected <- function(models,
                             projection_data,
                             out_dir,
                             mask = NULL,
                             consensus_per_model = TRUE,
                             consensus_general = TRUE,
                             consensus = c("median", "range", "mean", "stdev"),
                             write_replicates = FALSE,
                             extrapolation_type = "E",
                             var_to_restrict = NULL,
                             type = NULL,
                             overwrite = FALSE,
                             parallel = FALSE,
                             ncores = NULL,
                             progress_bar = TRUE,
                             verbose = TRUE) {
  #Check data
  if (missing(models)) {
    stop("Argument 'models' must be defined.")
  }
  if (missing(projection_data)) {
    stop("Argument 'projection_data' must be defined.")
  }
  if (missing(out_dir)) {
    stop("Argument 'out_dir' must be defined.")
  }

  if (!inherits(models, "fitted_models")) {
    stop("Argument 'models' must be a 'fitted_models' object.")
  }
  if (!inherits(projection_data, "projection_data")) {
    stop("Argument 'projection_data' must be a 'projection_data' object.")
  }
  if (!inherits(out_dir, "character")) {
    stop("Argument 'out_dir' must be a 'character'.")
  }

  if (!is.null(mask) & !inherits(mask, c("SpatRaster", "SpatVector",
                                         "SpatExtent"))) {
    stop("Argument 'mask' must be a 'SpatVector', 'SpatExtent' or 'SpatRaster'.")
  }

  if (!inherits(consensus, "character")) {
    stop("Argument 'consensus' must be a 'character'.")
  }
  consensus_out <- setdiff(consensus, c("median", "range", "mean", "stdev"))
  if (length(consensus_out) > 0) {
    stop("Invalid 'consensus' provided.",
         "\nAvailable options are: 'median', 'range', 'mean' and 'stdev'.")
  }
  if (!any(c("median", "mean") %in% consensus)) {
    stop("'consensus' must contain at least one of the options: 'median' or 'mean'.")
  }

  if (extrapolation_type %in% c("EC", "NE") & !is.null(var_to_restrict) &
      !inherits(var_to_restrict, "character")) {
    stop("Argument 'var_to_restrict' must be NULL or 'character'.")
  }

  if(is.null(type)){
    if(models$algorithm == "maxnet") {
      type <- "cloglog"
    } else if (models$algorithm == "glm") {
      type <- "response"
    }
  }

  if(!inherits(type, "character")){
    stop("Argument 'type' must be NULL or 'character'.")
  }

  if(models$algorithm == "maxnet"){
    if (!any(c("raw", "cumulative", "logistic", "cloglog") %in% type)) {
      stop("Invalid 'type' provided.",
           "\nAvailable options for maxnet models are: 'raw', 'cumulative',
           'logistic', or 'cloglog'.")
    }
    if(type == "raw")
      type <-  "exponential"
  }

  if(models$algorithm == "glm"){
    if (!any(c("response", "cloglog") %in% type)) {
      stop("Invalid 'type' provided.",
           "\nAvailable options for glm models are 'response' or 'cloglog'.")
    }
    if(type == "cloglog")
      type <- "link"
  }

  #Save parameters in a list to send to foreach nodes#
  par_list <- list(models = models,
                   mask = mask,
                   projection_data = projection_data,
                   consensus_per_model = consensus_per_model,
                   consensus_general = consensus_general,
                   consensus = consensus,
                   write_replicates = write_replicates,
                   extrapolation_type = extrapolation_type,
                   var_to_restrict = var_to_restrict,
                   type = type,
                   overwrite = overwrite)

  ####PREPARE DATAFRAME TO PREDICT####
  #Extract variables from best models
  vars <- names(models[["Models"]][[1]][[1]]$samplemeans)
  vars <- setdiff(vars, c("pr_bg", "fold"))

  if (!file.exists(out_dir)) {
    dir.create(out_dir, showWarnings = FALSE)  # We should show these warnings
  }
  #Normalize path
  out_dir <- normalizePath(out_dir)

  #Check scenarios for predicting
  res_path <- check_pred_scenarios(projection_data = projection_data,
                                   out_dir = out_dir)
  raster_pattern <- projection_data$raster_pattern


  ####Configure parallelization####
  n_models <- nrow(res_path)
  if (n_models == 1 & isTRUE(parallel)) {
    parallel <- FALSE
  }

  #Show progress bar?
  if (progress_bar) {
    pb <- utils::txtProgressBar(0, n_models, style = 3)
    progress <- function(n) utils::setTxtProgressBar(pb, n)
  }


  ###################################

  #If parallel and mask, wrap variables
  if (!is.null(mask) & parallel) {
    par_list$mask <- terra::wrap(par_list$mask)
  }

  #Run predictions
  if (parallel) {
    if (is.null(ncores)) {
      ncores <- max(1, parallel::detectCores() - 1)
    }
    if (n_models < ncores) {
      ncores <- n_models
    }

    #Make cluster
    cl <- parallel::makeCluster(ncores)

    doSNOW::registerDoSNOW(cl)
    if (progress_bar) {
      opts <- list(progress = progress)
    } else {
      opts <- NULL
    }

    foreach(
      x = 1:n_models, .options.snow = opts,
      .export = c("predict_selected", "multiple_projections")
    ) %dopar% {
      multiple_projections(i = x, res_path, raster_pattern, par_list)
    }
  } else { #Not in parallel (using %do%)
    # Loop for com barra de progresso manual
    for (x in 1:n_models) {
      # Execute a função fit_eval_models
      multiple_projections(i = x, res_path = res_path,
                           raster_pattern = raster_pattern,
                           par_list = par_list)

      # Sets the progress bar to the current state
      if (progress_bar) {
        utils::setTxtProgressBar(pb, x)
      }
    }
  }

  #Stop cluster
  if (parallel) {
    parallel::stopCluster(cl)
  }

  #Append threshold to final results
  res_final <- new_model_projections(paths = res_path,
                                     thresholds = models$thresholds)

  #Save
  saveRDS(res_final, file.path(out_dir, "Projection_paths.RDS"))
  return(res_final)
}

Try the kuenm2 package in your browser

Any scripts or data that you put into this service are public.

kuenm2 documentation built on April 21, 2026, 1:07 a.m.