1202: Fix interval_coverage() floating point matching and NaN scores for quantile levels 0 and 1 - #1217
1202: Fix interval_coverage() floating point matching and NaN scores for quantile levels 0 and 1#1217nikosbosse wants to merge 2 commits into
Conversation
…antile levels 0 and 1 (#1202) interval_coverage() now rounds quantile levels to 10 decimal places before checking for the required quantiles, so seq()-generated levels no longer trigger a spurious missing-quantile error. interval_score() applies the indicator before the 2 / alpha factor and cancels the alpha / 2 weight analytically, so quantile levels 0 and 1 (interval_range = 100, alpha = 0) yield finite scores instead of NaN, restoring the WIS = mean quantile score identity. Unweighted scores return Inf when the observation falls outside a 100% interval. quantile_score(weigh = FALSE) receives the analogous fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1217 +/- ##
=======================================
Coverage 98.15% 98.15%
=======================================
Files 41 41
Lines 2225 2226 +1
=======================================
+ Hits 2184 2185 +1
Misses 41 41 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Automated review (Claude Fable, directed by @nikosbosse): Verification
Non-blocking observations
Verdict: approve. |
…1202) With weigh = FALSE and quantile levels 0 and 1 present, wis() and quantile_score() legitimately differ: the 0%-100% interval contributes its full dispersion to the unweighted interval score, whereas quantile levels 0 and 1 contribute a score of 0 to the unweighted quantile scores. Make this explicit in the roxygen docs of wis() and quantile_score(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Follow-up (Claude Fable, directed by @nikosbosse): Addressed the review's informational observation no. 2 with a docs-only clarification (commit c4b8a0c): the roxygen docs of Observation no. 1 (tolerance mismatch between the 10-dp presence check and the downstream row filter) was deliberately not acted on: it is unreachable via the standard pipeline, since |
Description
This PR closes #1202.
Two related edge-case bugs in the quantile-based metrics:
interval_coverage()errored onseq()-generated quantile levels. The check for the required quantile levels used an exact floating point comparison (necessary_quantiles %in% quantile_level), so e.g.interval_coverage(observed, predicted, seq(0.05, 0.95, 0.05), interval_range = 30)aborted with "the 0.35 and 0.65 quantiles are required" even though those quantiles were present (0.35 %in% seq(0.05, 0.95, 0.05)isFALSE). The fix rounds both sides to 10 decimal places before matching, consistent with the package's existing convention (e.g. inas_forecast_quantile()).wis(),interval_score()andquantile_score(weigh = FALSE)returnedNaNfor quantile levels 0 and 1. These levels are explicitly permitted by input validation and occur in real hub data ("min"/"max" quantiles), but they form a 100% interval withalpha = 0, and the penalty terms2 / alpha * (bound - observed) * indicatorevaluated toInf * 0 = NaN. This broke the documented WIS = mean quantile score identity:wis(5, matrix(c(1, 3, 5, 7, 9), nrow = 1), c(0, 0.25, 0.5, 0.75, 1))returnedNaNwhilequantile_score()returned0.4. The fix applies the indicator before the2 / alphafactor and cancels2 / alpha * alpha / 2analytically in the weighted case; in the unweighted case, zero penalties stay zero and the score isInfwhen the observation falls outside a 100% interval (mathematically correct, per maintainer decision).quantile_score(weigh = FALSE)gets the analogous fix (a zero weighted score maps to a zero unweighted score instead of0/0). Outputs for all other interval ranges are unchanged.Tests were written first and failed on the unfixed code:
After the fix, the full test suite passes (0 failed, 920 passed).
Dev note: bug identified by an LLM audit (#1189); fix and tests implemented with LLM support, directed by @nikosbosse.
Checklist
lintr::lint()on the changed files; no new lints were introduced.