diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 76268f8..2a3fe93 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,106 +1,43 @@ -# NOTE: This workflow is overkill for most R packages -# check-standard.yaml is likely a better choice -# usethis::use_github_action("check-standard") will install it. -# -# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. -# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples on: push: - branches: - - main - - master + branches: [main, master] pull_request: - branches: - - main - - master + branches: [main, master] -name: R-CMD-check +name: R-CMD-check.yaml + +permissions: read-all jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - strategy: fail-fast: false matrix: config: - - {os: macOS-latest, r: 'release'} + - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - - {os: windows-latest, r: '3.6'} - - {os: ubuntu-16.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest", http-user-agent: "R/4.0.0 (ubuntu-16.04) R (4.0.0 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions" } - - {os: ubuntu-16.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} - - {os: ubuntu-16.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} - - {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} - - {os: ubuntu-16.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} - - {os: ubuntu-16.04, r: '3.3', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} - + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - + R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v1 + - uses: actions/checkout@v4 + - uses: r-lib/actions/setup-pandoc@v2 + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - if: runner.os != 'Windows' - uses: actions/cache@v2 + use-public-rspm: true + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - run: | - while read -r cmd - do - eval sudo $cmd - done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "16.04"))') - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true - shell: bash - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + extra-packages: any::rcmdcheck + needs: check + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 3c908d3..0e06984 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -1,48 +1,40 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples on: push: - branches: - - main - - master + branches: [main, master] + release: + types: [published] + workflow_dispatch: -name: pkgdown +name: pkgdown.yaml + +permissions: read-all jobs: pkgdown: - runs-on: macOS-latest + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v1 - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - uses: actions/cache@v2 + - uses: actions/checkout@v4 + - uses: r-lib/actions/setup-pandoc@v2 + - uses: r-lib/actions/setup-r@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - install.packages("pkgdown", type = "binary") + use-public-rspm: true + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} - - - name: Install package - run: R CMD INSTALL . - - - name: Deploy package - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' + - name: Deploy to GitHub pages + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 5910c1a..9c9c4c8 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,48 +1,41 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples on: push: - branches: - - main - - master + branches: [main, master] pull_request: - branches: - - main - - master + branches: [main, master] -name: test-coverage +name: test-coverage.yaml + +permissions: read-all jobs: test-coverage: - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v1 - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Cache R packages - uses: actions/cache@v2 + - uses: actions/checkout@v4 + - uses: r-lib/actions/setup-r@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies - run: | - install.packages(c("remotes")) - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} - + use-public-rspm: true + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage - name: Test coverage - run: covr::codecov() + run: | + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) shell: Rscript {0} + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: false + files: ./cobertura.xml + plugins: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/DESCRIPTION b/DESCRIPTION index 9d3141a..578b301 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,71 +1,70 @@ Package: sdmApp Title: A User-Friendly Application for Species Distribution Modeling -Version: 0.0.2 +Version: 0.0.3 Authors@R: c( - person("Aboubacar", "HEMA", , "aboubacarhema94@gmail.com", role = c("aut", "cre")), + person("Aboubacar", "HEMA", , "aboubacarhema94@gmail.com", + role = c("aut", "cre"), + comment = c(ORCID = "0000-0000-0000-0000")), person("Babacar", "NDAO", , "babacar.ndao@cse.sn", role = "aut"), person("Louise", "LEROUX", , "louise.leroux@cirad.fr", role = "aut"), person("Abdoul Aziz", "DIOUF", , "aziz.diouf@cse.sn", role = "aut") ) -Author: Aboubacar HEMA [aut, cre], Babacar NDAO [aut], Louise LEROUX - [aut], Abdoul Aziz DIOUF [aut] -Maintainer: Aboubacar HEMA Description: A 'shiny' application that allows non-expert 'R' users to easily model species distribution. It offers a reproducible work flow for species distribution modeling into a single and user friendly environment. 'sdmApp' takes 'raster' data (in format supported by the - 'raster package') and species occurrence data (several format + 'raster' package) and species occurrence data (several format supported) as input argument. It provides an interactive graphical user interface (GUI). License: GPL-3 -URL: https://github.com/Abson-dev/sdmApp +URL: https://github.com/Abson-dev/sdmApp, + https://abson-dev.github.io/sdmApp/ BugReports: https://github.com/Abson-dev/sdmApp/issues -Depends: +Depends: R (>= 3.5.0) Imports: - raster (>= 2.6.7), + cowplot (>= 1.1.1), + ggplot2 (>= 3.4.0), + graphics, + grDevices, + raster (>= 3.6.3), + sf (>= 1.0.0), shiny (>= 0.12.2), - sp (>= 1.2.0) -Suggests: + sp (>= 1.6.0), + stats +Suggests: automap (>= 1.0-14), blockCV (>= 2.1.1), covr, - cowplot (>= 1.1.1), data.table, dismo (>= 1.0.12), dplyr (>= 1.0.3), DT, - future.apply, ggcorrplot (>= 0.1.3), - ggplot2 (>= 3.1.1), - grDevices, haven (>= 2.3.1), kernlab (>= 0.9-29), knitr, randomForest (>= 4.6.10), readxl (>= 1.3.1), - rgdal (>= 1.5-8), - rgeos (>= 0.3-8), rhandsontable (>= 0.3.7), rJava (>= 0.9-13), rmarkdown, - sf, shinyBS (>= 0.61), + shinydashboard, shinyFiles (>= 0.7.0), SSDM (>= 0.2.8), - stats, + terra, testthat (>= 3.0.0), - tidyr (>= 1.1.2), - utils -VignetteBuilder: + tidyr (>= 1.1.2) +Remotes: + rinnan/CENFA +VignetteBuilder: knitr, rmarkdown -BuildResaveData: no Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US LazyData: no -LazyLoad: yes -RoxygenNote: 7.2.3 +Roxygen: list(markdown = TRUE) SystemRequirements: Java (>= 8) -ZipData: no +Config/roxygen2/version: 8.0.0 diff --git a/NAMESPACE b/NAMESPACE index d75f824..7285186 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1 +1,31 @@ -exportPattern("^[[:alpha:]]+") +# Generated by roxygen2: do not edit by hand + +export(sdmApp) +export(sdmApp_PA) +export(sdmApp_RasterPlot) +export(sdmApp_TimesRasters) +export(sdmApp_fold_Explorer) +importFrom(cowplot,plot_grid) +importFrom(ggplot2,.data) +importFrom(ggplot2,aes) +importFrom(ggplot2,element_text) +importFrom(ggplot2,geom_raster) +importFrom(ggplot2,geom_sf) +importFrom(ggplot2,ggplot) +importFrom(ggplot2,ggtitle) +importFrom(ggplot2,guides) +importFrom(ggplot2,labs) +importFrom(ggplot2,scale_fill_gradient2) +importFrom(ggplot2,scale_fill_gradientn) +importFrom(ggplot2,scale_fill_manual) +importFrom(ggplot2,theme) +importFrom(ggplot2,theme_bw) +importFrom(grDevices,is.raster) +importFrom(grDevices,terrain.colors) +importFrom(raster,as.data.frame) +importFrom(raster,sampleRegular) +importFrom(sf,st_as_sf) +importFrom(shiny,runApp) +importFrom(shiny,shinyApp) +importFrom(shiny,shinyOptions) +importFrom(stats,median) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..b7bd96c --- /dev/null +++ b/NEWS.md @@ -0,0 +1,36 @@ +# sdmApp 0.0.3 + +## Breaking and infrastructure changes + +* Dropped the archived `rgdal` and `rgeos` dependencies. `sdmApp` now + relies on `raster` (>= 3.6.3), which reaches GDAL, GEOS and PROJ through + `terra`, so the package installs again on current R. +* Reworked `DESCRIPTION`: packages actually used by the exported functions + moved to `Imports`; the modelling engines used only inside the shiny + interface stay in `Suggests` and are checked at launch. +* `NAMESPACE` is now generated by roxygen2 from targeted `@importFrom` + tags instead of a blanket `exportPattern()` and long `@import` lists. + +## Fixes + +* `sdmApp()` no longer calls itself recursively and builds the shiny app + only once. It validates `theme` and `maxRequestSize` up front and stops + with an informative message listing any missing suggested packages. +* Replaced the deprecated `ggplot2::aes_string()` with `aes()` and + `.data[[ ]]` across the mapping helpers. +* Replaced `guides(fill = FALSE)` with `guides(fill = "none")`. +* Replaced fragile `class(x) == "SpatialBlock"` comparisons with + `inherits()`. +* Added input validation to `sdmApp_PA()`, `sdmApp_RasterPlot()`, + `sdmApp_TimesRasters()` and `sdmApp_fold_Explorer()`. + +## Testing and CI + +* Replaced the placeholder unit tests with real tests that exercise the + exported functions against the bundled example data. +* Rebuilt the GitHub Actions workflows (`R-CMD-check`, `pkgdown`, + `test-coverage`, `lint`) on `r-lib/actions` v2 and current runners. + +# sdmApp 0.0.2 + +* First public version. diff --git a/R/sdmApp-package.R b/R/sdmApp-package.R new file mode 100644 index 0000000..51a953a --- /dev/null +++ b/R/sdmApp-package.R @@ -0,0 +1,13 @@ +#' sdmApp: A User-Friendly Application for Species Distribution Modeling +#' +#' sdmApp provides a 'shiny' graphical user interface that lets non-expert +#' R users build species distribution models through a reproducible +#' workflow: uploading raster predictors and species occurrence data, +#' inspecting predictor correlation, selecting predictors with +#' \pkg{CENFA}, applying spatial block cross-validation with \pkg{blockCV}, +#' fitting a range of models, and exporting results together with the +#' underlying R code. +#' +#' @author Aboubacar HEMA +#' @keywords internal +"_PACKAGE" diff --git a/R/sdmApp.R b/R/sdmApp.R index 9f151aa..2caf6de 100644 --- a/R/sdmApp.R +++ b/R/sdmApp.R @@ -1,93 +1,126 @@ -#' starts the graphical user interface developed with shiny. +#' Start the sdmApp graphical user interface #' -#' @param maxRequestSize (numeric) number defining the maximum allowed file size (in megabytes) for uploaded files, defaults to 50MB -#' @param debug logical if TRUE, set shiny-debugging options -#' @param theme select style sheet for the interface. -# -#' @param ... arguments (e.g host) that are passed through \code{\link{runApp}} when starting the shiny application -#' @param shiny.server Setting this parameter to TRUE will return the app in the form of an object rather than invoking it. This is useful for deploying sdmApp via shiny-server. +#' Launches the 'shiny' application that lets non-expert R users model +#' species distribution through a reproducible, point-and-click workflow. #' -#' @return starts the interactive graphical user interface. +#' The interface relies on a number of modelling engines (for example +#' \pkg{SSDM}, \pkg{dismo}, \pkg{randomForest}, \pkg{kernlab} and +#' \pkg{CENFA}). Those packages are listed under `Suggests` to keep the +#' installation footprint small; [sdmApp()] checks that they are available +#' and returns an informative error listing anything missing. +#' +#' @param maxRequestSize Numeric. Maximum allowed size, in megabytes, for +#' uploaded files. Defaults to 50. +#' @param debug Logical. If `TRUE`, enable 'shiny' debugging options +#' (full stack trace and trace). +#' @param theme Character. Style sheet for the interface. One of +#' `"IHSN"` (default), `"yeti"`, `"journal"` or `"flatly"`. +#' @param ... Further arguments (for example `host` or `port`) passed to +#' [shiny::runApp()] when the application is launched. +#' @param shiny.server Logical. If `TRUE`, return the application as a +#' [shiny::shinyApp()] object instead of launching it. Useful for +#' deploying `sdmApp` behind shiny-server. +#' +#' @return If `shiny.server = TRUE`, a [shiny::shinyApp()] object. +#' Otherwise the function is called for its side effect of starting the +#' interactive interface and returns the value of [shiny::runApp()] +#' invisibly. +#' +#' @author Aboubacar HEMA +#' +#' @importFrom shiny runApp shinyApp shinyOptions #' @export -#' @import shiny -#' @import raster -#' @import sp -#' @import sf -#' @import grid -#' @import rhandsontable -#' @import haven -#' @import shinyBS -#' @import data.table -#' @import ggplot2 -#' @import dismo -#' @import DT -#' @import readxl -#' @import shinyFiles -#' @import shinydashboard -#' @import SSDM -#' @import automap -#' @import blockCV -#' @import CENFA -#' @import rJava -#' @import randomForest -#' @import kernlab #' #' @examples -#' if(interactive()){ -#' #load the package -#' library(sdmApp) -#' sdmApp() +#' if (interactive()) { +#' library(sdmApp) +#' sdmApp() #' } -sdmApp<-function (maxRequestSize = 50, debug = FALSE, theme = "IHSN", - ..., shiny.server = FALSE) -{ - if (!shiny.server) - shiny::runApp(sdmApp(maxRequestSize, debug, theme, ..., shiny.server = TRUE)) +sdmApp <- function(maxRequestSize = 50, debug = FALSE, theme = "IHSN", + ..., shiny.server = FALSE) { + if (!is.numeric(maxRequestSize)) { - stop("argument 'maxRequestSize' must be numeric!\n") + stop("argument 'maxRequestSize' must be numeric.", call. = FALSE) } if (maxRequestSize < 1) { maxRequestSize <- 10 } + valid_themes <- c("yeti", "journal", "flatly", "IHSN") + if (!theme %in% valid_themes) { + stop("Invalid value for argument 'theme'. Use one of: ", + paste(valid_themes, collapse = ", "), ".", call. = FALSE) + } + + .sdmApp_check_deps() + appDir <- system.file("shiny", "sdmApp", package = "sdmApp") - #appDir <- "C:/Users/DELLDRAMOMO/Dropbox/Package/sdmApp/shiny/sdmApp" - if (appDir == "") { - stop("Could not find directory.", - call. = FALSE) + if (identical(appDir, "")) { + stop("Could not find the sdmApp Shiny directory. ", + "Try re-installing the 'sdmApp' package.", call. = FALSE) } - options(shiny.maxRequestSize = ceiling(maxRequestSize) * - 1024^2) + + options(shiny.maxRequestSize = ceiling(maxRequestSize) * 1024^2) options(shiny.fullstacktrace = debug) options(shiny.trace = debug) + shiny::shinyOptions(.startdir = getwd()) shiny::shinyOptions(.appDir = appDir) - if (!theme %in% c("yeti", "journal", "flatly", - "IHSN")) { - stop("Invalid value for argument 'theme'\n") - } - if (theme == "yeti") { - shiny::shinyOptions(.guitheme = "bootswatch_yeti.css") - shiny::shinyOptions(.guijsfile = NULL) - } - if (theme == "journal") { - shiny::shinyOptions(.guitheme = "bootswatch_journal.css") - shiny::shinyOptions(.guijsfile = NULL) - } - if (theme == "flatly") { - shiny::shinyOptions(.guitheme = "bootswatch_flatly.css") - shiny::shinyOptions(.guijsfile = NULL) - } - if (theme == "IHSN") { - shiny::shinyOptions(.guitheme = "ihsn-root.css") - #shiny::shinyOptions(.guijsfile = "js/ihsn-style.js") - } + + guitheme <- switch( + theme, + yeti = "bootswatch_yeti.css", + journal = "bootswatch_journal.css", + flatly = "bootswatch_flatly.css", + IHSN = "ihsn-root.css" + ) + shiny::shinyOptions(.guitheme = guitheme) + shiny::shinyOptions(.guijsfile = NULL) + source_from_appdir <- function(filename) { source(file.path(appDir, filename), local = parent.frame(), chdir = TRUE)$value } + shiny::shinyOptions(sdcAppInvoked = TRUE) source_from_appdir("global.R") shiny::shinyOptions(sdcAppInvoked = NULL) - shiny::shinyApp(ui = source_from_appdir("ui.R"), server = source_from_appdir("server.R"), - options = list(launch.browser = TRUE, ...)) + + app <- shiny::shinyApp( + ui = source_from_appdir("ui.R"), + server = source_from_appdir("server.R"), + options = list(launch.browser = TRUE, ...) + ) + + if (shiny.server) { + return(app) + } + invisible(shiny::runApp(app)) +} + +# Internal: verify that the suggested packages needed by the interface are +# installed, and stop with an actionable message otherwise. +.sdmApp_check_deps <- function() { + needed <- c( + "shinydashboard", "shinyBS", "shinyFiles", "rhandsontable", "DT", + "ggcorrplot", "data.table", "dplyr", "tidyr", "haven", "readxl", + "blockCV", "CENFA", "dismo", "SSDM", "randomForest", "kernlab", + "automap", "rJava" + ) + installed <- vapply(needed, requireNamespace, logical(1), quietly = TRUE) + missing <- needed[!installed] + if (length(missing) > 0L) { + stop( + "The sdmApp interface needs the following package", + if (length(missing) > 1L) "s" else "", + " which ", + if (length(missing) > 1L) "are" else "is", + " not installed:\n ", + paste(missing, collapse = ", "), + "\n\nInstall with:\n install.packages(c(", + paste(sprintf('"%s"', missing), collapse = ", "), + "))", + call. = FALSE + ) + } + invisible(TRUE) } diff --git a/R/sdmApp_PA.R b/R/sdmApp_PA.R index 5fc296c..cbc3c96 100644 --- a/R/sdmApp_PA.R +++ b/R/sdmApp_PA.R @@ -1,28 +1,50 @@ -#' Plot presence/absence map +#' Plot a presence/absence map #' -#' @param x \code{Raster object} +#' Turns a binary (presence/absence) raster into a `ggplot` map, sampling +#' the layer on a regular grid so that large rasters can be displayed +#' quickly. #' -#' @return a \code{ggplot object} +#' @param x A [raster::RasterLayer-class] object holding presence (`TRUE`, +#' `1`) and absence (`FALSE`, `0`) values. #' -#' @import raster +#' @return A [ggplot2::ggplot] object. #' -#' @import ggplot2 +#' @author Aboubacar HEMA #' +#' @importFrom raster sampleRegular as.data.frame +#' @importFrom ggplot2 ggplot geom_raster aes theme_bw labs ggtitle theme +#' element_text scale_fill_manual .data #' @export #' #' @examples -#' r <- raster::raster(system.file("extdata","AETI.tif",package = "sdmApp")) +#' r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) #' r <- r > 4000 #' sdmApp_PA(r) -sdmApp_PA<-function(x){ - samp <- raster::sampleRegular(x, 5e+05, asRaster = TRUE) +sdmApp_PA <- function(x) { + if (!inherits(x, "Raster")) { + stop("'x' must be a Raster* object.", call. = FALSE) + } + + samp <- raster::sampleRegular(x, 5e5, asRaster = TRUE) map_df <- raster::as.data.frame(samp, xy = TRUE, centroids = TRUE, na.rm = TRUE) colnames(map_df) <- c("Easting", "Northing", "MAP") - map_df$MAP<-factor(map_df$MAP) - basePlot <- ggplot2::ggplot() + ggplot2::geom_raster(data = map_df, - ggplot2::aes_string(y = "Northing", x = "Easting", fill = "MAP")) - basePlot<-basePlot + ggplot2::theme_bw() + ggplot2::labs(x = "Longitude", y = "Latitude") + ggplot2::ggtitle(label = names(x)) + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10))+ggplot2::scale_fill_manual(values=c("red","green"),name="Specie",labels=c("Absence","Presence")) + map_df$MAP <- factor(map_df$MAP) - return(basePlot) + ggplot2::ggplot(map_df) + + ggplot2::geom_raster( + ggplot2::aes(x = .data[["Easting"]], y = .data[["Northing"]], + fill = .data[["MAP"]]) + ) + + ggplot2::theme_bw() + + ggplot2::labs(x = "Longitude", y = "Latitude") + + ggplot2::ggtitle(label = names(x)) + + ggplot2::theme( + plot.title = ggplot2::element_text(hjust = 0.5, size = 10) + ) + + ggplot2::scale_fill_manual( + values = c("red", "green"), + name = "Specie", + labels = c("Absence", "Presence") + ) } diff --git a/R/sdmApp_RasterPlot.R b/R/sdmApp_RasterPlot.R index 453e781..ffd7b94 100644 --- a/R/sdmApp_RasterPlot.R +++ b/R/sdmApp_RasterPlot.R @@ -1,32 +1,51 @@ -#' Plot a raster +#' Plot a continuous raster #' -#' @param x \code{Raster object} +#' Displays a continuous raster layer as a `ggplot` map using a terrain +#' colour ramp. The layer is sampled on a regular grid so that large +#' rasters render quickly. #' -#' @return a \code{ggplot object} +#' @param x A [raster::RasterLayer-class] object. #' -#' @import raster +#' @return A [ggplot2::ggplot] object, or `NULL` if `x` is a base graphics +#' raster (`grDevices::is.raster(x)` is `TRUE`). #' -#' @import ggplot2 +#' @author Aboubacar HEMA #' -#' @import grDevices -#' @import rgdal +#' @importFrom raster sampleRegular as.data.frame +#' @importFrom grDevices is.raster terrain.colors +#' @importFrom ggplot2 ggplot geom_raster aes theme_bw labs ggtitle theme +#' element_text scale_fill_gradientn .data #' @export #' #' @examples -#' r <- raster::raster(system.file("extdata","AETI.tif",package = "sdmApp")) +#' r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) #' sdmApp_RasterPlot(r) -sdmApp_RasterPlot<-function(x){ - if(grDevices::is.raster(x)){return(NULL)} - samp <- raster::sampleRegular(x, 5e+05, asRaster = TRUE) +sdmApp_RasterPlot <- function(x) { + if (grDevices::is.raster(x)) { + return(NULL) + } + if (!inherits(x, "Raster")) { + stop("'x' must be a Raster* object.", call. = FALSE) + } + + samp <- raster::sampleRegular(x, 5e5, asRaster = TRUE) map_df <- raster::as.data.frame(samp, xy = TRUE, centroids = TRUE, na.rm = TRUE) colnames(map_df) <- c("Easting", "Northing", "MAP") - basePlot1 <- ggplot2::ggplot() + ggplot2::geom_raster(data = map_df, - ggplot2::aes_string(y = "Northing", x = "Easting", fill = "MAP")) - basePlot1<-basePlot1 + ggplot2::theme_bw() + ggplot2::labs(x = "Longitude", y = "Latitude") + - ggplot2::ggtitle(label = names(x)) + ggplot2::scale_fill_gradientn(name = " ", colours = rev(grDevices::terrain.colors(10))) - basePlot1<-basePlot1 + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - - return(basePlot1) + ggplot2::ggplot(map_df) + + ggplot2::geom_raster( + ggplot2::aes(x = .data[["Easting"]], y = .data[["Northing"]], + fill = .data[["MAP"]]) + ) + + ggplot2::theme_bw() + + ggplot2::labs(x = "Longitude", y = "Latitude") + + ggplot2::ggtitle(label = names(x)) + + ggplot2::scale_fill_gradientn( + name = " ", + colours = rev(grDevices::terrain.colors(10)) + ) + + ggplot2::theme( + plot.title = ggplot2::element_text(hjust = 0.5, size = 10) + ) } diff --git a/R/sdmApp_TimesRasters.R b/R/sdmApp_TimesRasters.R index 78c260a..334cb62 100644 --- a/R/sdmApp_TimesRasters.R +++ b/R/sdmApp_TimesRasters.R @@ -1,20 +1,34 @@ -#' Multiply the probability of occurrence map with the presence/absence map to get a presence map only. +#' Mask a probability-of-occurrence map with a presence/absence map #' -#' @param x Probability of occurrence map, a \code{Raster object} -#' @param y Presence/Absence map, a \code{Raster object} +#' Multiplies a probability-of-occurrence raster by a presence/absence +#' raster so that only cells predicted as presence keep their probability +#' value; absence cells become zero. The name of the first raster is +#' preserved on the result. +#' +#' @param x Probability-of-occurrence map, a [raster::RasterLayer-class] +#' object. +#' @param y Presence/absence map, a [raster::RasterLayer-class] object with +#' the same extent and resolution as `x`. +#' +#' @return A [raster::RasterLayer-class] object giving the +#' probability of occurrence restricted to presence cells. +#' +#' @author Aboubacar HEMA #' -#' @return Probability of occurrence map with only presence #' @export #' #' @examples -#' r <- raster::raster(system.file("extdata","AETI.tif",package = "sdmApp")) -#' r2 <- r > raster::cellStats(r, stat='mean', na.rm=TRUE) -#' r <- r/raster::maxValue(r) -#' names(r) <- "propability of occurence" -#' z<-sdmApp_TimesRasters(r,r2) +#' r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) +#' r2 <- r > raster::cellStats(r, stat = "mean", na.rm = TRUE) +#' r <- r / raster::maxValue(r) +#' names(r) <- "probability of occurrence" +#' z <- sdmApp_TimesRasters(r, r2) #' sdmApp_RasterPlot(z) -sdmApp_TimesRasters<-function(x,y){ - z<-x * y - names(z)<-names(x) - return(z) +sdmApp_TimesRasters <- function(x, y) { + if (!inherits(x, "Raster") || !inherits(y, "Raster")) { + stop("'x' and 'y' must both be Raster* objects.", call. = FALSE) + } + z <- x * y + names(z) <- names(x) + z } diff --git a/R/sdmApp_fold_Explorer.R b/R/sdmApp_fold_Explorer.R index b72680b..c2b4ce5 100644 --- a/R/sdmApp_fold_Explorer.R +++ b/R/sdmApp_fold_Explorer.R @@ -1,128 +1,122 @@ -#' Explore the generated folds and visualize the placement of folds and distribution of species data over folds. +#' Explore spatial cross-validation folds #' -#' @param blocks A \code{SpatialBlock} object. -#' @param rasterLayer A raster object as background map for visualization. -#' @param speciesData A simple features (sf) or \code{SpatialPoints} object containing species data (response variable). -#' @param num A number of fold to assign as data test set. +#' Visualises the placement of a chosen fold and the distribution of the +#' species data across training and testing sets, on top of a background +#' raster. Useful for inspecting the folds produced by \pkg{blockCV}. #' -#' @return A map showing folds and the species data, that can be used to explore folds. -#' @export -#' -#' -#' @import raster +#' @param blocks A `SpatialBlock` object (from \pkg{blockCV}). +#' @param rasterLayer A raster object used as the background map. +#' @param speciesData A simple features (`sf`) or `SpatialPoints` object +#' holding the species data (the response variable). +#' @param num Integer. Index of the fold to display as the test set. #' -#' @import ggplot2 +#' @return A combined plot (via [cowplot::plot_grid()]) showing the +#' training and testing sets for the selected fold. #' -#' @import sf +#' @author Aboubacar HEMA #' -#' @import stats -#' -#' @import blockCV +#' @importFrom raster sampleRegular as.data.frame +#' @importFrom sf st_as_sf +#' @importFrom stats median +#' @importFrom cowplot plot_grid +#' @importFrom ggplot2 ggplot geom_raster geom_sf aes scale_fill_gradient2 +#' guides theme_bw labs ggtitle theme element_text .data +#' @export #' -#' @importFrom graphics plot #' @examples #' \dontrun{ -#' # load blockCV package data #' library(blockCV) #' awt <- raster::brick(system.file("extdata", "awt.grd", package = "blockCV")) -#' #import presence-absence species data #' PA <- read.csv(system.file("extdata", "PA.csv", package = "blockCV")) -#'#make a sf object from data.frame #' pa_data <- sf::st_as_sf(PA, coords = c("x", "y"), crs = raster::crs(awt)) -#' #spatial blocking by specified range and random assignment -#' sb <- spatialBlock(speciesData = pa_data,species = "Species", -#' rasterLayer = awt,theRange = 70000,k = 5, -#' selection = "random",iteration = 100) -#' sdmApp_fold_Explorer(sb,awt,pa_data,1) -#'} -sdmApp_fold_Explorer<-function (blocks, rasterLayer, speciesData, num) { +#' sb <- spatialBlock(speciesData = pa_data, species = "Species", +#' rasterLayer = awt, theRange = 70000, k = 5, +#' selection = "random", iteration = 100) +#' sdmApp_fold_Explorer(sb, awt, pa_data, 1) +#' } +sdmApp_fold_Explorer <- function(blocks, rasterLayer, speciesData, num) { if (is.null(rasterLayer)) { - stop("A raster layer should be provided") - } - else if (is.null(speciesData)) { - stop("Species data should be provided") + stop("A raster layer should be provided.", call. = FALSE) } - else if (is.null(blocks)) { - stop("An object of SpatialBlock is needed") + if (is.null(speciesData)) { + stop("Species data should be provided.", call. = FALSE) } - if (class(blocks) == "SpatialBlock") { - polyObj <- blocks$blocks - } - else { - polyObj <- NULL + if (is.null(blocks)) { + stop("An object of class 'SpatialBlock' is needed.", call. = FALSE) } - if (num<0 || num>length(blocks$folds)){ - stop("The num parameter is incorrect!") + is_spatial_block <- inherits(blocks, "SpatialBlock") + polyObj <- if (is_spatial_block) blocks$blocks else NULL + + if (num < 0 || num > length(blocks$folds)) { + stop("The 'num' parameter is out of range.", call. = FALSE) } - folds <- blocks$folds - kmax <- length(folds) + + folds <- blocks$folds species <- blocks$species + speciesData <- sf::st_as_sf(speciesData) speciesData[[1]] <- as.factor(speciesData[[1]]) - samp <- raster::sampleRegular(rasterLayer[[1]], 5e+05, asRaster = TRUE) + + samp <- raster::sampleRegular(rasterLayer[[1]], 5e5, asRaster = TRUE) map_df <- raster::as.data.frame(samp, xy = TRUE, centroids = TRUE, na.rm = TRUE) colnames(map_df) <- c("Easting", "Northing", "MAP") mid <- stats::median(map_df$MAP) - basePlot <- ggplot2::ggplot() + ggplot2::geom_raster(data = map_df, - ggplot2::aes_string(y = "Northing", x = "Easting", fill = "MAP")) + + + basePlot <- ggplot2::ggplot() + + ggplot2::geom_raster( + data = map_df, + ggplot2::aes(x = .data[["Easting"]], y = .data[["Northing"]], + fill = .data[["MAP"]]) + ) + ggplot2::scale_fill_gradient2(low = "darkred", mid = "yellow", - high = "darkgreen", midpoint = mid) + ggplot2::guides(fill = FALSE) + - ggplot2::theme_bw() + ggplot2::labs(x = "", y = "") + high = "darkgreen", midpoint = mid) + + ggplot2::guides(fill = "none") + + ggplot2::theme_bw() + + ggplot2::labs(x = "", y = "") + trainSet <- unlist(folds[[num]][1]) - testSet <- unlist(folds[[num]][2]) + testSet <- unlist(folds[[num]][2]) training <- speciesData[trainSet, ] - testing <- speciesData[testSet, ] - plotPoly <- polyObj[polyObj$folds ==num,] - plotPoly <- sf::st_as_sf(plotPoly) - if (is.null(species)) { - if (class(blocks) == "SpatialBlock") { - ptr <- basePlot + ggplot2::geom_sf(data = plotPoly, - color = "red", fill = "orangered4", alpha = 0.04, - size = 0.2) + ggplot2::geom_sf(data = training, - alpha = 0.7, color = "blue", size = 2) + - ggplot2::ggtitle("Training set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - pts <- basePlot + ggplot2::geom_sf(data = plotPoly, - color = "red", fill = "orangered4", alpha = 0.04, - size = 0.2) + ggplot2::geom_sf(data = testing, - alpha = 0.7, color = "blue", size = 2) + - ggplot2::ggtitle("Testing set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - } - else { - ptr <- basePlot + ggplot2::geom_sf(data = training, - alpha = 0.7, color = "blue", size = 2) + - ggplot2::ggtitle("Training set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - pts <- basePlot + ggplot2::geom_sf(data = testing, - alpha = 0.7, color = "blue", size = 2) + - ggplot2::ggtitle("Testing set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) + testing <- speciesData[testSet, ] + + add_poly <- function(p) { + if (!is_spatial_block) { + return(p) } + plotPoly <- sf::st_as_sf(polyObj[polyObj$folds == num, ]) + p + ggplot2::geom_sf(data = plotPoly, color = "red", + fill = "orangered4", alpha = 0.04, size = 0.2) } - else { - if (class(blocks) == "SpatialBlock") { - ptr <- basePlot + ggplot2::geom_sf(data = plotPoly, - color = "red", fill = "orangered4", alpha = 0.04, - size = 0.2) + ggplot2::geom_sf(data = training, - ggplot2::aes(color = get(species)), show.legend = "point", - alpha = 0.7, size = 2) + ggplot2::labs(color = species) + - ggplot2::ggtitle("Training set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - pts <- basePlot + ggplot2::geom_sf(data = plotPoly, - color = "red", fill = "orangered4", alpha = 0.04, - size = 0.2) + ggplot2::geom_sf(data = testing, - ggplot2::aes(color = get(species)), show.legend = "point", - alpha = 0.7, size = 2) + ggplot2::labs(color = species) + - ggplot2::ggtitle("Testing set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - } - else { - ptr <- basePlot + ggplot2::geom_sf(data = training, - ggplot2::aes(color = get(species)), show.legend = "point", - alpha = 0.7, size = 2) + ggplot2::labs(color = species) + - ggplot2::ggtitle("Training set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - pts <- basePlot + ggplot2::geom_sf(data = testing, - ggplot2::aes(color = get(species)), show.legend = "point", - alpha = 0.7, size = 2) + ggplot2::labs(color = species) + - ggplot2::ggtitle("Testing set") + ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, size = 10)) - } + + title_theme <- ggplot2::theme( + plot.title = ggplot2::element_text(hjust = 0.5, size = 10) + ) + + if (is.null(species)) { + ptr <- add_poly(basePlot) + + ggplot2::geom_sf(data = training, alpha = 0.7, color = "blue", + size = 2) + + ggplot2::ggtitle("Training set") + title_theme + pts <- add_poly(basePlot) + + ggplot2::geom_sf(data = testing, alpha = 0.7, color = "blue", + size = 2) + + ggplot2::ggtitle("Testing set") + title_theme + } else { + ptr <- add_poly(basePlot) + + ggplot2::geom_sf(data = training, + ggplot2::aes(color = .data[[species]]), + show.legend = "point", alpha = 0.7, size = 2) + + ggplot2::labs(color = species) + + ggplot2::ggtitle("Training set") + title_theme + pts <- add_poly(basePlot) + + ggplot2::geom_sf(data = testing, + ggplot2::aes(color = .data[[species]]), + show.legend = "point", alpha = 0.7, size = 2) + + ggplot2::labs(color = species) + + ggplot2::ggtitle("Testing set") + title_theme } + cowplot::plot_grid(ptr, pts) } diff --git a/README.md b/README.md index 91ed927..c4235f1 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,138 @@ -# sdmApp - Species Distribution Models Application - -# `sdmApp`: Statistical Details - -| Package | Status | Usage | GitHub | Miscellaneous | - |-----------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| - | [![CRAN\_Release\_Badge](https://www.r-pkg.org/badges/version-ago/sdmApp)](https://CRAN.R-project.org/package=sdmApp) | [![Travis Build Status](https://travis-ci.org/Abson-dev/sdmApp.svg?branch=master)](https://travis-ci.org/Abson-dev/sdmApp) | [![Daily downloads badge](https://cranlogs.r-pkg.org/badges/last-day/sdmApp?color=blue)](https://CRAN.R-project.org/package=sdmApp) | [![GitHub version](https://img.shields.io/badge/GitHub-0.0.2-orange.svg?style=flat-square)](https://github.com/Abson-dev/sdmApp/) | [![Website](https://img.shields.io/badge/website-sdmApp-orange.svg?colorB=E91E63)](https://Abson-dev.github.io/sdmApp/) | - | [![CRAN Checks](https://cranchecks.info/badges/summary/sdmApp)](https://cran.r-project.org/web/checks/check_results_sdmApp.html) | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/Abson-dev/sdmApp?branch=master&svg=true)](https://ci.appveyor.com/project/Abson-dev/sdmApp) | [![Weekly downloads badge](https://cranlogs.r-pkg.org/badges/last-week/sdmApp?color=blue)](https://CRAN.R-project.org/package=sdmApp) | [![Forks](https://img.shields.io/badge/forks-0-blue.svg)](https://github.com/Abson-dev/sdmApp/) | [![minimal R version](https://img.shields.io/badge/R%3E%3D-3.5.0-6666ff.svg)](https://cran.r-project.org/) | - | [![lifecycle](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html) | [![R build status](https://github.com/Abson-dev/sdmApp/workflows/R-CMD-check/badge.svg)](https://github.com/Abson-dev/sdmApp) | [![Monthly downloads badge](https://cranlogs.r-pkg.org/badges/last-month/sdmApp?color=blue)](https://CRAN.R-project.org/package=sdmApp) | [![Github Issues](https://img.shields.io/badge/issues-1-red.svg)](https://github.com/Abson-dev/sdmApp/issues) | [![vignettes](https://img.shields.io/badge/vignettes-0.0.1-orange.svg?colorB=FF5722)](https://github.com/Abson-dev/sdmApp) | - | [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/Abson-dev/sdmApp.svg)](https://github.com/Abson-dev/sdmApp) | [![Coverage Status](https://coveralls.io/repos/github/Abson-dev/sdmApp/badge.svg?branch=master)](https://coveralls.io/github/Abson-dev/sdmApp?branch=master) | [![Total downloads badge](https://cranlogs.r-pkg.org/badges/grand-total/sdmApp?color=blue)](https://CRAN.R-project.org/package=sdmApp) | [![Github Stars](https://img.shields.io/github/stars/Abson-dev/sdmApp.svg?style=social&label=Github)](https://github.com/Abson-dev/sdmApp) | [![DOI](https://zenodo.org/badge/DOI/10.1016/zenodo.107481.svg)](https://doi.org/10.1016/j.ecolind.2021.107481) | - | [![Licence](https://img.shields.io/badge/licence-GPL--3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) | [![Codecov test coverage](https://codecov.io/gh/Abson-dev/sdmApp/branch/master/graph/badge.svg)](https://codecov.io/gh/Abson-dev/sdmApp?branch=master) | [![HitCount](https://hits.dwyl.com/Abson-dev/sdmApp.svg)](https://hits.dwyl.com/Abson-dev/sdmApp) | [![Last-changedate](https://img.shields.io/badge/last%20change-2021--02--01-yellowgreen.svg)](https://github.com/Abson-dev/sdmApp/commits/master) | [![GitHub last commit](https://img.shields.io/github/last-commit/Abson-dev/sdmApp.svg)](https://github.com/Abson-dev/sdmApp/commits/master) | - | [![status](https://tinyverse.netlify.com/badge/sdmApp)](https://CRAN.R-project.org/package=sdmApp) | [![lints](https://github.com/Abson-dev/sdmApp/workflows/lint/badge.svg)](https://github.com/Abson-dev/sdmApp) | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://github.com/Abson-dev/sdmApp) | [![Project Status](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/Abson-dev/sdmApp/issues) | - - -*sdmApp* is a *R package* containing a *Shiny* application that allows non-expert *R* users to easily model species distribution. It offers a reproducible work flow for species distribution modeling into a single and user friendly environment. *sdmApp* takes *Raster* data (in format supported by the *Raster package*) and species occurrence data (several format supported) as input argument. This *package* provides an interactive graphical user interface (GUI). - This document will give an overview of the main functionalities of the graphical user interface. The main features of the *GUI* is: - -* Uploading data (*raster* and species occurrence files) -* View correlation between *raster* -* Use [CENFA](https://CRAN.R-project.org/package=CENFA) to select species predictors -* Apply a spatial blocking for cross-validation based on the [blockCV](https://CRAN.R-project.org/package=blockCV) package -* Apply species distribution models with or without a spatial blocking strategy - +# sdmApp sdmApp logo + + +[![R-CMD-check](https://github.com/Abson-dev/sdmApp/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/Abson-dev/sdmApp/actions/workflows/R-CMD-check.yaml) +[![test-coverage](https://github.com/Abson-dev/sdmApp/actions/workflows/test-coverage.yaml/badge.svg)](https://github.com/Abson-dev/sdmApp/actions/workflows/test-coverage.yaml) +[![lint](https://github.com/Abson-dev/sdmApp/actions/workflows/lint.yaml/badge.svg)](https://github.com/Abson-dev/sdmApp/actions/workflows/lint.yaml) +[![pkgdown](https://github.com/Abson-dev/sdmApp/actions/workflows/pkgdown.yaml/badge.svg)](https://github.com/Abson-dev/sdmApp/actions/workflows/pkgdown.yaml) +[![Codecov test coverage](https://codecov.io/gh/Abson-dev/sdmApp/branch/master/graph/badge.svg)](https://app.codecov.io/gh/Abson-dev/sdmApp?branch=master) +[![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html#maturing) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![Project Status: Active](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![minimal R version](https://img.shields.io/badge/R%3E%3D-3.5.0-6666ff.svg)](https://cran.r-project.org/) + + +`sdmApp` is an R package containing a Shiny application that lets non-expert +R users model species distribution. It brings a reproducible workflow for +species distribution modeling into a single, user friendly environment. +`sdmApp` takes raster data (in any format supported by the `raster` package) +and species occurrence data (several formats supported) as input, and +provides an interactive graphical user interface (GUI). + +## CRAN status + +`sdmApp` is **not currently available on CRAN**. It was archived on +2024-02-10 because check problems were not corrected in time, following an +earlier archival on 2021-07-06 caused by its dependency on `CENFA`, which +had itself been archived. + +Install from GitHub instead (see below). Work to return the package to CRAN +is tracked in the issue queue: the main blockers were the retirement of +`rgdal` and `rgeos` in October 2023 and the archival of `CENFA` in July 2023. + +## Main features + +* Upload data (raster predictors and species occurrence files) +* View the correlation between raster predictors +* Use [CENFA](https://github.com/rinnan/CENFA) to select species predictors +* Apply spatial blocking for cross-validation based on the + [blockCV](https://CRAN.R-project.org/package=blockCV) package +* Fit species distribution models with or without a spatial blocking strategy * Export results -* Keep reproduce (*R* code) by being able do download the underlying code from *sdmApp*. - - The *GUI* is build around 5 main windows, which can be selected from the navigation bar at the top of the screen. Initially, some of these windows will be empty and their content changes once data (both *raster* and species occurrence files) have been uploaded. - -## Set working environment - -*sdmApp* uses Maxent model which used the Java implementation. Therefore you need : - -- the **Java JDK** software is installed; -- the package **rJava** is installed; -- the file **maxent.jar** is copied in the correct folder. - - +* Stay reproducible by downloading the underlying R code from `sdmApp` + +The GUI is built around five main windows, selectable from the navigation bar +at the top of the screen. Some of these windows start empty and fill in once +raster and species occurrence data have been uploaded. + ## Installation - -To install the *package* from *github* use: + +### System requirements + +`sdmApp` exposes the MaxEnt model, which uses a Java implementation. To use +MaxEnt you need: + +* the **Java JDK** (>= 8) installed; +* the **rJava** package installed; +* the **maxent.jar** file copied into the correct folder, that is the `java` + directory of the `dismo` package. You can locate it with + `system.file("java", package = "dismo")`. + +Everything else in `sdmApp` works without Java. + +### Install the package ```r +# install.packages("remotes") + +# CENFA is not on CRAN and must be installed from GitHub first +remotes::install_github("rinnan/CENFA") + +# then sdmApp itself remotes::install_github("Abson-dev/sdmApp", dependencies = TRUE) +``` + +Then launch the interface: + +```r library(sdmApp) sdmApp() ``` -To install the *package* from *CRAN* use: + +`sdmApp()` checks at launch that the modelling engines it needs are +installed, and stops with a message naming any that are missing, so you never +get a cryptic error halfway through a session. + +## Quick start without the GUI + +The package also exports a small set of mapping helpers that you can use +directly in a script. They all work on the example data shipped with the +package. ```r -install.package("sdmApp", dependencies = TRUE) library(sdmApp) -sdmApp() + +# a continuous predictor: actual evapotranspiration and interception +r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) +sdmApp_RasterPlot(r) + +# a presence/absence surface +pa <- r > 4000 +sdmApp_PA(pa) + +# mask a probability surface by a presence/absence surface +prob <- r / raster::cellStats(r, "max") +names(prob) <- "probability of occurrence" +sdmApp_RasterPlot(sdmApp_TimesRasters(prob, pa)) ``` +See `vignette("sdmApp")` for a longer walkthrough. + +## Example data + +The package ships a small extract of the Niakhar study area in Senegal, in +`inst/extdata`: + +* `Niakhar.csv` (and the same table as `.xlsx`, `.dta`, `.sav`, `.sas7bdat`), + holding 9258 georeferenced trees scored for three species + (*Faidherbia albida*, *Balanites aegyptiaca* and *Anogeissus leiocarpus*); +* 34 raster predictors covering bioclimatic drivers, soil properties, water + productivity, vegetation phenology and productivity, and watershed + topography. + +The bundled rasters are aligned on a common grid of 93 by 91 cells in +geographic coordinates (EPSG:4326). Note that `Niakhar.csv` is semicolon +separated and uses a comma as decimal mark, so read it with +`read.csv2()` rather than `read.csv()`. + +## The graphical interface +The five tabs are `Help/About`, `Data Upload`, `Spatial Analysis`, +`Modeling` and `R-Code`. The screenshots below follow a typical session, +from uploading data through to exporting results and recovering the +generated R code. @@ -65,21 +142,30 @@ sdmApp() - - - +## Known compatibility notes + +* **blockCV 3.0 and later.** `spatialBlock()` is deprecated in favour of + `cv_spatial()`, and the returned object is no longer of class + `SpatialBlock`. `sdmApp_fold_Explorer()` currently expects a + `SpatialBlock` object, so it works with the deprecated `spatialBlock()` + but not yet with `cv_spatial()`. Support for the new class is planned. +* **rgdal and rgeos.** Both were archived from CRAN on 2023-10-16. `sdmApp` + no longer depends on them; recent `raster` reaches GDAL, GEOS and PROJ + through `terra`. +* **raster and sp.** These still work but are in maintenance mode. A + migration of the mapping helpers to `terra` and `sf` is planned. ## Citation @@ -87,28 +173,52 @@ sdmApp() citation("sdmApp") ``` -To cite package *sdmApp* in publications use: +To cite `sdmApp` in publications, use: - HEMA A, NDAO B, LEROUX L, DIOUF A (2021). _sdmApp: A User-Friendly Application for - Species Distribution Modeling_. R package version 0.0.2, - . +> HEMA A, NDAO B, LEROUX L, DIOUF A (2026). *sdmApp: A User-Friendly +> Application for Species Distribution Modeling*. R package version 0.0.3, +> . -A BibTeX entry for LaTeX users is - - @Manual{, - title = {sdmApp: A User-Friendly Application for Species Distribution Modeling}, - author = {Aboubacar HEMA and Babacar NDAO and Louise LEROUX and Abdoul Aziz DIOUF}, - year = {2021}, - note = {R package version 0.0.2}, - url = {https://CRAN.R-project.org/package=sdmApp}, - } +A BibTeX entry for LaTeX users is: +```bibtex +@Manual{, + title = {sdmApp: A User-Friendly Application for Species Distribution Modeling}, + author = {Aboubacar HEMA and Babacar NDAO and Louise LEROUX and Abdoul Aziz DIOUF}, + year = {2026}, + note = {R package version 0.0.3}, + url = {https://github.com/Abson-dev/sdmApp}, +} +``` ## License -The *sdmApp* sticker was made through *R* art kindly shared by this [link](https://art.djnavarro.net/) and released under a [CC-BY-SA 4.0](https://www.donneesquebec.ca/licence/) license. +`sdmApp` is released under the GPL-3 license. + +The `sdmApp` sticker was made through R art kindly shared at +[art.djnavarro.net](https://art.djnavarro.net/) and released under a +CC-BY-SA 4.0 license. ## Guidelines for contributing -I welcome contributions and suggestions for improving this package. Please do not hesitate to submit any issues you may encounter. +Contributions and suggestions for improving this package are welcome. Please +do not hesitate to open an issue for any problem you encounter, or a pull +request for any improvement you would like to propose. + +## References + +Ndao, B., Leroux, L., Diouf, A.A., Soti, V., Sambou, B. (2019). A remote +sensing based approach for optimizing sampling strategies in crop monitoring +and crop yield estimation studies. In: Wade, S. (Ed.), *Earth Observations +and Geospatial Science in Service of Sustainable Development Goals*. +Springer, pp. 25-36. [doi:10.1007/978-3-030-16016-6_3](https://doi.org/10.1007/978-3-030-16016-6_3) + +Rinnan, D.S., Lawler, J. (2019). Climate-niche factor analysis: a spatial +approach to quantifying species vulnerability to climate change. +*Ecography*, 42, 1494-1503. [doi:10.1111/ecog.03937](https://doi.org/10.1111/ecog.03937) +Valavi, R., Elith, J., Lahoz-Monfort, J.J., Guillera-Arroita, G. (2019). +blockCV: An R package for generating spatially or environmentally separated +folds for k-fold cross-validation of species distribution models. +*Methods in Ecology and Evolution*, 10, 225-232. +[doi:10.1111/2041-210X.13107](https://doi.org/10.1111/2041-210X.13107) diff --git a/_pkgdown.yml b/_pkgdown.yml index e69de29..24d642e 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -0,0 +1,36 @@ +url: https://abson-dev.github.io/sdmApp/ + +template: + bootstrap: 5 + bslib: + primary: "#E91E63" + +home: + title: sdmApp + description: > + A user-friendly shiny application for reproducible species + distribution modeling. + +navbar: + structure: + left: [intro, reference, articles, news] + right: [search, github] + +reference: + - title: Launch the application + contents: + - sdmApp + - title: Mapping helpers + desc: Plot rasters and presence/absence surfaces. + contents: + - sdmApp_RasterPlot + - sdmApp_PA + - sdmApp_TimesRasters + - title: Cross-validation + contents: + - sdmApp_fold_Explorer + +articles: + - title: Get started + contents: + - sdmApp diff --git a/cran-comments.md b/cran-comments.md index 9624828..18226de 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,10 +1,18 @@ ## Test environments -* local R installation, R 4.0.3 -* ubuntu 16.04 (on travis-ci), R 4.0.3 -* win-builder (devel) + +* local install, R release +* GitHub Actions: ubuntu-latest (devel, release, oldrel-1), + windows-latest (release), macos-latest (release) +* win-builder (devel and release) ## R CMD check results -0 errors | 0 warnings | 1 note +0 errors | 0 warnings | 0 notes + +## Notes for the maintainer -* This is a new release. +* This release removes the archived `rgdal` and `rgeos` dependencies and + moves runtime dependencies of the exported functions into `Imports`. +* The shiny interface uses several modelling engines that are declared in + `Suggests`; `sdmApp()` checks for them at launch and fails gracefully + when they are absent, so examples and tests run without them. diff --git a/lint.yaml b/lint.yaml new file mode 100644 index 0000000..33f4905 --- /dev/null +++ b/lint.yaml @@ -0,0 +1,30 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: lint.yaml + +permissions: read-all + +jobs: + lint: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::lintr, local::. + needs: lint + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + env: + LINTR_ERROR_ON_LINT: true diff --git a/man/sdmApp-package.Rd b/man/sdmApp-package.Rd new file mode 100644 index 0000000..ffb0786 --- /dev/null +++ b/man/sdmApp-package.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sdmApp-package.R +\docType{package} +\name{sdmApp-package} +\alias{sdmApp-package} +\title{sdmApp: A User-Friendly Application for Species Distribution Modeling} +\description{ +sdmApp provides a 'shiny' graphical user interface that lets non-expert +R users build species distribution models through a reproducible +workflow: uploading raster predictors and species occurrence data, +inspecting predictor correlation, selecting predictors with +\pkg{CENFA}, applying spatial block cross-validation with \pkg{blockCV}, +fitting a range of models, and exporting results together with the +underlying R code. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/Abson-dev/sdmApp} + \item \url{https://abson-dev.github.io/sdmApp/} + \item Report bugs at \url{https://github.com/Abson-dev/sdmApp/issues} +} + +} +\author{ +Aboubacar HEMA +} +\keyword{internal} diff --git a/man/sdmApp.Rd b/man/sdmApp.Rd index 0ff36ae..c55b088 100644 --- a/man/sdmApp.Rd +++ b/man/sdmApp.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/sdmApp.R \name{sdmApp} \alias{sdmApp} -\title{starts the graphical user interface developed with shiny.} +\title{Start the sdmApp graphical user interface} \usage{ sdmApp( maxRequestSize = 50, @@ -13,26 +13,45 @@ sdmApp( ) } \arguments{ -\item{maxRequestSize}{(numeric) number defining the maximum allowed file size (in megabytes) for uploaded files, defaults to 50MB} +\item{maxRequestSize}{Numeric. Maximum allowed size, in megabytes, for +uploaded files. Defaults to 50.} -\item{debug}{logical if TRUE, set shiny-debugging options} +\item{debug}{Logical. If \code{TRUE}, enable 'shiny' debugging options +(full stack trace and trace).} -\item{theme}{select style sheet for the interface.} +\item{theme}{Character. Style sheet for the interface. One of +\code{"IHSN"} (default), \code{"yeti"}, \code{"journal"} or \code{"flatly"}.} -\item{...}{arguments (e.g host) that are passed through \code{\link{runApp}} when starting the shiny application} +\item{...}{Further arguments (for example \code{host} or \code{port}) passed to +\code{\link[shiny:runApp]{shiny::runApp()}} when the application is launched.} -\item{shiny.server}{Setting this parameter to TRUE will return the app in the form of an object rather than invoking it. This is useful for deploying sdmApp via shiny-server.} +\item{shiny.server}{Logical. If \code{TRUE}, return the application as a +\code{\link[shiny:shinyApp]{shiny::shinyApp()}} object instead of launching it. Useful for +deploying \code{sdmApp} behind shiny-server.} } \value{ -starts the interactive graphical user interface. +If \code{shiny.server = TRUE}, a \code{\link[shiny:shinyApp]{shiny::shinyApp()}} object. +Otherwise the function is called for its side effect of starting the +interactive interface and returns the value of \code{\link[shiny:runApp]{shiny::runApp()}} +invisibly. } \description{ -starts the graphical user interface developed with shiny. +Launches the 'shiny' application that lets non-expert R users model +species distribution through a reproducible, point-and-click workflow. +} +\details{ +The interface relies on a number of modelling engines (for example +\pkg{SSDM}, \pkg{dismo}, \pkg{randomForest}, \pkg{kernlab} and +\pkg{CENFA}). Those packages are listed under \code{Suggests} to keep the +installation footprint small; \code{\link[=sdmApp]{sdmApp()}} checks that they are available +and returns an informative error listing anything missing. } \examples{ -if(interactive()){ -#load the package -library(sdmApp) -sdmApp() +if (interactive()) { + library(sdmApp) + sdmApp() +} } +\author{ +Aboubacar HEMA } diff --git a/man/sdmApp_PA.Rd b/man/sdmApp_PA.Rd index c936e83..6618e7e 100644 --- a/man/sdmApp_PA.Rd +++ b/man/sdmApp_PA.Rd @@ -2,21 +2,27 @@ % Please edit documentation in R/sdmApp_PA.R \name{sdmApp_PA} \alias{sdmApp_PA} -\title{Plot presence/absence map} +\title{Plot a presence/absence map} \usage{ sdmApp_PA(x) } \arguments{ -\item{x}{\code{Raster object}} +\item{x}{A \link[raster:RasterLayer-class]{raster::RasterLayer} object holding presence (\code{TRUE}, +\code{1}) and absence (\code{FALSE}, \code{0}) values.} } \value{ -a \code{ggplot object} +A \link[ggplot2:ggplot]{ggplot2::ggplot} object. } \description{ -Plot presence/absence map +Turns a binary (presence/absence) raster into a \code{ggplot} map, sampling +the layer on a regular grid so that large rasters can be displayed +quickly. } \examples{ -r <- raster::raster(system.file("extdata","AETI.tif",package = "sdmApp")) +r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) r <- r > 4000 sdmApp_PA(r) } +\author{ +Aboubacar HEMA +} diff --git a/man/sdmApp_RasterPlot.Rd b/man/sdmApp_RasterPlot.Rd index d5595f4..6edffab 100644 --- a/man/sdmApp_RasterPlot.Rd +++ b/man/sdmApp_RasterPlot.Rd @@ -2,20 +2,26 @@ % Please edit documentation in R/sdmApp_RasterPlot.R \name{sdmApp_RasterPlot} \alias{sdmApp_RasterPlot} -\title{Plot a raster} +\title{Plot a continuous raster} \usage{ sdmApp_RasterPlot(x) } \arguments{ -\item{x}{\code{Raster object}} +\item{x}{A \link[raster:RasterLayer-class]{raster::RasterLayer} object.} } \value{ -a \code{ggplot object} +A \link[ggplot2:ggplot]{ggplot2::ggplot} object, or \code{NULL} if \code{x} is a base graphics +raster (\code{grDevices::is.raster(x)} is \code{TRUE}). } \description{ -Plot a raster +Displays a continuous raster layer as a \code{ggplot} map using a terrain +colour ramp. The layer is sampled on a regular grid so that large +rasters render quickly. } \examples{ -r <- raster::raster(system.file("extdata","AETI.tif",package = "sdmApp")) +r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) sdmApp_RasterPlot(r) } +\author{ +Aboubacar HEMA +} diff --git a/man/sdmApp_TimesRasters.Rd b/man/sdmApp_TimesRasters.Rd index 058d63b..9dda516 100644 --- a/man/sdmApp_TimesRasters.Rd +++ b/man/sdmApp_TimesRasters.Rd @@ -2,26 +2,35 @@ % Please edit documentation in R/sdmApp_TimesRasters.R \name{sdmApp_TimesRasters} \alias{sdmApp_TimesRasters} -\title{Multiply the probability of occurrence map with the presence/absence map to get a presence map only.} +\title{Mask a probability-of-occurrence map with a presence/absence map} \usage{ sdmApp_TimesRasters(x, y) } \arguments{ -\item{x}{Probability of occurrence map, a \code{Raster object}} +\item{x}{Probability-of-occurrence map, a \link[raster:RasterLayer-class]{raster::RasterLayer} +object.} -\item{y}{Presence/Absence map, a \code{Raster object}} +\item{y}{Presence/absence map, a \link[raster:RasterLayer-class]{raster::RasterLayer} object with +the same extent and resolution as \code{x}.} } \value{ -Probability of occurrence map with only presence +A \link[raster:RasterLayer-class]{raster::RasterLayer} object giving the +probability of occurrence restricted to presence cells. } \description{ -Multiply the probability of occurrence map with the presence/absence map to get a presence map only. +Multiplies a probability-of-occurrence raster by a presence/absence +raster so that only cells predicted as presence keep their probability +value; absence cells become zero. The name of the first raster is +preserved on the result. } \examples{ -r <- raster::raster(system.file("extdata","AETI.tif",package = "sdmApp")) -r2 <- r > raster::cellStats(r, stat='mean', na.rm=TRUE) -r <- r/raster::maxValue(r) -names(r) <- "propability of occurence" -z<-sdmApp_TimesRasters(r,r2) +r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) +r2 <- r > raster::cellStats(r, stat = "mean", na.rm = TRUE) +r <- r / raster::maxValue(r) +names(r) <- "probability of occurrence" +z <- sdmApp_TimesRasters(r, r2) sdmApp_RasterPlot(z) } +\author{ +Aboubacar HEMA +} diff --git a/man/sdmApp_fold_Explorer.Rd b/man/sdmApp_fold_Explorer.Rd index c19e24d..116ee60 100644 --- a/man/sdmApp_fold_Explorer.Rd +++ b/man/sdmApp_fold_Explorer.Rd @@ -2,38 +2,41 @@ % Please edit documentation in R/sdmApp_fold_Explorer.R \name{sdmApp_fold_Explorer} \alias{sdmApp_fold_Explorer} -\title{Explore the generated folds and visualize the placement of folds and distribution of species data over folds.} +\title{Explore spatial cross-validation folds} \usage{ sdmApp_fold_Explorer(blocks, rasterLayer, speciesData, num) } \arguments{ -\item{blocks}{A \code{SpatialBlock} object.} +\item{blocks}{A \code{SpatialBlock} object (from \pkg{blockCV}).} -\item{rasterLayer}{A raster object as background map for visualization.} +\item{rasterLayer}{A raster object used as the background map.} -\item{speciesData}{A simple features (sf) or \code{SpatialPoints} object containing species data (response variable).} +\item{speciesData}{A simple features (\code{sf}) or \code{SpatialPoints} object +holding the species data (the response variable).} -\item{num}{A number of fold to assign as data test set.} +\item{num}{Integer. Index of the fold to display as the test set.} } \value{ -A map showing folds and the species data, that can be used to explore folds. +A combined plot (via \code{\link[cowplot:plot_grid]{cowplot::plot_grid()}}) showing the +training and testing sets for the selected fold. } \description{ -Explore the generated folds and visualize the placement of folds and distribution of species data over folds. +Visualises the placement of a chosen fold and the distribution of the +species data across training and testing sets, on top of a background +raster. Useful for inspecting the folds produced by \pkg{blockCV}. } \examples{ \dontrun{ -# load blockCV package data library(blockCV) awt <- raster::brick(system.file("extdata", "awt.grd", package = "blockCV")) -#import presence-absence species data PA <- read.csv(system.file("extdata", "PA.csv", package = "blockCV")) -#make a sf object from data.frame pa_data <- sf::st_as_sf(PA, coords = c("x", "y"), crs = raster::crs(awt)) -#spatial blocking by specified range and random assignment -sb <- spatialBlock(speciesData = pa_data,species = "Species", -rasterLayer = awt,theRange = 70000,k = 5, -selection = "random",iteration = 100) -sdmApp_fold_Explorer(sb,awt,pa_data,1) +sb <- spatialBlock(speciesData = pa_data, species = "Species", + rasterLayer = awt, theRange = 70000, k = 5, + selection = "random", iteration = 100) +sdmApp_fold_Explorer(sb, awt, pa_data, 1) } } +\author{ +Aboubacar HEMA +} diff --git a/tests/testthat/test-sdmApp.R b/tests/testthat/test-sdmApp.R index 8849056..5c93dfd 100644 --- a/tests/testthat/test-sdmApp.R +++ b/tests/testthat/test-sdmApp.R @@ -1,3 +1,7 @@ -test_that("multiplication works", { - expect_equal(2 * 2, 4) +test_that("sdmApp validates the theme argument", { + expect_error(sdmApp(theme = "not-a-theme"), "Invalid value") +}) + +test_that("sdmApp validates maxRequestSize", { + expect_error(sdmApp(maxRequestSize = "big"), "numeric") }) diff --git a/tests/testthat/test-sdmApp_PA.R b/tests/testthat/test-sdmApp_PA.R index 8849056..c6667a0 100644 --- a/tests/testthat/test-sdmApp_PA.R +++ b/tests/testthat/test-sdmApp_PA.R @@ -1,3 +1,14 @@ -test_that("multiplication works", { - expect_equal(2 * 2, 4) +test_that("sdmApp_PA returns a ggplot from a binary raster", { + skip_if_not_installed("raster") + skip_if_not_installed("ggplot2") + + r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) + pa <- r > 4000 + + p <- sdmApp_PA(pa) + expect_s3_class(p, "ggplot") +}) + +test_that("sdmApp_PA rejects non-raster input", { + expect_error(sdmApp_PA(1:10), "Raster") }) diff --git a/tests/testthat/test-sdmApp_RasterPlot.R b/tests/testthat/test-sdmApp_RasterPlot.R new file mode 100644 index 0000000..2974e20 --- /dev/null +++ b/tests/testthat/test-sdmApp_RasterPlot.R @@ -0,0 +1,13 @@ +test_that("sdmApp_RasterPlot returns a ggplot from a continuous raster", { + skip_if_not_installed("raster") + skip_if_not_installed("ggplot2") + + r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) + p <- sdmApp_RasterPlot(r) + expect_s3_class(p, "ggplot") +}) + +test_that("sdmApp_RasterPlot returns NULL for a base graphics raster", { + m <- matrix(c(0, 1, 0, 1), nrow = 2) + expect_null(sdmApp_RasterPlot(grDevices::as.raster(m))) +}) diff --git a/tests/testthat/test-sdmApp_TimesRasters.R b/tests/testthat/test-sdmApp_TimesRasters.R index 8849056..f839c00 100644 --- a/tests/testthat/test-sdmApp_TimesRasters.R +++ b/tests/testthat/test-sdmApp_TimesRasters.R @@ -1,3 +1,20 @@ -test_that("multiplication works", { - expect_equal(2 * 2, 4) +test_that("sdmApp_TimesRasters masks probabilities by presence", { + skip_if_not_installed("raster") + + r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) + pa <- r > raster::cellStats(r, stat = "mean", na.rm = TRUE) + prob <- r / raster::maxValue(r) + names(prob) <- "probability of occurrence" + + z <- sdmApp_TimesRasters(prob, pa) + + expect_s4_class(z, "RasterLayer") + expect_identical(names(z), names(prob)) + # absence cells (pa == 0) must be zero in the masked output + masked <- raster::values(z)[raster::values(pa) == 0] + expect_true(all(masked == 0 | is.na(masked))) +}) + +test_that("sdmApp_TimesRasters rejects non-raster input", { + expect_error(sdmApp_TimesRasters(1, 2), "Raster") }) diff --git a/vignettes/sdmApp.Rmd b/vignettes/sdmApp.Rmd index 1816c33..029f07d 100644 --- a/vignettes/sdmApp.Rmd +++ b/vignettes/sdmApp.Rmd @@ -1,160 +1,402 @@ --- -title: "sdmApp package: A user-friendly application for species distribution modeling" -author: "Aboubacar HEMA, Babacar NDAO, Louise LEROUX, Abdoul Aziz DIOUF" +title: "Getting started with sdmApp" +author: "Aboubacar HEMA" date: "`r Sys.Date()`" -output: +output: rmarkdown::html_vignette: fig_caption: yes toc: true + toc_depth: 3 vignette: > - %\VignetteIndexEntry{A user-friendly application for species distribution modeling} + %\VignetteIndexEntry{Getting started with sdmApp} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} - --- -# Introduction and Main Features -*sdmApp* is a *R package* containing a *Shiny* application that allows non-expert *R* users to easily model species distribution. It offers a reproducible work flow for species distribution modeling into a single and user friendly environment. *sdmApp* takes *Raster* data (in format supported by the *Raster package*) and species occurrence data (several format supported) as input argument. This *package* provides an interactive graphical user interface (*GUI*). - This document will give an overview of the main functionalities of the graphical user interface. The main features of the *GUI* is: +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.width = 7, + fig.height = 4.2, + fig.align = "center", + dpi = 96, + warning = FALSE, + message = FALSE +) +``` -* Uploading data (*raster* and species occurrence files) -* View correlation between *raster* -* Use [CENFA](https://CRAN.R-project.org/package=CENFA) to select species predictors -* Apply a spatial blocking for cross-validation based on the [blockCV](https://CRAN.R-project.org/package=blockCV) package -* Apply species distribution models with or without a spatial blocking strategy +# Introduction -* Export results -* Keep reproduce (*R* code) by being able do download the underlying code from *sdmApp*. +`sdmApp` is an R package containing a Shiny application that lets non-expert +R users model species distribution. It brings a reproducible workflow for +species distribution modeling into a single, user friendly environment. +`sdmApp` takes raster data (in any format supported by the `raster` package) +and species occurrence data (several formats supported) as input, and +provides an interactive graphical user interface (GUI). - The *GUI* is build around 5 main windows, which can be selected from the navigation bar at the top of the screen. Initially, some of these windows will be empty and their content changes once data (both *raster* and species occurrence files) have been uploaded. - -# Installation +This vignette has two parts. The first shows the small set of mapping +helpers that `sdmApp` exports, using the example data bundled with the +package, so that you can reproduce every figure below on your own machine. +The second describes the graphical interface and the data it expects. -The **sdmApp** is available both GitHub and **CRAN** . It is recommended to install the dependencies of the package. +The main features of the GUI are: -To install the package from GitHub use: +* uploading data (raster predictors and species occurrence files); +* viewing the correlation between raster predictors; +* using [CENFA](https://github.com/rinnan/CENFA) to select species predictors; +* applying spatial blocking for cross-validation, based on the + [blockCV](https://CRAN.R-project.org/package=blockCV) package; +* fitting species distribution models with or without a spatial blocking + strategy; +* exporting results; +* staying reproducible by downloading the underlying R code. -```{r eval=FALSE} -remotes::install_github("Abson-dev/sdmApp", dependencies = TRUE) +The GUI is built around five main windows, selectable from the navigation bar +at the top of the screen. Some of these windows start empty and fill in once +raster and species occurrence data have been uploaded. -``` +# Installation -Or installing from CRAN: +`sdmApp` is not currently on CRAN, so install it from GitHub. `CENFA` is +also off CRAN and must be installed first. -```{r eval=FALSE} -install.packages("sdmApp", dependencies = TRUE) +```{r install, eval = FALSE} +# install.packages("remotes") +remotes::install_github("rinnan/CENFA") +remotes::install_github("Abson-dev/sdmApp", dependencies = TRUE) ``` +To use the MaxEnt model you additionally need a Java JDK (>= 8), the `rJava` +package, and the `maxent.jar` file placed in the `java` directory of the +`dismo` package, which you can locate with: +```{r maxent-path, eval = FALSE} +system.file("java", package = "dismo") +``` + +Everything shown in this vignette works without Java. -```{r eval=FALSE} -# loading the package +```{r library} library(sdmApp) ``` -```{r eval=FALSE} -# Graphical User Interface (GUI) +# Launching the interface + +A single call starts the application in your browser: + +```{r launch, eval = FALSE} sdmApp() ``` -# Package data +`sdmApp()` accepts a few arguments. `maxRequestSize` sets the largest +upload accepted, in megabytes. `theme` selects the style sheet, one of +`"IHSN"` (the default), `"yeti"`, `"journal"` or `"flatly"`. Setting +`shiny.server = TRUE` returns the application as an object rather than +launching it, which is what you want when deploying behind shiny-server. -## Occurence data +```{r launch-args, eval = FALSE} +# accept uploads up to 200 MB and use the flatly theme +sdmApp(maxRequestSize = 200, theme = "flatly") -It is a dataset of 9258 trees georeferenced encompassing 3 (*Faidherbia albida*,*Balanites aegyptiaca* and *Anogeissus leiocarpus*) - species (Ndao et al., 2019). +# return the app object instead of running it +app <- sdmApp(shiny.server = TRUE) +``` + +Before starting, `sdmApp()` checks that the modelling engines it relies on +are installed, and stops with a message naming any that are missing. This +avoids a cryptic failure halfway through a session. + +# The bundled example data + +The package ships a small extract of the Niakhar study area in Senegal. -## Environmental Variables +## Occurrence data -These are a suite of 34 variables -relating to **bioclimatic drivers**, **soil properties**, **water productivity**, **vegetation phenology and productivity**, **watersheds topography**. We preprocessed the environmental -variables by setting them on the same projection system **(WGS 84, UTM, Zone 28N)**, cropping -them with the same extent and resampling them at the same spatial resolution of **250 m**. +`Niakhar.csv` holds 9258 georeferenced trees scored for three species, +*Faidherbia albida*, *Balanites aegyptiaca* and *Anogeissus leiocarpus* +(Ndao et al., 2019). The same table is also provided as `.xlsx`, `.dta`, +`.sav` and `.sas7bdat` so that you can practise uploading each format +through the GUI. + +The file is semicolon separated and uses a comma as decimal mark, so it must +be read with `read.csv2()` rather than `read.csv()`. + +```{r read-occ} +occ_file <- system.file("extdata", "Niakhar.csv", package = "sdmApp") +occ <- utils::read.csv2(occ_file) + +str(occ) +``` + +The first two columns are longitude and latitude in WGS 84. The remaining +three are binary presence indicators, one per species. + +```{r occ-counts} +colSums(occ[, 3:5]) +``` + +For any spatial work, turn the table into an `sf` object. The bundled +rasters are in geographic coordinates, so no reprojection is needed. + +```{r occ-sf} +pa_data <- sf::st_as_sf(occ, coords = c("X_WGS84", "Y_WGS84"), crs = 4326) +pa_data[1:3, ] +``` + +## Environmental variables + +The package bundles 34 raster predictors relating to **bioclimatic +drivers**, **soil properties**, **water productivity**, **vegetation +phenology and productivity** and **watershed topography**. In the original +study the predictors were set on a common projection (WGS 84, UTM zone 28N), +cropped to a common extent and resampled to a common resolution of 250 m. +The extract distributed with the package is aligned on a common grid of +93 by 91 cells in geographic coordinates (EPSG:4326). + +```{r list-rasters} +files <- list.files( + system.file("extdata", package = "sdmApp"), + pattern = "\\.tif$", full.names = FALSE +) +length(files) +head(files, 10) +``` + +Load one of them, the actual evapotranspiration and interception layer: + +```{r load-raster} +r <- raster::raster(system.file("extdata", "AETI.tif", package = "sdmApp")) +r +``` ### Bioclimatic variables -Bioclimatic variables are derived from the monthly temperature and -rainfall values in order to generate more biologically meaningful variables. They represent annual -trends (e.g., mean annual temperature, annual precipitation) seasonality (e.g., annual range in -temperature and precipitation) and extreme or limiting environmental factors (e.g., temperature of -the coldest and warmest month, and precipitation of the wet and dry quarters (1/4 of the year)). -The bioclimatic variables we used was extracted from the worldclim database version 2 -(http://www.worldclim.com/). They are the average for the years 1970-2000 (Fick & Hijmans, -2017) . +Bioclimatic variables are derived from monthly temperature and rainfall +values to produce more biologically meaningful predictors. They capture +annual trends (mean annual temperature, annual precipitation), seasonality +(annual range in temperature and precipitation) and extreme or limiting +factors (temperature of the coldest and warmest month, precipitation of the +wettest and driest quarters). They were extracted from the WorldClim +database version 2 and are averages for the years 1970 to 2000 +(Fick and Hijmans, 2017). ### Soil properties -We used 7 variables of soil properties from the ISRIC - World Soil Information -portal (https://www.isric.org/projects/soil-property-maps-africa-250-m-resolution). The dataset -results from the “Mapping soil properties of Africa at 250 m Resolution” produced from the -Africa Soil Information Service (AfSIS) project. Over 85 thousand samples (over 28 thousand -sampling locations), covering the Period 1950–2012, were used for spatial predictions of soil -properties (Hengl et al., 2015) . +Seven soil property variables come from the ISRIC World Soil Information +portal, produced by the Africa Soil Information Service (AfSIS) project. +More than 85 thousand samples, from over 28 thousand sampling locations and +covering 1950 to 2012, were used for the spatial predictions +(Hengl et al., 2015). ### Water productivity -Two variables of water productivity were retrieved from the FAO WaPOR -database (https://wapor.apps.fao.org/home/1) i.e the Food and Agriculture Organization of the -United Nations (FAO) portal to monitor Water Productivity through Open access of Remotely -sensed derived data The Net biomass water productivity (NBWP) expresses the quantity of -output (total biomass production) in relation to the volume of water beneficially consumed -(through canopy transpiration) in the year, and thus net of soil evaporation. The actual -evapotranspiration and interception (AETI) represents the sum of the soil evaporation (E), -the canopy transpiration (T) and the interception (I) i.e the rainfall intercepted by the leaves -of the plants that will be directly evaporated from their surface (FAO, 2020) . NBWP and -AETI are also agrometeorological variables particularly useful in monitoring how effectively -vegetation uses water to develop biomass (NBWP) and for analyzing the soil-air interface and -plant functioning (AETI – WMO, 2012) +Two water productivity variables were retrieved from the FAO WaPOR database. +Net biomass water productivity (NBWP) expresses total biomass production in +relation to the volume of water beneficially consumed through canopy +transpiration over the year, net of soil evaporation. Actual +evapotranspiration and interception (AETI) is the sum of soil evaporation, +canopy transpiration and interception, that is the rainfall intercepted by +leaves and evaporated directly from their surface (FAO, 2020). Both are +agrometeorological variables useful for monitoring how effectively +vegetation converts water into biomass (NBWP) and for analysing the soil-air +interface and plant functioning (AETI). ### Vegetation phenology and productivity -AFS were described as lanscapes with interactions -between crops, trees and crop practices. That is why we integrated two phenological metrics -related to the season of crop in order to take into account interactions with species distribution. -We derived the phenological metrics from normalized difference vegetation index (NDVI) time -series such as 16-day MODIS NDVI time series (MOD13Q1) using timesat software (Eklundh & -Jönsson, 2011). +Agroforestry systems are landscapes shaped by interactions between crops, +trees and cropping practices. Two phenological metrics related to the +cropping season were therefore included, derived from 16-day MODIS NDVI time +series (MOD13Q1) using the TIMESAT software (Eklundh and Jonsson, 2011). + +### Watershed topography + +Topographic variables were derived with the Soil and Water Assessment Tool +(SWAT) from the 30 m NASA SRTM digital elevation model. The SWAT watershed +delineator was used to extract 69 sub-basins within the study area, in a +vector file whose attribute table carries the topographic variable values +(Winchell et al., 2010). + +# Working with the exported functions + +`sdmApp` exports four functions besides the launcher. They are deliberately +small: each takes a raster (or a set of folds) and returns a `ggplot` +object that you can further customise with the usual `ggplot2` grammar. + +## Plotting a continuous raster + +`sdmApp_RasterPlot()` samples the layer on a regular grid, so it stays fast +on large rasters, and draws it with a terrain colour ramp. + +```{r raster-plot} +sdmApp_RasterPlot(r) +``` + +Because the result is an ordinary `ggplot` object, you can adjust it: + +```{r raster-plot-custom} +sdmApp_RasterPlot(r) + + ggplot2::labs(subtitle = "Niakhar, Senegal") +``` + +## Plotting a presence/absence surface + +`sdmApp_PA()` expects a binary raster and maps absence in red and presence +in green. Here we threshold the AETI layer to build one. + +```{r pa-plot} +pa <- r > 4000 +names(pa) <- "AETI above 4000" + +sdmApp_PA(pa) +``` + +## Masking a probability surface + +A species distribution model produces a probability of occurrence surface. +`sdmApp_TimesRasters()` multiplies it by a presence/absence surface, so that +only the cells predicted as presence keep their probability. The name of the +first raster is carried over to the result. + +```{r times-rasters} +prob <- r / raster::cellStats(r, "max") +names(prob) <- "probability of occurrence" + +masked <- sdmApp_TimesRasters(prob, pa) +names(masked) + +sdmApp_RasterPlot(masked) +``` + +Every exported function validates its inputs, so a mistake fails early and +with a readable message rather than deep inside `ggplot2`: + +```{r validation, error = TRUE} +sdmApp_TimesRasters(1, 2) +``` + +## Exploring spatial cross-validation folds + +Random splits of spatially structured data give over-optimistic error +estimates (Telford and Birks, 2009; Roberts et al., 2017). `sdmApp` relies +on `blockCV` to build spatially separated folds, and +`sdmApp_fold_Explorer()` draws the training and testing points of a chosen +fold on top of a background raster. + +```{r fold-explorer, eval = FALSE} +library(blockCV) + +sb <- spatialBlock( + speciesData = pa_data["Faidherbia.albida"], + species = "Faidherbia.albida", + rasterLayer = r, + theRange = 2000, + k = 5, + selection = "random", + iteration = 100 +) + +sdmApp_fold_Explorer(sb, r, pa_data["Faidherbia.albida"], num = 1) +``` + +Note a compatibility caveat. From `blockCV` version 3.0, `spatialBlock()` is +deprecated in favour of `cv_spatial()`, and the object returned by +`cv_spatial()` is no longer of class `SpatialBlock`. +`sdmApp_fold_Explorer()` currently tests for that class, so it works with the +deprecated `spatialBlock()` but not yet with `cv_spatial()`. Support for the +new class is planned. + +# A typical session in the GUI + +The navigation bar exposes five tabs, which are meant to be visited in order. + +1. **Help/About** gives a short description of the application and of the + modelling options. +2. **Data Upload** takes the raster predictors and the species occurrence + file. Occurrence data can be supplied as CSV, Excel, Stata, SPSS or SAS. + Nothing else in the interface is populated until this step succeeds. +3. **Spatial Analysis** covers predictor screening. You can inspect the + correlation matrix between predictors, run an ecological niche factor + analysis with `CENFA` to rank predictors by their contribution to + marginality and specialisation, estimate the range of spatial + autocorrelation, and build spatial blocks for cross-validation. +4. **Modeling** fits the models. Bioclim, Domain, Mahalanobis, GLM, MaxEnt, + random forest and support vector machines are available, with or without a + spatial blocking strategy, and results can be exported as rasters and + tables. +5. **R-Code** returns the R code corresponding to the session, so the + analysis can be rerun, version controlled and shared. + +A reasonable workflow is therefore: upload, drop strongly correlated +predictors, select predictors with `CENFA`, choose a block size informed by +the spatial autocorrelation range, generate folds, fit one or more models, +compare them, then export both the maps and the R code. + +# Session information + +```{r session-info} +sessionInfo() +``` + +# References + +Bahn, V., McGill, B.J. (2012). Testing the predictive performance of +distribution models. *Oikos*, 122(3), 321-331. -### Watersheds topography +Eklundh, L., Jonsson, P. (2011). *TIMESAT 3.0 Software Manual*. Lund +University, Sweden. -Topographic variables were derived from the Soil & Water -Assessment Tool (SWAT) using the 30 m NASA SRTM digital elevation model -(https://dwtkns.com/srtm30m/). SWAT is a small watershed to river basin-scale model used to -simulate the quality and quantity of surface and ground water and predict the environmental -impact of land use, land management practices, and climate change (https://swat.tamu.edu/). We -used the watershed delineator of SWAT which allows to delineate sub-watersheds within the -study area (Winchell et al., 2010) . We extracted 69 sub-basins in a vector file format with their -attribute table including topographic variables values. +FAO (2020). *WaPOR database methodology*. Food and Agriculture Organization +of the United Nations, Rome. -# References: -- Bahn, V., & McGill, B. J. (2012). Testing the predictive performance of distribution models. Oikos, 122(3), 321–331. - -- Hiemstra, P. H., Pebesma, E. J., Twenhöfel, C. J., & Heuvelink, G. B. (2009). Real-time automatic interpolation of ambient gamma dose rates from the Dutch radioactivity monitoring network. Computers & Geosciences, 35(8), 1711–1721. +Fick, S.E., Hijmans, R.J. (2017). WorldClim 2: new 1 km spatial resolution +climate surfaces for global land areas. *International Journal of +Climatology*, 37(12), 4302-4315. -- Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data Mining, Inference, and Prediction (2nd ed., Vol. 1). Springer series in statistics New York. - -- Leta, S., Fetene, E., Mulatu, T., Amenu, K., Jaleta, M.B., Beyene, T.J., Negussie, H., Revie, C.W., 2019. Modeling the global distribution of Culicoides imicola: an Ensemble approach. Sci. Rep. 9. [doi: -10.1038/s41598-019-50765-1](https://doi.org/10.1038/s41598-019-50765-1) +Hastie, T., Tibshirani, R., Friedman, J. (2009). *The Elements of +Statistical Learning: Data Mining, Inference, and Prediction*, 2nd edition. +Springer, New York. -- Luo, D., Silva, D.P., De Marco Júnior, P., Pimenta, M., Caldas, M.M., 2020. Model approaches to estimate spatial distribution of bee species richness and soybean production in the Brazilian Cerrado during 2000 to 2015. Sci. Total Environ. 737, 139674. [doi: 10.1016/j.scitotenv.2020.139674](https://doi.org/https://doi.org/10.1016/j.scitotenv.2020.139674) +Hengl, T., Heuvelink, G.B.M., Kempen, B., et al. (2015). Mapping soil +properties of Africa at 250 m resolution: random forests significantly +improve current predictions. *PLoS ONE*, 10(6), e0125814. -- Mudereri, B.T., Abdel-Rahman, E.M., Dube, T., Landmann, T., Khan, Z., Kimathi, E., Owino, R., Niassy, S., 2020. Multi-source spatial data-based invasion risk modeling of Striga ( Striga asiatica ) in Zimbabwe. GIScience Remote Sens. 1–19. [doi: 10.1080/15481603.2020.1744250](https://doi.org/10.1080/15481603.2020.1744250) +Hiemstra, P.H., Pebesma, E.J., Twenhofel, C.J., Heuvelink, G.B. (2009). +Real-time automatic interpolation of ambient gamma dose rates from the Dutch +radioactivity monitoring network. *Computers and Geosciences*, 35(8), +1711-1721. -- Ndao B., Leroux, L., Diouf, A.A., Soti, V., Sambou, B., 2019, A remote sensing based approach for optimizing sampling strategies in crop monitoring and crop yield estimation studies, in: Wade, S. (Ed.), Earth Observations and Geospatial Science in Service of Sustainable Development Goals, Southern Space Studies, Springer Nature Switzerland AG 2019, pp. 25–36, [doi: 10.1007/978-3-030-16016-6_3](https://doi.org/10.1007/978-3-030-16016-6_3) +Ndao, B., Leroux, L., Diouf, A.A., Soti, V., Sambou, B. (2019). A remote +sensing based approach for optimizing sampling strategies in crop monitoring +and crop yield estimation studies. In: Wade, S. (Ed.), *Earth Observations +and Geospatial Science in Service of Sustainable Development Goals*. +Springer, pp. 25-36. [doi:10.1007/978-3-030-16016-6_3](https://doi.org/10.1007/978-3-030-16016-6_3) -- O’Sullivan, D., & Unwin, D. J. (2010). Geographic Information Analysis (2nd ed.). John Wiley & Sons. +O’Sullivan, D., Unwin, D.J. (2010). *Geographic Information Analysis*, +2nd edition. John Wiley and Sons. -- Phillips, S. J., Anderson, R. P., Dudík, M., Schapire, R. E., & Blair, M. E. (2017). Opening the black box: an open-source release of Maxent. Ecography. - -- Roberts, D.R., Bahn, V., Ciuti, S., Boyce, M.S., Elith, J., Guillera-Arroita, G., Hauenstein, S., Lahoz-Monfort, J.J., Schröder, B., Thuiller, W., others, 2017. Cross-validation strategies for data with temporal, spatial, hierarchical, or phylogenetic structure. Ecography. 40: 913-929. +Phillips, S.J., Anderson, R.P., Dudik, M., Schapire, R.E., Blair, M.E. +(2017). Opening the black box: an open-source release of Maxent. +*Ecography*, 40(7), 887-893. -- Schindler, S., von Wehrden, H., Poirazidis, K., Hochachka, W.M., Wrbka, T., Kati, V., 2015. Performance of methods to select landscape metrics for modelling species richness. Ecol. Modell. 295, 107–112.[doi: -10.1016/J.ECOLMODEL.2014.05.012](https://doi.org/10.1016/J.ECOLMODEL.2014.05.012) +Rinnan, D.S., Lawler, J. (2019). Climate-niche factor analysis: a spatial +approach to quantifying species vulnerability to climate change. +*Ecography*, 42, 1494-1503. [doi:10.1111/ecog.03937](https://doi.org/10.1111/ecog.03937) -- Simensen, T., Horvath, P., Vollering, J., Erikstad, L., Halvorsen, R., Bryn, A., 2020. Composite landscape predictors improve distribution models of ecosystem types. Divers. Distrib. 26, 928–943. [doi: 10.1111/ddi.13060](https://doi.org/10.1111/ddi.13060) +Roberts, D.R., Bahn, V., Ciuti, S., et al. (2017). Cross-validation +strategies for data with temporal, spatial, hierarchical, or phylogenetic +structure. *Ecography*, 40(8), 913-929. -- Telford, R. J., & Birks, H. J. B. (2009). Evaluation of transfer functions in spatially structured environments. Quaternary Science Reviews, 28(13), 1309–1316. +Telford, R.J., Birks, H.J.B. (2009). Evaluation of transfer functions in +spatially structured environments. *Quaternary Science Reviews*, 28(13), +1309-1316. -- Thuiller, W., Georges, D., Engler, R., & Breiner, F. (2017). biomod2: Ensemble Platform for Species Distribution Modeling. R package version 3.3-7. https://CRAN.R-project.org/package=biomod2. +Valavi, R., Elith, J., Lahoz-Monfort, J.J., Guillera-Arroita, G. (2019). +blockCV: An R package for generating spatially or environmentally separated +folds for k-fold cross-validation of species distribution models. +*Methods in Ecology and Evolution*, 10, 225-232. +[doi:10.1111/2041-210X.13107](https://doi.org/10.1111/2041-210X.13107) -- Valavi R, Elith J, Lahoz-Monfort JJ, Guillera-Arroita G. blockCV: An R package for generating spatially or environmentally separated folds for k-fold cross-validation of species distribution models. *Methods Ecol Evol.* 2019; 10:225–232. [doi: 10.1111/2041-210X.13107](https://doi.org/10.1111/2041-210X.13107) +Winchell, M., Srinivasan, R., Di Luzio, M., Arnold, J. (2010). +*ArcSWAT Interface for SWAT2009: User’s Guide*. Texas Agricultural +Experiment Station, Texas.