From 7c64285b4f959eaf84b4bbf9fb6f87652bfa1dac Mon Sep 17 00:00:00 2001 From: chross22 <52218551+chross22@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:19:36 -0400 Subject: [PATCH] Follow fancygam to its new name, which had quietly disabled it fancygam was renamed fancyfx when it grew past GAMs. GitHub still redirects chross22/fancygam, so `Remotes: chross22/fancygam` kept installing something - but what it installs declares `Package: fancyfx`, so the `requireNamespace("fancygam")` behind has_fancygam() has been returning FALSE on every fresh install since the rename. That gate guards a diagnostic rather than the run, so nothing failed. GAM smooth plots simply stopped being written, and gam_smooths.png stopped appearing in the output, with no error and no message. It looked fine here only because the old fancygam 0.0.0.9000 was still sitting in the library next to the new package. The rename is mechanical - fancygam to fancyfx across DESCRIPTION, the two call sites, the app, the tests and the README - and the one call that matters needed no change: combinePlots() keeps its first three arguments and has only gained optional ones. It gained `interval`, defaulting to "auto", so the smooths now come with their standard error bands rather than without. model_engine_fit()'s example moves from plotSmooths() to plotEffects(). plotSmooths() still works and produces an identical plot, but it is deprecated now and warns, and a documented example is the wrong place to teach the old name. Verified by drawing the plot rather than by loading the namespace: a GAM fitted on the mock config writes a three-panel figure with a curve, an error band and a data rug per term. Checked against fancyfx 0.3.0, the current release. Co-Authored-By: Claude Opus 5 --- DESCRIPTION | 4 ++-- R/plot_effects.R | 40 +++++++++++++++++++++++------------ README.md | 6 +++--- inst/shiny/app.R | 12 +++++------ man/has_fancyfx.Rd | 31 +++++++++++++++++++++++++++ man/has_fancygam.Rd | 17 --------------- man/model_engine_fit.Rd | 4 ++-- man/plot_gam_smooths.Rd | 4 ++-- tests/testthat/test-effects.R | 14 ++++++------ 9 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 man/has_fancyfx.Rd delete mode 100644 man/has_fancygam.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 938a487..1bd0629 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -38,7 +38,7 @@ Imports: yardstick Suggests: derivoce, - fancygam, + fancyfx, knitr, leaflet, ranger, @@ -55,7 +55,7 @@ Suggests: Remotes: chross22/datamatch, chross22/derivoce, - chross22/fancygam + chross22/fancyfx Config/testthat/edition: 3 VignetteBuilder: knitr URL: https://github.com/chross22/taupatch diff --git a/R/plot_effects.R b/R/plot_effects.R index 5d2eaf2..0997a89 100644 --- a/R/plot_effects.R +++ b/R/plot_effects.R @@ -188,7 +188,7 @@ plot_glm_coefficients <- function(coefficients, path = NULL) { #' The `ranger`, `xgb.Booster`, `glm`, or `gam` object inside the workflow, #' rather than the workflow wrapping it. Anything that plots or interrogates a #' model directly needs this rather than the tidymodels object — including -#' [fancygam](https://github.com/chross22/fancygam), whose `plotSmooths()` takes +#' [fancyfx](https://github.com/chross22/fancyfx), whose `plotEffects()` takes #' an `mgcv` fit, which is what this returns for `model.type: gam`. #' #' @param model a fitted model from [fit_patch_model()], or a fitted workflow @@ -196,7 +196,7 @@ plot_glm_coefficients <- function(coefficients, path = NULL) { #' @examples #' \dontrun{ #' # Prettier smooths than the generic partial effects can give: -#' fancygam::plotSmooths(model_engine_fit(model)) +#' fancyfx::plotEffects(model_engine_fit(model), dat, "SST") #' } #' @export model_engine_fit <- function(model) { @@ -239,16 +239,28 @@ gam_smooth_terms <- function(fitted) { out[order(-out$edf), ] } -#' Whether fancygam is available to draw smooths +#' Whether fancyfx is available to draw smooths #' #' Its own function so the optional path can be exercised in tests without #' mocking `requireNamespace()` itself, which every package that loads a #' graphics device also goes through. #' -#' @return `TRUE` when fancygam is installed +#' @section It used to be called fancygam: +#' The package was renamed when it grew past GAMs. The rename is why this +#' matters more than a find-and-replace: `chross22/fancygam` still resolves on +#' GitHub, so `Remotes: chross22/fancygam` kept installing — but what it +#' installs now declares `Package: fancyfx`, so `requireNamespace("fancygam")` +#' returned `FALSE` on every fresh install and the smooth plots were skipped in +#' silence. Anyone with the old package still sitting in their library saw +#' nothing wrong. +#' +#' That is the failure mode to watch for here: this function gates a diagnostic +#' rather than the run, so a wrong answer costs a plot and no error. +#' +#' @return `TRUE` when fancyfx is installed #' @keywords internal -has_fancygam <- function() { - requireNamespace("fancygam", quietly = TRUE) +has_fancyfx <- function() { + requireNamespace("fancyfx", quietly = TRUE) } #' Variables a fitted GAM gave a smooth to @@ -267,7 +279,7 @@ gam_smoothed_variables <- function(fitted) { sub("^s\\((.*)\\)$", "\\1", terms) } -#' Plot a GAM's fitted smooths, with fancygam +#' Plot a GAM's fitted smooths, with fancyfx #' #' The partial effect of each smooth term on the log-odds scale, with its #' standard error band and a rug showing where the data actually is. This is the @@ -275,7 +287,7 @@ gam_smoothed_variables <- function(fitted) { #' rather than reconstructed by prediction, so it carries uncertainty, which a #' partial dependence curve cannot. #' -#' Drawn by [fancygam](https://github.com/chross22/fancygam), which is a Suggests +#' Drawn by [fancyfx](https://github.com/chross22/fancyfx), which is a Suggests #' — a run without it still gets the generic partial effect curves. #' #' @section Why the axes read in standard deviations: @@ -297,9 +309,9 @@ gam_smoothed_variables <- function(fitted) { #' @seealso [gam_smooth_terms()] for the numbers behind these #' @export plot_gam_smooths <- function(model, vars = NULL, path = NULL) { - if (!has_fancygam()) { - stop("The 'fancygam' package is required to plot GAM smooths. ", - "Install it with remotes::install_github('chross22/fancygam').", + if (!has_fancyfx()) { + stop("The 'fancyfx' package is required to plot GAM smooths. ", + "Install it with remotes::install_github('chross22/fancyfx').", call. = FALSE) } workflow <- if (inherits(model, "workflow")) model else model$workflow @@ -314,7 +326,7 @@ plot_gam_smooths <- function(model, vars = NULL, path = NULL) { # would be drawn on different x scales and quietly disagree. baked <- as.data.frame(workflows::extract_mold(workflow)$predictors) - plot <- fancygam::combinePlots(model_engine_fit(workflow), baked, vars) + plot <- fancyfx::combinePlots(model_engine_fit(workflow), baked, vars) if (is.null(path)) return(plot) ggplot2::ggsave(path, plot = plot, dpi = 150, @@ -371,9 +383,9 @@ write_effect_plots <- function(model, out) { } # The fitted smooths themselves, with their uncertainty. Skipped without a - # word when fancygam is absent: it is a Suggests, and the generic partial + # word when fancyfx is absent: it is a Suggests, and the generic partial # effect curves above already cover the question. - if (has_fancygam()) { + if (has_fancyfx()) { smooths <- try_diagnostic( plot_gam_smooths(model, path = file.path(out, "gam_smooths.png")), "GAM smooth plots" diff --git a/README.md b/README.md index b6868e0..da86acf 100644 --- a/README.md +++ b/README.md @@ -877,13 +877,13 @@ On top of that, each model contributes what only it can: | `gam` | Effective degrees of freedom per smooth. An `edf` of 1 means the smooth collapsed to a line | | `rf` / `brt` | None. The partial effect curve *is* their answer | -With [`fancygam`](https://github.com/chross22/fancygam) installed, a GAM also +With [`fancyfx`](https://github.com/chross22/fancyfx) installed, a GAM also gets its **fitted smooths** drawn — each term with its standard error band and a rug showing where the data actually is. Those carry uncertainty, which a partial dependence curve cannot: ```r -remotes::install_github("chross22/fancygam") +remotes::install_github("chross22/fancyfx") ``` Their x axes read in standard deviations, because the smooths belong to the model @@ -1182,7 +1182,7 @@ diagnostics/cv_predictions.csv held-out predictions, for any metric not tabu diagnostics/partial_effects.png what each predictor does to patch probability diagnostics/coefficients.png glm only: signed effects with intervals diagnostics/smooth_terms.csv gam only: effective degrees of freedom per smooth -diagnostics/gam_smooths.png gam only, with fancygam: fitted smooths with error bands +diagnostics/gam_smooths.png gam only, with fancyfx: fitted smooths with error bands diagnostics/members// with model.ensemble: the above, one directory per algorithm projections/suitability.csv every cell of every month: species, year, month, lon, lat, probability plus the interval and novelty columns, with projection.uncertainty diff --git a/inst/shiny/app.R b/inst/shiny/app.R index ed882ad..ac3e084 100644 --- a/inst/shiny/app.R +++ b/inst/shiny/app.R @@ -1210,16 +1210,16 @@ server <- function(input, output, session) { # A GAM has its own partial effects, and they are better than the generic # ones: read out of the fitted model rather than reconstructed by prediction, # so they carry the uncertainty a partial dependence curve cannot. - use_fancygam <- reactive({ + use_fancyfx <- reactive({ identical(run_result()$model$type, "gam") && - requireNamespace("fancygam", quietly = TRUE) + requireNamespace("fancyfx", quietly = TRUE) }) output$partial_effects_note <- renderUI({ req(run_result()) - if (isTRUE(use_fancygam())) { + if (isTRUE(use_fancyfx())) { return(helpText("The model's own smooths, with standard error bands and a", - "rug showing where the data is, drawn by fancygam. The x", + "rug showing where the data is, drawn by fancyfx. The x", "axes are in standard deviations because the model was", "fitted on the centred and scaled predictors - turn off", "'Centre and scale' to read them in the covariate's own", @@ -1233,7 +1233,7 @@ server <- function(input, output, session) { output$partial_effects <- renderPlot({ req(run_result()) - if (isTRUE(use_fancygam())) { + if (isTRUE(use_fancyfx())) { return(suppressMessages(plot_gam_smooths(run_result()$model))) } effects <- run_effects() @@ -1257,7 +1257,7 @@ server <- function(input, output, session) { } if (identical(model$type, "gam")) { # The smooths themselves are the partial effects panel above when - # fancygam is present, so they are not repeated here. + # fancyfx is present, so they are not repeated here. return(tagList( h4("Smooth terms"), helpText("Effective degrees of freedom per smooth. An edf of 1 means", diff --git a/man/has_fancyfx.Rd b/man/has_fancyfx.Rd new file mode 100644 index 0000000..a35d08d --- /dev/null +++ b/man/has_fancyfx.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot_effects.R +\name{has_fancyfx} +\alias{has_fancyfx} +\title{Whether fancyfx is available to draw smooths} +\usage{ +has_fancyfx() +} +\value{ +\code{TRUE} when fancyfx is installed +} +\description{ +Its own function so the optional path can be exercised in tests without +mocking \code{requireNamespace()} itself, which every package that loads a +graphics device also goes through. +} +\section{It used to be called fancygam}{ + +The package was renamed when it grew past GAMs. The rename is why this +matters more than a find-and-replace: \code{chross22/fancygam} still resolves on +GitHub, so \code{Remotes: chross22/fancygam} kept installing — but what it +installs now declares \code{Package: fancyfx}, so \code{requireNamespace("fancygam")} +returned \code{FALSE} on every fresh install and the smooth plots were skipped in +silence. Anyone with the old package still sitting in their library saw +nothing wrong. + +That is the failure mode to watch for here: this function gates a diagnostic +rather than the run, so a wrong answer costs a plot and no error. +} + +\keyword{internal} diff --git a/man/has_fancygam.Rd b/man/has_fancygam.Rd deleted file mode 100644 index f28451e..0000000 --- a/man/has_fancygam.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot_effects.R -\name{has_fancygam} -\alias{has_fancygam} -\title{Whether fancygam is available to draw smooths} -\usage{ -has_fancygam() -} -\value{ -\code{TRUE} when fancygam is installed -} -\description{ -Its own function so the optional path can be exercised in tests without -mocking \code{requireNamespace()} itself, which every package that loads a -graphics device also goes through. -} -\keyword{internal} diff --git a/man/model_engine_fit.Rd b/man/model_engine_fit.Rd index 6439a70..5912a0d 100644 --- a/man/model_engine_fit.Rd +++ b/man/model_engine_fit.Rd @@ -16,12 +16,12 @@ the engine's own fitted object The \code{ranger}, \code{xgb.Booster}, \code{glm}, or \code{gam} object inside the workflow, rather than the workflow wrapping it. Anything that plots or interrogates a model directly needs this rather than the tidymodels object — including -\href{https://github.com/chross22/fancygam}{fancygam}, whose \code{plotSmooths()} takes +\href{https://github.com/chross22/fancyfx}{fancyfx}, whose \code{plotEffects()} takes an \code{mgcv} fit, which is what this returns for \code{model.type: gam}. } \examples{ \dontrun{ # Prettier smooths than the generic partial effects can give: -fancygam::plotSmooths(model_engine_fit(model)) +fancyfx::plotEffects(model_engine_fit(model), dat, "SST") } } diff --git a/man/plot_gam_smooths.Rd b/man/plot_gam_smooths.Rd index 1f8ddde..e0831bb 100644 --- a/man/plot_gam_smooths.Rd +++ b/man/plot_gam_smooths.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/plot_effects.R \name{plot_gam_smooths} \alias{plot_gam_smooths} -\title{Plot a GAM's fitted smooths, with fancygam} +\title{Plot a GAM's fitted smooths, with fancyfx} \usage{ plot_gam_smooths(model, vars = NULL, path = NULL) } @@ -24,7 +24,7 @@ rather than reconstructed by prediction, so it carries uncertainty, which a partial dependence curve cannot. } \details{ -Drawn by \href{https://github.com/chross22/fancygam}{fancygam}, which is a Suggests +Drawn by \href{https://github.com/chross22/fancyfx}{fancyfx}, which is a Suggests — a run without it still gets the generic partial effect curves. } \section{Why the axes read in standard deviations}{ diff --git a/tests/testthat/test-effects.R b/tests/testthat/test-effects.R index 674908f..8df1153 100644 --- a/tests/testthat/test-effects.R +++ b/tests/testthat/test-effects.R @@ -113,7 +113,7 @@ test_that("a GAM reports effective degrees of freedom per smooth", { test_that("the engine object comes out for anything that plots it directly", { skip_on_cran() - # fancygam::plotSmooths() and friends take the mgcv fit, not the workflow. + # fancyfx::plotEffects() and friends take the mgcv fit, not the workflow. expect_s3_class(model_engine_fit(fitted_of_type("glm")), "glm") skip_if_not_installed("mgcv") expect_s3_class(model_engine_fit(fitted_of_type("gam")), "gam") @@ -173,10 +173,10 @@ test_that("the smoothed variables are read back off the fitted model", { expect_false(any(grepl("^s\\(", smoothed))) }) -test_that("fancygam draws the fitted smooths for a GAM", { +test_that("fancyfx draws the fitted smooths for a GAM", { skip_on_cran() skip_if_not_installed("mgcv") - skip_if_not_installed("fancygam") + skip_if_not_installed("fancyfx") model <- fitted_of_type("gam") plot <- suppressMessages(plot_gam_smooths(model)) @@ -194,7 +194,7 @@ test_that("fancygam draws the fitted smooths for a GAM", { test_that("smooth plots are drawn against the data the model actually saw", { skip_on_cran() skip_if_not_installed("mgcv") - skip_if_not_installed("fancygam") + skip_if_not_installed("fancyfx") model <- fitted_of_type("gam") # gratia reports the smooths in the recipe's output units, so the rug has to @@ -207,12 +207,12 @@ test_that("smooth plots are drawn against the data the model actually saw", { expect_gt(abs(mean(model$model_data$SST)), 1) }) -test_that("without fancygam the run still gets its generic curves", { +test_that("without fancyfx the run still gets its generic curves", { skip_on_cran() skip_if_not_installed("mgcv") - # fancygam is a Suggests: its absence removes the extra plot, not the + # fancyfx is a Suggests: its absence removes the extra plot, not the # diagnostics. - local_mocked_bindings(has_fancygam = function() FALSE) + local_mocked_bindings(has_fancyfx = function() FALSE) out <- tempfile("diag"); dir.create(out) suppressMessages(suppressWarnings(write_effect_plots(fitted_of_type("gam"), out))) written <- list.files(out)