diff --git a/R/plot.gg_variable.R b/R/plot.gg_variable.R index e6d7ae38..4e0cf715 100644 --- a/R/plot.gg_variable.R +++ b/R/plot.gg_variable.R @@ -526,7 +526,11 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter ) } } else { - # Factor predictor: jitter + boxplot coloured by observed class + # Factor predictor: jitter + boxplot coloured by observed class. + # smooth=TRUE is intentionally a no-op here: geom_smooth requires + # a continuous x-axis and has no meaningful interpretation for + # discrete factor levels. The boxplot IQR serves as the spread + # summary. gg_plt[[ind]] <- gg_plt[[ind]] + ggplot2::geom_jitter( ggplot2::aes( @@ -565,6 +569,10 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter ) } } else { + # Factor predictor (multi-class): boxplot + jitter per facet. + # smooth=TRUE is intentionally a no-op here for the same reason + # as the binary factor path above — geom_smooth requires a + # continuous x-axis. gg_plt[[ind]] <- gg_plt[[ind]] + ggplot2::geom_boxplot( ggplot2::aes(x = .data$var, y = .data$yhat), @@ -605,7 +613,10 @@ plot.gg_variable <- function(x, # nolint: cyclocomp_linter ggplot2::geom_smooth(ggplot2::aes(x = .data$var, y = .data$yhat), ...) } } else { - # Factor predictor: boxplot + jitter + # Factor predictor (regression): boxplot + jitter. + # smooth=TRUE is intentionally a no-op here: geom_smooth requires a + # continuous x-axis and has no meaningful interpretation for discrete + # factor levels. The boxplot IQR serves as the spread summary. gg_plt[[ind]] <- gg_plt[[ind]] + ggplot2::geom_boxplot( ggplot2::aes(x = .data$var, y = .data$yhat), diff --git a/tests/testthat/test_gg_variable.R b/tests/testthat/test_gg_variable.R index e4bd9e40..cbe54087 100644 --- a/tests/testthat/test_gg_variable.R +++ b/tests/testthat/test_gg_variable.R @@ -492,3 +492,56 @@ test_that("gg_variable.randomForest: oob=FALSE triggers a warning", { expect_s3_class(gg, "gg_variable") expect_true("yhat.setosa" %in% names(gg)) }) + +## ── smooth=TRUE is a no-op for factor predictors (all families) ────────────── + +# geom_smooth requires a continuous x-axis, so smooth=TRUE has no meaning for a +# factor predictor. The contract these tests lock in: no GeomSmooth layer is +# added for a factor x. Assert that directly (rather than a brittle layer +# count) so benign plot-composition changes do not break the suite. +has_smooth_layer <- function(p) { + any(vapply(p$layers, function(l) inherits(l$geom, "GeomSmooth"), logical(1))) +} + +test_that("plot.gg_variable binary classification + factor predictor: smooth=TRUE adds no smooth layer", { + skip_if_not_installed("randomForest") + set.seed(42L) + bin_data <- iris[iris$Species != "virginica", ] + bin_data$Species <- droplevels(bin_data$Species) + bin_data$size <- cut(bin_data$Petal.Length, 2L, labels = c("small", "large")) + rf <- randomForest::randomForest(Species ~ size + Sepal.Width, data = bin_data, + ntree = 25L) + gg <- gg_variable(rf) + # smooth=TRUE with a factor predictor must not error + expect_no_error(p <- plot(gg, xvar = "size", smooth = TRUE)) + expect_s3_class(p, "ggplot") + expect_false(has_smooth_layer(p)) +}) + +test_that("plot.gg_variable multi-class classification + factor predictor: smooth=TRUE adds no smooth layer", { + skip_if_not_installed("randomForest") + set.seed(42L) + iris2 <- iris + iris2$size <- cut(iris2$Petal.Length, 3L, labels = c("small", "medium", "large")) + rf <- randomForest::randomForest(Species ~ size + Sepal.Width, data = iris2, + ntree = 25L) + gg <- gg_variable(rf) + # smooth=TRUE with a factor predictor must not error + expect_no_error(p <- plot(gg, xvar = "size", smooth = TRUE)) + expect_s3_class(p, "ggplot") + expect_false(has_smooth_layer(p)) +}) + +test_that("plot.gg_variable regression + factor predictor: smooth=TRUE adds no smooth layer", { + skip_if_not_installed("randomForest") + set.seed(42L) + iris2 <- iris + iris2$size <- cut(iris2$Petal.Length, 3L, labels = c("small", "medium", "large")) + rf <- randomForest::randomForest(Sepal.Length ~ size + Sepal.Width, data = iris2, + ntree = 25L) + gg <- gg_variable(rf) + # smooth=TRUE with a factor predictor must not error + expect_no_error(p <- plot(gg, xvar = "size", smooth = TRUE)) + expect_s3_class(p, "ggplot") + expect_false(has_smooth_layer(p)) +})