diff --git a/R-package/tests/testthat/test_interactions.R b/R-package/tests/testthat/test_interactions.R index 2c2e31df3a11..2a1cab0d54e5 100644 --- a/R-package/tests/testthat/test_interactions.R +++ b/R-package/tests/testthat/test_interactions.R @@ -81,6 +81,39 @@ test_that("predict feature interactions works", { expect_lt(max(abs(intr - gt_intr)), 0.1) }) +test_that("SHAP contribution values are not NAN", { + d <- data.frame( + x1 = c(-2.3, 1.4, 5.9, 2, 2.5, 0.3, -3.6, -0.2, 0.5, -2.8, -4.6, 3.3, -1.2, + -1.1, -2.3, 0.4, -1.5, -0.2, -1, 3.7), + x2 = c(291.179171, 269.198331, 289.942097, 283.191669, 269.673332, + 294.158346, 287.255835, 291.530838, 285.899586, 269.290833, + 268.649586, 291.530841, 280.074593, 269.484168, 293.94042, + 294.327506, 296.20709, 295.441669, 283.16792, 270.227085), + y = c(9, 15, 5.7, 9.2, 22.4, 5, 9, 3.2, 7.2, 13.1, 7.8, 16.9, 6.5, 22.1, + 5.3, 10.4, 11.1, 13.9, 11, 20.5), + fold = c(2, 2, 2, 1, 2, 2, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2)) + + ivs <- c("x1", "x2") + + fit <- xgboost( + verbose = 0, + params = list( + objective = "reg:linear", + eval_metric = "rmse"), + data = as.matrix(subset(d, fold == 2)[, ivs]), + label = subset(d, fold == 2)$y, + nthread = 1, + nrounds = 3) + + shaps <- as.data.frame(predict(fit, + newdata = as.matrix(subset(d, fold == 1)[, ivs]), + predcontrib = T)) + result <- cbind(shaps, sum = rowSums(shaps), pred = predict(fit, + newdata = as.matrix(subset(d, fold == 1)[, ivs]))) + + expect_true(identical(TRUE, all.equal(result$sum, result$pred, tol = 1e-6))) +}) + test_that("multiclass feature interactions work", { dm <- xgb.DMatrix(as.matrix(iris[,-5]), label=as.numeric(iris$Species)-1) diff --git a/src/tree/tree_model.cc b/src/tree/tree_model.cc index 5052f1383bf0..88aa795d098e 100644 --- a/src/tree/tree_model.cc +++ b/src/tree/tree_model.cc @@ -293,9 +293,12 @@ bst_float UnwoundPathSum(const PathElement *unique_path, unsigned unique_depth, total += tmp; next_one_portion = unique_path[i].pweight - tmp * zero_fraction * ((unique_depth - i) / static_cast(unique_depth + 1)); - } else { + } else if (zero_fraction != 0) { total += (unique_path[i].pweight / zero_fraction) / ((unique_depth - i) / static_cast(unique_depth + 1)); + } else { + CHECK_EQ(unique_path[i].pweight, 0) + << "Unique path " << i << " must have zero weight"; } } return total;