diff --git a/DESCRIPTION b/DESCRIPTION index 401b376..27d6f4e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -57,14 +57,15 @@ Imports: portalr (>= 0.3.2), purrr, rdataretriever (>= 2.0.0), - reticulate (>= 1.14.9002), + reticulate (>= 1.15), rlang, RSQLite, rstudioapi, stringr, tibble, tidyr, - usethis + usethis, + vctrs Suggests: covr, knitr, @@ -76,9 +77,6 @@ Suggests: visNetwork VignetteBuilder: knitr -Remotes: - rstudio/reticulate, - weecology/portalr Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) diff --git a/NAMESPACE b/NAMESPACE index 8ee186a..37dbf35 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -40,10 +40,19 @@ export(get_portal_rodents) export(get_sdl_data) export(get_sgs_data) export(get_times_from_data) +export(has_integer_times) +export(has_missing_samples) export(import_retriever_data) export(install_retriever_data) +export(interpolate_missing_samples) export(interpolate_obs) export(invoke) +export(is_equitimed) +export(is_evenly_sampled) +export(is_fully_sampled) +export(make_equitimed) +export(make_evenly_sampled) +export(make_integer_times) export(normalize_effort) export(normalize_obs) export(normalize_times) diff --git a/R/create_MATSS_compendium.R b/R/create_MATSS_compendium.R index 7b31cbb..ee0a7d6 100644 --- a/R/create_MATSS_compendium.R +++ b/R/create_MATSS_compendium.R @@ -56,7 +56,8 @@ create_MATSS_compendium <- function(path, matss_ref_key = matss_citation$key), package = "MATSS") if (DEPLOY) - preamble_text <- ":rotating_light: **THIS IS AN AUTO-GENERATED EXAMPLE COMPENDIUM** :rotating_light:\n" + preamble_text <- c(":rotating_light: **THIS IS AN AUTO-GENERATED EXAMPLE COMPENDIUM** :rotating_light:\n", + "*last updated: ", Sys.Date(), "*\n") else preamble_text <- "" diff --git a/R/utils-data-processing.R b/R/utils-data-processing.R new file mode 100644 index 0000000..43466e7 --- /dev/null +++ b/R/utils-data-processing.R @@ -0,0 +1,373 @@ +#' @title Check that the times of a dataset are evenly sampled +#' @aliases is_evenly_sampled +#' +#' @param data dataset to check +#' @param period period to check the times against (if `NULL`, first check to +#' see if there is a known `period` set in the metadata, otherwise assumes 1) +#' @param tol tolerance for the period +#' +#' @return `TRUE` or `FALSE` +#' +#' @export +is_equitimed <- function(data, period = NULL, tol = 1e-06) +{ + stopifnot(check_data_format(data)) + + full_times <- get_full_times(data, period, tol) + times <- get_times_from_data(data) + isTRUE(all.equal(times, full_times)) +} + +#' @export +is_evenly_sampled <- is_equitimed + +#' @title Insert rows if necessary so that time series are evenly sampled +#' @aliases make_evenly_sampled +#' +#' @param data dataset to modify +#' @inheritParams is_equitimed +#' @param method one of `c("mean", "method", "closest")` that determines how +#' the rows of the original data will get coerced into the output here. +#' @inheritParams base::mean +#' +#' @return the dataset, with rows coerced according to the equitimed time +#' indices, and additional empty rows inserted if needed +#' +#' @details First, `get_full_times()` computes the sequence of time index values +#' at a regular sampling interval of period. These will be the final time +#' index values for the output. *Some* set of rows of the original dataset +#' will map to each of these time indices. +#' +#' The `method` argument determines how these rows get coerced: +#' \describe{ +#' \item{mean}{the values in the rows are averaged together using `mean`} +#' \item{median}{the values in the rows are averaged together using `median`} +#' \item{closest}{the values in the row that is closest in time to the +#' desired time index are used.} +#' } +#' +#' @export +make_equitimed <- function(data, period = NULL, tol = 1e-06, + method = c("mean", "method", "closest"), + na.rm = TRUE) +{ + stopifnot(check_data_format(data)) + + full_times <- get_full_times(data, period, tol) + if (is.null(full_times)) + { + stop("Unable to construct an evenly spaced time index.") + } + + times <- get_times_from_data(data) + if (isTRUE(all.equal(times, full_times))) + { + message("Dataset is already evenly sampled in time.") + return(invisible(data)) + } + + # generate empty matrices to hold final abundance and covariates + abundance <- matrix(NA, nrow = length(full_times), ncol = NCOL(data$abundance)) + covariates <- data$covariates[0, , drop = FALSE] + + # compute separation between times and full_times + times_dist <- outer(times, full_times, function(a, b) {abs(b - a)}) + + # fill abundance and covariates + method <- match.arg(method) + switch(method, + mean = { + idx <- times_dist <= tol + for (i in seq_along(full_times)) + { + abundance[i, ] <- colMeans(data$abundance[idx[, i], , drop = FALSE], na.rm = na.rm) + covariates[i, ] <- purrr::map_dfc(data$covariates[idx[, i], , drop = FALSE], mean, na.rm = TRUE) + } + }, + median = { + idx <- times_dist <= tol + for (i in seq_along(full_times)) + { + abundance[i, ] <- apply(data$abundance[idx[, i], , drop = FALSE], 2, median, na.rm = na.rm) + covariates[i, ] <- purrr::map_dfc(data$covariates[idx[, i], , drop = FALSE], median, na.rm = TRUE) + } + }, + closest = { + idx <- apply(times_dist, 2, which.min) + abundance <- data$abundance[idx,] + covariates <- data$covariates[idx,] + }) + + # restore column names and convert to tibbles + colnames(abundance) <- colnames(data$abundance) + abundance <- tibble::as_tibble(abundance) + covariates <- tibble::as_tibble(covariates) + + # make sure times column is properly filled + time_var <- resolve_covariate_variable(data, "timename") + if (is.null(time_var)) + { + # make sure timename variable is unique + new_col_names <- vctrs::vec_as_names(c(colnames(covariates), "time"), + repair = "unique", quiet = TRUE) + time_var <- tail(new_col_names, 1) + data$metadata$timename <- time_var + } + covariates[time_var] <- full_times + + # assemble data to return + out <- list(abundance = abundance, + covariates = covariates, + metadata = data$metadata) + attr(out, "class") <- "matssdata" + + return(out) +} + +#' @export +make_evenly_sampled <- make_equitimed + +#' @title Check if a dataset has integer times +#' +#' @param data dataset to check +#' +#' @return `TRUE` or `FALSE` +#' +#' @details If the times are already integer or Date, true. Otherwise FALSE, +#' with a message if times are missing, or if times could potentially be +#' rounded. +#' +#' @export +has_integer_times <- function(data) +{ + # check for existence of times + times <- get_times_from_data(data) + if (is.null(times)) + { + message("Dataset is missing times.") + return(FALSE) + } + + # check for integer times + if (is.integer(times) || inherits(times, "Date")) + { + return(TRUE) + } else if (all(is.wholenumber(times))) { + message("Dataset has close to integer times, but they need to be rounded.\n", + "Perhaps you want to call ", usethis::ui_code("make_integer_times()"), ".\n") + return(FALSE) + } + + # otherwise + return(FALSE) +} + +#' @title Add a time variable with integer values for evenly sampled data +#' +#' @param data dataset to modify +#' @inheritParams is_equitimed +#' +#' @return the dataset, with integer times +#' +#' @details First, check if the data are evenly sampled in time. If not, we +#' exit early. Next, if the times are already integer or Date, we don't do +#' anything. If the times are numeric, but roundable to integer, we round. +#' Otherwise, we add a new variable to `covariates` from 1:n and designate +#' this variable as the `timename`. +#' +#' @export +make_integer_times <- function(data, period = NULL, tol = 1e-06) +{ + times <- get_times_from_data(data) + + # do checks based on existing times + if (!is.null(times)) + { + # check for equitimed + if (!is_equitimed(data, period, tol)) + { + stop(c("Dataset is not evenly sampled in time.\n", + "Perhaps you want to call ", usethis::ui_code("make_equitimed()"), " first.\n")) + } + + # check for integer times + if (is.integer(times)) + { + message("Dataset is evenly sampled with integer times already.") + return(invisible(data)) + } else if (inherits(times, "Date")) { + message("Dataset is evenly sampled with `Date` formatted times already.") + return(invisible(data)) + } else if (all(is.wholenumber(times))) { + message("Dataset is evenly sampled with (close to) integer times already.") + message("Rounding times to integer and replacing them...") + time_var <- data$metadata$timename + data$covariates[time_var] <- as.integer(round(times)) + return(invisible(data)) + } + } + + # add time + times <- seq_len(NROW(data$abundance)) + if (is.null(data$covariates)) # create covariates + { + time_var <- "time" + data$covariates <- tibble::tibble(time_var = times) + } else { + new_col_names <- vctrs::vec_as_names(c(colnames(data$covariates), "time"), + repair = "unique", quiet = TRUE) + time_var <- tail(new_col_names, 1) + data$covariates[time_var] <- times + } + data$metadata$timename <- time_var + message("Integer times created in variable ", usethis::ui_code(time_var), ".") + return(invisible(data)) +} + +#' Check for missing samples +#' @aliases is_fully_sampled +#' +#' @description Some analyses may require evenly sampled data without missing +#' values. `has_missing_samples` checks that the dataset is equitimed, and +#' then for missing values within `abundance` (and optionally, `covariates`) +#' +#' `is_full_sampled()` does the same check, but returns `TRUE` if there are +#' NO missing samples. +#' +#' @inheritParams is_equitimed +#' @param check_covariates `TRUE` or `FALSE` (whether to check covariates, too) +#' +#' @return `TRUE` or `FALSE` +#' +#' @export +has_missing_samples <- function(data, period = NULL, tol = 1e-06, + check_covariates = FALSE) +{ + if (!is_equitimed(data, period, tol)) + { + message(c("Dataset is not evenly sampled in time.\n", + "Perhaps you want to call ", usethis::ui_code("make_equitimed()"), " first.\n")) + return(TRUE) + } + + # check abundance + if (any(is.na(data$abundance))) + { + message("Dataset has NA values in ", usethis::ui_code("abundance"), ".") + return(TRUE) + } + + # check covariates + if (check_covariates && any(is.na(data$covariates))) + { + message("Dataset has NA values in ", usethis::ui_code("covariates"), ".") + return(TRUE) + } + + return(FALSE) +} + +#' @export +is_fully_sampled <- function(data, period = NULL, tol = 1e-06, + check_covariates = FALSE) +{ + return(!has_missing_samples(data, period, tol, check_covariates)) +} + +#' @title Impute missing samples using linear interpolation +#' +#' @param data dataset to modify +#' @inheritParams is_equitimed +#' @param interpolate_covariates `TRUE` or `FALSE` (whether to do covariates, too) +#' +#' @return the dataset, with interpolated samples +#' +#' @details First, check if the data are evenly sampled in time. If not, we +#' exit early. Next, apply `forecast::na.interp()` to each variable that has +#' non-finite values. +#' +#' @export +interpolate_missing_samples <- function(data, period = NULL, tol = 1e-06, + interpolate_covariates = FALSE) +{ + interpolate_tbl <- function(df) + { + finite_cols_idx <- apply(is.na(df), 2, all) + # replace all non finite values with NA + + for (j in which(!finite_cols_idx)) + { + x <- df[[j]] + x[!is.finite(x)] <- NA + interpolated <- forecast::na.interp(x) + df[[j]] <- as.numeric(interpolated) + class(df[[j]]) <- class(x) + } + return(df) + } + + if (!is_equitimed(data, period, tol)) + { + stop(c("Dataset is not evenly sampled in time.\n", + "Perhaps you want to call ", usethis::ui_code("make_equitimed()"), " first.\n")) + } + + data$abundance <- interpolate_tbl(data$abundance) + + if (interpolate_covariates) + { + data$covariates <- interpolate_tbl(data$covariates) + } + + return(invisible(data)) +} + +#' get the complete time index, filling in gaps where necessary, and using the +#' period to establish the sampling frequency +#' +#' @noRd +get_full_times <- function(data, period = NULL, tol = 1e-06) +{ + times <- get_times_from_data(data) + if (is.null(times)) + { + stop("Dataset does not appear to have a times variable.\n", + "Check", usethis::ui_code("covariates"), " and ", + usethis::ui_code("metadata$timename"), ".\n") + } + period <- get_period_from_data(data, period) + + full_times <- tryCatch(tidyr::full_seq(times, period, tol), + error = function(e) { + message(e$message) + return(NULL) + }) + return(full_times) +} + +#' extract the period, given the value from the metadata field, and a value +#' specified by the user. The flowchart is: +#' (1) if user has supplied non-null `period`, use that +#' (2) if metadata period is non-null, use that +#' (3) use a default value of 1 and print a message +#' +#' @noRd +get_period_from_data <- function(data, period = NULL) +{ + if (is.null(period)) + { + period <- data$metadata$period + if (is.null(period)) + { + message("No time period found. Assuming period = 1.") + period <- 1 + } + } + return(period) +} + +#' @noRd +is.wholenumber <- function(x, tol = .Machine$double.eps ^ 0.5) +{ + abs(x - round(x)) < tol +} diff --git a/man/has_integer_times.Rd b/man/has_integer_times.Rd new file mode 100644 index 0000000..c324189 --- /dev/null +++ b/man/has_integer_times.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-data-processing.R +\name{has_integer_times} +\alias{has_integer_times} +\title{Check if a dataset has integer times} +\usage{ +has_integer_times(data) +} +\arguments{ +\item{data}{dataset to check} +} +\value{ +\code{TRUE} or \code{FALSE} +} +\description{ +Check if a dataset has integer times +} +\details{ +If the times are already integer or Date, true. Otherwise FALSE, +with a message if times are missing, or if times could potentially be +rounded. +} diff --git a/man/has_missing_samples.Rd b/man/has_missing_samples.Rd new file mode 100644 index 0000000..87d6f9d --- /dev/null +++ b/man/has_missing_samples.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-data-processing.R +\name{has_missing_samples} +\alias{has_missing_samples} +\alias{is_fully_sampled} +\title{Check for missing samples} +\usage{ +has_missing_samples(data, period = NULL, tol = 1e-06, check_covariates = FALSE) +} +\arguments{ +\item{data}{dataset to check} + +\item{period}{period to check the times against (if \code{NULL}, first check to +see if there is a known \code{period} set in the metadata, otherwise assumes 1)} + +\item{tol}{tolerance for the period} + +\item{check_covariates}{\code{TRUE} or \code{FALSE} (whether to check covariates, too)} +} +\value{ +\code{TRUE} or \code{FALSE} +} +\description{ +Some analyses may require evenly sampled data without missing +values. \code{has_missing_samples} checks that the dataset is equitimed, and +then for missing values within \code{abundance} (and optionally, \code{covariates}) + +\code{is_full_sampled()} does the same check, but returns \code{TRUE} if there are +NO missing samples. +} diff --git a/man/interpolate_missing_samples.Rd b/man/interpolate_missing_samples.Rd new file mode 100644 index 0000000..5ba58e7 --- /dev/null +++ b/man/interpolate_missing_samples.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-data-processing.R +\name{interpolate_missing_samples} +\alias{interpolate_missing_samples} +\title{Impute missing samples using linear interpolation} +\usage{ +interpolate_missing_samples( + data, + period = NULL, + tol = 1e-06, + interpolate_covariates = FALSE +) +} +\arguments{ +\item{data}{dataset to modify} + +\item{period}{period to check the times against (if \code{NULL}, first check to +see if there is a known \code{period} set in the metadata, otherwise assumes 1)} + +\item{tol}{tolerance for the period} + +\item{interpolate_covariates}{\code{TRUE} or \code{FALSE} (whether to do covariates, too)} +} +\value{ +the dataset, with interpolated samples +} +\description{ +Impute missing samples using linear interpolation +} +\details{ +First, check if the data are evenly sampled in time. If not, we +exit early. Next, apply \code{forecast::na.interp()} to each variable that has +non-finite values. +} diff --git a/man/is_equitimed.Rd b/man/is_equitimed.Rd new file mode 100644 index 0000000..9afd089 --- /dev/null +++ b/man/is_equitimed.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-data-processing.R +\name{is_equitimed} +\alias{is_equitimed} +\alias{is_evenly_sampled} +\title{Check that the times of a dataset are evenly sampled} +\usage{ +is_equitimed(data, period = NULL, tol = 1e-06) +} +\arguments{ +\item{data}{dataset to check} + +\item{period}{period to check the times against (if \code{NULL}, first check to +see if there is a known \code{period} set in the metadata, otherwise assumes 1)} + +\item{tol}{tolerance for the period} +} +\value{ +\code{TRUE} or \code{FALSE} +} +\description{ +Check that the times of a dataset are evenly sampled +} diff --git a/man/make_equitimed.Rd b/man/make_equitimed.Rd new file mode 100644 index 0000000..f07d32c --- /dev/null +++ b/man/make_equitimed.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-data-processing.R +\name{make_equitimed} +\alias{make_equitimed} +\alias{make_evenly_sampled} +\title{Insert rows if necessary so that time series are evenly sampled} +\usage{ +make_equitimed( + data, + period = NULL, + tol = 1e-06, + method = c("mean", "method", "closest"), + na.rm = TRUE +) +} +\arguments{ +\item{data}{dataset to modify} + +\item{period}{period to check the times against (if \code{NULL}, first check to +see if there is a known \code{period} set in the metadata, otherwise assumes 1)} + +\item{tol}{tolerance for the period} + +\item{method}{one of \code{c("mean", "method", "closest")} that determines how +the rows of the original data will get coerced into the output here.} + +\item{na.rm}{a logical value indicating whether \code{NA} + values should be stripped before the computation proceeds.} +} +\value{ +the dataset, with rows coerced according to the equitimed time +indices, and additional empty rows inserted if needed +} +\description{ +Insert rows if necessary so that time series are evenly sampled +} +\details{ +First, \code{get_full_times()} computes the sequence of time index values +at a regular sampling interval of period. These will be the final time +index values for the output. \emph{Some} set of rows of the original dataset +will map to each of these time indices. + +The \code{method} argument determines how these rows get coerced: +\describe{ +\item{mean}{the values in the rows are averaged together using \code{mean}} +\item{median}{the values in the rows are averaged together using \code{median}} +\item{closest}{the values in the row that is closest in time to the +desired time index are used.} +} +} diff --git a/man/make_integer_times.Rd b/man/make_integer_times.Rd new file mode 100644 index 0000000..c4f5b66 --- /dev/null +++ b/man/make_integer_times.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-data-processing.R +\name{make_integer_times} +\alias{make_integer_times} +\title{Add a time variable with integer values for evenly sampled data} +\usage{ +make_integer_times(data, period = NULL, tol = 1e-06) +} +\arguments{ +\item{data}{dataset to modify} + +\item{period}{period to check the times against (if \code{NULL}, first check to +see if there is a known \code{period} set in the metadata, otherwise assumes 1)} + +\item{tol}{tolerance for the period} +} +\value{ +the dataset, with integer times +} +\description{ +Add a time variable with integer values for evenly sampled data +} +\details{ +First, check if the data are evenly sampled in time. If not, we +exit early. Next, if the times are already integer or Date, we don't do +anything. If the times are numeric, but roundable to integer, we round. +Otherwise, we add a new variable to \code{covariates} from 1:n and designate +this variable as the \code{timename}. +} diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 0000000..f0f90c7 --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1,5 @@ +pre_test_options <- options( + ## make ui_*() output easier to test against + ## just say no to ANSI escape codes + "crayon.enabled" = FALSE +) diff --git a/tests/testthat/teardown.R b/tests/testthat/teardown.R new file mode 100644 index 0000000..6c7ef9f --- /dev/null +++ b/tests/testthat/teardown.R @@ -0,0 +1 @@ +options(pre_test_options) diff --git a/tests/testthat/test-02-dataset-processing.R b/tests/testthat/test-02-dataset-processing.R index 9f2c56e..1418c94 100644 --- a/tests/testthat/test-02-dataset-processing.R +++ b/tests/testthat/test-02-dataset-processing.R @@ -1,7 +1,6 @@ context("Check Dataset Processing Code") test_that("BBS data processing works", { - species_table <- data.frame(aou = c(1, 3, 33, 44), spanish_common_name = c("a x", "c x", "c x / yy", "d x zz")) @@ -14,3 +13,114 @@ test_that("BBS data processing works", { expect_equal(out$species_id, c(1, 2, 3, 44)) expect_equal(out$abundance, c(2, 2, 4, 2)) }) + +sample_dat <- list( + abundance = data.frame(a = c(1, 2, 3, 5, 6, 9, 10, 11, 12, 13), + b = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)), + covariates = data.frame(time = c(0, 0.5, 1, 2, 2.5, 4, 4.5, 5, 5.5, 6)), + metadata = list(timename = "time", + period = 0.5, + is_community = FALSE, + citation = "NA") +) +attr(sample_dat, "class") <- "matssdata" + +test_that("get_full_times works", { + expect_error(full_times <- get_full_times(sample_dat), NA) + expect_equal(full_times, seq(0.0, 6.0, by = 0.5)) + + error_dat <- sample_dat + error_dat$metadata$timename <- NULL + expect_error(get_full_times(error_dat)) +}) + +test_that("is_equitimed works", { + m <- capture_messages(expect_false(is_equitimed(dragons))) + expect_match(m, "`x` is not a regular sequence.", fixed = TRUE) + + path <- system.file("extdata", "subsampled", + package = "MATSS", mustWork = TRUE) + dat <- get_mtquad_data(path = file.path(path, "mapped-plant-quads-mt")) + m <- capture_messages(expect_true(is_equitimed(dat))) + expect_match(m, "No time period found. Assuming period = 1.", fixed = TRUE) +}) + +test_that("make_equitimed works", { + expect_error(make_equitimed(dragons), + "Unable to construct an evenly spaced time index.", + fixed = TRUE) + + path <- system.file("extdata", "subsampled", + package = "MATSS", mustWork = TRUE) + dat <- get_mtquad_data(path = file.path(path, "mapped-plant-quads-mt")) + expect_error(m <- capture_messages(out <- make_equitimed(dat)), NA) + expect_match(m, "No time period found. Assuming period = 1.", fixed = TRUE, all = FALSE) + expect_match(m, "Dataset is already evenly sampled in time.", fixed = TRUE, all = FALSE) + expect_equal(out, dat) + + expect_error(out <- make_equitimed(sample_dat), NA) + expect_true(is_equitimed(out)) + expect_true(check_data_format(out)) + expect_equal(dim(out$abundance), c(13, 2)) + expect_true(all(is.na(out$abundance[c(4, 7, 8), ]))) + expect_true(!any(is.na(out$abundance[-c(4, 7, 8), ]))) +}) + +test_that("has_integer_times works", { + expect_true(has_integer_times(dragons)) + expect_false(has_integer_times(sample_dat)) + + path <- system.file("extdata", "subsampled", + package = "MATSS", mustWork = TRUE) + dat <- get_mtquad_data(path = file.path(path, "mapped-plant-quads-mt")) + expect_true(has_integer_times(dat)) +}) + +test_that("make_integer_times works", { + expect_error(make_integer_times(sample_dat), + "Dataset is not evenly sampled in time.") + + expect_error(out <- make_integer_times(make_equitimed(sample_dat)), NA) + expect_true(check_data_format(out)) + expect_equal(dim(out$covariates), c(13, 2)) + expect_equal(out$covariates[[2]], 1:13) + + path <- system.file("extdata", "subsampled", + package = "MATSS", mustWork = TRUE) + dat <- get_mtquad_data(path = file.path(path, "mapped-plant-quads-mt")) + m <- capture_messages(out <- make_integer_times(dat)) + expect_match(m, "No time period found. Assuming period = 1.", fixed = TRUE, all = FALSE) + expect_match(m, "Dataset is evenly sampled with integer times already.", fixed = TRUE, all = FALSE) + expect_equal(out, dat) +}) + +test_that("is_fully_sampled works", { + m <- capture_messages(expect_false(is_fully_sampled(sample_dat))) + expect_match(m, "Dataset is not evenly sampled in time.") + + sample_dat$covariates["temp"] <- rnorm(NROW(sample_dat$covariates)) + + out <- make_equitimed(sample_dat) + m <- capture_messages(expect_false(is_fully_sampled(out))) + expect_match(m, "Dataset has NA values in `abundance`", fixed = TRUE, all = FALSE) + idx <- is.na(out$abundance) + out$abundance[idx] <- -999 + expect_true(is_fully_sampled(out)) + + m <- capture_messages(expect_false(is_fully_sampled(out, check_covariates = TRUE))) + expect_match(m, "Dataset has NA values in `covariates`", fixed = TRUE, all = FALSE) + out$covariates[is.na(out$covariates)] <- -999 + expect_true(is_fully_sampled(out, check_covariates = TRUE)) +}) + +test_that("interpolate_missing_samples works", { + sample_dat$covariates["temp"] <- rnorm(NROW(sample_dat$covariates)) + + dat <- make_equitimed(sample_dat) + interpolated_dat <- interpolate_missing_samples(dat) + expect_true(is_fully_sampled(interpolated_dat)) + expect_false(is_fully_sampled(interpolated_dat, check_covariates = TRUE)) + expect_true(is_fully_sampled(interpolate_missing_samples(dat, interpolate_covariates = TRUE), + check_covariates = TRUE)) +}) + \ No newline at end of file