Nothing
#' Flag potential issues in matrices of a COM(P)ADRE database
#'
#' @description
#' Adds columns to the data slot of a `CompadreDB` object that flag potential
#' problems in the matrix population models. These columns can subsequently be
#' used to subset the database by logical argument.
#'
#' Optional checks include:
#' \itemize{
#' \item \code{check_NA_A}: missing values in `matA`? Missing (`NA`) values in
#' matrices prevent most calculations using those matrices.
#' \item \code{check_NA_U}: missing values in `matU`? Missing (`NA`) values in
#' matrices prevent most calculations using those matrices.
#' \item \code{check_NA_F}: missing values in `matF`? Missing (`NA`) values in
#' matrices prevent most calculations using those matrices.
#' \item \code{check_NA_C}: missing values in `matC`? Missing (`NA`) values in
#' matrices prevent most calculations using those matrices.
#' \item \code{check_zero_U}: `matU` all zeros (including `NA`)? Submatrices
#' composed entirely of zero values can be problematic. There may be good
#' biological reasons for this phenomenon. For example, in the particular
#' focal population in the particular focal year, there was truly zero
#' survival recorded. Nevertheless, zero-value submatrices can cause some
#' calculations to fail and it may be necessary to exclude them.
#' \item \code{check_zero_F}: `matF` all zeros (including `NA`)? Submatrices
#' composed entirely of zero values can be problematic. There may be good
#' biological reasons for this phenomenon. For example, in the particular
#' focal population in the particular focal year, there was truly zero
#' reproduction recorded. Nevertheless, zero-value submatrices can cause some
#' calculations to fail and it may be necessary to exclude them.
#' \item \code{check_zero_U_colsum}: Columns of `matU` that sum to zero imply
#' that there is is no survival from that particular stage. This may be a
#' perfectly valid parameterisation for a particular year/place but is
#' biologically unreasonable in the longer term and users may wish to exclude
#' problematic matrices from their analysis.
#' \item \code{check_singular_U}: `matU` singular? Matrices are said to be
#' singular if they cannot be inverted. Inversion is required for many matrix
#' calculations and, therefore, singularity can cause some calculations to
#' fail.
#' \item \code{check_component_sum}: do `matU`/`matF`/`matC` submatrices sum
#' to `matA` (see \emph{Details})? A complete MPM (`matA`) can be split into
#' its component submatrices (i.e., `matU`, `matF` and `matC`). The sum of
#' these submatrices should equal the complete MPM (i.e., `matA` = `matU` +
#' `matF` + `matC`). Sometimes, however, errors occur so that the submatrices
#' do NOT sum to `matA`. Normally, this is caused by rounding errors, but more
#' significant errors are possible.
#' \item \code{check_ergodic}: is `matA` ergodic (see
#' \code{\link[popdemo]{isErgodic}})? Some matrix calculations require that
#' the MPM (`matA`) be ergodic. Ergodic MPMs are those where there is a single
#' asymptotic stable state that does not depend on initial stage structure.
#' Conversely, non-ergodic MPMs are those where there are multiple asymptotic
#' stable states, which depend on initial stage structure. MPMs that are
#' non-ergodic are usually biologically unreasonable, both in terms of their
#' life cycle description and their projected dynamics. They cause some
#' calculations to fail.
#' \item \code{check_irreducible}: is `matA` irreducible (see
#' \code{\link[popdemo]{isIrreducible}})? Some matrix calculations require
#' that the MPM (`matA`) be irreducible. Irreducible MPMs are those where
#' parameterised transition rates facilitate pathways from all stages to all
#' other stages. Conversely, reducible MPMs depict incomplete life cycles
#' where pathways from all stages to every other stage are not possible. MPMs
#' that are reducible are usually biologically unreasonable, both in terms of
#' their life cycle description and their projected dynamics. They cause some
#' calculations to fail. Irreducibility is necessary but not sufficient for
#' ergodicity.
#' \item \code{check_primitive}: is `matA` primitive (see
#' \code{\link[popdemo]{isPrimitive}})? A primitive matrix is non-negative
#' matrix that is irreducible and has only a single eigenvalue of maximum
#' modulus. This check is therefore redundant due to the overlap with
#' `check_irreducible` and `checkErdogic`.
#' \item \code{check_surv_gte_1}: does `matU` contains values that are equal
#' to or greater than 1? Survival is bounded between 0 and 1. Values in excess
#' of 1 are biologically unreasonable.
#' }
#'
#' @param cdb A CompadreDB object
#' @param checks Character vector specifying which checks to run.
#'
#' Defaults to all, i.e. \code{c("check_NA_A", "check_NA_U", "check_NA_F",
#' "check_NA_C", "check_zero_U", "check_singular_U", "check_component_sum",
#' "check_ergodic", "check_irreducible", "check_primitive",
#' "check_surv_gte_1")}
#'
#' @return Returns \code{cdb} with extra columns appended to the data slot
#' (columns have the same names as the corresponding elements of
#' \code{checks}) to indicate (TRUE/FALSE) whether there are potential
#' problems with the matrices corresponding to a given row of the data.
#'
#' @details
#' For the flag \code{check_component_sum}, a value of \code{NA} will be
#' returned if the matrix sum of matU, matF, and matC consists only of zeros
#' and/or \code{NA}, indicating that the matrix has not been split.
#'
#' @author Owen Jones <jones@@biology.sdu.dk>
#' @author Julia Jones <juliajones@@biology.sdu.dk>
#' @author Roberto Salguero-Gomez <rob.salguero@@zoo.ox.ac.uk>
#' @author Danny Buss <dlb50@@cam.ac.uk>
#' @author Patrick Barks <patrick.barks@@gmail.com>
#'
#' @family data checking
#'
#' @references Stott, I., Townley, S., & Carslake, D. 2010. On reducibility and
#' ergodicity of population projection matrix models. Methods in Ecology and
#' Evolution. 1 (3), 242-252
#'
#' @examples
#' CompadreFlag <- cdb_flag(Compadre)
#'
#' # only check whether matA has missing values, and whether matA is ergodic
#' CompadreFlag <- cdb_flag(Compadre, checks = c("check_NA_A", "check_ergodic"))
#' @importFrom popdemo isErgodic isIrreducible isPrimitive
#' @importFrom methods new
#' @export cdb_flag
cdb_flag <- function(cdb, checks = c(
"check_NA_A",
"check_NA_U",
"check_NA_F",
"check_NA_C",
"check_zero_U",
"check_zero_F",
"check_zero_C",
"check_zero_U_colsum",
"check_singular_U",
"check_component_sum",
"check_ergodic",
"check_irreducible",
"check_primitive",
"check_surv_gte_1"
)) {
if (!inherits(cdb, "CompadreDB")) {
stop("cdb must be of class CompadreDB. See function as_cdb")
}
checks_allow <- c(
"check_NA_A",
"check_NA_U",
"check_NA_F",
"check_NA_C",
"check_zero_U",
"check_zero_F",
"check_zero_C",
"check_zero_U_colsum",
"check_singular_U",
"check_component_sum",
"check_ergodic",
"check_irreducible",
"check_primitive",
"check_surv_gte_1"
)
checks_check <- checks %in% checks_allow
if (!any(checks_check)) {
stop("The following elements of argument 'checks' are not valid: ",
toString(checks[!checks_check]),
call. = FALSE
)
}
dat <- cdb@data
matA <- matA(cdb)
matU <- matU(cdb)
matF <- matF(cdb)
matC <- matC(cdb)
# calculate outside conditionals because may be required later
vec_NA_A <- vapply(matA, function(x) anyNA(x), FALSE)
vec_NA_U <- vapply(matU, function(x) anyNA(x), FALSE)
vec_NA_F <- vapply(matF, function(x) anyNA(x), FALSE)
vec_NA_C <- vapply(matC, function(x) anyNA(x), FALSE)
if ("check_NA_A" %in% checks) {
dat$check_NA_A <- vec_NA_A
}
if ("check_NA_U" %in% checks) {
dat$check_NA_U <- vec_NA_U
}
if ("check_NA_F" %in% checks) {
dat$check_NA_F <- vec_NA_F
}
if ("check_NA_C" %in% checks) {
dat$check_NA_C <- vec_NA_C
}
if ("check_zero_U" %in% checks) {
dat$check_zero_U <- vapply(matU, function(x) all(x == 0 | is.na(x)), FALSE)
}
if ("check_zero_F" %in% checks) {
dat$check_zero_F <- vapply(matF, function(x) all(x == 0 | is.na(x)), FALSE)
}
if ("check_zero_C" %in% checks) {
dat$check_zero_C <- vapply(matC, function(x) all(x == 0 | is.na(x)), FALSE)
}
if ("check_zero_U_colsum" %in% checks) {
dat$check_zero_U_colsum <- vapply(
matU,
function(x) {
any(colSums(x,
na.rm = TRUE
) == 0)
}, FALSE
)
}
if ("check_singular_U" %in% checks) {
dat$check_singular_U <- mapply(
CheckMats,
has_na = vec_NA_U,
mat = matU,
MoreArgs = list(fn = CheckSingular)
)
}
if ("check_component_sum" %in% checks) {
dat$check_component_sum <- unlist(Map(ComponentSum,
mA = matA,
mU = matU,
mF = matF,
mC = matC
))
}
if ("check_ergodic" %in% checks) {
dat$check_ergodic <- mapply(
CheckMats,
has_na = vec_NA_A,
mat = matA,
MoreArgs = list(fn = isErgodic)
)
}
if ("check_irreducible" %in% checks) {
dat$check_irreducible <- mapply(
CheckMats,
has_na = vec_NA_A,
mat = matA,
MoreArgs = list(fn = isIrreducible)
)
}
if ("check_primitive" %in% checks) {
dat$check_primitive <- mapply(
CheckMats,
has_na = vec_NA_A,
mat = matA,
MoreArgs = list(fn = isPrimitive)
)
}
maxifnotNAs <- function(x) {
if (sum(is.na(x)) == length(x)) {
return(NA)
} else {
return(max(x, na.rm = TRUE))
}
}
if ("check_surv_gte_1" %in% checks) {
dat$check_surv_gte_1 <- vapply(matU, maxifnotNAs, numeric(1)) >= 1
}
new("CompadreDB",
data = dat,
version = cdb@version
)
}
# utilities
#' Check matrices for NA values and apply a function
#'
#' This function checks a matrix for NA values and applies a specified function
#' to it. If the matrix contains any NA values, the function returns NA.
#'
#' @param has_na A logical value indicating whether the matrix may contain NA
#' values
#' @param mat A matrix
#' @param fn A function to apply to the matrix
#'
#' @return The result of applying the function to the matrix, or NA if the
#' matrix contains any NA values
#' @keywords internal
#' @noRd
CheckMats <- function(has_na, mat, fn) {
fn <- match.fun(fn)
ifelse(has_na, NA, fn(mat))
}
#' Check if a matrix is singular
#'
#' This function checks if a matrix is singular by attempting to calculate its
#' fundamental matrix using the \code{\link{solve}} function. If the matrix is
#' singular, the function returns `TRUE`.
#' s
#' @param matU A matrix
#'
#' @return A logical value indicating whether the matrix is singular
#' @keywords internal
#' @noRd
CheckSingular <- function(matU) {
# try calculating fundamental matrix
N <- try(solve(diag(nrow(matU)) - matU), silent = TRUE)
# flag if singular
out <- inherits(N, "try-error") && grepl("singular", N[1], fixed = TRUE)
return(out)
}
#' Calculate the sum of three matrices and compare to a reference matrix
#'
#' This function calculates the sum of three matrices (`mU`, `mF`, and `mC`) and
#' compares it to a reference matrix (`mA`). If the sum of the three matrices is
#' equal to the reference matrix, the function returns `TRUE.` Otherwise, it
#' returns `FALSE` or `NA` if the sum of the three matrices is zero or contains
#' NA values.
#'
#' @param mA A matrix
#' @param mU A matrix
#' @param mF A matrix
#' @param mC A matrix
#'
#' @return A logical value indicating whether the sum of the three matrices is
#' equal to the reference matrix
#' @keywords internal
#' @noRd
ComponentSum <- function(mA, mU, mF, mC) {
mat_dim <- nrow(mA)
if (all(is.na(mU))) {
mU <- matrix(0, mat_dim, mat_dim)
}
if (all(is.na(mF))) {
mF <- matrix(0, mat_dim, mat_dim)
}
if (all(is.na(mC))) {
mC <- matrix(0, mat_dim, mat_dim)
}
mat_sum <- mU + mF + mC
if (all(mat_sum == 0 | is.na(mat_sum))) {
out <- NA
} else {
val_check <- unlist(Map(
function(x, y) {
isTRUE(all.equal(x, y))
},
x = mat_sum, y = mA
))
out <- all(val_check)
}
return(out)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.