Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #502 - Add tests and Improve error handling #924

Merged
merged 8 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions R/metrics-quantile.R
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,14 @@ interval_coverage <- function(observed, predicted,
) / 100
if (!all(necessary_quantiles %in% quantile_level)) {
#nolint start: keyword_quote_linter object_usage_linter
cli_warn(
cli_abort(
c(
"!" = "To compute the interval coverage for an interval range of
{.val {interval_range}%}, the {.val {necessary_quantiles}} quantiles
are required.",
"i" = "Returning {.val {NA}}."
are required"
)
)
#nolint end
return(NA)
}
r <- interval_range
reformatted <- quantile_to_interval(observed, predicted, quantile_level)
Expand Down Expand Up @@ -332,7 +330,10 @@ interval_coverage <- function(observed, predicted,
#' Bias can assume values between -1 and 1 and is 0 ideally (i.e. unbiased).
#'
#' Note that if the given quantiles do not contain the median, the median is
#' imputed as the mean of the two innermost quantiles.
#' imputed as a linear interpolation of the two innermost quantiles. If the
#' median is not available and cannot be imputed, an error will be thrown.
#' Note that in order to compute bias, quantiles must be non-decreasing with
#' increasing quantile levels.
#'
#' For a large enough number of quantiles, the
#' percentile rank will equal the proportion of predictive samples below the
Expand All @@ -357,6 +358,11 @@ interval_coverage <- function(observed, predicted,
#' bias_quantile(observed, predicted, quantile_level)
bias_quantile <- function(observed, predicted, quantile_level, na.rm = TRUE) {
assert_input_quantile(observed, predicted, quantile_level)
# for bias quantile to work, at least one quantile level has to be <= 0.5
# and at least one >= 0.5
assert_vector(quantile_level[quantile_level <= 0.5], min.len = 1)
assert_vector(quantile_level[quantile_level >= 0.5], min.len = 1)

n <- length(observed)
N <- length(quantile_level)
if (is.null(dim(predicted))) {
Expand Down Expand Up @@ -506,15 +512,13 @@ ae_median_quantile <- function(observed, predicted, quantile_level) {
assert_input_quantile(observed, predicted, quantile_level)
if (!any(quantile_level == 0.5)) {
#nolint start: keyword_quote_linter
cli_warn(
cli_abort(
c(
"x" = "In order to compute the absolute error of the median,
{.val 0.5} must be among the quantiles given.",
"i" = "Returning {.val NA}."
"!" = "In order to compute the absolute error of the median,
{.val 0.5} must be among the quantiles given"
)
)
#nolint end
return(NA_real_)
}
if (is.null(dim(predicted))) {
predicted <- matrix(predicted, nrow = 1)
Expand Down
5 changes: 4 additions & 1 deletion man/bias_quantile.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 15 additions & 10 deletions tests/testthat/test-metrics-quantile.R
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ test_that("interval_coverage rejects wrong inputs", {
test_that("interval_coverage_quantile throws a warning when a required quantile is not available", {
dropped_quantile_pred <- predicted[, -4]
dropped_quantiles <- quantile_level[-4]
expect_warning(
expect_error(
interval_coverage(
observed, dropped_quantile_pred, dropped_quantiles, interval_range = 50
),
Expand Down Expand Up @@ -858,6 +858,17 @@ test_that("bias_quantile() works with point forecasts", {
})


test_that("bias_quantile() handles cases where median is not available", {
predicted <- c(1, 10)
observed <- 15
quantile_level <- c(0.2, 0.4)

expect_error(
bias_quantile(observed, predicted, quantile_level),
"Assertion on 'quantile_level\\[quantile_leve\\l >= 0.5]' failed: Must have length >= 1, but has length 0."
)
})

# `interpolate_median` ======================================================= #
test_that("interpolation in `interpolate_median` works", {
predicted <- c(1, 10)
Expand Down Expand Up @@ -910,16 +921,10 @@ test_that("ae_median_quantile() works as_expected", {
)

# test that we get a warning if there are inputs without a 0.5 quantile
expect_warning(
expect_equal(
ae_median_quantile(observed, predicted_values, quantile_level = 0.6),
NA_real_
),
'In order to compute the absolute error of the median, "0.5" must be among the quantiles given.'
expect_error(
ae_median_quantile(observed, predicted_values, quantile_level = 0.6),
'In order to compute the absolute error of the median, '
)


dim(1:10)
})


Expand Down
50 changes: 50 additions & 0 deletions tests/testthat/test-score.R
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,56 @@ test_that("score.forecast_quantile() works as expected in edge cases", {
})



test_that("score() works even if only some quantiles are missing", {

# only the median is there
onlymedian <- example_quantile[quantile_level == 0.5]
expect_no_condition(
score(onlymedian, metrics = get_metrics(
example_quantile,
exclude = c("interval_coverage_50", "interval_coverage_90")
))
)


# asymmetric intervals
asymm <- example_quantile[!quantile_level > 0.6]
expect_warning(
expect_warning(
score_a <- score(asymm) %>% summarise_scores(by = "model"),
"Computation for `interval_coverage_50` failed."
),
"Computation for `interval_coverage_90` failed."
)

# check that the result is equal to a case where we discard the entire
# interval in terms of WIS
inner <- example_quantile[quantile_level %in% c(0.4, 0.45, 0.5, 0.55, 0.6)]
score_b <- score(inner, get_metrics(
inner, exclude = c("interval_coverage_50", "interval_coverage_90")
)) %>%
summarise_scores(by = "model")
expect_equal(
score_a$wis,
score_b$wis
)

# median is not there, but only in a single model
test <- data.table::copy(example_quantile)
test_no_median <- test[model == "epiforecasts-EpiNow2" & !(quantile_level %in% c(0.5)), ]
test <- rbind(test[model != "epiforecasts-EpiNow2"], test_no_median)

test <- suppressWarnings(as_forecast_quantile(test))
expect_message(
expect_warning(
score(test),
"Computation for `ae_median` failed."
),
"interpolating median from the two innermost quantiles"
)
})

# test integer and continuous case ---------------------------------------------
test_that("function produces output for a continuous format case", {

Expand Down