Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
31271d3
Add new function, `interval_coverage_deviation_quantile()` - I know t…
nikosbosse Nov 10, 2023
9c9f286
Create a new helper function to compute ranges of central prediction …
nikosbosse Nov 10, 2023
b883dd8
Update `metrics_quantile`
nikosbosse Nov 10, 2023
7b62919
Small update to `metrics_quantile` + fixing a typo
nikosbosse Nov 10, 2023
c674764
update `ae_median_quantile()` and documentation associated to `wis()`…
nikosbosse Nov 10, 2023
27d4a78
add input checks to `ae_median_sample()` and `se_mean_sample()`
nikosbosse Nov 10, 2023
e2618cd
Update documentation for `metrics_quantile` and remove # no lint from…
nikosbosse Nov 10, 2023
2ef9a52
Correct error in ae_median_quantile, update data file and documentation
nikosbosse Nov 11, 2023
7e635eb
Correct an issue with `pit()` where this still relies on the computat…
nikosbosse Nov 11, 2023
77fcde4
commit previously forgotten change in `metrics_quantile`
nikosbosse Nov 11, 2023
295eb51
Fix issues with ae_median_sample(), ae_median_quantile() and se_mean_…
nikosbosse Nov 12, 2023
88fcdce
Change "interval_score" to "wis" as a metric name. Comment out code f…
nikosbosse Nov 12, 2023
337ea1f
Replace score.scoringutils_quantile() with new function
nikosbosse Nov 12, 2023
7e6dbe8
update available_metrics() to include wis and coverage_ values (this …
nikosbosse Nov 12, 2023
08dab31
Update vignette to update / comment out code that cannot be run anymore
nikosbosse Nov 12, 2023
f4d5d43
Fix test for ae_median_sample()
nikosbosse Nov 12, 2023
9607615
Update tests
nikosbosse Nov 12, 2023
c214ade
comment out / delete failing tests related to ranges in the output of…
nikosbosse Nov 12, 2023
71d6d65
Delete old version of `score.score_quantile()`
nikosbosse Nov 12, 2023
596dcee
improve the way that data.frames are split in `score()` to deal with …
nikosbosse Nov 12, 2023
6989f5d
Add test file for binary metrics and input checks
nikosbosse Nov 12, 2023
376de95
Remove comment (to be turned into an issue)
nikosbosse Nov 12, 2023
b13f00a
small fix in score.scoringutils_quantile to avoid a warning
nikosbosse Nov 12, 2023
983c030
move tests around so they better correspond to file names used for ac…
nikosbosse Nov 12, 2023
c89b1f5
fix small test issues
nikosbosse Nov 12, 2023
cd0d154
Add input checks for quantile-based forecasts
nikosbosse Nov 13, 2023
40807ad
remove old test for sharpness (test has been moved to test-metrics-sa…
nikosbosse Nov 13, 2023
fa3a7ab
move piece of code around within the metrics-quantile.R file (since m…
nikosbosse Nov 13, 2023
0d42acc
Add more tests for quantile metrics
nikosbosse Nov 13, 2023
32357f1
Rework existing `add_coverage()` function to work with raw forecasts
nikosbosse Nov 13, 2023
1be9055
Update News
nikosbosse Nov 13, 2023
8719dbe
Update `get_protetcted_columns()` with coverage columns
nikosbosse Nov 13, 2023
1252e13
Update `quantile_to_interval.data.frame()` to work with NA values
nikosbosse Nov 13, 2023
3af9fff
Don't remove NA values in `add_coverage()` anymore
nikosbosse Nov 13, 2023
02c4b1c
Update `add_coverage` to store an attribute `metric_names` with the n…
nikosbosse Nov 13, 2023
b41d655
Update tests and code snippets to get stuff working again that was pr…
nikosbosse Nov 13, 2023
79fc533
Automatic readme update
actions-user Nov 13, 2023
f7faff2
horrible, but working version of a refactoring of score.scoringutils_…
nikosbosse Nov 14, 2023
cb9bcbe
More elegant version, currently failing for sample-based forecasts be…
nikosbosse Nov 14, 2023
102370d
Fix score.scoringutils_sample by using matrices
nikosbosse Nov 14, 2023
004d2b8
move the expression into `apply_metrics()`
nikosbosse Nov 14, 2023
7eb7c96
Update metrics_quantile (improved error handling + getting rid of `ru…
nikosbosse Nov 14, 2023
6c213fc
Add global variable to fix failing test
nikosbosse Nov 14, 2023
9705994
Create function to ensure an object is a data.table
nikosbosse Nov 14, 2023
afdd3ac
Simplify set_forcast_unit and ensure that it operates on a data.table
nikosbosse Nov 14, 2023
218c01c
Make message in `check_columns_present` nicer
nikosbosse Nov 14, 2023
21d887a
Reintroduce `run_safely()` into `metrics_quantile` - the alternative …
nikosbosse Nov 14, 2023
ebe3869
correct `check_columns_present()`
nikosbosse Nov 14, 2023
ba9adbf
Update `set_forecast_unit()` and add tests
nikosbosse Nov 14, 2023
be1f53c
Merge pull request #419 from epiforecasts/rework_ae_median
nikosbosse Nov 15, 2023
c6c2dfc
Merge pull request #421 from epiforecasts/rework-score.scoringutils_q…
nikosbosse Nov 15, 2023
3928002
Merge pull request #422 from epiforecasts/expand-tests
nikosbosse Nov 15, 2023
b45ec5a
Merge pull request #423 from epiforecasts/expand-tests2
nikosbosse Nov 15, 2023
e3fcec3
Merge pull request #426 from epiforecasts/reword-add_coverage2
nikosbosse Nov 15, 2023
cc72c53
Merge pull request #430 from epiforecasts/create-apply-metrics
nikosbosse Nov 15, 2023
ad411da
Merge pull request #437 from epiforecasts/fix-set_forecast_unit
nikosbosse Nov 15, 2023
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
4 changes: 3 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ S3method(score,default)
S3method(score,scoringutils_binary)
S3method(score,scoringutils_point)
S3method(score,scoringutils_quantile)
S3method(score,scoringutils_quantile_new)
S3method(score,scoringutils_sample)
S3method(validate,default)
S3method(validate,scoringutils_binary)
Expand All @@ -32,6 +31,7 @@ export(crps_sample)
export(dispersion)
export(dss_sample)
export(get_duplicate_forecasts)
export(interval_coverage_deviation_quantile)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the reason we have to do all this stuff is we are only supporting one metric per function call vs allowing the output to be a data.frame and unnesting?

Copy link
Copy Markdown
Collaborator Author

@nikosbosse nikosbosse Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, we could have all functions return a data.frame and then merging things together in score().
Or a wrapper in score() that checks whether the output is a data.frame, list or a vector? I'd be open to that, I just haven't thought about it long enough to have a good vision for how it would look like.
I created an issue, $455. Could you please elaborate a bit on how you'd imagine this?

export(interval_coverage_quantile)
export(interval_coverage_sample)
export(interval_score)
Expand Down Expand Up @@ -82,6 +82,7 @@ importFrom(checkmate,assert_data_frame)
importFrom(checkmate,assert_data_table)
importFrom(checkmate,assert_factor)
importFrom(checkmate,assert_list)
importFrom(checkmate,assert_logical)
importFrom(checkmate,assert_number)
importFrom(checkmate,assert_numeric)
importFrom(checkmate,check_atomic_vector)
Expand All @@ -108,6 +109,7 @@ importFrom(data.table,nafill)
importFrom(data.table,rbindlist)
importFrom(data.table,setDT)
importFrom(data.table,setattr)
importFrom(data.table,setcolorder)
importFrom(data.table,setnames)
importFrom(ggdist,geom_lineribbon)
importFrom(ggplot2,.data)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The update introduces a lot of breaking changes. If you want to keep using the o
- `quantile`: numeric, a vector with quantile-levels. Can alternatively be a matrix of the same shape as `predicted`.
- `check_forecasts()` was replaced by a new function `validate()`. `validate()` validates the input and in that sense fulfills the purpose of `check_forecasts()`. It has different methods: `validate.default()` assigns the input a class based on their forecast type. Other methods validate the input specifically for the various forecast types.
- The functionality for computing pairwise comparisons was now split from `summarise_scores()`. Instead of doing pairwise comparisons as part of summarising scores, a new function, `add_pairwise_comparison()`, was introduced that takes summarised scores as an input and adds pairwise comparisons to it.
- `add_coverage()` was reworked completely. It's new purpose is now to add coverage information to the raw forecast data (essentially fulfilling some of the functionality that was previously covered by `score_quantile()`)
- The function `find_duplicates()` was renamed to `get_duplicate_forecasts()`
- Changes to `avail_forecasts()` and `plot_avail_forecasts()`:
- The function `avail_forecasts()` was renamed to `available_forecasts()` for consistency with `available_metrics()`. The old function, `avail_forecasts()` is still available as an alias, but will be removed in the future.
Expand Down
83 changes: 83 additions & 0 deletions R/add_coverage.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#' @title Add Coverage Values to Quantile-Based Forecasts
#'
#' @description Adds interval coverage of central prediction intervals,
#' quantile coverage for predictive quantiles, as well as the deviation between
#' desired and actual coverage to a data.table. Forecasts should be in a
#' quantile format (following the input requirements of `score()`).
#'
#' **Interval coverage**
#'
#' Coverage for a given interval range is defined as the proportion of
#' observations that fall within the corresponding central prediction intervals.
#' Central prediction intervals are symmetric around the median and and formed
#' by two quantiles that denote the lower and upper bound. For example, the 50%
#' central prediction interval is the interval between the 0.25 and 0.75
#' quantiles of the predictive distribution.
#'
#' The function `add_coverage()` computes the coverage per central prediction
#' interval, so the coverage will always be either `TRUE` (observed value falls
#' within the interval) or `FALSE` (observed value falls outside the interval).
#' You can summarise the coverage values to get the proportion of observations
#' that fall within the central prediction intervals.
#'
#' **Quantile coverage**
#'
#' Quantile coverage for a given quantile is defined as the proportion of
#' observed values that are smaller than the corresponding predictive quantile.
#' For example, the 0.5 quantile coverage is the proportion of observed values
#' that are smaller than the 0.5 quantile of the predictive distribution.
#'
#' **Coverage deviation**
#'
#' The coverage deviation is the difference between the desired coverage and the
#' actual coverage. For example, if the desired coverage is 90% and the actual
#' coverage is 80%, the coverage deviation is -0.1.
#'
#' @inheritParams score
#' @return a data.table with the input and columns "interval_coverage",
#' "interval_coverage_deviation", "quantile_coverage",
#' "quantile_coverage_deviation" added.
#' @importFrom data.table setcolorder
#' @examples
#' library(magrittr) # pipe operator
#' example_quantile %>%
#' add_coverage()
#' @export
#' @keywords scoring
#' @export
add_coverage <- function(data) {
stored_attributes <- get_scoringutils_attributes(data)
data <- validate(data)
forecast_unit <- get_forecast_unit(data)
data_cols <- colnames(data) # store so we can reset column order later

# what happens if quantiles are not symmetric around the median?
# should things error? Also write tests for that.
interval_data <- quantile_to_interval(data, format = "wide")
interval_data[, interval_coverage := ifelse(
observed <= upper & observed >= lower,
TRUE,
FALSE)
][, c("lower", "upper", "observed") := NULL]

data[, range := get_range_from_quantile(quantile)]

data <- merge(interval_data, data, by = unique(c(forecast_unit, "range")))
data[, interval_coverage_deviation := interval_coverage - range / 100]
data[, quantile_coverage := observed <= predicted]
data[, quantile_coverage_deviation := quantile_coverage - quantile]

# reset column order
new_metrics <- c("interval_coverage", "interval_coverage_deviation",
"quantile_coverage", "quantile_coverage_deviation")
setcolorder(data, unique(c(data_cols, "range", new_metrics)))

# add coverage "metrics" to list of stored metrics
# this makes it possible to use `summarise_scores()` later on
stored_attributes[["metric_names"]] <- c(
stored_attributes[["metric_names"]],
new_metrics
)
data <- assign_attributes(data, stored_attributes)
return(data[])
}
14 changes: 12 additions & 2 deletions R/check-input-helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,22 @@ check_columns_present <- function(data, columns) {
}
assert_character(columns, min.len = 1)
colnames <- colnames(data)
missing <- list()
for (x in columns){
if (!(x %in% colnames)) {
msg <- paste0("Column '", x, "' not found in data")
return(msg)
missing[[x]] <- x
}
}
missing <- unlist(missing)
if (length(missing) > 1) {
msg <- paste0(
"Columns '", paste(missing, collapse = "', '"), "' not found in data"
)
return(msg)
} else if (length(missing) == 1) {
msg <- paste0("Column '", missing, "' not found in data")
return(msg)
}
return(TRUE)
}

Expand Down
18 changes: 5 additions & 13 deletions R/convenience-functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -235,21 +235,13 @@ log_shift <- function(x, offset = 0, base = exp(1)) {
#' example_quantile,
#' c("location", "target_end_date", "target_type", "horizon", "model")
#' )

set_forecast_unit <- function(data, forecast_unit) {

datacols <- colnames(data)
missing <- forecast_unit[!(forecast_unit %in% datacols)]

if (length(missing) > 0) {
warning(
"Column(s) '",
missing,
"' are not columns of the data and will be ignored."
)
forecast_unit <- intersect(forecast_unit, datacols)
data <- ensure_data.table(data)
missing <- check_columns_present(data, forecast_unit)
if (!is.logical(missing)) {
warning(missing)
forecast_unit <- intersect(forecast_unit, colnames(data))
}

keep_cols <- c(get_protected_columns(data), forecast_unit)
out <- unique(data[, .SD, .SDcols = keep_cols])[]
return(out)
Expand Down
21 changes: 13 additions & 8 deletions R/data.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#' \item{model}{name of the model that generated the forecasts}
#' \item{horizon}{forecast horizon in weeks}
#' }
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/} # nolint
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/}
"example_quantile"


Expand All @@ -44,7 +44,7 @@
#' \item{model}{name of the model that generated the forecasts}
#' \item{horizon}{forecast horizon in weeks}
#' }
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/} # nolint
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/}
"example_point"


Expand All @@ -69,7 +69,7 @@
#' \item{predicted}{predicted value}
#' \item{sample_id}{id for the corresponding sample}
#' }
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/} # nolint
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/}
"example_continuous"


Expand Down Expand Up @@ -124,7 +124,7 @@
#' \item{horizon}{forecast horizon in weeks}
#' \item{predicted}{predicted value}
#' }
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/} # nolint
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/}
"example_binary"


Expand All @@ -147,7 +147,7 @@
#' \item{model}{name of the model that generated the forecasts}
#' \item{horizon}{forecast horizon in weeks}
#' }
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/} # nolint
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/}
"example_quantile_forecasts_only"


Expand All @@ -167,7 +167,7 @@
#' \item{observed}{observed values}
#' \item{location_name}{name of the country for which a prediction was made}
#' }
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/} # nolint
#' @source \url{https://github.com/covid19-forecast-hub-europe/covid19-forecast-hub-europe/commit/a42867b1ea152c57e25b04f9faa26cfd4bfd8fa6/}
"example_truth_only"

#' Summary information for selected metrics
Expand Down Expand Up @@ -216,8 +216,13 @@
#'
#' A named list with functions:
#' - "wis" = [wis()]
#' - "overprediction" = [overprediction()]
#' - "underprediction" = [underprediction()]
#' - "dispersion" = [dispersion()]
#' - "bias" = [bias_quantile()]
#' - "coverage_50" = \(...) {run_safely(..., range = 50, fun = interval_coverage_quantile)} #nolint
#' - "coverage_90" = \(...) {run_safely(..., range = 90, fun = interval_coverage_quantile)} #nolint
#' - "coverage_50" = \(...) {run_safely(..., range = 50, fun = [interval_coverage_quantile][interval_coverage_quantile()])}
#' - "coverage_90" = \(...) {run_safely(..., range = 90, fun = [interval_coverage_quantile][interval_coverage_quantile()])}
#' - "coverage_deviation" = [interval_coverage_deviation_quantile()],
#' - "ae_median" = [ae_median_quantile()]
#' @keywords info
"metrics_quantile"
2 changes: 2 additions & 0 deletions R/get_-functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ get_protected_columns <- function(data = NULL) {
protected_columns <- c(
"predicted", "observed", "sample_id", "quantile", "upper", "lower",
"pit_value", "range", "boundary", "relative_skill", "scaled_rel_skill",
"interval_coverage", "interval_coverage_deviation",
"quantile_coverage", "quantile_coverage_deviation",
available_metrics(),
grep("coverage_", names(data), fixed = TRUE, value = TRUE)
)
Expand Down
Loading