as_forecast_quantile.forecast_sample() produces silently wrong output or errors whenever probs is not symmetric around 0.5 (or contains duplicates).
Reproduction
Samples 1:100, observed = 50, one forecast unit:
library(scoringutils)
samples <- data.table::data.table(
model = "m", observed = 50, predicted = 1:100, sample_id = 1:100
) |> as_forecast_sample()
# Single asymmetric prob: TWO rows come back, and the 0.6 level
# silently receives the 0.4 quantile via data.table length-1 recycling
as_forecast_quantile(samples, probs = 0.4)
#> model observed quantile_level predicted
#> 1: m 50 0.4 40.6
#> 2: m 50 0.6 40.6 # should be 60.4, or not exist at all
# Multiple asymmetric probs: hard error
as_forecast_quantile(samples, probs = c(0.1, 0.2))
#> Error: Supplied 2 items for column 2 of group 1 which has 4 rows.
#> The RHS length must either be 1 ... or match the LHS length exactly.
# Duplicated probs: error from assert_forecast
as_forecast_quantile(samples, probs = c(0.5, 0.5))
#> Error: ... more than one forecast for the same target
Symmetric prob sets (including the default c(0.05, 0.25, 0.5, 0.75, 0.95)) are unaffected because the symmetrized vector then equals probs elementwise.
Cause
In R/class-forecast-sample.R (lines 110-118) the quantile_level column is built from a symmetrized vector, while quantile() is called with the raw probs:
quantile_level <- unique(round(c(probs, 1 - probs), digits = 10))
...
predicted = quantile(x = predicted, probs = ..probs, ...)
This is a refactoring regression from 4d9ecf7 (#876, Jul 2024): the predecessor sample_to_quantile() used the same symmetrized vector for both the column and the probs argument of quantile(), so it was internally consistent; the refactor updated only the quantile() call.
Intended fix
Per maintainer decision: compute quantiles at exactly the requested probs (matching the current documentation of probs), i.e. use a single quantile_level <- unique(round(probs, digits = 10)) for both the column and the quantile() call — do NOT restore the old symmetrization. For every symmetric prob set (including the default) the output is byte-identical to current behaviour. Additionally, tighten the input check to assert_numeric(probs, min.len = 1, lower = 0, upper = 1) so out-of-range probs fail with a clear checkmate message.
Part of the bug audit in #1189.
as_forecast_quantile.forecast_sample()produces silently wrong output or errors wheneverprobsis not symmetric around 0.5 (or contains duplicates).Reproduction
Samples 1:100, observed = 50, one forecast unit:
Symmetric prob sets (including the default
c(0.05, 0.25, 0.5, 0.75, 0.95)) are unaffected because the symmetrized vector then equalsprobselementwise.Cause
In
R/class-forecast-sample.R(lines 110-118) thequantile_levelcolumn is built from a symmetrized vector, whilequantile()is called with the rawprobs:This is a refactoring regression from 4d9ecf7 (#876, Jul 2024): the predecessor
sample_to_quantile()used the same symmetrized vector for both the column and theprobsargument ofquantile(), so it was internally consistent; the refactor updated only thequantile()call.Intended fix
Per maintainer decision: compute quantiles at exactly the requested probs (matching the current documentation of
probs), i.e. use a singlequantile_level <- unique(round(probs, digits = 10))for both the column and thequantile()call — do NOT restore the old symmetrization. For every symmetric prob set (including the default) the output is byte-identical to current behaviour. Additionally, tighten the input check toassert_numeric(probs, min.len = 1, lower = 0, upper = 1)so out-of-range probs fail with a clear checkmate message.Part of the bug audit in #1189.