Scoping issue in `summary_rows()`'s `fns`
Prework
- [x] Read and agree to the code of conduct and contributing guidelines.
- [x] If there is already a relevant issue, whether open or closed, comment on the existing thread instead of posting a new issue.
Description
Starting with gt v0.9.0, there's a fundamental scoping flaw in resolving argument fns of gt::summary_rows / gt::grand_summary_rows which becomes clear when wrapping any of them in a wrapper function and referring to a locally defined variable in fns.
Reproducible example
Issue reprex for gt v0.9.0:
wrapper_fn <- function() {
capped_sum <- function(x, cap = 10) min(sum(x, na.rm = TRUE), cap)
mtcars |>
dplyr::select(gear) |>
gt::gt(rownames_to_stub = TRUE) |>
gt::grand_summary_rows(fns = ~ capped_sum(.))
}
wrapper_fn()
#> Error in `summarise()`:
#> ℹ In argument: `gear = (structure(function (..., .x = ..1, .y = ..2, . =
#> ..1) ...`.
#> ℹ In group 1: `::group_id:: = "::GRAND_SUMMARY"`.
#> Caused by error in `capped_sum()`:
#> ! could not find function "capped_sum"
#> Backtrace:
#> ▆
#> 1. ├─base::tryCatch(...)
#> 2. │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 3. │ ├─base (local) tryCatchOne(...)
#> 4. │ │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 5. │ └─base (local) tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
#> 6. │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 7. │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 8. ├─base::withCallingHandlers(...)
#> 9. ├─base::saveRDS(...)
#> 10. ├─base::do.call(...)
#> 11. ├─base (local) `<fn>`(...)
#> 12. ├─global `<fn>`(input = base::quote("cushy-kitty_reprex.R"))
#> 13. │ └─rmarkdown::render(input, quiet = TRUE, envir = globalenv(), encoding = "UTF-8")
#> 14. │ └─knitr::knit(knit_input, knit_output, envir = envir, quiet = quiet)
#> 15. │ └─knitr:::process_file(text, output)
#> 16. │ ├─base::withCallingHandlers(...)
#> 17. │ ├─knitr:::process_group(group)
#> 18. │ └─knitr:::process_group.block(group)
#> 19. │ └─knitr:::call_block(x)
#> 20. │ └─knitr:::block_exec(params)
#> 21. │ └─knitr:::eng_r(options)
#> 22. │ ├─knitr:::in_input_dir(...)
#> 23. │ │ └─knitr:::in_dir(input_dir(), expr)
#> 24. │ └─knitr (local) evaluate(...)
#> 25. │ └─evaluate::evaluate(...)
#> 26. │ └─evaluate:::evaluate_call(...)
#> 27. │ ├─evaluate (local) handle(...)
#> 28. │ │ └─base::try(f, silent = TRUE)
#> 29. │ │ └─base::tryCatch(...)
#> 30. │ │ └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 31. │ │ └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 32. │ │ └─base (local) doTryCatch(return(expr), name, parentenv, handler)
#> 33. │ ├─base::withCallingHandlers(...)
#> 34. │ ├─base::withVisible(value_fun(ev$value, ev$visible))
#> 35. │ └─knitr (local) value_fun(ev$value, ev$visible)
#> 36. │ └─knitr (local) fun(x, options = options)
#> 37. │ ├─base::withVisible(knit_print(x, ...))
#> 38. │ ├─knitr::knit_print(x, ...)
#> 39. │ └─gt:::knit_print.gt_tbl(x, ...)
#> 40. │ └─gt:::as.tags.gt_tbl(x, ...)
#> 41. │ ├─htmltools::HTML(render_as_html(data = x))
#> 42. │ └─gt:::render_as_html(data = x)
#> 43. │ └─gt:::build_data(data = data, context = "html")
#> 44. │ └─gt:::dt_summary_build(data = data, context = context)
#> 45. │ ├─dplyr::bind_rows(...)
#> 46. │ │ └─rlang::list2(...)
#> 47. │ └─base::lapply(...)
#> 48. │ └─gt (local) FUN(X[[i]], ...)
#> 49. │ ├─dplyr::ungroup(...)
#> 50. │ └─dplyr::summarize_at(select_data_tbl, columns, .funs = fn_formula)
#> 51. │ ├─dplyr::summarise(.tbl, !!!funs)
#> 52. │ └─dplyr:::summarise.grouped_df(.tbl, !!!funs)
#> 53. │ └─dplyr:::summarise_cols(.data, dplyr_quosures(...), by, "summarise")
#> 54. │ ├─base::withCallingHandlers(...)
#> 55. │ └─dplyr:::map(quosures, summarise_eval_one, mask = mask)
#> 56. │ └─base::lapply(.x, .f, ...)
#> 57. │ └─dplyr (local) FUN(X[[i]], ...)
#> 58. │ └─mask$eval_all_summarise(quo)
#> 59. │ └─dplyr (local) eval()
#> 60. ├─gt (local) `<inln_cl_>`(gear)
#> 61. │ └─rlang::eval_bare(`_quo`, base::parent.frame())
#> 62. ├─rlang (local) capped_sum(.)
#> 63. └─base::.handleSimpleError(...)
#> 64. └─dplyr (local) h(simpleError(msg, call))
#> 65. └─dplyr (local) handler(cnd)
#> 66. └─rlang::abort(message, class = error_class, parent = parent, call = error_call)
Created on 2023-04-06 with reprex v2.0.2
Equivalent expression for gt v0.8.0 works fine:
remotes::install_github("rstudio/[email protected]")
#> Using github PAT from envvar GITHUB_PAT
#> Skipping install of 'gt' from a github remote, the SHA1 (0acc7fbb) has not changed since last install.
#> Use `force = TRUE` to force installation
wrapper_fn <- function() {
capped_sum <- function(x, cap = 10) min(sum(x, na.rm = TRUE), cap)
mtcars |>
dplyr::select(gear) |>
gt::gt(rownames_to_stub = TRUE) |>
gt::grand_summary_rows(fns = list(~ capped_sum(.)))
}
wrapper_fn()
| gear | |
|---|---|
| Mazda RX4 | 4 |
| Mazda RX4 Wag | 4 |
| Datsun 710 | 4 |
| Hornet 4 Drive | 3 |
| Hornet Sportabout | 3 |
| Valiant | 3 |
| Duster 360 | 3 |
| Merc 240D | 4 |
| Merc 230 | 4 |
| Merc 280 | 4 |
| Merc 280C | 4 |
| Merc 450SE | 3 |
| Merc 450SL | 3 |
| Merc 450SLC | 3 |
| Cadillac Fleetwood | 3 |
| Lincoln Continental | 3 |
| Chrysler Imperial | 3 |
| Fiat 128 | 4 |
| Honda Civic | 4 |
| Toyota Corolla | 4 |
| Toyota Corona | 3 |
| Dodge Challenger | 3 |
| AMC Javelin | 3 |
| Camaro Z28 | 3 |
| Pontiac Firebird | 3 |
| Fiat X1-9 | 4 |
| Porsche 914-2 | 5 |
| Lotus Europa | 5 |
| Ford Pantera L | 5 |
| Ferrari Dino | 5 |
| Maserati Bora | 5 |
| Volvo 142E | 4 |
| capped_sum | 10 |
Created on 2023-04-06 with reprex v2.0.2
Expected result
I'd expect the above reprex to give the same result as:
capped_sum <- function(x, cap = 10) min(sum(x, na.rm = TRUE), cap)
mtcars |>
dplyr::select(gear) |>
gt::gt(rownames_to_stub = TRUE) |>
gt::grand_summary_rows(fns = ~ capped_sum(.))
| gear | |
|---|---|
| Mazda RX4 | 4 |
| Mazda RX4 Wag | 4 |
| Datsun 710 | 4 |
| Hornet 4 Drive | 3 |
| Hornet Sportabout | 3 |
| Valiant | 3 |
| Duster 360 | 3 |
| Merc 240D | 4 |
| Merc 230 | 4 |
| Merc 280 | 4 |
| Merc 280C | 4 |
| Merc 450SE | 3 |
| Merc 450SL | 3 |
| Merc 450SLC | 3 |
| Cadillac Fleetwood | 3 |
| Lincoln Continental | 3 |
| Chrysler Imperial | 3 |
| Fiat 128 | 4 |
| Honda Civic | 4 |
| Toyota Corolla | 4 |
| Toyota Corona | 3 |
| Dodge Challenger | 3 |
| AMC Javelin | 3 |
| Camaro Z28 | 3 |
| Pontiac Firebird | 3 |
| Fiat X1-9 | 4 |
| Porsche 914-2 | 5 |
| Lotus Europa | 5 |
| Ford Pantera L | 5 |
| Ferrari Dino | 5 |
| Maserati Bora | 5 |
| Volvo 142E | 4 |
| capped_sum | 10 |
Created on 2023-04-06 with reprex v2.0.2
I briefly looked at the source code but cancelled my investigation when I saw the complexity of the internal normalize_summary_fns(). Someone already familiar with that code should have a look at this issue!
Session info
Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.2.3 (2023-03-15)
#> os Ubuntu 22.04.2 LTS
#> system x86_64, linux-gnu
#> ui X11
#> language en_US.utf8
#> collate en_US.utf8
#> ctype en_US.utf8
#> tz Europe/Zurich
#> date 2023-04-06
#> pandoc 3.1.2 @ /usr/bin/ (via rmarkdown)
#>
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> package * version date (UTC) lib source
#> cli 3.6.1 2023-03-23 [1] RSPM (R 4.2.0)
#> digest 0.6.31 2022-12-11 [1] RSPM (R 4.2.0)
#> dplyr 1.1.1 2023-03-22 [1] CRAN (R 4.2.3)
#> evaluate 0.20 2023-01-17 [1] RSPM (R 4.2.0)
#> fansi 1.0.4 2023-01-22 [1] RSPM (R 4.2.0)
#> fastmap 1.1.1 2023-02-24 [1] RSPM (R 4.2.0)
#> fs 1.6.1 2023-02-06 [1] RSPM (R 4.2.0)
#> generics 0.1.3 2022-07-05 [1] RSPM (R 4.2.0)
#> glue 1.6.2 2022-02-24 [1] RSPM (R 4.2.0)
#> gt 0.9.0 2023-03-31 [1] RSPM (R 4.2.0)
#> htmltools 0.5.5 2023-03-23 [1] RSPM (R 4.2.0)
#> knitr 1.42 2023-01-25 [1] RSPM (R 4.2.0)
#> lifecycle 1.0.3 2022-10-07 [1] RSPM (R 4.2.0)
#> magrittr 2.0.3 2022-03-30 [1] RSPM (R 4.2.0)
#> pillar 1.9.0 2023-03-22 [1] RSPM (R 4.2.0)
#> pkgconfig 2.0.3 2019-09-22 [1] RSPM (R 4.2.0)
#> purrr 1.0.1 2023-01-10 [1] RSPM (R 4.2.0)
#> R.cache 0.16.0 2022-07-21 [1] RSPM (R 4.2.0)
#> R.methodsS3 1.8.2 2022-06-13 [1] RSPM (R 4.2.0)
#> R.oo 1.25.0 2022-06-12 [1] RSPM (R 4.2.0)
#> R.utils 2.12.2 2022-11-11 [1] RSPM (R 4.2.0)
#> R6 2.5.1 2021-08-19 [1] RSPM (R 4.2.0)
#> reprex 2.0.2 2022-08-17 [1] RSPM (R 4.2.0)
#> rlang 1.1.0 2023-03-14 [1] RSPM (R 4.2.0)
#> rmarkdown 2.21 2023-03-26 [1] RSPM (R 4.2.0)
#> rstudioapi 0.14 2022-08-22 [1] RSPM (R 4.2.0)
#> sass 0.4.5 2023-01-24 [1] RSPM (R 4.2.0)
#> sessioninfo 1.2.2 2021-12-06 [1] RSPM (R 4.2.0)
#> styler 1.9.1 2023-03-04 [1] RSPM (R 4.2.0)
#> tibble 3.2.1 2023-03-20 [1] RSPM (R 4.2.0)
#> tidyselect 1.2.0 2022-10-10 [1] RSPM (R 4.2.0)
#> utf8 1.2.3 2023-01-31 [1] RSPM (R 4.2.0)
#> vctrs 0.6.1 2023-03-22 [1] RSPM (R 4.2.0)
#> withr 2.5.0 2022-03-03 [1] RSPM (R 4.2.0)
#> xfun 0.38 2023-03-24 [1] RSPM (R 4.2.0)
#> xml2 1.3.3 2021-11-30 [1] RSPM (R 4.2.0)
#> yaml 2.3.7 2023-01-23 [1] RSPM (R 4.2.0)
#>
#> [1] /home/salim/.software/managed/r
#> [2] /usr/local/lib/R/site-library
#> [3] /usr/lib/R/site-library
#> [4] /usr/lib/R/library
#>
#> ──────────────────────────────────────────────────────────────────────────────
Thanks for posting this issue! Yes, something is strange here with scoping and this should definitely be fixed.
The formatter argument in grand_summary_rows() still works properly. It's just a bit annoying that you get a "deprecated" warning all the time. But for now it's a good workaround.