From dcda3db48da3a56c3a898d1476ad7c44984b8d2d Mon Sep 17 00:00:00 2001 From: taekop Date: Tue, 29 Sep 2026 11:32:26 +0900 Subject: [PATCH] Fix brier_class() case weight normalization --- NEWS.md | 4 ++++ R/prob-brier_class.R | 8 -------- tests/testthat/test-orderedprob-ranked_prob_score.R | 2 +- tests/testthat/test-prob-brier_class.R | 12 +++++++++++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5fe2d362..faaa98ec 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # yardstick (development version) +## Bug fixes + +* `brier_class()` now computes the correct result with `case_weights` when the weights are on very different scales, rather than silently renormalizing them with a softmax (#633). + # yardstick 1.4.0 ## Breaking Changes diff --git a/R/prob-brier_class.R b/R/prob-brier_class.R index b2a51dd8..f11b3b8e 100644 --- a/R/prob-brier_class.R +++ b/R/prob-brier_class.R @@ -182,14 +182,6 @@ brier_ind <- function(truth, estimate, case_weights = NULL) { resids <- resids[not_missing, , drop = FALSE] case_weights <- case_weights[not_missing] - # Normalize weights (in case negative weights) - # subtracting max to avoid Inf in calculations - # exp(x - max(x)) / sum(exp(x - max(x))) - # = (exp(x) / exp(max(x))) / (sum(exp(x)) / exp(max(x))) - # = exp(x) / sum(exp(x)) - case_weights <- case_weights - max(case_weights) - case_weights <- exp(case_weights) / sum(exp(case_weights)) - res <- sum(resids * case_weights) / (2 * sum(case_weights)) res } diff --git a/tests/testthat/test-orderedprob-ranked_prob_score.R b/tests/testthat/test-orderedprob-ranked_prob_score.R index 0452d26f..3537eaff 100644 --- a/tests/testthat/test-orderedprob-ranked_prob_score.R +++ b/tests/testthat/test-orderedprob-ranked_prob_score.R @@ -52,7 +52,7 @@ test_that("Case weights calculations are correct", { hpc_cv$obs <- as.ordered(hpc_cv$obs) hpc_cv$weights <- rep(c(0, 1), c(100, nrow(hpc_cv) - 100)) # With orf:::rps(as.matrix(hpc_cv[-(1:100), 3:6]), hpc_cv$obs[-(1:100)]) - hpc_exp <- 0.0868733 + hpc_exp <- 0.0875957 expect_equal( yardstick:::ranked_prob_score_vec( diff --git a/tests/testthat/test-prob-brier_class.R b/tests/testthat/test-prob-brier_class.R index 784e60bb..767571c3 100644 --- a/tests/testthat/test-prob-brier_class.R +++ b/tests/testthat/test-prob-brier_class.R @@ -114,12 +114,22 @@ test_that("doesn't produce NaN with high valued case weights (#614)", { wts <- rep(1, (nrow(df))) wts_high <- wts * 5000 - expect_identical( + expect_equal( brier_class_vec(df$truth, df$Class1, case_weights = wts), brier_class_vec(df$truth, df$Class1, case_weights = wts_high) ) }) +test_that("case weights match uncounted data with widely spread weights (#633)", { + hpc_wts <- hpc_cv + hpc_wts$wts <- rep(1:10, length.out = nrow(hpc_wts)) + + expect_equal( + brier_class(hpc_wts, obs, VF:L, case_weights = wts)[[".estimate"]], + brier_class(tidyr::uncount(hpc_wts, wts), obs, VF:L)[[".estimate"]] + ) +}) + test_that("`event_level = 'second'` works", { df <- two_class_example