Skip to content

Commit

Permalink
Move {mgcv} to suggests (#5987)
Browse files Browse the repository at this point in the history
* move all method setup to `setup_params()`

* fallback to `method = "lm"` in absence of {mgcv}

* adjust tests

* move {mgcv} from Imports to Suggests

* add news bullet

* Revert "fallback to `method = "lm"` in absence of {mgcv}"

This reverts commit 5824b1d.

* homebrew an install prompt

* change fallback

* fix `gam_method()` in absence of mgcv

* tweak message
  • Loading branch information
teunbrand committed Aug 26, 2024
1 parent 8ca3bbc commit bcb87fc
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 26 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Imports:
isoband,
lifecycle (> 1.0.1),
MASS,
mgcv,
rlang (>= 1.1.0),
scales (>= 1.3.0),
stats,
Expand All @@ -55,6 +54,7 @@ Suggests:
knitr,
mapproj,
maps,
mgcv,
multcomp,
munsell,
nlme,
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ggplot2 (development version)

* Moved {mgcv} from Imports to Suggests (@teunbrand, #5986)
* New `reset_geom_defaults()` and `reset_stat_defaults()` to restore all geom or
stat default aesthetics at once (@teunbrand, #5975).
* `facet_wrap()` can have `space = "free_x"` with 1-row layouts and
Expand Down
66 changes: 41 additions & 25 deletions R/stat-smooth.R
Original file line number Diff line number Diff line change
Expand Up @@ -95,36 +95,63 @@ StatSmooth <- ggproto("StatSmooth", Stat,
setup_params = function(data, params) {
params$flipped_aes <- has_flipped_aes(data, params, ambiguous = TRUE)
msg <- character()
if (is.null(params$method) || identical(params$method, "auto")) {
method <- params$method
if (is.null(method) || identical(method, "auto")) {
# Use loess for small datasets, gam with a cubic regression basis for
# larger. Based on size of the _largest_ group to avoid bad memory
# behaviour of loess
max_group <- max(table(interaction(data$group, data$PANEL, drop = TRUE)))

if (max_group < 1000) {
params$method <- "loess"
method <- "loess"
} else {
params$method <- "gam"
method <- "gam"
}
msg <- c(msg, paste0("method = '", params$method, "'"))
msg <- c(msg, paste0("method = '", method, "'"))
}

if (identical(method, "gam") &&
!prompt_install("mgcv", "for using {.code method = \"gam\"}")) {
cli::cli_inform(c(
"The {.arg method} was set to {.val gam}, but {.pkg mgcv} is not installed.",
"!" = "Falling back to {.code method = \"lm\"}.",
i = "Install {.pkg mgcv} or change the {.arg method} argument to \\
resolve this issue."
))
method <- "lm"
}

if (is.null(params$formula)) {
if (identical(params$method, "gam")) {
if (identical(method, "gam")) {
params$formula <- y ~ s(x, bs = "cs")
} else {
params$formula <- y ~ x
}
msg <- c(msg, paste0("formula = '", deparse(params$formula), "'"))
}
if (identical(params$method, "gam")) {
params$method <- gam_method()

# Special case span because it's the most commonly used model argument
if (identical(method, "loess")) {
params$method.args$span <- params$span %||% 0.75
}

if (is.character(method)) {
if (identical(method, "gam")) {
method <- gam_method()
} else {
method <- match.fun(method)
}
}
# If gam and gam's method is not specified by the user then use REML
if (identical(method, gam_method())) {
params$method.args$method <- params$method.args$method %||% "REML"
}

if (length(msg) > 0) {
cli::cli_inform("{.fn geom_smooth} using {msg}")
}

params$method <- method
params
},

Expand Down Expand Up @@ -159,23 +186,6 @@ StatSmooth <- ggproto("StatSmooth", Stat,
}
}

# Special case span because it's the most commonly used model argument
if (identical(method, "loess")) {
method.args$span <- span
}

if (is.character(method)) {
if (identical(method, "gam")) {
method <- gam_method()
} else {
method <- match.fun(method)
}
}
# If gam and gam's method is not specified by the user then use REML
if (identical(method, gam_method()) && is.null(method.args$method)) {
method.args$method <- "REML"
}

prediction <- try_fetch(
{
model <- inject(method(
Expand Down Expand Up @@ -205,4 +215,10 @@ StatSmooth <- ggproto("StatSmooth", Stat,
)

# This function exists to silence an undeclared import warning
gam_method <- function() mgcv::gam
gam_method <- function() {
if (is_installed("mgcv")) {
mgcv::gam
} else {
NA
}
}
29 changes: 29 additions & 0 deletions R/utilities.R
Original file line number Diff line number Diff line change
Expand Up @@ -846,3 +846,32 @@ as_unordered_factor <- function(x) {
class(x) <- setdiff(class(x), "ordered")
x
}

# TODO: Replace me if rlang/#1730 gets implemented
# Similar to `rlang::check_installed()` but returns boolean and misses
# features such as versions, comparisons and using {pak}.
prompt_install <- function(pkg, reason = NULL) {
if (length(pkg) < 1 || is_installed(pkg)) {
return(TRUE)
}
if (!interactive()) {
return(FALSE)
}

pkg <- pkg[!vapply(pkg, is_installed, logical(1))]

message <- "The {.pkg {pkg}} package{?s} {?is/are} required"
if (is.null(reason)) {
message <- paste0(message, ".")
} else {
message <- paste0(message, " ", reason)
}
question <- "Would you like to install {cli::qty(pkg)}{?it/them}?"

cli::cli_bullets(c("!" = message, "i" = question))
if (utils::menu(c("Yes", "No")) != 1) {
return(FALSE)
}
utils::install.packages(pkg)
is_installed(pkg)
}
14 changes: 14 additions & 0 deletions tests/testthat/test-geom-smooth.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ test_that("default smoothing methods for small and large data sets work", {
y = x^2 + 0.5 * rnorm(1001)
)

skip_if_not_installed("mgcv")

m <- mgcv::gam(y ~ s(x, bs = "cs"), data = df, method = "REML")
range <- range(df$x, na.rm = TRUE)
xseq <- seq(range[1], range[2], length.out = 80)
Expand Down Expand Up @@ -96,6 +98,18 @@ test_that("geom_smooth() works when one group fails", {
expect_gte(nrow(ld), 2)
})

test_that("a fallback message is thrown when `method = 'gam'` and {mgcv} is absent", {
p <- ggplot(mpg, aes(displ, hwy)) +
geom_smooth(method = "gam", formula = y ~ x)

with_mocked_bindings(
expect_message(
ggplot_build(p), regexp = "Falling back to `method = \"lm\"`"
),
is_installed = function(...) FALSE
)
})

# Visual tests ------------------------------------------------------------

test_that("geom_smooth() works with alternative stats", {
Expand Down

0 comments on commit bcb87fc

Please sign in to comment.