1221, 1222: Speed up get_pairwise_comparisons() and skip unused tests in add_relative_skill() - #1227
Conversation
Pivot the scores of each group into a forecast unit by comparator matrix once, instead of merging the scores separately for every pair of comparators. The overlapping forecasts of a pair are the rows where both columns are non-missing. Results are identical to the previous implementation. The per-pair statistics are moved into a new internal helper, .compare_scores(), which is shared with compare_forecasts(). The latter is retained as a reference implementation and is used in tests to check the pivot-based implementation against the per-pair merge. Scores with more than one row per forecast unit and comparator now produce an informative error instead of silently comparing duplicated rows. Closes #1221 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1227 +/- ##
==========================================
+ Coverage 98.38% 98.41% +0.02%
==========================================
Files 41 41
Lines 2235 2273 +38
==========================================
+ Hits 2199 2237 +38
Misses 36 36 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
add_relative_skill() drops the p-value columns before returning, but previously still ran a Wilcoxon test for every pair of comparators. It now has an explicit test_type argument defaulting to NULL so that no test is run unless requested. Relative skill scores are unchanged and spurious wilcox.test() warnings for tied scores no longer appear. Closes #1222 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
Use a small subset with tied but non-zero paired differences so that wilcox.test() warns about ties on all supported R versions. Identical scores for two models no longer trigger the warning on R 4.6. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
Mock wilcox.test() and count its calls instead of relying on its warnings, whose behaviour with ties differs across R versions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
There was a problem hiding this comment.
This PR pivots scores once into a forecast-unit-by-comparator matrix instead of re-merging per pair, and skips the wilcoxon test by default in add_relative_skill(). The logic checks out: duplicate handling, factor comparators, and the pivoted results are verified against a per-pair compare_forecasts() reference in new tests, and the default test-skipping is verified via a mocked wilcox.test() call count. Only issue is a few roxygen/comment lines that narrate the before/after of the refactor rather than describing the current code.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: first pass. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
Rewrite three roxygen/comment passages flagged by seabbs-review-bot that narrated the before/after of the pivot refactor instead of describing what the code does now: - pairwise_comparison_one_group() docs no longer contrast against the old per-pair compare_forecasts() approach. - The duplicate-removal comment in .pivot_scores() describes its current purpose rather than the merge it replaces. - compare_forecasts() docs state plainly that it is not called by pairwise_comparison_one_group() and is kept for testing. No behaviour change; docs regenerated with roxygen2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
|
@seabbs-review-bot addressed all three comment/doc nits in 35f7580 — rewrote the three flagged passages to describe current behaviour instead of narrating the refactor. Threads resolved. Generated by Claude Code |
There was a problem hiding this comment.
This PR replaces the per-pair merge in pairwise_comparison_one_group() with a single pivot into a forecast-unit-by-comparator matrix, and lets add_relative_skill() skip the wilcox.test by default. The refactor is validated by a new test that checks the pivot-based path against the original compare_forecasts() implementation across grouping, factors, missing overlap and permutation-test seeding, and NEWS.md/docs are updated appropriately. The change looks correct and well tested; only minor documentation and consistency points remain.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: re-review requested by seabbs-bot. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
Rename the two new internal helpers to match the package's existing convention for unexported functions (plain snake_case with @Keywords internal, as used by compare_forecasts(), permutation_test() and pairwise_comparison_one_group()): - .pivot_scores() -> pivot_scores() - .compare_scores() -> compare_scores() Also clarify pivot_scores()'s docs: exact duplicate rows are dropped silently, and only rows that share a forecast unit and comparator but differ in value raise an error. No behaviour change; docs regenerated with roxygen2, man/dot-*.Rd replaced by man/pivot_scores.Rd and man/compare_scores.Rd. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
|
@seabbs-review-bot addressed both in 970dbd7 — renamed Generated by Claude Code |
There was a problem hiding this comment.
This PR replaces the per-pair merge-based comparison in pairwise_comparison_one_group() with a single dcast into a forecast-unit x comparator matrix (pivot_scores()), extracts the shared ratio/p-value logic into compare_scores(), and adds a test_type argument to add_relative_skill() so it skips the p-value test by default. The refactor is well tested (factor comparator handling, duplicate-forecast error, no-overlap NA case, and a reference-implementation cross-check against the old compare_forecasts()), NEWS.md and roxygen docs are updated accurately, and I found no correctness bugs in the new pivot/matching logic.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: re-review requested by seabbs-bot. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
pivot_scores(): as.matrix() on a data.table subset already carries column names from the subset, so the explicit colnames<- assignment was a no-op. Verified empirically before removing. Also correct the docstring: the duplicate check (anyDuplicated(scores, by = forecast_unit)) runs on the full row before subsetting to `metric`, so it fires on any column differing between rows sharing a forecast unit and comparator, not only the metric value. No behaviour change; docs regenerated with roxygen2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
|
@seabbs-review-bot addressed both in b7c1c10 — confirmed Generated by Claude Code |
There was a problem hiding this comment.
This PR replaces the per-pair merge in pairwise_comparison_one_group() with a single pivot (pivot_scores()) into a forecast-unit-by-comparator matrix, extracts the ratio/p-value computation into compare_scores(), and adds a test_type argument so add_relative_skill() skips wilcox.test() by default. The refactor is sound and well tested (parity tests against the old merge logic, factor-compare handling, duplicate-row error), but it leaves compare_forecasts() as dead code kept only for test parity, and the new duplicate-row error is verified only against the internal pivot_scores() helper rather than the public entry points it's advertised for.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: re-review requested by seabbs-bot. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
Add a test verifying that the duplicate forecast unit/comparator error (raised inside pivot_scores()) surfaces through get_pairwise_comparisons() and add_relative_skill(), not just when calling the internal helper directly. Also checks that exact duplicates continue to be silently dropped through both entry points. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
|
@seabbs-review-bot pushed 52d96f2 adding the requested test verifying the duplicate-row error surfaces through Replied on the Generated by Claude Code |
There was a problem hiding this comment.
This PR replaces the per-pair merge in pairwise_comparison_one_group() with a single pivot into a forecast-unit x comparator matrix (pivot_scores()), extracts the shared ratio/p-value logic into compare_scores(), adds a new duplicate-row error, and makes add_relative_skill() skip the significance test by default via a new test_type argument. The refactor is well covered by tests that differentially check the new path against the kept compare_forecasts() reference implementation, duplicate handling, factor comparators, and the wilcox.test()-skip behaviour, and NEWS.md documents both user-facing changes. Verdict: sound change; only a minor documentation nit found.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: re-review requested by seabbs-bot. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
pivot_scores() inherited its @PARAM metric doc from get_pairwise_comparisons(), which describes a default ("crps", "wis" or "brier_score" if available). pivot_scores()'s metric argument has no default and is required, so that text was misleading. Give it its own @PARAM doc instead. No behaviour change; docs regenerated with roxygen2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17
|
@seabbs-review-bot addressed in 0c61c06 — Generated by Claude Code |
There was a problem hiding this comment.
The PR replaces the pairwise-comparison O(n²) merge-per-pair approach with a single pivot into a forecast-unit-by-comparator matrix (pivot_scores() + compare_scores()), adds a duplicate-forecast-unit error, and makes add_relative_skill() skip the significance test by default. The diff has already been through several review-bot rounds; documentation, NEWS.md, and the new tests (parity against the old compare_forecasts() implementation, duplicate-row error, factor comparator, and a mocked wilcox.test() call count) are thorough and I could not find a correctness bug or an undocumented behaviour change. compare_forecasts() is retained unexported purely as a test fixture, which is a reasonable but slightly unusual long-term maintenance choice worth a second look.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: re-review requested by seabbs-bot. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
seabbs
left a comment
There was a problem hiding this comment.
This looks good to me and in local testing is much faster. Given this is an upstream blocker I am going to merge this but happy to revert or deal with additional PRs as needed if there is clean up.
Description
This PR closes #1221 and closes #1222.
Pivot scores once (#1221)
pairwise_comparison_one_group()previously re-merged the scores of two comparators for every pair of comparators.For
nmodels this meantchoose(n, 2)joins per group, each re-keying and re-sorting the same data, which dominated the run time of hubverse evaluation dashboards (see the benchmark in #1221 by @annakrystalli).This PR follows the approach proposed in the issue:
pivot_scores().The overlapping forecasts of a pair of comparators are simply the rows where both columns are non-missing.
compare_scores().compare_forecasts()is retained as a simple per-pair reference implementation and now delegates tocompare_scores().It is used in the tests to check the pivot-based implementation against the per-pair merge.
Exact duplicate rows are still removed silently, as before.
Results are identical to the previous implementation.
New tests check that ratios and p-values match
compare_forecasts()byte-for-byte with and withoutby, withtest_type = NULL, with the permutation test under a fixed seed, with pairs that have no overlapping forecasts, and with a factorcomparecolumn.On a synthetic set of scores with 40 models and roughly 190k rows, one group of
pairwise_comparison_one_group()went from 13.7 s and 9.2 GB allocated to 5.2 s and 1.3 GB allocated, with identical output.Skip the discarded test in
add_relative_skill()(#1222)add_relative_skill()drops thepvalandadj_pvalcolumns before returning but previously still ran a Wilcoxon test for every pair of comparators.It now has an explicit
test_typeargument defaulting toNULL, so no test is run unless requested.Making it an explicit argument rather than hard-coding
NULLmeans existing calls that passtest_typethrough...keep working.Relative skill scores are unchanged, and spurious
wilcox.test()warnings for tied scores no longer appear.A test mocks
wilcox.test()and checks that it is not called by default, that it is called when a test is requested, and that the output is identical either way.Checklist
lintr::lint_package()to check for style issues introduced by my changes.🤖 Generated with Claude Code
https://claude.ai/code/session_01LpZEQ26Z76K1a3SxFKkR17