Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Imports:
yardstick
Suggests:
derivoce,
fancygam,
fancyfx,
knitr,
leaflet,
ranger,
Expand All @@ -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
Expand Down
40 changes: 26 additions & 14 deletions R/plot_effects.R
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,15 @@ 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
#' @return the engine's own fitted object
#' @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) {
Expand Down Expand Up @@ -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
Expand All @@ -267,15 +279,15 @@ 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
#' exact version of [partial_effects()] for a GAM: read out of the fitted model
#' 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:
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/<type>/ 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
Expand Down
12 changes: 6 additions & 6 deletions inst/shiny/app.R
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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()
Expand All @@ -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",
Expand Down
31 changes: 31 additions & 0 deletions man/has_fancyfx.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 0 additions & 17 deletions man/has_fancygam.Rd

This file was deleted.

4 changes: 2 additions & 2 deletions man/model_engine_fit.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions man/plot_gam_smooths.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions tests/testthat/test-effects.R
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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)
Expand Down
Loading