From a9c180e74a7d4cb432e9638545939236a96c5da3 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 12:12:25 +0100 Subject: [PATCH 01/19] add some basic functons for as_point and as_quantile --- R/as_point.R | 46 ++++++++++++++++++++++++++++++++ R/as_quantile.R | 39 ++++++++++++++++++++++++++++ R/forecast.R | 62 ++++++++++++++++++++++++-------------------- R/summarise_scores.R | 8 +++++- 4 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 R/as_point.R create mode 100644 R/as_quantile.R diff --git a/R/as_point.R b/R/as_point.R new file mode 100644 index 00000000..f6c018d8 --- /dev/null +++ b/R/as_point.R @@ -0,0 +1,46 @@ +as_point <- function(forecast, ...) { + UseMethod("as_point") +} + +as_point.default <- function(forecast, ...) { + cli_abort( + c( + "!" = "The input needs to be a forecast object.", + "i" = "Please run `as_forecast()` first." # nolint + ) + ) +} + +as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { + assert_forecast(forecast, verbose = FALSE) + assert_numeric(quantile_level, lower = 0, upper = 1, len = 1) + + forecast <- forecast[ + quantile_level == target_quantile_level, , + env = list(target_quantile_level = quantile_level) + ] + forecast[, "quantile_level" := NULL] + + point_forecast <- as_forecast.default( + forecast, forecast_type = "point", check_forecast_type = FALSE + ) + return(point_forecast[]) +} + + +as_point.forecast_sample <- function(forecast, quantile_level = 0.5, fun, ...) { + assert_forecast(forecast, verbose = FALSE) + if (missing(fun)) { + quantile_forecast <- as_quantile(forecast, quantile_levels = quantile_level) + point_forecast <- as_point(quantile_forecast) + }else { + sum_forecast <- summarise_score( + forecast, fun = fun, by = get_forecast_unit(forecast), + metrics = "predicted" + ) + point_forecast <- as_forecast.default( + sum_forecast, forecast_type = "point", check_forecast_type = FALSE + ) + } + return(point_forecast[]) +} \ No newline at end of file diff --git a/R/as_quantile.R b/R/as_quantile.R new file mode 100644 index 00000000..36e6e05b --- /dev/null +++ b/R/as_quantile.R @@ -0,0 +1,39 @@ +as_quantile <- function(forecast, ...) { + UseMethod("as_quantile") +} + +as_quantile.default <- function(forecast, ...) { + cli_abort( + c( + "!" = "The input needs to be a forecast object.", + "i" = "Please run `as_forecast()` first." # nolint + ) + ) +} + +as_quantile.forecast_sample <- function( + forecast, quantile_levels = seq(from = 0.01, to = 0.99, by = 0.01), ... +) { + assert_forecast(forecast, verbose = FALSE) + + sum_forecast <- forecast[, + as.list(quantile(predicted, probs = quantile_levels, na.rm = TRUE)), + by = c(eval(get_forecast_unit(forecast)), "observed") + ] + + sum_forecast <- melt( + sum_forecast, + measure.vars = paste0(quantile_levels * 100, "%"), + variable.name = "quantile_level", + value.name = "predicted" + ) + + sum_forecast[, + quantile_level := as.numeric(gsub("\\%", "", quantile_level)) / 100 + ] + + quantile_forecast <- as_forecast.default( + sum_forecast, forecast_type = "quantile", check_forecast_type = FALSE + ) + return(quantile_forecast[]) +} diff --git a/R/forecast.R b/R/forecast.R index 9903e634..38cffcae 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -65,6 +65,9 @@ as_forecast <- function(data, #' @param sample_id (optional) Name of the column in `data` that contains the #' sample id. This column will be renamed to "sample_id". Only applicable to #' sample-based forecasts. +#' @param check_forecast_type Logical, defaults to `FALSE`. Should the +#' `forecast_type` be checked and reset if not as implied by the `data`? +#' #' @export #' @importFrom cli cli_warn as_forecast.default <- function(data, @@ -75,6 +78,7 @@ as_forecast.default <- function(data, model = NULL, quantile_level = NULL, sample_id = NULL, + check_forecast_type = TRUE, ...) { # check inputs data <- ensure_data.table(data) @@ -119,42 +123,44 @@ as_forecast.default <- function(data, } # assert forecast type is as expected - assert_forecast_type(data, desired = forecast_type) - forecast_type <- get_forecast_type(data) + if (isTRUE(check_forecast_type)) { + assert_forecast_type(data, desired = forecast_type) + forecast_type <- get_forecast_type(data) - # produce warning if old format is suspected - # old quantile format - if (forecast_type == "point" && "quantile" %in% colnames(data)) { - #nolint start: keyword_quote_linter - cli_warn( - c( - "Found column 'quantile' in the input data", - "i" = "This column name was used before scoringutils version 2.0.0. - Should the column be called 'quantile_level' instead?" - ), - .frequency = "once", - .frequency_id = "quantile_col_present" - ) - #nolint end - } - #old binary format - if (forecast_type == "point") { - looks_binary <- check_input_binary(factor(data$observed), data$predicted) - if (is.logical(looks_binary)) { - #nolint start: keyword_quote_linter duplicate_argument_linter + # produce warning if old format is suspected + # old quantile format + if (forecast_type == "point" && "quantile" %in% colnames(data)) { + #nolint start: keyword_quote_linter cli_warn( c( - "All observations are either 1 or 0.", - "i" = "The forecast type was classified as 'point', but it looks like - it could be a binary forecast as well.", - "i" = "In case you want it to be a binary forecast, convert `observed` - to a factor. See `?as_forecast()` for more information." + "Found column 'quantile' in the input data", + "i" = "This column name was used before scoringutils version 2.0.0. + Should the column be called 'quantile_level' instead?" ), .frequency = "once", - .frequency_id = "looks_binary" + .frequency_id = "quantile_col_present" ) #nolint end } + #old binary format + if (forecast_type == "point") { + looks_binary <- check_input_binary(factor(data$observed), data$predicted) + if (is.logical(looks_binary)) { + #nolint start: keyword_quote_linter duplicate_argument_linter + cli_warn( + c( + "All observations are either 1 or 0.", + "i" = "The forecast type was classified as 'point', but it looks like + it could be a binary forecast as well.", + "i" = "In case you want it to be a binary forecast, convert `observed` + to a factor. See `?as_forecast()` for more information." + ), + .frequency = "once", + .frequency_id = "looks_binary" + ) + #nolint end + } + } } # construct class diff --git a/R/summarise_scores.R b/R/summarise_scores.R index 29b385ae..4c6cf00d 100644 --- a/R/summarise_scores.R +++ b/R/summarise_scores.R @@ -20,6 +20,8 @@ #' alternative to specifying `by` directly. If `across` is set, `by` will be #' ignored. If `across` is `NULL` (default), then `by` will be used. #' @param fun A function used for summarising scores. Default is [mean()]. +#' @param metrics The metrics to summarise. If missing then `get_metrics()` +#' is used internally to infer these. #' @param ... Additional parameters that can be passed to the summary function #' provided to `fun`. For more information see the documentation of the #' respective function. @@ -60,13 +62,17 @@ summarise_scores <- function(scores, by = "model", across = NULL, fun = mean, + metrics, ...) { # input checking ------------------------------------------------------------ assert_data_frame(scores) assert_subset(by, names(scores), empty.ok = TRUE) assert_subset(across, names(scores), empty.ok = TRUE) assert_function(fun) - metrics <- get_metrics(scores, error = TRUE) + if (missing(metrics)) { + metrics <- get_metrics(scores, error = TRUE) + } + forecast_unit <- get_forecast_unit(scores) From bc21d3e42b64be4f5d4408e772074add737a9343 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 18:31:17 +0100 Subject: [PATCH 02/19] add documentation for as_point and as_quantile --- NAMESPACE | 7 ++ R/as_point.R | 147 ++++++++++++++++++++++++++++- R/as_quantile.R | 39 -------- man/as_forecast.Rd | 4 + man/as_point.Rd | 31 ++++++ man/as_point.forecast_quantile.Rd | 28 ++++++ man/as_point.forecast_sample.Rd | 40 ++++++++ man/as_quantile.Rd | 30 ++++++ man/as_quantile.forecast_sample.Rd | 32 +++++++ man/summarise_scores.Rd | 7 +- 10 files changed, 322 insertions(+), 43 deletions(-) create mode 100644 man/as_point.Rd create mode 100644 man/as_point.forecast_quantile.Rd create mode 100644 man/as_point.forecast_sample.Rd create mode 100644 man/as_quantile.Rd create mode 100644 man/as_quantile.forecast_sample.Rd diff --git a/NAMESPACE b/NAMESPACE index b80e5887..e93f887e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,11 @@ S3method(`[`,scores) S3method(as_forecast,default) +S3method(as_point,default) +S3method(as_point,forecast_quantile) +S3method(as_point,forecast_sample) +S3method(as_quantile,default) +S3method(as_quantile,forecast_sample) S3method(assert_forecast,default) S3method(assert_forecast,forecast_binary) S3method(assert_forecast,forecast_point) @@ -27,6 +32,8 @@ export(add_relative_skill) export(ae_median_quantile) export(ae_median_sample) export(as_forecast) +export(as_point) +export(as_quantile) export(assert_forecast) export(assert_forecast_generic) export(bias_quantile) diff --git a/R/as_point.R b/R/as_point.R index f6c018d8..6308b97f 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -1,7 +1,28 @@ +#' @title Convert a forecast object to a point forecast +#' +#' @description +#' The `as_point` function is used to convert a forecast object to a point +#' forecast. It is a generic function that dispatches the conversion to the +#' appropriate method based on the class of the input forecast object. +#' +#' @param forecast An object of class `forecast_{type}`, +#' representing a forecast. +#' @param ... Additional arguments to be passed to the specific method. +#' @return The function returns a point forecast object, which is a specific +#' type of forecast object that represents a single value prediction. +#' +#' @export +#' @keywords check-forecasts +#' @examples +#' as_point(as_forecast(example_quantile)) +#' as_point(as_forecast(example_sample_continuous)) as_point <- function(forecast, ...) { UseMethod("as_point") } +#' @rdname as_point +#' @export +#' @importFrom cli cli_abort as_point.default <- function(forecast, ...) { cli_abort( c( @@ -11,6 +32,23 @@ as_point.default <- function(forecast, ...) { ) } +#' Convert a quantile forecast to a point forecast +#' +#' This function takes a quantile forecast and converts it to a point forecast +#' by selecting the forecast corresponding to the specified quantile level. +#' +#' @param forecast The `forecast_quantile` object. +#' @param quantile_level The desired quantile level for the point forecast. +#' Defaults to 0.5 (median). +#' @param ... Additional arguments passing inherited from the default method but +#' unused. +#' +#' @return A `forecast_point` object. +#' +#' @export +#' @keywords check-forecasts +#' @examples +#' as_point(as_forecast(example_quantile)) as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { assert_forecast(forecast, verbose = FALSE) assert_numeric(quantile_level, lower = 0, upper = 1, len = 1) @@ -28,12 +66,36 @@ as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { } +#' Convert a sample based forecast to a point forecast +#' +#' This function converts a forecast object to a point forecast by either +#' taking the quantile forecast at a specified quantile level or by summarizing +#' the forecast using a custom function (for example `mean`). +#' +#' @param forecast The `forecast_sample` object to be converted to a point +#' forecast. +#' +#' @param fun A custom function to summarize the forecast. If provided, this +#' function will be used instead of the quantile method. An example could +#' be the `mean`. +#' @inheritParams as_point.forecast_quantile +#' @return A `forecast_point` object. +#' @export +#' @keywords check-forecasts +#' @examples +#' sample_forecast <- as_forecast(example_sample_continuous) +#' +#' # Quantile approach +#' as_point(sample_forecast) +#' +#' # Function approach +#' as_point(sample_forecast, fun = function(...) {mean(..., na.rm = TRUE)}) as_point.forecast_sample <- function(forecast, quantile_level = 0.5, fun, ...) { assert_forecast(forecast, verbose = FALSE) if (missing(fun)) { quantile_forecast <- as_quantile(forecast, quantile_levels = quantile_level) point_forecast <- as_point(quantile_forecast) - }else { + } else { sum_forecast <- summarise_score( forecast, fun = fun, by = get_forecast_unit(forecast), metrics = "predicted" @@ -43,4 +105,85 @@ as_point.forecast_sample <- function(forecast, quantile_level = 0.5, fun, ...) { ) } return(point_forecast[]) -} \ No newline at end of file +} + +#' Convert a forecast object to a quantile object +#' +#' This function is used to convert a forecast object to a quantile object. +#' It dispatches to the appropriate method based on the class of the forecast +#' object. +#' +#' @inheritParams as_point +#' +#' @return A `forecast_quantile` object. +#' +#' @export +#' @keywords check-forecasts +#' @examples +#' as_quantile(as_forecast(example_sample_continuous)) +#' +as_quantile <- function(forecast, ...) { + UseMethod("as_quantile") +} + +#' @rdname as_quantile +#' @export +#' @importFrom cli cli_abort +as_quantile.default <- function(forecast, ...) { + cli_abort( + c( + "!" = "The input needs to be a forecast object.", + "i" = "Please run `as_forecast()` first." # nolint + ) + ) +} + +#' Convert a sample forecast to a quantile forecast +#' +#' This function takes a sample forecast and converts it to a quantile forecast +#' sample. +#' +#' @param forecast The `forecast_sample`` object to convert to a +#' `forecast_quantile`. +#' +#' @param quantile_levels A vector of quantile levels. Defaults to +#' 0.01 to 0.99 by 0.01. +#' @inheritParams as_point.forecast_quantile +#' +#' @return A `forecast_quantile` object. +#' @export +#' @keywords check-forecasts +#' @importFrom data.table melt +#' +#' @export +#' @examples +#' as_quantile(as_forecast(example_sample_continuous)) +as_quantile.forecast_sample <- function( + forecast, quantile_levels = seq(from = 0.01, to = 0.99, by = 0.01), ... +) { + assert_forecast(forecast, verbose = FALSE) + assert_numeric(quantile_levels, lower = 0, upper = 1) + + sum_forecast <- forecast[, + as.list(quantile(predicted, probs = quantile_levels, na.rm = TRUE)), + by = c(eval(get_forecast_unit(forecast)), "observed") + ] + + sum_forecast <- melt( + sum_forecast, + measure.vars = paste0(quantile_levels * 100, "%"), + variable.name = "quantile_level", + value.name = "predicted" + ) + + sum_forecast[, + quantile_level := as.numeric( + gsub("%", "", quantile_level, fixed = TRUE) + ) / 100 + ] + + quantile_forecast <- as_forecast.default( + sum_forecast, forecast_type = "quantile", check_forecast_type = FALSE + ) + return(quantile_forecast[]) +} diff --git a/R/as_quantile.R b/R/as_quantile.R index 36e6e05b..e69de29b 100644 --- a/R/as_quantile.R +++ b/R/as_quantile.R @@ -1,39 +0,0 @@ -as_quantile <- function(forecast, ...) { - UseMethod("as_quantile") -} - -as_quantile.default <- function(forecast, ...) { - cli_abort( - c( - "!" = "The input needs to be a forecast object.", - "i" = "Please run `as_forecast()` first." # nolint - ) - ) -} - -as_quantile.forecast_sample <- function( - forecast, quantile_levels = seq(from = 0.01, to = 0.99, by = 0.01), ... -) { - assert_forecast(forecast, verbose = FALSE) - - sum_forecast <- forecast[, - as.list(quantile(predicted, probs = quantile_levels, na.rm = TRUE)), - by = c(eval(get_forecast_unit(forecast)), "observed") - ] - - sum_forecast <- melt( - sum_forecast, - measure.vars = paste0(quantile_levels * 100, "%"), - variable.name = "quantile_level", - value.name = "predicted" - ) - - sum_forecast[, - quantile_level := as.numeric(gsub("\\%", "", quantile_level)) / 100 - ] - - quantile_forecast <- as_forecast.default( - sum_forecast, forecast_type = "quantile", check_forecast_type = FALSE - ) - return(quantile_forecast[]) -} diff --git a/man/as_forecast.Rd b/man/as_forecast.Rd index dccb753e..5625f16d 100644 --- a/man/as_forecast.Rd +++ b/man/as_forecast.Rd @@ -16,6 +16,7 @@ as_forecast(data, ...) model = NULL, quantile_level = NULL, sample_id = NULL, + check_forecast_type = TRUE, ... ) } @@ -55,6 +56,9 @@ the quantile level of the predicted values. This column will be renamed to \item{sample_id}{(optional) Name of the column in \code{data} that contains the sample id. This column will be renamed to "sample_id". Only applicable to sample-based forecasts.} + +\item{check_forecast_type}{Logical, defaults to \code{FALSE}. Should the +\code{forecast_type} be checked and reset if not as implied by the \code{data}?} } \value{ Depending on the forecast type, an object of the following class will be diff --git a/man/as_point.Rd b/man/as_point.Rd new file mode 100644 index 00000000..b53575b0 --- /dev/null +++ b/man/as_point.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_point.R +\name{as_point} +\alias{as_point} +\alias{as_point.default} +\title{Convert a forecast object to a point forecast} +\usage{ +as_point(forecast, ...) + +\method{as_point}{default}(forecast, ...) +} +\arguments{ +\item{forecast}{An object of class \verb{forecast_\{type\}}, +representing a forecast.} + +\item{...}{Additional arguments to be passed to the specific method.} +} +\value{ +The function returns a point forecast object, which is a specific +type of forecast object that represents a single value prediction. +} +\description{ +The \code{as_point} function is used to convert a forecast object to a point +forecast. It is a generic function that dispatches the conversion to the +appropriate method based on the class of the input forecast object. +} +\examples{ +as_point(as_forecast(example_quantile)) +as_point(as_forecast(example_sample_continuous)) +} +\keyword{check-forecasts} diff --git a/man/as_point.forecast_quantile.Rd b/man/as_point.forecast_quantile.Rd new file mode 100644 index 00000000..5213720e --- /dev/null +++ b/man/as_point.forecast_quantile.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_point.R +\name{as_point.forecast_quantile} +\alias{as_point.forecast_quantile} +\title{Convert a quantile forecast to a point forecast} +\usage{ +\method{as_point}{forecast_quantile}(forecast, quantile_level = 0.5, ...) +} +\arguments{ +\item{forecast}{The \code{forecast_quantile} object.} + +\item{quantile_level}{The desired quantile level for the point forecast. +Defaults to 0.5 (median).} + +\item{...}{Additional arguments passing inherited from the default method but +unused.} +} +\value{ +A \code{forecast_point} object. +} +\description{ +This function takes a quantile forecast and converts it to a point forecast +by selecting the forecast corresponding to the specified quantile level. +} +\examples{ +as_point(as_forecast(example_quantile)) +} +\keyword{check-forecasts} diff --git a/man/as_point.forecast_sample.Rd b/man/as_point.forecast_sample.Rd new file mode 100644 index 00000000..ec18c3eb --- /dev/null +++ b/man/as_point.forecast_sample.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_point.R +\name{as_point.forecast_sample} +\alias{as_point.forecast_sample} +\title{Convert a sample based forecast to a point forecast} +\usage{ +\method{as_point}{forecast_sample}(forecast, quantile_level = 0.5, fun, ...) +} +\arguments{ +\item{forecast}{The \code{forecast_sample} object to be converted to a point +forecast.} + +\item{quantile_level}{The desired quantile level for the point forecast. +Defaults to 0.5 (median).} + +\item{fun}{A custom function to summarize the forecast. If provided, this +function will be used instead of the quantile method. An example could +be the \code{mean}.} + +\item{...}{Additional arguments passing inherited from the default method but +unused.} +} +\value{ +A \code{forecast_point} object. +} +\description{ +This function converts a forecast object to a point forecast by either +taking the quantile forecast at a specified quantile level or by summarizing +the forecast using a custom function (for example \code{mean}). +} +\examples{ +sample_forecast <- as_forecast(example_sample_continuous) + +# Quantile approach +as_point(sample_forecast) + +# Function approach +as_point(sample_forecast, fun = function(...) {mean(..., na.rm = TRUE)}) +} +\keyword{check-forecasts} diff --git a/man/as_quantile.Rd b/man/as_quantile.Rd new file mode 100644 index 00000000..d04793bc --- /dev/null +++ b/man/as_quantile.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_point.R +\name{as_quantile} +\alias{as_quantile} +\alias{as_quantile.default} +\title{Convert a forecast object to a quantile object} +\usage{ +as_quantile(forecast, ...) + +\method{as_quantile}{default}(forecast, ...) +} +\arguments{ +\item{forecast}{An object of class \verb{forecast_\{type\}}, +representing a forecast.} + +\item{...}{Additional arguments to be passed to the specific method.} +} +\value{ +A \code{forecast_quantile} object. +} +\description{ +This function is used to convert a forecast object to a quantile object. +It dispatches to the appropriate method based on the class of the forecast +object. +} +\examples{ +as_quantile(as_forecast(example_sample_continuous)) + +} +\keyword{check-forecasts} diff --git a/man/as_quantile.forecast_sample.Rd b/man/as_quantile.forecast_sample.Rd new file mode 100644 index 00000000..e5bf4644 --- /dev/null +++ b/man/as_quantile.forecast_sample.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_point.R +\name{as_quantile.forecast_sample} +\alias{as_quantile.forecast_sample} +\title{Convert a sample forecast to a quantile forecast} +\usage{ +\method{as_quantile}{forecast_sample}( + forecast, + quantile_levels = seq(from = 0.01, to = 0.99, by = 0.01), + ... +) +} +\arguments{ +\item{forecast}{The \verb{forecast_sample`` object to convert to a }forecast_quantile`.} + +\item{quantile_levels}{A vector of quantile levels. Defaults to +0.01 to 0.99 by 0.01.} + +\item{...}{Additional arguments passing inherited from the default method but +unused.} +} +\value{ +A \code{forecast_quantile} object. +} +\description{ +This function takes a sample forecast and converts it to a quantile forecast +sample. +} +\examples{ +as_quantile(as_forecast(example_sample_continuous)) +} +\keyword{check-forecasts} diff --git a/man/summarise_scores.Rd b/man/summarise_scores.Rd index 127cdcd4..af5c6e28 100644 --- a/man/summarise_scores.Rd +++ b/man/summarise_scores.Rd @@ -5,9 +5,9 @@ \alias{summarize_scores} \title{Summarise scores as produced by \code{\link[=score]{score()}}} \usage{ -summarise_scores(scores, by = "model", across = NULL, fun = mean, ...) +summarise_scores(scores, by = "model", across = NULL, fun = mean, metrics, ...) -summarize_scores(scores, by = "model", across = NULL, fun = mean, ...) +summarize_scores(scores, by = "model", across = NULL, fun = mean, metrics, ...) } \arguments{ \item{scores}{An object of class \code{scores} (a data.table with @@ -23,6 +23,9 @@ ignored. If \code{across} is \code{NULL} (default), then \code{by} will be used. \item{fun}{A function used for summarising scores. Default is \code{\link[=mean]{mean()}}.} +\item{metrics}{The metrics to summarise. If missing then \code{get_metrics()} +is used internally to infer these.} + \item{...}{Additional parameters that can be passed to the summary function provided to \code{fun}. For more information see the documentation of the respective function.} From bb3c8dda156ec60e0e07ebdde554ee4c17ef5cd6 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 18:59:55 +0100 Subject: [PATCH 03/19] add a news item and changes for CRAN check --- NEWS.md | 3 + R/as_point.R | 100 +++-------------------------- R/as_quantile.R | 80 +++++++++++++++++++++++ R/forecast.R | 26 ++++++++ R/z_globalVariables.R | 3 +- man/as_point.forecast_sample.Rd | 2 +- man/as_quantile.Rd | 2 +- man/as_quantile.forecast_sample.Rd | 2 +- man/remake_forecast.Rd | 26 ++++++++ 9 files changed, 149 insertions(+), 95 deletions(-) create mode 100644 man/remake_forecast.Rd diff --git a/NEWS.md b/NEWS.md index cda4d87e..1eaed20b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -51,6 +51,9 @@ scores <- score(forecast_quantile) - To create and validate a new `forecast` object, users can use `as_forecast()`. To revalidate an existing `forecast` object users can call `assert_forecast()` (which validates the input and returns `invisible(NULL)`. `assert_forecast()` is a generic with methods for the different forecast types. Alternatively, `validate_forecast()` can be used (which calls `assert_forecast()`), which returns the input and is useful in a pipe. Lastly, users can simply print the object to obtain additional information. - Users can test whether an object is of class `forecast_*()` using the function `is_forecast()`. Users can also test for a specific `forecast_*` class using the appropriate `is_forecast.forecast_*` method. For example, to check whether an object is of class `forecast_quantile`, you would use you would use `scoringutils:::is_forecast.forecast_quantile()`. +### Updating a forecast object +- The functions `as_point` and `as_quantile` have been added to allow conversion of various forecast types in point and quantile forecasts respectively. + ### Pairwise comparisons and relative skill - 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_relative_skill()`, was introduced that takes summarised scores as an input and adds columns with relative skill scores and scaled relative skill scores. - The function `pairwise_comparison()` was renamed to `get_pairwise_comparisons()`, in line with other `get_`-functions. Analogously, `plot_pairwise_comparison()` was renamed to `plot_pairwise_comparisons()`. diff --git a/R/as_point.R b/R/as_point.R index 6308b97f..afed6ee2 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -59,13 +59,12 @@ as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { ] forecast[, "quantile_level" := NULL] - point_forecast <- as_forecast.default( - forecast, forecast_type = "point", check_forecast_type = FALSE + point_forecast <- remake_forecast( + forecast, "forecast_quantile", "forecast_point", verbose = FALSE ) - return(point_forecast[]) + return(point_forecast) } - #' Convert a sample based forecast to a point forecast #' #' This function converts a forecast object to a point forecast by either @@ -89,101 +88,20 @@ as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { #' as_point(sample_forecast) #' #' # Function approach -#' as_point(sample_forecast, fun = function(...) {mean(..., na.rm = TRUE)}) +#' as_point(sample_forecast, fun = mean) as_point.forecast_sample <- function(forecast, quantile_level = 0.5, fun, ...) { assert_forecast(forecast, verbose = FALSE) if (missing(fun)) { quantile_forecast <- as_quantile(forecast, quantile_levels = quantile_level) point_forecast <- as_point(quantile_forecast) } else { - sum_forecast <- summarise_score( - forecast, fun = fun, by = get_forecast_unit(forecast), + sum_forecast <- summarise_scores( + forecast, fun = fun, by = c(get_forecast_unit(forecast), "observed"), metrics = "predicted" ) - point_forecast <- as_forecast.default( - sum_forecast, forecast_type = "point", check_forecast_type = FALSE + point_forecast <- remake_forecast( + sum_forecast, "forecast_sample", "forecast_point" ) } - return(point_forecast[]) -} - -#' Convert a forecast object to a quantile object -#' -#' This function is used to convert a forecast object to a quantile object. -#' It dispatches to the appropriate method based on the class of the forecast -#' object. -#' -#' @inheritParams as_point -#' -#' @return A `forecast_quantile` object. -#' -#' @export -#' @keywords check-forecasts -#' @examples -#' as_quantile(as_forecast(example_sample_continuous)) -#' -as_quantile <- function(forecast, ...) { - UseMethod("as_quantile") -} - -#' @rdname as_quantile -#' @export -#' @importFrom cli cli_abort -as_quantile.default <- function(forecast, ...) { - cli_abort( - c( - "!" = "The input needs to be a forecast object.", - "i" = "Please run `as_forecast()` first." # nolint - ) - ) -} - -#' Convert a sample forecast to a quantile forecast -#' -#' This function takes a sample forecast and converts it to a quantile forecast -#' sample. -#' -#' @param forecast The `forecast_sample`` object to convert to a -#' `forecast_quantile`. -#' -#' @param quantile_levels A vector of quantile levels. Defaults to -#' 0.01 to 0.99 by 0.01. -#' @inheritParams as_point.forecast_quantile -#' -#' @return A `forecast_quantile` object. -#' @export -#' @keywords check-forecasts -#' @importFrom data.table melt -#' -#' @export -#' @examples -#' as_quantile(as_forecast(example_sample_continuous)) -as_quantile.forecast_sample <- function( - forecast, quantile_levels = seq(from = 0.01, to = 0.99, by = 0.01), ... -) { - assert_forecast(forecast, verbose = FALSE) - assert_numeric(quantile_levels, lower = 0, upper = 1) - - sum_forecast <- forecast[, - as.list(quantile(predicted, probs = quantile_levels, na.rm = TRUE)), - by = c(eval(get_forecast_unit(forecast)), "observed") - ] - - sum_forecast <- melt( - sum_forecast, - measure.vars = paste0(quantile_levels * 100, "%"), - variable.name = "quantile_level", - value.name = "predicted" - ) - - sum_forecast[, - quantile_level := as.numeric( - gsub("%", "", quantile_level, fixed = TRUE) - ) / 100 - ] - - quantile_forecast <- as_forecast.default( - sum_forecast, forecast_type = "quantile", check_forecast_type = FALSE - ) - return(quantile_forecast[]) + return(point_forecast) } diff --git a/R/as_quantile.R b/R/as_quantile.R index e69de29b..f79049e6 100644 --- a/R/as_quantile.R +++ b/R/as_quantile.R @@ -0,0 +1,80 @@ +#' Convert a forecast object to a quantile object +#' +#' This function is used to convert a forecast object to a quantile object. +#' It dispatches to the appropriate method based on the class of the forecast +#' object. +#' +#' @inheritParams as_point +#' +#' @return A `forecast_quantile` object. +#' +#' @export +#' @keywords check-forecasts +#' @examples +#' as_quantile(as_forecast(example_sample_continuous)) +#' +as_quantile <- function(forecast, ...) { + UseMethod("as_quantile") +} + +#' @rdname as_quantile +#' @export +#' @importFrom cli cli_abort +as_quantile.default <- function(forecast, ...) { + cli_abort( + c( + "!" = "The input needs to be a forecast object.", + "i" = "Please run `as_forecast()` first." # nolint + ) + ) +} + +#' Convert a sample forecast to a quantile forecast +#' +#' This function takes a sample forecast and converts it to a quantile forecast +#' sample. +#' +#' @param forecast The `forecast_sample`` object to convert to a +#' `forecast_quantile`. +#' +#' @param quantile_levels A vector of quantile levels. Defaults to +#' 0.01 to 0.99 by 0.01. +#' @inheritParams as_point.forecast_quantile +#' +#' @return A `forecast_quantile` object. +#' @export +#' @keywords check-forecasts +#' @importFrom data.table melt +#' +#' @export +#' @examples +#' as_quantile(as_forecast(example_sample_continuous)) +as_quantile.forecast_sample <- function( + forecast, quantile_levels = seq(from = 0.01, to = 0.99, by = 0.01), ... +) { + assert_forecast(forecast, verbose = FALSE) + assert_numeric(quantile_levels, lower = 0, upper = 1) + + sum_forecast <- forecast[, + as.list(quantile(predicted, probs = quantile_levels, na.rm = TRUE)), + by = c(eval(get_forecast_unit(forecast)), "observed") + ] + + sum_forecast <- melt( + sum_forecast, + measure.vars = paste0(quantile_levels * 100, "%"), + variable.name = "quantile_level", + value.name = "predicted" + ) + + sum_forecast[, + quantile_level := as.numeric( + gsub("%", "", quantile_level, fixed = TRUE) + ) / 100 + ] + + quantile_forecast <- remake_forecast( + sum_forecast, "forecast_sample", "forecast_quantile" + ) + return(quantile_forecast) +} diff --git a/R/forecast.R b/R/forecast.R index 38cffcae..eb2e44d8 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -432,6 +432,32 @@ new_forecast <- function(data, classname) { } +#' Remakes a forecast object with a subclass +#' +#' This function takes a forecast object and modifies its class name by removing +#' the old class name and adding a new class name. It also performs an assertion +#' check on the modified forecast object. +#' +#' @param forecast The forecast object to be modified +#' @param old_classname The name of the class to be removed from the forecast +#' object +#' @param new_classname The name of the class to be added to the forecast object +#' @param verbose A logical value indicating whether to print verbose output +#' +#' @return The modified forecast object +remake_forecast <- function( + forecast, old_classname, new_classname, verbose = TRUE +) { + remade_forecast <- forecast + class(remade_forecast ) <- setdiff(class(forecast), old_classname) + remade_forecast <- new_forecast( + remade_forecast , classname = new_classname + ) + assert_forecast(remade_forecast, verbose = verbose) + return(remade_forecast) +} + + #' @title Test whether an object is a forecast object #' #' @description diff --git a/R/z_globalVariables.R b/R/z_globalVariables.R index d2037617..290fb0f4 100644 --- a/R/z_globalVariables.R +++ b/R/z_globalVariables.R @@ -80,5 +80,6 @@ globalVariables(c( "wis_component_name", "x", "y", - "g" + "g", + "target_quantile_level" )) diff --git a/man/as_point.forecast_sample.Rd b/man/as_point.forecast_sample.Rd index ec18c3eb..20e5c8f7 100644 --- a/man/as_point.forecast_sample.Rd +++ b/man/as_point.forecast_sample.Rd @@ -35,6 +35,6 @@ sample_forecast <- as_forecast(example_sample_continuous) as_point(sample_forecast) # Function approach -as_point(sample_forecast, fun = function(...) {mean(..., na.rm = TRUE)}) +as_point(sample_forecast, fun = mean) } \keyword{check-forecasts} diff --git a/man/as_quantile.Rd b/man/as_quantile.Rd index d04793bc..76e6a0f5 100644 --- a/man/as_quantile.Rd +++ b/man/as_quantile.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/as_point.R +% Please edit documentation in R/as_quantile.R \name{as_quantile} \alias{as_quantile} \alias{as_quantile.default} diff --git a/man/as_quantile.forecast_sample.Rd b/man/as_quantile.forecast_sample.Rd index e5bf4644..b4635627 100644 --- a/man/as_quantile.forecast_sample.Rd +++ b/man/as_quantile.forecast_sample.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/as_point.R +% Please edit documentation in R/as_quantile.R \name{as_quantile.forecast_sample} \alias{as_quantile.forecast_sample} \title{Convert a sample forecast to a quantile forecast} diff --git a/man/remake_forecast.Rd b/man/remake_forecast.Rd new file mode 100644 index 00000000..bceb8fe2 --- /dev/null +++ b/man/remake_forecast.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/forecast.R +\name{remake_forecast} +\alias{remake_forecast} +\title{Remakes a forecast object with a subclass} +\usage{ +remake_forecast(forecast, old_classname, new_classname, verbose = TRUE) +} +\arguments{ +\item{forecast}{The forecast object to be modified} + +\item{old_classname}{The name of the class to be removed from the forecast +object} + +\item{new_classname}{The name of the class to be added to the forecast object} + +\item{verbose}{A logical value indicating whether to print verbose output} +} +\value{ +The modified forecast object +} +\description{ +This function takes a forecast object and modifies its class name by removing +the old class name and adding a new class name. It also performs an assertion +check on the modified forecast object. +} From c5cf871d1c20b5a66472d6a3b6bac4e55b49d6fc Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 19:17:05 +0100 Subject: [PATCH 04/19] add simple tests --- R/as_point.R | 4 +- tests/test/testthat/test-remake_forecast.R | 21 +++++++++ tests/testthat/test-as_point.R | 54 ++++++++++++++++++++++ tests/testthat/test-as_quantile.R | 32 +++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tests/test/testthat/test-remake_forecast.R create mode 100644 tests/testthat/test-as_point.R create mode 100644 tests/testthat/test-as_quantile.R diff --git a/R/as_point.R b/R/as_point.R index afed6ee2..925b59dd 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -93,7 +93,9 @@ as_point.forecast_sample <- function(forecast, quantile_level = 0.5, fun, ...) { assert_forecast(forecast, verbose = FALSE) if (missing(fun)) { quantile_forecast <- as_quantile(forecast, quantile_levels = quantile_level) - point_forecast <- as_point(quantile_forecast) + point_forecast <- as_point( + quantile_forecast, quantile_level = quantile_level + ) } else { sum_forecast <- summarise_scores( forecast, fun = fun, by = c(get_forecast_unit(forecast), "observed"), diff --git a/tests/test/testthat/test-remake_forecast.R b/tests/test/testthat/test-remake_forecast.R new file mode 100644 index 00000000..bd38bd13 --- /dev/null +++ b/tests/test/testthat/test-remake_forecast.R @@ -0,0 +1,21 @@ +test_that("remake_forecast modifies the class name correctly", { + # Create a sample forecast object + forecast <- list(data = 1:10, class = c("old_class", "forecast")) + + # Call the remake_forecast function + remade_forecast <- remake_forecast(forecast, "old_class", "new_class") + + # Check if the class name has been modified correctly + expect_equal(class(remade_forecast), c("new_class", "forecast")) +}) + +test_that("remake_forecast performs assertion check", { + # Create a sample forecast object + forecast <- list(data = 1:10, class = c("old_class", "forecast")) + + # Call the remake_forecast function + remade_forecast <- remake_forecast(forecast, "old_class", "new_class") + + # Check if the assertion check passes + expect_true(assertion_check_passed(remade_forecast)) +}) \ No newline at end of file diff --git a/tests/testthat/test-as_point.R b/tests/testthat/test-as_point.R new file mode 100644 index 00000000..ebbd7cff --- /dev/null +++ b/tests/testthat/test-as_point.R @@ -0,0 +1,54 @@ +test_that("as_point converts forecast_quantile to forecast_point", { + # Create a forecast_quantile object + forecast_quantile <- as_forecast(example_quantile) + + # Convert to forecast_point using as_point + point_forecast <- as_point(forecast_quantile) + + # Check if the result is of class forecast_point + expect_s3_class(point_forecast, "forecast_point") + + # Convert to forecast_point using as_point with custom quantile_level + point_forecast_0.9 <- as_point(forecast_quantile, quantile_level = 0.9) + + # Check if the result is of class forecast_point + expect_s3_class(point_forecast_0.9, "forecast_point") + expect_true(!all(point_forecast_0.9 == point_forecast)) +}) + +test_that("as_point converts forecast_sample to forecast_point using quantile approach", { + # Create a forecast_sample object + forecast_sample <- as_forecast(example_sample_continuous) + + # Convert to forecast_point using as_point + point_forecast <- as_point(forecast_sample) + + # Check if the result is of class forecast_point + expect_s3_class(point_forecast, "forecast_point") + + # Convert to forecast_point using as_point with custom quantile_level + point_forecast_0.9 <- as_point(forecast_sample, quantile_level = 0.9) + + # Check if the result is of class forecast_point + expect_s3_class(point_forecast_0.9, "forecast_point") + expect_true(!all(point_forecast_0.9 == point_forecast)) +}) + +test_that("as_point converts forecast_sample to forecast_point using custom function", { + # Create a forecast_sample object + forecast_sample <- as_forecast(example_sample_continuous) + + # Convert to forecast_point using as_point with custom function + point_forecast <- as_point(forecast_sample, fun = mean) + + # Check if the result is of class forecast_point + expect_s3_class(point_forecast, "forecast_point") +}) + +test_that("as_point throws an error for non-forecast input", { + # Create a non-forecast object + non_forecast <- data.frame(x = 1:10) + + # Check if as_point throws an error + expect_error(as_point(non_forecast)) +}) \ No newline at end of file diff --git a/tests/testthat/test-as_quantile.R b/tests/testthat/test-as_quantile.R new file mode 100644 index 00000000..f24ac322 --- /dev/null +++ b/tests/testthat/test-as_quantile.R @@ -0,0 +1,32 @@ +test_that("as_quantile converts sample forecast to quantile forecast", { + # Create a sample forecast object + forecast <- as_forecast(example_sample_continuous) + + # Convert the sample forecast to a quantile forecast + quantile_forecast <- as_quantile(forecast) + + # Check if the quantile forecast has the correct structure + expect_s3_class(quantile_forecast, "forecast_quantile") + expect_equal(length(unique(quantile_forecast$quantile_level)), 99) +}) + +test_that("as_quantile throws an error for non-forecast input", { + # Create a non-forecast object + non_forecast <- data.frame(x = 1:10, y = 11:20) + + # Check if as_quantile throws an error for non-forecast input + expect_error(as_quantile(non_forecast), "The input needs to be a forecast object.") +}) + +test_that("as_quantile can handle custom quantiles", { + # Create a sample forecast object + forecast <- as_forecast(example_sample_continuous) + + # Convert the sample forecast to a quantile forecast with custom quantiles + quantile_forecast <- as_quantile(forecast, quantile_levels = c(0.1, 0.9)) + + # Check if the quantile forecast has the correct structure + expect_s3_class(quantile_forecast, "forecast_quantile") + expect_equal(length(unique(quantile_forecast$quantile_level)), 2) + expect_equal(unique(quantile_forecast$quantile_level), c(0.1, 0.9)) +}) \ No newline at end of file From e64aae97662d933384fb350f1ee9ead5acee12a4 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 20:47:31 +0100 Subject: [PATCH 05/19] clean up linting --- R/as_quantile.R | 2 +- R/forecast.R | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/as_quantile.R b/R/as_quantile.R index f79049e6..91f78b17 100644 --- a/R/as_quantile.R +++ b/R/as_quantile.R @@ -68,7 +68,7 @@ as_quantile.forecast_sample <- function( ) sum_forecast[, - quantile_level := as.numeric( + quantile_level := as.numeric( gsub("%", "", quantile_level, fixed = TRUE) ) / 100 ] diff --git a/R/forecast.R b/R/forecast.R index eb2e44d8..ecca9a80 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -449,9 +449,9 @@ remake_forecast <- function( forecast, old_classname, new_classname, verbose = TRUE ) { remade_forecast <- forecast - class(remade_forecast ) <- setdiff(class(forecast), old_classname) + class(remade_forecast) <- setdiff(class(forecast), old_classname) remade_forecast <- new_forecast( - remade_forecast , classname = new_classname + remade_forecast, classname = new_classname ) assert_forecast(remade_forecast, verbose = verbose) return(remade_forecast) From 112fe325cdbf4f46dc715f6860916533a8ef604c Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 20:52:22 +0100 Subject: [PATCH 06/19] add remake_forecast internal flag for pkgdown --- R/forecast.R | 1 + man/remake_forecast.Rd | 1 + 2 files changed, 2 insertions(+) diff --git a/R/forecast.R b/R/forecast.R index ecca9a80..02fd58b4 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -445,6 +445,7 @@ new_forecast <- function(data, classname) { #' @param verbose A logical value indicating whether to print verbose output #' #' @return The modified forecast object +#' @keywords internal remake_forecast <- function( forecast, old_classname, new_classname, verbose = TRUE ) { diff --git a/man/remake_forecast.Rd b/man/remake_forecast.Rd index bceb8fe2..b9887bff 100644 --- a/man/remake_forecast.Rd +++ b/man/remake_forecast.Rd @@ -24,3 +24,4 @@ This function takes a forecast object and modifies its class name by removing the old class name and adding a new class name. It also performs an assertion check on the modified forecast object. } +\keyword{internal} From e4a43c73a242de195d85bc4f03b2dff930d39529 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Apr 2024 21:07:12 +0100 Subject: [PATCH 07/19] drop not very good remake_forecast tests --- tests/test/testthat/test-remake_forecast.R | 21 --------------------- tests/testthat/test-as_point.R | 2 +- tests/testthat/test-as_quantile.R | 2 +- 3 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 tests/test/testthat/test-remake_forecast.R diff --git a/tests/test/testthat/test-remake_forecast.R b/tests/test/testthat/test-remake_forecast.R deleted file mode 100644 index bd38bd13..00000000 --- a/tests/test/testthat/test-remake_forecast.R +++ /dev/null @@ -1,21 +0,0 @@ -test_that("remake_forecast modifies the class name correctly", { - # Create a sample forecast object - forecast <- list(data = 1:10, class = c("old_class", "forecast")) - - # Call the remake_forecast function - remade_forecast <- remake_forecast(forecast, "old_class", "new_class") - - # Check if the class name has been modified correctly - expect_equal(class(remade_forecast), c("new_class", "forecast")) -}) - -test_that("remake_forecast performs assertion check", { - # Create a sample forecast object - forecast <- list(data = 1:10, class = c("old_class", "forecast")) - - # Call the remake_forecast function - remade_forecast <- remake_forecast(forecast, "old_class", "new_class") - - # Check if the assertion check passes - expect_true(assertion_check_passed(remade_forecast)) -}) \ No newline at end of file diff --git a/tests/testthat/test-as_point.R b/tests/testthat/test-as_point.R index ebbd7cff..efe22222 100644 --- a/tests/testthat/test-as_point.R +++ b/tests/testthat/test-as_point.R @@ -51,4 +51,4 @@ test_that("as_point throws an error for non-forecast input", { # Check if as_point throws an error expect_error(as_point(non_forecast)) -}) \ No newline at end of file +}) diff --git a/tests/testthat/test-as_quantile.R b/tests/testthat/test-as_quantile.R index f24ac322..b462a0cd 100644 --- a/tests/testthat/test-as_quantile.R +++ b/tests/testthat/test-as_quantile.R @@ -29,4 +29,4 @@ test_that("as_quantile can handle custom quantiles", { expect_s3_class(quantile_forecast, "forecast_quantile") expect_equal(length(unique(quantile_forecast$quantile_level)), 2) expect_equal(unique(quantile_forecast$quantile_level), c(0.1, 0.9)) -}) \ No newline at end of file +}) From dd23db383dede4152e5042cf477d906b440ceaeb Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Mon, 15 Apr 2024 23:02:45 +0100 Subject: [PATCH 08/19] Update NEWS.md --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 1eaed20b..d9df07e9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,7 +52,7 @@ scores <- score(forecast_quantile) - Users can test whether an object is of class `forecast_*()` using the function `is_forecast()`. Users can also test for a specific `forecast_*` class using the appropriate `is_forecast.forecast_*` method. For example, to check whether an object is of class `forecast_quantile`, you would use you would use `scoringutils:::is_forecast.forecast_quantile()`. ### Updating a forecast object -- The functions `as_point` and `as_quantile` have been added to allow conversion of various forecast types in point and quantile forecasts respectively. +- The functions `as_point` and `as_quantile` have been added to allow conversion of various forecast types to point and quantile forecasts respectively. ### Pairwise comparisons and relative skill - 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_relative_skill()`, was introduced that takes summarised scores as an input and adds columns with relative skill scores and scaled relative skill scores. From 82fd28dae5ac1ff4045aec58fc1f4a10d52f4322 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Wed, 17 Apr 2024 10:22:13 +0100 Subject: [PATCH 09/19] Update R/as_quantile.R Co-authored-by: Sebastian Funk --- R/as_quantile.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/as_quantile.R b/R/as_quantile.R index 91f78b17..fd9ddec3 100644 --- a/R/as_quantile.R +++ b/R/as_quantile.R @@ -32,7 +32,7 @@ as_quantile.default <- function(forecast, ...) { #' Convert a sample forecast to a quantile forecast #' #' This function takes a sample forecast and converts it to a quantile forecast -#' sample. +#' using the empirical quantiles across samples. #' #' @param forecast The `forecast_sample`` object to convert to a #' `forecast_quantile`. From 07918714f839ce8807893ec5bc0720db1ffb3280 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Wed, 17 Apr 2024 10:26:14 +0100 Subject: [PATCH 10/19] Update forecast.R --- R/forecast.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/forecast.R b/R/forecast.R index 02fd58b4..0445be6e 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -66,7 +66,7 @@ as_forecast <- function(data, #' sample id. This column will be renamed to "sample_id". Only applicable to #' sample-based forecasts. #' @param check_forecast_type Logical, defaults to `FALSE`. Should the -#' `forecast_type` be checked and reset if not as implied by the `data`? +#' `forecast_type` be checked against the type implied by the `data`? #' #' @export #' @importFrom cli cli_warn From e13dfd0d033e6369e044515a8c72bf070f754de7 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Wed, 17 Apr 2024 10:26:30 +0100 Subject: [PATCH 11/19] Update R/forecast.R Co-authored-by: Sebastian Funk --- R/forecast.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/forecast.R b/R/forecast.R index 0445be6e..12bd4353 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -142,7 +142,7 @@ as_forecast.default <- function(data, ) #nolint end } - #old binary format + # old binary format if (forecast_type == "point") { looks_binary <- check_input_binary(factor(data$observed), data$predicted) if (is.logical(looks_binary)) { From 6fd896261baa23b4541c1dc09b97961f0b44694f Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Wed, 17 Apr 2024 10:36:11 +0100 Subject: [PATCH 12/19] Update R/as_point.R Co-authored-by: Nikos Bosse <37978797+nikosbosse@users.noreply.github.com> --- R/as_point.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/as_point.R b/R/as_point.R index 925b59dd..c01e04e9 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -5,7 +5,7 @@ #' forecast. It is a generic function that dispatches the conversion to the #' appropriate method based on the class of the input forecast object. #' -#' @param forecast An object of class `forecast_{type}`, +#' @param A forecast object of class `forecast_{type}` (a validated data.table with predicted and observed values, see as_forecast()) #' representing a forecast. #' @param ... Additional arguments to be passed to the specific method. #' @return The function returns a point forecast object, which is a specific From 90d17a469f8ebb731b7b4beaeaf9425ae4002be0 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Wed, 17 Apr 2024 10:36:27 +0100 Subject: [PATCH 13/19] Update R/as_point.R Co-authored-by: Sebastian Funk --- R/as_point.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/as_point.R b/R/as_point.R index c01e04e9..928bbba5 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -68,8 +68,8 @@ as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { #' Convert a sample based forecast to a point forecast #' #' This function converts a forecast object to a point forecast by either -#' taking the quantile forecast at a specified quantile level or by summarizing -#' the forecast using a custom function (for example `mean`). +#' estimating a specified quantile level of the predictive distribution, or by +#' summarising the forecast using a custom function (for example `mean`). #' #' @param forecast The `forecast_sample` object to be converted to a point #' forecast. From 82a36f31e9eca9b8f784cea0ed8678a65403c7c1 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Apr 2024 10:54:02 +0100 Subject: [PATCH 14/19] fix broken PR suggests --- R/as_point.R | 9 +++++---- R/as_quantile.R | 4 ++-- man/as_forecast.Rd | 2 +- man/as_point.Rd | 3 ++- man/as_point.forecast_quantile.Rd | 4 ++-- man/as_point.forecast_sample.Rd | 8 ++++---- man/as_quantile.Rd | 3 ++- man/as_quantile.forecast_sample.Rd | 7 +++++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/R/as_point.R b/R/as_point.R index 928bbba5..5be410be 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -5,9 +5,11 @@ #' forecast. It is a generic function that dispatches the conversion to the #' appropriate method based on the class of the input forecast object. #' -#' @param A forecast object of class `forecast_{type}` (a validated data.table with predicted and observed values, see as_forecast()) +#' @param forecast A forecast object of class `forecast_{type}` (a validated +#' `data.table` with predicted and observed values, see [as_forecast()]) #' representing a forecast. #' @param ... Additional arguments to be passed to the specific method. +#' #' @return The function returns a point forecast object, which is a specific #' type of forecast object that represents a single value prediction. #' @@ -36,10 +38,9 @@ as_point.default <- function(forecast, ...) { #' #' This function takes a quantile forecast and converts it to a point forecast #' by selecting the forecast corresponding to the specified quantile level. -#' #' @param forecast The `forecast_quantile` object. -#' @param quantile_level The desired quantile level for the point forecast. -#' Defaults to 0.5 (median). +#' @param quantile_level The desired quantile level of the current forecast +#' that should become the point forecast. Defaults to 0.5 (median). #' @param ... Additional arguments passing inherited from the default method but #' unused. #' diff --git a/R/as_quantile.R b/R/as_quantile.R index fd9ddec3..441cc40b 100644 --- a/R/as_quantile.R +++ b/R/as_quantile.R @@ -34,8 +34,8 @@ as_quantile.default <- function(forecast, ...) { #' This function takes a sample forecast and converts it to a quantile forecast #' using the empirical quantiles across samples. #' -#' @param forecast The `forecast_sample`` object to convert to a -#' `forecast_quantile`. +#' @param A forecast object of class `forecast_sample` (a validated `data.table` +#' with predicted and observed values, see [as_forecast()]) `forecast_quantile`. #' #' @param quantile_levels A vector of quantile levels. Defaults to #' 0.01 to 0.99 by 0.01. diff --git a/man/as_forecast.Rd b/man/as_forecast.Rd index 5625f16d..2a7e4f17 100644 --- a/man/as_forecast.Rd +++ b/man/as_forecast.Rd @@ -58,7 +58,7 @@ sample id. This column will be renamed to "sample_id". Only applicable to sample-based forecasts.} \item{check_forecast_type}{Logical, defaults to \code{FALSE}. Should the -\code{forecast_type} be checked and reset if not as implied by the \code{data}?} +\code{forecast_type} be checked against the type implied by the \code{data}?} } \value{ Depending on the forecast type, an object of the following class will be diff --git a/man/as_point.Rd b/man/as_point.Rd index b53575b0..8357eeff 100644 --- a/man/as_point.Rd +++ b/man/as_point.Rd @@ -10,7 +10,8 @@ as_point(forecast, ...) \method{as_point}{default}(forecast, ...) } \arguments{ -\item{forecast}{An object of class \verb{forecast_\{type\}}, +\item{forecast}{A forecast object of class \verb{forecast_\{type\}} (a validated +\code{data.table} with predicted and observed values, see \code{\link[=as_forecast]{as_forecast()}}) representing a forecast.} \item{...}{Additional arguments to be passed to the specific method.} diff --git a/man/as_point.forecast_quantile.Rd b/man/as_point.forecast_quantile.Rd index 5213720e..486fd8f8 100644 --- a/man/as_point.forecast_quantile.Rd +++ b/man/as_point.forecast_quantile.Rd @@ -9,8 +9,8 @@ \arguments{ \item{forecast}{The \code{forecast_quantile} object.} -\item{quantile_level}{The desired quantile level for the point forecast. -Defaults to 0.5 (median).} +\item{quantile_level}{The desired quantile level of the current forecast +that should become the point forecast. Defaults to 0.5 (median).} \item{...}{Additional arguments passing inherited from the default method but unused.} diff --git a/man/as_point.forecast_sample.Rd b/man/as_point.forecast_sample.Rd index 20e5c8f7..fb5e9045 100644 --- a/man/as_point.forecast_sample.Rd +++ b/man/as_point.forecast_sample.Rd @@ -10,8 +10,8 @@ \item{forecast}{The \code{forecast_sample} object to be converted to a point forecast.} -\item{quantile_level}{The desired quantile level for the point forecast. -Defaults to 0.5 (median).} +\item{quantile_level}{The desired quantile level of the current forecast +that should become the point forecast. Defaults to 0.5 (median).} \item{fun}{A custom function to summarize the forecast. If provided, this function will be used instead of the quantile method. An example could @@ -25,8 +25,8 @@ A \code{forecast_point} object. } \description{ This function converts a forecast object to a point forecast by either -taking the quantile forecast at a specified quantile level or by summarizing -the forecast using a custom function (for example \code{mean}). +estimating a specified quantile level of the predictive distribution, or by +summarising the forecast using a custom function (for example \code{mean}). } \examples{ sample_forecast <- as_forecast(example_sample_continuous) diff --git a/man/as_quantile.Rd b/man/as_quantile.Rd index 76e6a0f5..1851f570 100644 --- a/man/as_quantile.Rd +++ b/man/as_quantile.Rd @@ -10,7 +10,8 @@ as_quantile(forecast, ...) \method{as_quantile}{default}(forecast, ...) } \arguments{ -\item{forecast}{An object of class \verb{forecast_\{type\}}, +\item{forecast}{A forecast object of class \verb{forecast_\{type\}} (a validated +\code{data.table} with predicted and observed values, see \code{\link[=as_forecast]{as_forecast()}}) representing a forecast.} \item{...}{Additional arguments to be passed to the specific method.} diff --git a/man/as_quantile.forecast_sample.Rd b/man/as_quantile.forecast_sample.Rd index b4635627..14e28a1d 100644 --- a/man/as_quantile.forecast_sample.Rd +++ b/man/as_quantile.forecast_sample.Rd @@ -11,20 +11,23 @@ ) } \arguments{ -\item{forecast}{The \verb{forecast_sample`` object to convert to a }forecast_quantile`.} +\item{forecast}{The \code{forecast_quantile} object.} \item{quantile_levels}{A vector of quantile levels. Defaults to 0.01 to 0.99 by 0.01.} \item{...}{Additional arguments passing inherited from the default method but unused.} + +\item{A}{forecast object of class \code{forecast_sample} (a validated \code{data.table} +with predicted and observed values, see \code{\link[=as_forecast]{as_forecast()}}) \code{forecast_quantile}.} } \value{ A \code{forecast_quantile} object. } \description{ This function takes a sample forecast and converts it to a quantile forecast -sample. +using the empirical quantiles across samples. } \examples{ as_quantile(as_forecast(example_sample_continuous)) From 6be7724207d960c25ce88fa1320de127a98473f2 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Apr 2024 11:45:11 +0100 Subject: [PATCH 15/19] make sample_to_quantile internal and remove quantile_level from 'as_point' --- NAMESPACE | 1 - R/as_point.R | 37 +++++++++++------------------- R/as_quantile.R | 26 ++++++++------------- R/utils_data_handling.R | 11 +++------ R/z_globalVariables.R | 2 +- man/as_point.forecast_sample.Rd | 18 ++++----------- man/as_quantile.forecast_sample.Rd | 3 +-- man/sample_to_quantile.Rd | 5 +--- tests/testthat/test-as_point.R | 7 ------ 9 files changed, 33 insertions(+), 77 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e93f887e..c8ec84da 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -78,7 +78,6 @@ export(plot_wis) export(quantile_score) export(quantile_to_interval) export(run_safely) -export(sample_to_quantile) export(score) export(se_mean_sample) export(select_metrics) diff --git a/R/as_point.R b/R/as_point.R index 5be410be..84ac1570 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -68,43 +68,32 @@ as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { #' Convert a sample based forecast to a point forecast #' -#' This function converts a forecast object to a point forecast by either -#' estimating a specified quantile level of the predictive distribution, or by +#' This function converts a forecast object to a point forecast by #' summarising the forecast using a custom function (for example `mean`). #' #' @param forecast The `forecast_sample` object to be converted to a point #' forecast. #' -#' @param fun A custom function to summarize the forecast. If provided, this -#' function will be used instead of the quantile method. An example could -#' be the `mean`. -#' @inheritParams as_point.forecast_quantile +#' @param fun A custom function to summarize the forecast. Defaults to `median`. +#' +#' @param ... Additional arguments passed to `fun`.. +#' #' @return A `forecast_point` object. #' @export #' @keywords check-forecasts #' @examples #' sample_forecast <- as_forecast(example_sample_continuous) #' -#' # Quantile approach #' as_point(sample_forecast) -#' -#' # Function approach #' as_point(sample_forecast, fun = mean) -as_point.forecast_sample <- function(forecast, quantile_level = 0.5, fun, ...) { +as_point.forecast_sample <- function(forecast, fun = median, ...) { assert_forecast(forecast, verbose = FALSE) - if (missing(fun)) { - quantile_forecast <- as_quantile(forecast, quantile_levels = quantile_level) - point_forecast <- as_point( - quantile_forecast, quantile_level = quantile_level - ) - } else { - sum_forecast <- summarise_scores( - forecast, fun = fun, by = c(get_forecast_unit(forecast), "observed"), - metrics = "predicted" - ) - point_forecast <- remake_forecast( - sum_forecast, "forecast_sample", "forecast_point" - ) - } + sum_forecast <- summarise_scores( + forecast, fun = fun, by = c(get_forecast_unit(forecast), "observed"), + metrics = "predicted", ... + ) + point_forecast <- remake_forecast( + sum_forecast, "forecast_sample", "forecast_point" + ) return(point_forecast) } diff --git a/R/as_quantile.R b/R/as_quantile.R index 441cc40b..6834f0a6 100644 --- a/R/as_quantile.R +++ b/R/as_quantile.R @@ -39,13 +39,15 @@ as_quantile.default <- function(forecast, ...) { #' #' @param quantile_levels A vector of quantile levels. Defaults to #' 0.01 to 0.99 by 0.01. +#' +#' @param ... Pass additional arguments to [quantile()]. +#' #' @inheritParams as_point.forecast_quantile #' #' @return A `forecast_quantile` object. #' @export #' @keywords check-forecasts -#' @importFrom data.table melt -#' +#' @importFrom stats quantile #' @export #' @examples #' as_quantile(as_forecast(example_sample_continuous)) @@ -56,23 +58,15 @@ as_quantile.forecast_sample <- function( assert_numeric(quantile_levels, lower = 0, upper = 1) sum_forecast <- forecast[, - as.list(quantile(predicted, probs = quantile_levels, na.rm = TRUE)), + .( + quantile_level = quantile_levels, + predicted = quantile( + x = predicted, probs = ..quantile_levels, na.rm = TRUE, ... + ) + ), by = c(eval(get_forecast_unit(forecast)), "observed") ] - sum_forecast <- melt( - sum_forecast, - measure.vars = paste0(quantile_levels * 100, "%"), - variable.name = "quantile_level", - value.name = "predicted" - ) - - sum_forecast[, - quantile_level := as.numeric( - gsub("%", "", quantile_level, fixed = TRUE) - ) / 100 - ] - quantile_forecast <- remake_forecast( sum_forecast, "forecast_sample", "forecast_quantile" ) diff --git a/R/utils_data_handling.R b/R/utils_data_handling.R index f989fcc5..204c3118 100644 --- a/R/utils_data_handling.R +++ b/R/utils_data_handling.R @@ -1,3 +1,5 @@ +# ==================== Functions internally used for scoring =================== +# These functions would ideally be replaced in the future #' @title Change data from a sample based format to a quantile format #' #' @description @@ -16,10 +18,7 @@ #' @importFrom stats quantile #' @importFrom methods hasArg #' @importFrom checkmate assert_numeric -#' @keywords data-handling -#' @export -#' @examples -#' sample_to_quantile(as_forecast(example_sample_discrete)) +#' @keywords internal sample_to_quantile <- function(forecast, quantile_level = c(0.05, 0.25, 0.5, 0.75, 0.95), type = 7) { @@ -42,10 +41,6 @@ sample_to_quantile <- function(forecast, return(as_forecast(forecast)) } - -# ==================== Functions internally used for scoring =================== -# These functions would ideally be replaced in the future - #' @title Change forecast from an interval format to a quantile format #' #' @description diff --git a/R/z_globalVariables.R b/R/z_globalVariables.R index 290fb0f4..ba93d6fd 100644 --- a/R/z_globalVariables.R +++ b/R/z_globalVariables.R @@ -1,6 +1,6 @@ globalVariables(c( "..index", - "..quantile_level", + "..quantile_levels", "..type", ".", ".SD", diff --git a/man/as_point.forecast_sample.Rd b/man/as_point.forecast_sample.Rd index fb5e9045..076f647e 100644 --- a/man/as_point.forecast_sample.Rd +++ b/man/as_point.forecast_sample.Rd @@ -4,37 +4,27 @@ \alias{as_point.forecast_sample} \title{Convert a sample based forecast to a point forecast} \usage{ -\method{as_point}{forecast_sample}(forecast, quantile_level = 0.5, fun, ...) +\method{as_point}{forecast_sample}(forecast, fun = median, ...) } \arguments{ \item{forecast}{The \code{forecast_sample} object to be converted to a point forecast.} -\item{quantile_level}{The desired quantile level of the current forecast -that should become the point forecast. Defaults to 0.5 (median).} +\item{fun}{A custom function to summarize the forecast. Defaults to \code{median}.} -\item{fun}{A custom function to summarize the forecast. If provided, this -function will be used instead of the quantile method. An example could -be the \code{mean}.} - -\item{...}{Additional arguments passing inherited from the default method but -unused.} +\item{...}{Additional arguments passed to \code{fun}..} } \value{ A \code{forecast_point} object. } \description{ -This function converts a forecast object to a point forecast by either -estimating a specified quantile level of the predictive distribution, or by +This function converts a forecast object to a point forecast by summarising the forecast using a custom function (for example \code{mean}). } \examples{ sample_forecast <- as_forecast(example_sample_continuous) -# Quantile approach as_point(sample_forecast) - -# Function approach as_point(sample_forecast, fun = mean) } \keyword{check-forecasts} diff --git a/man/as_quantile.forecast_sample.Rd b/man/as_quantile.forecast_sample.Rd index 14e28a1d..9ecd6e83 100644 --- a/man/as_quantile.forecast_sample.Rd +++ b/man/as_quantile.forecast_sample.Rd @@ -16,8 +16,7 @@ \item{quantile_levels}{A vector of quantile levels. Defaults to 0.01 to 0.99 by 0.01.} -\item{...}{Additional arguments passing inherited from the default method but -unused.} +\item{...}{Pass additional arguments to \code{\link[=quantile]{quantile()}}.} \item{A}{forecast object of class \code{forecast_sample} (a validated \code{data.table} with predicted and observed values, see \code{\link[=as_forecast]{as_forecast()}}) \code{forecast_quantile}.} diff --git a/man/sample_to_quantile.Rd b/man/sample_to_quantile.Rd index efa82c12..f74a1802 100644 --- a/man/sample_to_quantile.Rd +++ b/man/sample_to_quantile.Rd @@ -27,7 +27,4 @@ a data.table in a long interval range format Transform data from a format that is based on predictive samples to a format based on plain quantiles. } -\examples{ -sample_to_quantile(as_forecast(example_sample_discrete)) -} -\keyword{data-handling} +\keyword{internal} diff --git a/tests/testthat/test-as_point.R b/tests/testthat/test-as_point.R index efe22222..26d0e895 100644 --- a/tests/testthat/test-as_point.R +++ b/tests/testthat/test-as_point.R @@ -25,13 +25,6 @@ test_that("as_point converts forecast_sample to forecast_point using quantile ap # Check if the result is of class forecast_point expect_s3_class(point_forecast, "forecast_point") - - # Convert to forecast_point using as_point with custom quantile_level - point_forecast_0.9 <- as_point(forecast_sample, quantile_level = 0.9) - - # Check if the result is of class forecast_point - expect_s3_class(point_forecast_0.9, "forecast_point") - expect_true(!all(point_forecast_0.9 == point_forecast)) }) test_that("as_point converts forecast_sample to forecast_point using custom function", { From 737f4e9bbf7dedd0ee488444d2d9514d466343b0 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 17 Apr 2024 20:27:14 +0100 Subject: [PATCH 16/19] linting and catch public use of sample_to_quantile --- R/z_globalVariables.R | 1 + vignettes/scoringutils.Rmd | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/z_globalVariables.R b/R/z_globalVariables.R index ba93d6fd..59cd7b17 100644 --- a/R/z_globalVariables.R +++ b/R/z_globalVariables.R @@ -1,5 +1,6 @@ globalVariables(c( "..index", + "..quantile_level", "..quantile_levels", "..type", ".", diff --git a/vignettes/scoringutils.Rmd b/vignettes/scoringutils.Rmd index d9dea94c..f4d545da 100644 --- a/vignettes/scoringutils.Rmd +++ b/vignettes/scoringutils.Rmd @@ -330,8 +330,8 @@ You can convert your sample-based forecasts into a quantile-based format using t ```{r} example_sample_discrete %>% as_forecast() %>% - sample_to_quantile( - quantile_level = c(0.01, 0.025, seq(0.05, 0.95, 0.05), 0.975, 0.99) + as_quantile( + quantile_levels = c(0.01, 0.025, seq(0.05, 0.95, 0.05), 0.975, 0.99) ) %>% as_forecast() %>% score() From 5fae245fc59bb78313d5d0432eb642c7459b8a42 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Fri, 19 Apr 2024 10:04:02 +0100 Subject: [PATCH 17/19] Update NEWS.md Co-authored-by: Sebastian Funk --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index d9df07e9..48b28306 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,7 +52,7 @@ scores <- score(forecast_quantile) - Users can test whether an object is of class `forecast_*()` using the function `is_forecast()`. Users can also test for a specific `forecast_*` class using the appropriate `is_forecast.forecast_*` method. For example, to check whether an object is of class `forecast_quantile`, you would use you would use `scoringutils:::is_forecast.forecast_quantile()`. ### Updating a forecast object -- The functions `as_point` and `as_quantile` have been added to allow conversion of various forecast types to point and quantile forecasts respectively. +- The functions `as_point` and `as_quantile` have been added to allow conversion of various forecast types to point and quantile forecasts respectively. The function `sample_to_quantile` is no longer exported. ### Pairwise comparisons and relative skill - 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_relative_skill()`, was introduced that takes summarised scores as an input and adds columns with relative skill scores and scaled relative skill scores. From c2b62f30013fd687a374c57c9d658046299b4b57 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Fri, 19 Apr 2024 10:04:31 +0100 Subject: [PATCH 18/19] Update R/forecast.R Co-authored-by: Sebastian Funk --- R/forecast.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/forecast.R b/R/forecast.R index 12bd4353..912d9bbe 100644 --- a/R/forecast.R +++ b/R/forecast.R @@ -66,7 +66,8 @@ as_forecast <- function(data, #' sample id. This column will be renamed to "sample_id". Only applicable to #' sample-based forecasts. #' @param check_forecast_type Logical, defaults to `FALSE`. Should the -#' `forecast_type` be checked against the type implied by the `data`? +#' `forecast_type` be checked against the type implied by the `data`? If +#' `forecast_type` is NULL it will be inferred from the data before checking. #' #' @export #' @importFrom cli cli_warn From 6688f2f612772ed685175fa676744bb2b1f0ad65 Mon Sep 17 00:00:00 2001 From: Sam Abbott Date: Fri, 19 Apr 2024 10:06:54 +0100 Subject: [PATCH 19/19] Update R/as_point.R Co-authored-by: Nikos Bosse <37978797+nikosbosse@users.noreply.github.com> --- R/as_point.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/as_point.R b/R/as_point.R index 84ac1570..f2040afa 100644 --- a/R/as_point.R +++ b/R/as_point.R @@ -52,7 +52,7 @@ as_point.default <- function(forecast, ...) { #' as_point(as_forecast(example_quantile)) as_point.forecast_quantile <- function(forecast, quantile_level = 0.5, ...) { assert_forecast(forecast, verbose = FALSE) - assert_numeric(quantile_level, lower = 0, upper = 1, len = 1) + assert_subset(quantile_level, unique(forecast$quantile_level) forecast <- forecast[ quantile_level == target_quantile_level, ,