Calculate total cover per species in vegetation transects

Calculate total cover per species in vegetation transects

#'
#' Calculate total cover per species in each transect, layer within transect, etc.
#' Optionally, the function can also calculate length of bare ground (as ground not covered
#' by any plant species).
#'
#' @export
#' @author F Rodriguez-Sanchez
#' @importFrom plyr ddply
#' @importFrom gtools odd even
#' @importFrom tidyr gather
#' @importFrom dplyr arrange_
#' @param df A dataframe with one column per recorded species, plus additional columns
#' to identify transects (and possibly sections within transects, shrub or tree layers, etc).
#' For each species, introduce initial and ending distances every time the species appears along
#' the transect (see example).
#' @param split.cols A character vector with the names of the variables in \code{df}
#' that identify transects or any other structure of the data (e.g. sections within transects,
#' shrub or tree layers, etc.)
#' @param first.spcol Integer. Number of the first column containing species cover data.
#' @param last.spcol Integer. Number of the last column containing species cover data
#' (last column in \code{df} by default).
#' @param check.incremental Logical (default is \code{TRUE}). Check that cover data are
#' sorted incrementally? Must be \code{TRUE} if calculating bare cover with this function.
#' @param bare Logical. Calculate bare cover? Default is \code{TRUE}.
#' @param tr.length Numeric. Total transect length (e.g. in meters).
#' @param precision Numeric. Precision of species cover measurements. Only used to calculate
#' bare cover by exclusion.
#' @param prop Logical. If \code{TRUE}, results are expressed as proportion
#' of \code{tr.length} covered by each species. If \code{FALSE} (the default), results represent
#' linear absolute length covered by each species.
#' @param long.format Logical. Produce the output dataframe in long format? Default is wide format.
#' See \code{\link[tidyr]{gather}}.
#' @param show.progress Logical. Show progress bar?
#' @return A dataframe with total cover values per species and transect (plus optionally bare ground).
#' @examples
#' df <- data.frame(transect = sort(rep(c(1,2,3), 20)),
#'                  section = rep(1, 60),
#'                  species1 = c(replicate(3, sort(round(runif(20, 0, 100), digits = 1)))),
#'                  species2 = c(sort(round(runif(8, 0, 100), digits = 1)), rep(NA, times = 60 - 8)))
#' View(df)
#' calculate_cover(df, split.cols = "transect")
#' calculate_cover(df, split.cols = "transect", long.format = TRUE)

calculate_cover <- function(df, split.cols,
                            first.spcol, last.spcol = ncol(df),
                            tr.length,
                            bare = FALSE, precision,
                            check.incremental = TRUE,
                            prop = FALSE, long.format = FALSE,
                            show.progress = TRUE){


  out <- plyr::ddply(df, split.cols, sotobosque:::calculate_cover_transect,
                     # calculate_cover_transect args below:
                     id.col = split.cols[1],
                     first.col = first.spcol, last.col = last.spcol,
                     check.increm = check.incremental,
                     calc.bare = bare, tr.len = tr.length, precis = precision,
                     # back to ddply args:
                     .progress = ifelse(show.progress, "text", "none"))


  if (prop){
    out[, (length(split.cols)+1):ncol(out)] <- out[, (length(split.cols)+1):ncol(out)] / tr.length
  }

  if (long.format){
    out.long <- tidyr::gather(out, "species", "cover", (length(split.cols)+1):ncol(out))
    out <- dplyr::arrange_(out.long, .dots = split.cols)
  }

  out

}



calculate_cover_transect <- function(df.split, id.col,
                                     first.col, last.col,
                                     check.increm,
                                     calc.bare, tr.len, precis) {


  fdata <- df.split[, first.col:last.col]  # retain cover data only; exclude other columns


  ### check that number of values is even for each column

  nvals <- apply(fdata, 2, function(x) sum(!is.na(x)))
  if (any(gtools::odd(nvals))){
    stop("\nIn transect ", as.character(df.split[1, id.col]),
         ", the following taxa do not have an even number of values:\n",
         paste(names(fdata[which(gtools::odd(nvals))]), collapse = " "),
         "\nPlease check your data.")
  }



  ### check that values are always incrementing positively

  if (calc.bare) check.increm <- TRUE  # bare cover calculations require cover data sorted incrementally

  if (check.increm){

    increm <- apply(fdata, 2, is.unsorted, na.rm = TRUE, strictly = TRUE)

    if (any(increm)){
      stop("\nIn transect ", as.character(df.split[1, id.col]),
           ", cover values for the following taxa seem not to be sorted incrementally:\n",
           paste(names(fdata[which(increm)]), collapse = " "),
           "\nPlease check your data.")
    }

  }



  ### Now add up values to get total cover per species

  cover.allsp <- apply(fdata, 2, addup)


  ## Calculate bare cover

  if (calc.bare){

    cover.sp <- apply(fdata, 2, covered_species, precis, tr.len)
    bare.c <- apply(cover.sp, 1, function(x) ifelse(isTRUE(any(x)), 0, precis))
    bare.cover <- round(sum(bare.c)/precis) * precis


    cover.allsp <- c(cover.allsp, bare = bare.cover)

  } else cover.allsp

  ## Check that total cover values are < total transect length
  if (any(cover.allsp > tr.len)){
    stop("\nIn transect ", as.character(df.split[1, id.col]),
         ", total cover for ",
         paste(names(cover.allsp[which(cover.allsp > tr.len)]), collapse = " "),
         " exceeds the transect length (", tr.len, ")")
  }

  return(cover.allsp)

}



addup <- function(species){

  species <- as.matrix(species)
  sum(species[even(row(species))] - species[odd(row(species))], na.rm = TRUE)
}


covered_species <- function(species, precision, tr.length){

  species <- as.matrix(species)
  mat <- matrix(c(species[odd(row(species))], species[even(row(species))]),
                nrow(species)/2, ncol = 2)

  spcover <- as.character(unlist(apply(mat, 1, function(x) {
    if (!is.na(x[1])) {seq(from = x[1], to = x[2], by = precision)}
  })))

  # to avoid overestimating species cover, eliminate all starting points
  spcover2 <- spcover[!spcover %in% as.character(mat[, 1])]

  allvals <- seq(precision, tr.length, by = precision)

  as.character(allvals) %in% spcover2

}


Learn More :