From 3052e6da0a8f3c19e0e604db5c35ea71afeee54c Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 10:55:19 -0400 Subject: [PATCH 01/11] chore: open v2.7.3.9005 dev increment (PR #87) --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ebbc16b1..0426059a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: ggRandomForests Type: Package Title: Visually Exploring Random Forests -Version: 2.7.3.9004 -Date: 2026-05-20 +Version: 2.7.3.9005 +Date: 2026-05-21 Authors@R: person("John", "Ehrlinger", role = c("aut", "cre"), email = "john.ehrlinger@gmail.com") From c40862194f3ee05ba2fe08ce15bce92ca99c43ee Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 10:56:23 -0400 Subject: [PATCH 02/11] test: add three failing tests for gg_variable.randomForest classification (PR #87) Tests verify that gg_variable.randomForest classification forests produce: - Per-class vote fraction columns (yhat.setosa, yhat.versicolor, yhat.virginica) - No bare yhat column for multi-class prediction - Valid vote fractions (0-1) that row-sum to 1 - Plottable output for multi-xvar case - Layer_data access on single-xvar plots All three tests currently FAIL, as expected (TDD red phase). Implementation fix comes in T2. --- tests/testthat/test_gg_variable.R | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index 4c6633d1..6974ef8c 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -368,3 +368,42 @@ test_that("gg_variable.randomForest classification: class attr uses 'class' not expect_false("classification" %in% class(gg_dta)) expect_s3_class(gg_dta, "gg_variable") }) + +## ── randomForest classification (PR #87) ───────────────────────────────────── + +test_that("gg_variable.randomForest classification: produces yhat.* columns not yhat", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg <- gg_variable(rf) + # Must have one column per class + expect_true(all(c("yhat.setosa", "yhat.versicolor", "yhat.virginica") + %in% names(gg))) + # Must NOT have a bare yhat column for multi-class + expect_false("yhat" %in% names(gg)) + # Observed-class column must be present + expect_true("yvar" %in% names(gg)) + # Vote fractions must be in [0, 1] and row-sum to ~1 + vote_cols <- c("yhat.setosa", "yhat.versicolor", "yhat.virginica") + expect_true(all(gg[, vote_cols] >= 0)) + expect_true(all(gg[, vote_cols] <= 1)) + expect_true(all(abs(rowSums(gg[, vote_cols]) - 1) < 1e-6)) +}) + +test_that("gg_variable.randomForest classification: plot returns patchwork for all xvar", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg <- gg_variable(rf) + p <- plot(gg) + expect_true(inherits(p, "patchwork") || inherits(p, "ggplot")) +}) + +test_that("gg_variable.randomForest classification: layer_data works on single-xvar plot", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg <- gg_variable(rf) + p <- plot(gg, xvar = "Sepal.Length") + expect_no_error(ggplot2::layer_data(p, 1L)) +}) From 35514ecf4fabeed8bdbadcbeee9c698f34745435 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 10:57:48 -0400 Subject: [PATCH 03/11] fix: gg_variable.randomForest classification uses object\$votes for yhat.* columns Co-Authored-By: Claude Sonnet 4.6 --- R/gg_variable.R | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/R/gg_variable.R b/R/gg_variable.R index 6ddcd8dc..3c26a1a4 100644 --- a/R/gg_variable.R +++ b/R/gg_variable.R @@ -307,10 +307,16 @@ gg_variable.randomForest <- function(object, } gg_dta <- predictors - # Append the forest's in-bag predicted values. - gg_dta$yhat <- as.vector(object$predicted) + # For classification forests use per-class OOB vote fractions (object$votes), + # stored as yhat. columns — the same shape gg_variable.rfsrc + # produces. For regression a single numeric yhat column suffices. if (object$type == "classification") { - gg_dta$yvar <- response + preds <- object$votes # n × n_classes matrix of OOB vote fractions + colnames(preds) <- paste0("yhat.", colnames(preds)) + gg_dta <- cbind(gg_dta, preds) + gg_dta$yvar <- response + } else { + gg_dta$yhat <- as.vector(object$predicted) } # randomForest uses object$type ("classification" / "regression"); the From c35c4ea8e4a14541b2926bd3218a37ecf0cf8efc Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:03:30 -0400 Subject: [PATCH 04/11] fix: normalise object\$votes rows to defend against norm.votes=FALSE; update stale oob comment Co-Authored-By: Claude Sonnet 4.6 --- R/gg_variable.R | 14 ++++++++++---- tests/testthat/test_gg_variable.R | 13 +++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/R/gg_variable.R b/R/gg_variable.R index 3c26a1a4..6337276a 100644 --- a/R/gg_variable.R +++ b/R/gg_variable.R @@ -271,10 +271,12 @@ gg_variable.randomForest <- function(object, ...) { arg_list <- list(...) - # randomForest objects do not store OOB predictions in a way that maps back - # to the predictor space, so we always use in-bag (full-forest) predictions. + # For randomForest the OOB vote matrix (object$votes) is used unconditionally — + # it is the only honest per-class probability estimate. The oob argument is + # silently overridden to TRUE; randomForest does not expose in-bag class + # probabilities through a consistent API. if (!is.null(arg_list$oob)) { - arg_list$oob <- FALSE + arg_list$oob <- TRUE } if (!inherits(object, "randomForest")) { @@ -311,7 +313,11 @@ gg_variable.randomForest <- function(object, # stored as yhat. columns — the same shape gg_variable.rfsrc # produces. For regression a single numeric yhat column suffices. if (object$type == "classification") { - preds <- object$votes # n × n_classes matrix of OOB vote fractions + preds <- object$votes # n × n_classes matrix; may be raw counts or fractions + rs <- rowSums(preds) + if (any(rs > 1 + 1e-8, na.rm = TRUE)) { + preds <- preds / rs # normalise raw vote counts to [0, 1] + } colnames(preds) <- paste0("yhat.", colnames(preds)) gg_dta <- cbind(gg_dta, preds) gg_dta$yvar <- response diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index 6974ef8c..a4d9f154 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -407,3 +407,16 @@ test_that("gg_variable.randomForest classification: layer_data works on single-x p <- plot(gg, xvar = "Sepal.Length") expect_no_error(ggplot2::layer_data(p, 1L)) }) + +test_that("gg_variable.randomForest classification: norm.votes=FALSE still gives [0,1] fractions", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L, + norm.votes = FALSE) + gg <- gg_variable(rf) + vote_cols <- c("yhat.setosa", "yhat.versicolor", "yhat.virginica") + expect_true(all(c("yhat.setosa", "yhat.versicolor", "yhat.virginica") %in% names(gg))) + expect_true(all(gg[, vote_cols] >= 0)) + expect_true(all(gg[, vote_cols] <= 1)) + expect_true(all(abs(rowSums(gg[, vote_cols]) - 1) < 1e-6)) +}) From 2b8e8b2420d45dc1e93a0624308c21e5f151cc7d Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:04:56 -0400 Subject: [PATCH 05/11] test: add failing tests for plot.gg_variable smooth bugs (PR #87) --- tests/testthat/test_gg_variable.R | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index a4d9f154..293011ba 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -420,3 +420,30 @@ test_that("gg_variable.randomForest classification: norm.votes=FALSE still gives expect_true(all(gg[, vote_cols] <= 1)) expect_true(all(abs(rowSums(gg[, vote_cols]) - 1) < 1e-6)) }) + +test_that("plot.gg_variable RF classification: smooth=TRUE layer_data smokeable (binary smooth aes bug)", { + skip_if_not_installed("randomForest") + # Two-class subset to exercise the *binary* classification path + set.seed(42L) + bin_data <- iris[iris$Species != "virginica", ] + bin_data$Species <- droplevels(bin_data$Species) + rf <- randomForest::randomForest(Species ~ ., data = bin_data, ntree = 50L) + gg <- gg_variable(rf) + p <- plot(gg, xvar = "Sepal.Length", smooth = TRUE) + # Before the fix, geom_smooth(...) has no aes and layer_data errors with + # "stat_smooth() requires the following missing aesthetics: x and y" + expect_no_error(ggplot2::layer_data(p, 2L)) +}) + +test_that("plot.gg_variable RF classification: smooth=TRUE works for multi-class (missing block)", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg <- gg_variable(rf) + # Before the fix the multi-class numeric path silently skips smooth=TRUE + # but does not error; after the fix a smooth layer is present (layer 2). + p <- plot(gg, xvar = "Sepal.Length", smooth = TRUE) + expect_s3_class(p, "ggplot") + ld <- ggplot2::layer_data(p, 2L) # layer 2 = geom_smooth + expect_gt(nrow(ld), 0L) +}) From cc5d13f89fbed03cd9e8e062459a89901b7ce7e2 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:15:49 -0400 Subject: [PATCH 06/11] fix: plot.gg_variable binary smooth aes + add multi-class smooth block (#87) Co-Authored-By: Claude Sonnet 4.6 --- NEWS.md | 2 +- R/plot.gg_variable.R | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5d0a97a4..78d19a8b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,5 @@ Package: ggRandomForests -Version: 2.7.3.9004 +Version: 2.7.3.9005 ggRandomForests v2.8.0 (development) — continued ================================================= diff --git a/R/plot.gg_variable.R b/R/plot.gg_variable.R index e8a7f273..dd0c1c8d 100644 --- a/R/plot.gg_variable.R +++ b/R/plot.gg_variable.R @@ -516,7 +516,10 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter } if (smooth) { gg_plt[[ind]] <- gg_plt[[ind]] + - ggplot2::geom_smooth(...) + ggplot2::geom_smooth( + ggplot2::aes(x = .data$var, y = .data$yhat), + ... + ) } } else { # Factor predictor: jitter + boxplot coloured by observed class @@ -550,6 +553,13 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter ), ... ) + if (smooth) { + gg_plt[[ind]] <- gg_plt[[ind]] + + ggplot2::geom_smooth( + ggplot2::aes(x = .data$var, y = .data$yhat), + ... + ) + } } else { gg_plt[[ind]] <- gg_plt[[ind]] + ggplot2::geom_boxplot( From b1f0726c6f8831ec8a307c810a730a15574592f9 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:19:48 -0400 Subject: [PATCH 07/11] test: add vdiffr snapshots for gg_variable RF classification (PR #87) --- tests/testthat/test_snapshots.R | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/testthat/test_snapshots.R b/tests/testthat/test_snapshots.R index 7894ea9b..9c0b7738 100644 --- a/tests/testthat/test_snapshots.R +++ b/tests/testthat/test_snapshots.R @@ -274,4 +274,27 @@ local({ }) } +## ── randomForest classification snapshots (PR #87) ─────────────────────────── +if (requireNamespace("randomForest", quietly = TRUE)) { + local({ + set.seed(42L) + rf_iris <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg_iris <- gg_variable(rf_iris) + + test_that("snapshot: gg-variable-rf-classification-default", { + vdiffr::expect_doppelganger( + "gg-variable-rf-classification-default", + plot(gg_iris) + ) + }) + + test_that("snapshot: gg-variable-rf-classification-smooth", { + vdiffr::expect_doppelganger( + "gg-variable-rf-classification-smooth", + plot(gg_iris, xvar = "Sepal.Length", smooth = TRUE) + ) + }) + }) +} + } # end CI guard From c5744c62fc179cb75060cf7edc3a51c4248cbbf2 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:26:48 -0400 Subject: [PATCH 08/11] =?UTF-8?q?docs:=20update=20NEWS=20for=20PR=20#87=20?= =?UTF-8?q?=E2=80=94=20gg=5Fvariable=20RF=20classification=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NEWS.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/NEWS.md b/NEWS.md index 78d19a8b..6271461c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -31,6 +31,19 @@ ggRandomForests v2.8.0 (development) — continued ggRandomForests v2.8.0 (development) ==================================== +* **`gg_variable.randomForest` classification fix (#87).** + - `gg_variable.randomForest()` for classification forests now stores + per-class OOB vote fractions as `yhat.` columns (from + `object$votes`), matching the `rfsrc` path. Previously a single + `yhat` factor column (class labels from `object$predicted`) was + stored, which prevented the multi-class pivot in `plot.gg_variable` + from firing. Vote fractions are row-normalised to `[0, 1]` even + when the forest was fit with `norm.votes = FALSE`. + - `plot.gg_variable` binary classification: `smooth = TRUE` now + correctly maps x/y aesthetics onto the smooth layer. + - `plot.gg_variable` multi-class numeric path: `smooth = TRUE` now + adds a smooth layer (was silently skipped). + - Closes stale issues #81 (fixed in PR #83) and #82. * **varPro partial dependence: `gg_partial_varpro()` (#84).** - `gg_partial_varpro()` replaces `gg_partialpro()` as the primary entry point for varPro partial dependence plots. The new extractor accepts From 100240094e5518c870e959e863279534f3cb1b91 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:34:38 -0400 Subject: [PATCH 09/11] fix: plot.gg_variable multi-class facets use class names not integer indices Strip the yhat. prefix from column names when building the outcome column in the multi-class pivot loop (line ~169), so facet labels show "setosa"/ "versicolor"/"virginica" instead of 1/2/3. Add a regression test that verifies p@data$outcome contains class names after plotting. Co-Authored-By: Claude Sonnet 4.6 --- R/plot.gg_variable.R | 3 ++- tests/testthat/test_gg_variable.R | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/R/plot.gg_variable.R b/R/plot.gg_variable.R index dd0c1c8d..bc20b0c0 100644 --- a/R/plot.gg_variable.R +++ b/R/plot.gg_variable.R @@ -166,7 +166,8 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter gg_dta_y <- gg_dta[, grep("yhat.", colnames(gg_dta))] lng <- ncol(gg_dta_y) gg2 <- parallel::mclapply(seq_len(ncol(gg_dta_y)), function(ind) { - cbind(gg_dta_x, yhat = gg_dta_y[, ind], outcome = ind) + cbind(gg_dta_x, yhat = gg_dta_y[, ind], + outcome = sub("^yhat\\.", "", colnames(gg_dta_y)[ind])) }) gg3 <- do.call(rbind, gg2) gg3$outcome <- factor(gg3$outcome) diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index 293011ba..be9bb873 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -447,3 +447,18 @@ test_that("plot.gg_variable RF classification: smooth=TRUE works for multi-class ld <- ggplot2::layer_data(p, 2L) # layer 2 = geom_smooth expect_gt(nrow(ld), 0L) }) + +test_that("plot.gg_variable RF classification multi-class: outcome column is class names not integers", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg <- gg_variable(rf) + p <- plot(gg, xvar = "Sepal.Length") + expect_s3_class(p, "ggplot") + # The 'outcome' column in the plot data drives facet labels. + # It must contain class names, not integer indices. + # ggplot2 >= 3.5 uses S7 slots; fall back to $ accessor for older versions. + pd <- tryCatch(p@data, error = function(e) p$data) + expect_false(is.numeric(pd$outcome)) + expect_true(all(c("setosa", "versicolor", "virginica") %in% as.character(pd$outcome))) +}) From bd2779042c165130c7232289d95ab2213fb27781 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 11:59:48 -0400 Subject: [PATCH 10/11] test: tighten patchwork assertion to match corrected plan (#87 Copilot review) Plan #87 was corrected (via PR #87 Copilot review) to assert patchwork specifically for the 4-predictor iris default-plot case, rather than the loose patchwork||ggplot form that wouldn't catch a regression to a bare list (issue #80). Align the implementation test to match. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/testthat/test_gg_variable.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index be9bb873..85f5b920 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -396,7 +396,10 @@ test_that("gg_variable.randomForest classification: plot returns patchwork for a rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) gg <- gg_variable(rf) p <- plot(gg) - expect_true(inherits(p, "patchwork") || inherits(p, "ggplot")) + # iris has 4 predictors so the no-xvar default assembles a multi-panel + # patchwork; assert patchwork specifically to catch regressions to a bare + # list (#80). + expect_s3_class(p, "patchwork") }) test_that("gg_variable.randomForest classification: layer_data works on single-xvar plot", { From 99be00e27f9e094a40f426ada7be547ec0f68857 Mon Sep 17 00:00:00 2001 From: John Ehrlinger Date: Thu, 21 May 2026 14:23:12 -0400 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20address=20Copilot=20review=20on=20?= =?UTF-8?q?PR=20#88=20=E2=80=94=20oob=20warning=20+=20factor=20level=20ord?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gg_variable.randomForest: replace silent no-op oob override with an explicit warning when oob=FALSE is supplied, since in-bag class probabilities are not available via the randomForest API. - plot.gg_variable: set outcome factor levels from gg_dta_y column order rather than factor() default (alphabetical), so multi-class facet panels follow the model's class ordering regardless of locale. - Add two new tests: oob=FALSE warning, and outcome factor level order. Co-Authored-By: Claude Sonnet 4.5 --- R/gg_variable.R | 16 ++++++++++------ R/plot.gg_variable.R | 5 ++++- tests/testthat/test_gg_variable.R | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/R/gg_variable.R b/R/gg_variable.R index 6337276a..f6cc447c 100644 --- a/R/gg_variable.R +++ b/R/gg_variable.R @@ -271,12 +271,16 @@ gg_variable.randomForest <- function(object, ...) { arg_list <- list(...) - # For randomForest the OOB vote matrix (object$votes) is used unconditionally — - # it is the only honest per-class probability estimate. The oob argument is - # silently overridden to TRUE; randomForest does not expose in-bag class - # probabilities through a consistent API. - if (!is.null(arg_list$oob)) { - arg_list$oob <- TRUE + # randomForest uses object$votes (OOB vote matrix) unconditionally — it is the + # only honest per-class probability estimate. In-bag class probabilities are + # not exposed through a consistent randomForest API, so oob=FALSE is not + # supported. Warn the caller rather than silently ignoring the argument. + if (!is.null(arg_list$oob) && identical(arg_list$oob, FALSE)) { + warning( + "oob = FALSE is not supported for randomForest objects: ", + "in-bag class probabilities are unavailable. ", + "OOB vote fractions (object$votes) will be used instead." + ) } if (!inherits(object, "randomForest")) { diff --git a/R/plot.gg_variable.R b/R/plot.gg_variable.R index bc20b0c0..e6d7ae38 100644 --- a/R/plot.gg_variable.R +++ b/R/plot.gg_variable.R @@ -170,7 +170,10 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter outcome = sub("^yhat\\.", "", colnames(gg_dta_y)[ind])) }) gg3 <- do.call(rbind, gg2) - gg3$outcome <- factor(gg3$outcome) + # Use column order from gg_dta_y (not alphabetical) so facet panels + # appear in the same order as the model's class levels. + outcome_levels <- sub("^yhat\\.", "", colnames(gg_dta_y)) + gg3$outcome <- factor(gg3$outcome, levels = outcome_levels) gg_dta <- gg3 } } diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index 85f5b920..e4bd9e40 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -465,3 +465,30 @@ test_that("plot.gg_variable RF classification multi-class: outcome column is cla expect_false(is.numeric(pd$outcome)) expect_true(all(c("setosa", "versicolor", "virginica") %in% as.character(pd$outcome))) }) + +test_that("plot.gg_variable RF classification multi-class: outcome factor levels match column order", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + gg <- gg_variable(rf) + p <- plot(gg, xvar = "Sepal.Length") + pd <- tryCatch(p@data, error = function(e) p$data) + # Levels must follow the yhat.* column order in gg_variable output, + # not alphabetical order (which factor() would impose by default). + expected_levels <- sub("^yhat\\.", "", grep("^yhat\\.", names(gg), value = TRUE)) + expect_equal(levels(pd$outcome), expected_levels) +}) + +test_that("gg_variable.randomForest: oob=FALSE triggers a warning", { + skip_if_not_installed("randomForest") + set.seed(42L) + rf <- randomForest::randomForest(Species ~ ., data = iris, ntree = 50L) + # oob=FALSE is not supported for randomForest; a warning must be emitted + # and OOB vote fractions are still returned. + expect_warning( + gg <- gg_variable(rf, oob = FALSE), + regexp = "oob = FALSE is not supported" + ) + expect_s3_class(gg, "gg_variable") + expect_true("yhat.setosa" %in% names(gg)) +})