R/ct_to_occupancy.R

Defines functions ct_to_occupancy

Documented in ct_to_occupancy

#' Convert camera trap data to occupancy format
#'
#' This function transforms camera trap detection data into an occupancy format
#' suitable for analysis. It aggregates detections into user-defined time windows
#' and optionally converts counts into presence-absence (0/1) data.
#'
#' @param data A data frame containing camera trap detection records.
#' @param date_column The name of the column containing detection dates.
#' @param format a character string. If not specified when converting from a
#' character representation, it will try c("%Y-%m-%d", "%Y/%m/%d") one by one,
#' and give an error if none works. Otherwise, the processing is
#' via [strptime()] whose help page describes available conversion specifications.
#' @param site_column The name of the column identifying sampling sites.
#' @param species_column The name of the column containing species names.
#' Can be NULL if species information is not needed.
#' @param size_column The name of the column representing detection counts.
#' @param by_day An integer specifying the number of days per time window (default: `7`).
#' @param presence_absence Logical. If `TRUE`, converts counts to presence-absence
#' data (1 = detected, 0 = not detected). Default is `TRUE`.
#'
#' @return A wide-format data frame where rows represent sites (and optionally species),
#' and columns represent detection windows. Values indicate either detection counts
#' or presence-absence (0/1).
#'
#' @seealso [ct_to_community()]
#'
#'
#' @examples
#'
#' data <- data.frame(
#'   date = c("01-01-2023", "03-01-2023", "10-01-2023", "15-01-2023"),
#'   site = c("A", "A", "B", "B"),
#'   species = c("Tiger", "Tiger", "Deer", "Deer"),
#'   count = c(1, 2, 3, 1)
#' )
#'
#' occupancy_data <- ct_to_occupancy(
#'   data,
#'   date_column = date,
#'   site_column = site,
#'   species_column = species,
#'   size_column = count,
#'   by_day = 7,
#'   presence_absence = TRUE
#' )
#'
#' occupancy_data
#'
#'
#' @import dplyr tidyr rlang
#'
#' @export
ct_to_occupancy <- function(data,
                            date_column,
                            format = "%Y-%m-%d",
                            site_column,
                            species_column,
                            size_column,
                            by_day = 7,
                            presence_absence = TRUE
) {

  date_col <- site_col <- species_col <- size_col <- NULL
  try({date_col <- rlang::as_name(rlang::enquo(date_column))}, silent = TRUE)
  try({site_col <- rlang::as_name(rlang::enquo(site_column))}, silent = TRUE)
  try({species_col <- rlang::as_name(rlang::enquo(species_column))}, silent = TRUE)
  try({size_col <- rlang::as_name(rlang::enquo(size_column))}, silent = TRUE)

  if (is.null(size_col)) {rlang::abort("Size column is required to identify detection.")}
  if (is.null(site_col)) {rlang::abort("Site column must be provided.")}
  if (is.null(date_col)) {rlang::abort("Date column is required.")}

  # Select part of data
  cols <- c(date_col, site_col, species_col, size_col)
  data <- data %>%
    dplyr::select(dplyr::all_of(cols))

  # Select and convert date
  data[[date_col]] <- as.Date(as.character(data[[date_col]]), format = format)

  # Create date windows based on the user-defined interval
  min_date <- min(data[[date_col]])
  max_date <- max(data[[date_col]])
  date_windows <- seq(min_date, max_date, by = paste(by_day, "days"))

  # Create labels for the date windows
  window_labels <- paste(date_windows[-length(date_windows)],
                         date_windows[-1] - 1, sep = " to ")

  # Assign each date to a window
  data$window <- cut(data[[date_col]], breaks = date_windows,
                     labels = window_labels, right = FALSE)

  # Create a complete grid of all combinations of site, species, and window
  if(!is.null(species_col)){
    complete_grid <- expand.grid(
      site = unique(data[[site_col]]),
      species = unique(data[[species_col]]),
      window = window_labels,
      stringsAsFactors = FALSE
    ) %>% dplyr::rename("{site_col}" := site, "{species_col}" := species)

  }else{
    complete_grid <- expand.grid(
      site = unique(data[[site_col]]),
      window = window_labels,
      stringsAsFactors = FALSE
    ) %>% dplyr::rename("{site_col}" := site)
  }

  # Aggregate the data by site, species, and window
  formula_ <- paste(size_col, "~", paste0(c(cols[!cols %in% c(date_col, size_col)], "window"), collapse = " + "))
  formula_ <- as.formula(formula_)
  aggregated_data <- aggregate(formula_, data = data, sum)

  # Merge the aggregated data with the complete grid to ensure all combinations are present
  complete_data <- merge(complete_grid, aggregated_data, by = c(site_col, species_col, "window"), all.x = TRUE)
  complete_data[is.na(complete_data[[size_col]]), size_col] <- 0

  # Reshape the data to wide format
  wide_data <- complete_data %>%
    tidyr::pivot_wider(names_from = window, values_from = !!size_col, values_fill = 0)

  # Produce 0/1 data if accepted
  remaining_col <- cols[! cols %in% c(size_col, date_col)]
  window_cols <- colnames(wide_data)[! colnames(wide_data) %in% remaining_col]

  if (presence_absence) {
    wide_data[, window_cols] <- ifelse(wide_data[, window_cols] > 1, 1, 0)
  }

  return(wide_data)
}

Try the ct package in your browser

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

ct documentation built on Oct. 22, 2026, 1:07 a.m.