diff --git a/DESCRIPTION b/DESCRIPTION index 8181d1f0..6706fea1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,7 +4,7 @@ Title: Plotting for Bayesian Models Version: 1.13.0.9000 Date: 2025-06-18 Authors@R: c(person("Jonah", "Gabry", role = c("aut", "cre"), email = "jsg2201@columbia.edu"), - person("Tristan", "Mahr", role = "aut"), + person("Tristan", "Mahr", role = "aut", comment = c(ORCID = "0000-0002-8890-5116")), person("Paul-Christian", "Bürkner", role = "ctb"), person("Martin", "Modrák", role = "ctb"), person("Malcolm", "Barrett", role = "ctb"), @@ -26,7 +26,7 @@ URL: https://mc-stan.org/bayesplot/ BugReports: https://github.com/stan-dev/bayesplot/issues/ SystemRequirements: pandoc (>= 1.12.3), pandoc-citeproc Depends: - R (>= 3.1.0) + R (>= 4.1.0) Imports: dplyr (>= 0.8.0), ggplot2 (>= 3.4.0), diff --git a/NEWS.md b/NEWS.md index 95ea6ab7..4f30cd1a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # bayesplot (development version) +* PPC "avg" functions (`ppc_scatter_avg()`, `ppc_error_scatter_avg()`, etc.) gain a `stat` argument to set the averaging function. (Suggestion of #348, @kruschke). +* `ppc_error_scatter_avg_vs_x(x = some_expression)` labels the *x* axis with `some_expression`. + # bayesplot 1.13.0 * Add `ppc_loo_pit_ecdf()` by @TeemuSailynoja (#345) diff --git a/R/bayesplot-helpers.R b/R/bayesplot-helpers.R index 573835aa..d3d86ec3 100644 --- a/R/bayesplot-helpers.R +++ b/R/bayesplot-helpers.R @@ -469,3 +469,53 @@ grid_lines_y <- function(color = "gray50", size = 0.2) { overlay_function <- function(...) { stat_function(..., inherit.aes = FALSE) } + + + +# Resolve a function name and store the expression passed in by the user +#' @noRd +#' @param f a function-like thing: a string naming a function, a function +#' object, an anonymous function object, a formula-based lambda, and `NULL`. +#' @param fallback character string providing a fallback function name +#' @return the function named in `f` with an added `"tagged_expr"` attribute +#' containing the expression to represent the function name and an +#' `"is_anonymous_function"` attribute to flag if the expression is a call to +#' `function()`. +as_tagged_function <- function(f = NULL, fallback = "func") { + qf <- enquo(f) + f <- eval_tidy(qf) + if (!is.null(attr(f, "tagged_expr"))) return(f) + + f_expr <- quo_get_expr(qf) + f_fn <- f + + if (is_character(f)) { # f = "mean" + # using sym() on the evaluated `f` means that a variable that names a + # function string `x <- "mean"; as_tagged_function(x)` will be lost + # but that seems okay + f_expr <- sym(f) + f_fn <- match.fun(f) + } else if (is_null(f)) { # f = NULL + f_fn <- identity + f_expr <- sym(fallback) + } else if (is_callable(f)) { # f = mean or f = function(x) mean(x) + f_expr <- f_expr # or f = ~mean(.x) + f_fn <- as_function(f) + } + + # Setting attributes on primitive functions is deprecated, so wrap them + # and then tag + if (is_primitive(f_fn)) { + f_fn_old <- f_fn + f_factory <- function(f) { function(...) f(...) } + f_fn <- f_factory(f_fn_old) + } + + attr(f_fn, "tagged_expr") <- f_expr + attr(f_fn, "is_anonymous_function") <- + is_call(f_expr, name = "function") || is_formula(f_expr) + f_fn +} + + + diff --git a/R/bayesplot-package.R b/R/bayesplot-package.R index 1b0f0817..a2d4531d 100644 --- a/R/bayesplot-package.R +++ b/R/bayesplot-package.R @@ -1,6 +1,5 @@ #' **bayesplot**: Plotting for Bayesian Models #' -#' @docType package #' @name bayesplot-package #' @aliases bayesplot #' @@ -96,7 +95,7 @@ #' ppd_hist(ypred[1:8, ]) #' } #' -NULL +"_PACKAGE" # internal ---------------------------------------------------------------- diff --git a/R/ppc-errors.R b/R/ppc-errors.R index 510e95f9..d038aa70 100644 --- a/R/ppc-errors.R +++ b/R/ppc-errors.R @@ -10,6 +10,10 @@ #' @template args-group #' @template args-facet_args #' @param ... Currently unused. +#' @param stat A function or a string naming a function for computing the +#' posterior average. In both cases, the function should take a vector input and +#' return a scalar statistic. The function name is displayed in the axis-label. +#' Defaults to `"mean"`. #' @param size,alpha For scatterplots, arguments passed to #' [ggplot2::geom_point()] to control the appearance of the points. For the #' binned error plot, arguments controlling the size of the outline and @@ -209,6 +213,7 @@ ppc_error_scatter_avg <- function(y, yrep, ..., + stat = "mean", size = 2.5, alpha = 0.8) { check_ignored_arguments(...) @@ -216,14 +221,18 @@ ppc_error_scatter_avg <- y <- validate_y(y) yrep <- validate_predictions(yrep, length(y)) errors <- compute_errors(y, yrep) + + stat <- as_tagged_function({{ stat }}) + ppc_scatter_avg( y = y, yrep = errors, size = size, alpha = alpha, - ref_line = FALSE + ref_line = FALSE, + stat = stat ) + - labs(x = error_avg_label(), y = y_label()) + labs(x = error_avg_label(stat), y = y_label()) } @@ -234,6 +243,7 @@ ppc_error_scatter_avg_grouped <- yrep, group, ..., + stat = "mean", facet_args = list(), size = 2.5, alpha = 0.8) { @@ -241,6 +251,8 @@ ppc_error_scatter_avg_grouped <- y <- validate_y(y) yrep <- validate_predictions(yrep, length(y)) + stat <- as_tagged_function({{ stat }}) + errors <- compute_errors(y, yrep) ppc_scatter_avg_grouped( y = y, @@ -249,9 +261,10 @@ ppc_error_scatter_avg_grouped <- size = size, alpha = alpha, facet_args = facet_args, - ref_line = FALSE + ref_line = FALSE, + stat = stat ) + - labs(x = error_avg_label(), y = y_label()) + labs(x = error_avg_label(stat), y = y_label()) } @@ -260,29 +273,37 @@ ppc_error_scatter_avg_grouped <- #' @param x A numeric vector the same length as `y` to use as the x-axis #' variable. #' -ppc_error_scatter_avg_vs_x <- - function(y, - yrep, - x, - ..., - size = 2.5, - alpha = 0.8) { - check_ignored_arguments(...) +ppc_error_scatter_avg_vs_x <- function( + y, + yrep, + x, + ..., + stat = "mean", + size = 2.5, + alpha = 0.8 +) { + check_ignored_arguments(...) - y <- validate_y(y) - yrep <- validate_predictions(yrep, length(y)) - x <- validate_x(x, y) - errors <- compute_errors(y, yrep) - ppc_scatter_avg( - y = x, - yrep = errors, - size = size, - alpha = alpha, - ref_line = FALSE + y <- validate_y(y) + yrep <- validate_predictions(yrep, length(y)) + qx <- enquo(x) + x <- validate_x(x, y) + stat <- as_tagged_function({{ stat }}) + errors <- compute_errors(y, yrep) + ppc_scatter_avg( + y = x, + yrep = errors, + size = size, + alpha = alpha, + ref_line = FALSE, + stat = stat + ) + + labs( + x = error_avg_label(stat), + y = as_label((qx)) ) + - labs(x = error_avg_label(), y = expression(italic(x))) + - coord_flip() - } + coord_flip() +} #' @rdname PPC-errors @@ -414,8 +435,21 @@ error_hist_facets <- error_label <- function() { expression(italic(y) - italic(y)[rep]) } -error_avg_label <- function() { - expression(paste("Average ", italic(y) - italic(y)[rep])) + +error_avg_label <- function(stat = NULL) { + stat <- as_tagged_function({{ stat }}, fallback = "stat") + e <- attr(stat, "tagged_expr") + if (attr(stat, "is_anonymous_function")) { + e <- sym("stat") + } + de <- deparse1(e) + + # create some dummy variables to pass the R package check for + # global variables in the expression below + italic <- sym("italic") + y <- sym("y") + + expr(paste((!!de))*(italic(y) - italic(y)[rep])) } diff --git a/R/ppc-scatterplots.R b/R/ppc-scatterplots.R index dd16620d..50802f82 100644 --- a/R/ppc-scatterplots.R +++ b/R/ppc-scatterplots.R @@ -11,6 +11,11 @@ #' @template args-group #' @template args-facet_args #' @param ... Currently unused. +#' @param stat A function or a string naming a function for computing the +#' posterior average. In both cases, the function should take a vector input +#' and return a scalar statistic. The function name is displayed in the +#' axis-label, and the underlying `$rep_label` for `ppc_scatter_avg_data()` +#' includes the function name. Defaults to `"mean"`. #' @param size,alpha Arguments passed to [ggplot2::geom_point()] to control the #' appearance of the points. #' @param ref_line If `TRUE` (the default) a dashed line with intercept 0 and @@ -31,10 +36,10 @@ #' } #' \item{`ppc_scatter_avg()`}{ #' A single scatterplot of `y` against the average values of `yrep`, i.e., -#' the points `(x,y) = (mean(yrep[, n]), y[n])`, where each `yrep[, n]` is -#' a vector of length equal to the number of posterior draws. Unlike -#' for `ppc_scatter()`, for `ppc_scatter_avg()` `yrep` should contain many -#' draws (rows). +#' the points `(x,y) = (average(yrep[, n]), y[n])`, where each `yrep[, n]` is +#' a vector of length equal to the number of posterior draws and `average()` +#' is a summary statistic. Unlike for `ppc_scatter()`, for +#' `ppc_scatter_avg()` `yrep` should contain many draws (rows). #' } #' \item{`ppc_scatter_avg_grouped()`}{ #' The same as `ppc_scatter_avg()`, but a separate plot is generated for @@ -59,6 +64,9 @@ #' p1 + lims #' p2 + lims #' +#' # "average" function is customizable +#' ppc_scatter_avg(y, yrep, stat = "median", ref_line = FALSE) +#' #' # for ppc_scatter_avg_grouped the default is to allow the facets #' # to have different x and y axes #' group <- example_group_data() @@ -116,16 +124,19 @@ ppc_scatter_avg <- function(y, yrep, ..., + stat = "mean", size = 2.5, alpha = 0.8, ref_line = TRUE) { dots <- list(...) + stat <- as_tagged_function({{ stat }}) + if (!from_grouped(dots)) { check_ignored_arguments(...) dots$group <- NULL } - data <- ppc_scatter_avg_data(y, yrep, group = dots$group) + data <- ppc_scatter_avg_data(y, yrep, group = dots$group, stat = stat) if (is.null(dots$group) && nrow(yrep) == 1) { inform( "With only 1 row in 'yrep' ppc_scatter_avg is the same as ppc_scatter." @@ -143,7 +154,7 @@ ppc_scatter_avg <- # ppd instead of ppc (see comment in ppc_scatter) scale_color_ppd() + scale_fill_ppd() + - labs(x = yrep_avg_label(), y = y_label()) + + labs(x = yrep_avg_label(stat), y = y_label()) + bayesplot_theme_get() } @@ -155,6 +166,7 @@ ppc_scatter_avg_grouped <- yrep, group, ..., + stat = "mean", facet_args = list(), size = 2.5, alpha = 0.8, @@ -184,16 +196,19 @@ ppc_scatter_data <- function(y, yrep) { #' @rdname PPC-scatterplots #' @export -ppc_scatter_avg_data <- function(y, yrep, group = NULL) { +ppc_scatter_avg_data <- function(y, yrep, group = NULL, stat = "mean") { y <- validate_y(y) yrep <- validate_predictions(yrep, length(y)) if (!is.null(group)) { group <- validate_group(group, length(y)) } + stat <- as_tagged_function({{ stat }}) - data <- ppc_scatter_data(y = y, yrep = t(colMeans(yrep))) + data <- ppc_scatter_data(y = y, yrep = t(apply(yrep, 2, FUN = stat))) data$rep_id <- NA_integer_ - levels(data$rep_label) <- "mean(italic(y)[rep]))" + levels(data$rep_label) <- yrep_avg_label(stat) |> + as.expression() |> + as.character() if (!is.null(group)) { data <- tibble::add_column(data, @@ -206,7 +221,22 @@ ppc_scatter_avg_data <- function(y, yrep, group = NULL) { } # internal ---------------------------------------------------------------- -yrep_avg_label <- function() expression(paste("Average ", italic(y)[rep])) + +yrep_avg_label <- function(stat = NULL) { + stat <- as_tagged_function({{ stat }}, fallback = "stat") + e <- attr(stat, "tagged_expr") + if (attr(stat, "is_anonymous_function")) { + e <- sym("stat") + } + de <- deparse1(e) + + # create some dummy variables to pass the R package check for + # global variables in the expression below + italic <- sym("italic") + y <- sym("y") + + expr(paste((!!de))*(italic(y)[rep])) +} scatter_aes <- function(...) { aes(x = .data$value, y = .data$y_obs, ...) diff --git a/man/PPC-errors.Rd b/man/PPC-errors.Rd index 88610f8b..047590bf 100644 --- a/man/PPC-errors.Rd +++ b/man/PPC-errors.Rd @@ -37,19 +37,28 @@ ppc_error_hist_grouped( ppc_error_scatter(y, yrep, ..., facet_args = list(), size = 2.5, alpha = 0.8) -ppc_error_scatter_avg(y, yrep, ..., size = 2.5, alpha = 0.8) +ppc_error_scatter_avg(y, yrep, ..., stat = "mean", size = 2.5, alpha = 0.8) ppc_error_scatter_avg_grouped( y, yrep, group, ..., + stat = "mean", facet_args = list(), size = 2.5, alpha = 0.8 ) -ppc_error_scatter_avg_vs_x(y, yrep, x, ..., size = 2.5, alpha = 0.8) +ppc_error_scatter_avg_vs_x( + y, + yrep, + x, + ..., + stat = "mean", + size = 2.5, + alpha = 0.8 +) ppc_error_binned( y, @@ -106,6 +115,11 @@ to the corresponding observation.} binned error plot, arguments controlling the size of the outline and opacity of the shaded region indicating the 2-SE bounds.} +\item{stat}{A function or a string naming a function for computing the +posterior average. In both cases, the function should take a vector input and +return a scalar statistic. The function name is displayed in the axis-label. +Defaults to \code{"mean"}.} + \item{x}{A numeric vector the same length as \code{y} to use as the x-axis variable.} } diff --git a/man/PPC-scatterplots.Rd b/man/PPC-scatterplots.Rd index 64963c40..2c22bbd3 100644 --- a/man/PPC-scatterplots.Rd +++ b/man/PPC-scatterplots.Rd @@ -19,13 +19,22 @@ ppc_scatter( ref_line = TRUE ) -ppc_scatter_avg(y, yrep, ..., size = 2.5, alpha = 0.8, ref_line = TRUE) +ppc_scatter_avg( + y, + yrep, + ..., + stat = "mean", + size = 2.5, + alpha = 0.8, + ref_line = TRUE +) ppc_scatter_avg_grouped( y, yrep, group, ..., + stat = "mean", facet_args = list(), size = 2.5, alpha = 0.8, @@ -34,7 +43,7 @@ ppc_scatter_avg_grouped( ppc_scatter_data(y, yrep) -ppc_scatter_avg_data(y, yrep, group = NULL) +ppc_scatter_avg_data(y, yrep, group = NULL, stat = "mean") } \arguments{ \item{y}{A vector of observations. See \strong{Details}.} @@ -61,6 +70,12 @@ appearance of the points.} \item{ref_line}{If \code{TRUE} (the default) a dashed line with intercept 0 and slope 1 is drawn behind the scatter plot.} +\item{stat}{A function or a string naming a function for computing the +posterior average. In both cases, the function should take a vector input +and return a scalar statistic. The function name is displayed in the +axis-label, and the underlying \verb{$rep_label} for \code{ppc_scatter_avg_data()} +includes the function name. Defaults to \code{"mean"}.} + \item{group}{A grouping variable of the same length as \code{y}. Will be coerced to \link[base:factor]{factor} if not already a factor. Each value in \code{group} is interpreted as the group level pertaining @@ -92,10 +107,10 @@ small number of rows. } \item{\code{ppc_scatter_avg()}}{ A single scatterplot of \code{y} against the average values of \code{yrep}, i.e., -the points \verb{(x,y) = (mean(yrep[, n]), y[n])}, where each \code{yrep[, n]} is -a vector of length equal to the number of posterior draws. Unlike -for \code{ppc_scatter()}, for \code{ppc_scatter_avg()} \code{yrep} should contain many -draws (rows). +the points \verb{(x,y) = (average(yrep[, n]), y[n])}, where each \code{yrep[, n]} is +a vector of length equal to the number of posterior draws and \code{average()} +is a summary statistic. Unlike for \code{ppc_scatter()}, for +\code{ppc_scatter_avg()} \code{yrep} should contain many draws (rows). } \item{\code{ppc_scatter_avg_grouped()}}{ The same as \code{ppc_scatter_avg()}, but a separate plot is generated for @@ -121,6 +136,9 @@ lims <- ggplot2::lims(x = c(0, 160), y = c(0, 160)) p1 + lims p2 + lims +# "average" function is customizable +ppc_scatter_avg(y, yrep, stat = "median", ref_line = FALSE) + # for ppc_scatter_avg_grouped the default is to allow the facets # to have different x and y axes group <- example_group_data() diff --git a/man/bayesplot-package.Rd b/man/bayesplot-package.Rd index 9da7e946..f7404672 100644 --- a/man/bayesplot-package.Rd +++ b/man/bayesplot-package.Rd @@ -119,7 +119,7 @@ for plotting. Authors: \itemize{ - \item Tristan Mahr + \item Tristan Mahr (\href{https://orcid.org/0000-0002-8890-5116}{ORCID}) } Other contributors: diff --git a/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-default.svg b/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-default.svg index 445e86d6..516b7bdb 100644 --- a/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-default.svg +++ b/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-default.svg @@ -20,147 +20,149 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - --2 --1 -0 -1 -2 -3 - - - - - - - - - - - - - --2 --1 -0 -1 -2 -3 -Average -y - -y -r -e -p -y + +-2 +-1 +0 +1 +2 +3 + + + + + + + + + + + + + +-2 +-1 +0 +1 +2 +3 +mean +( +y + +y +r +e +p +) +y ppc_error_scatter_avg (default) diff --git a/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-grouped-default.svg b/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-grouped-default.svg index 6eb818c0..306ec994 100644 --- a/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-grouped-default.svg +++ b/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-grouped-default.svg @@ -20,166 +20,166 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - -C + +C - - + + - -D + +D @@ -202,102 +202,104 @@ B - - - - - - --2 --1 -0 -1 -2 - - - - - --1 -0 -1 -2 - - - - - - --2 --1 -0 -1 -2 - - - - - - - --2 --1 -0 -1 -2 -3 - --2 --1 -0 -1 -2 -3 - - - - - - - --1 -0 -1 -2 - - - - - --2 --1 -0 -1 -2 - - - - - - --2 --1 -0 -1 -2 - - - - - -Average -y - -y -r -e -p -y + + + + + + +-2 +-1 +0 +1 +2 + + + + + +-1 +0 +1 +2 + + + + + + +-2 +-1 +0 +1 +2 + + + + + + + +-2 +-1 +0 +1 +2 +3 + +-2 +-1 +0 +1 +2 +3 + + + + + + + +-1 +0 +1 +2 + + + + + +-2 +-1 +0 +1 +2 + + + + + + +-2 +-1 +0 +1 +2 + + + + + +mean +( +y + +y +r +e +p +) +y ppc_error_scatter_avg_grouped (default) diff --git a/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-vs-x-default.svg b/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-vs-x-default.svg index 8adb4a27..a3d03288 100644 --- a/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-vs-x-default.svg +++ b/tests/testthat/_snaps/ppc-errors/ppc-error-scatter-avg-vs-x-default.svg @@ -20,145 +20,147 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - --2 --1 -0 -1 -2 -3 - - - - - - - - - - - - -0 -25 -50 -75 -100 -x -Average -y - -y -r -e -p -ppc_error_scatter_avg_vs_x (default) + +-2 +-1 +0 +1 +2 +3 + + + + + + + + + + + + +0 +25 +50 +75 +100 +seq_along(vdiff_y) +mean +( +y + +y +r +e +p +) +ppc_error_scatter_avg_vs_x (default) diff --git a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-default.svg b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-default.svg index 67f900cb..0596082a 100644 --- a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-default.svg +++ b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-default.svg @@ -20,140 +20,142 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - --2 --1 -0 -1 -2 -3 - - - - - - - - - - --0.25 -0.00 -0.25 -Average -y -r -e -p -y + +-2 +-1 +0 +1 +2 +3 + + + + + + + + + + +-0.25 +0.00 +0.25 +mean +( +y +r +e +p +) +y ppc_scatter_avg (default) diff --git a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-default.svg b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-default.svg index ef927ddb..f613d159 100644 --- a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-default.svg +++ b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-default.svg @@ -20,170 +20,170 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - -C + +C - - + + - -D + +D @@ -206,92 +206,94 @@ B - - - - - --0.4 --0.2 -0.0 -0.2 - - - - - --0.2 -0.0 -0.2 -0.4 - - - - --0.25 -0.00 -0.25 - - - - - - --0.4 --0.2 -0.0 -0.2 -0.4 - --2 --1 -0 -1 -2 -3 - - - - - - - --1 -0 -1 -2 - - - - - --2 --1 -0 -1 -2 - - - - - - --2 --1 -0 -1 -2 - - - - - -Average -y -r -e -p -y + + + + + +-0.4 +-0.2 +0.0 +0.2 + + + + + +-0.2 +0.0 +0.2 +0.4 + + + + +-0.25 +0.00 +0.25 + + + + + + +-0.4 +-0.2 +0.0 +0.2 +0.4 + +-2 +-1 +0 +1 +2 +3 + + + + + + + +-1 +0 +1 +2 + + + + + +-2 +-1 +0 +1 +2 + + + + + + +-2 +-1 +0 +1 +2 + + + + + +mean +( +y +r +e +p +) +y ppc_scatter_avg_grouped (default) diff --git a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-size-alpha-ref-line.svg b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-size-alpha-ref-line.svg index 9e142966..86af1d39 100644 --- a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-size-alpha-ref-line.svg +++ b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-grouped-size-alpha-ref-line.svg @@ -20,166 +20,166 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - -C + +C - - + + - -D + +D @@ -202,92 +202,94 @@ B - - - - - --0.4 --0.2 -0.0 -0.2 - - - - - --0.2 -0.0 -0.2 -0.4 - - - - --0.25 -0.00 -0.25 - - - - - - --0.4 --0.2 -0.0 -0.2 -0.4 - --2 --1 -0 -1 -2 -3 - - - - - - - --1 -0 -1 -2 - - - - - --2 --1 -0 -1 -2 - - - - - - --2 --1 -0 -1 -2 - - - - - -Average -y -r -e -p -y + + + + + +-0.4 +-0.2 +0.0 +0.2 + + + + + +-0.2 +0.0 +0.2 +0.4 + + + + +-0.25 +0.00 +0.25 + + + + + + +-0.4 +-0.2 +0.0 +0.2 +0.4 + +-2 +-1 +0 +1 +2 +3 + + + + + + + +-1 +0 +1 +2 + + + + + +-2 +-1 +0 +1 +2 + + + + + + +-2 +-1 +0 +1 +2 + + + + + +mean +( +y +r +e +p +) +y ppc_scatter_avg_grouped (size, alpha, ref_line) diff --git a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-size-alpha.svg b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-size-alpha.svg index 3bc59a66..743c0dae 100644 --- a/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-size-alpha.svg +++ b/tests/testthat/_snaps/ppc-scatterplots/ppc-scatter-avg-size-alpha.svg @@ -20,140 +20,142 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - --2 --1 -0 -1 -2 -3 - - - - - - - - - - --0.25 -0.00 -0.25 -Average -y -r -e -p -y + +-2 +-1 +0 +1 +2 +3 + + + + + + + + + + +-0.25 +0.00 +0.25 +mean +( +y +r +e +p +) +y ppc_scatter_avg (size, alpha) diff --git a/tests/testthat/test-convenience-functions.R b/tests/testthat/test-convenience-functions.R index 7389973e..068e24a9 100644 --- a/tests/testthat/test-convenience-functions.R +++ b/tests/testthat/test-convenience-functions.R @@ -187,3 +187,76 @@ test_that("overlay_function returns the correct object", { a$constructor <- b$constructor <- NULL expect_equal(a, b, check.environment = FALSE) }) + + +# tagged functions ------------------------------------------------------- + +test_that("as_tagged_function handles bare function (symbol)", { + fn <- as_tagged_function(mean) + expect_type(fn, "closure") + expect_equal(fn(1:10), mean(1:10)) + expect_equal(attr(fn, "tagged_expr"), rlang::expr(mean)) + + # primitive functions are wrapped then tagged + fn <- as_tagged_function(max) + expect_equal(fn(1:10), 10) + expect_equal(attr(fn, "tagged_expr"), rlang::expr(max)) +}) + +test_that("as_tagged_function handles string input", { + fn <- as_tagged_function("mean") + expect_type(fn, "closure") + expect_equal(fn(1:10), mean(1:10)) + expect_equal(attr(fn, "tagged_expr"), rlang::sym("mean")) +}) + +test_that("as_tagged_function handles anonymous functions", { + fn <- as_tagged_function(function(x) mean(x^2)) + expect_type(fn, "closure") + expect_equal(fn(1:3), mean((1:3)^2)) + expect_equal(attr(fn, "tagged_expr"), rlang::expr( function(x) mean(x^2))) + + fn <- as_tagged_function(~mean(.x^2)) + expect_type(fn, "closure") + expect_equal(fn(1:3), mean((1:3)^2)) + expect_equal(attr(fn, "tagged_expr"), rlang::expr( ~mean(.x^2))) +}) + +test_that("as_tagged_function handles NULL with fallback name", { + fn <- as_tagged_function(NULL, fallback = "my_func") + expect_type(fn, "closure") + expect_equal(fn(1:5), 1:5) + expect_equal(attr(fn, "tagged_expr"), rlang::sym("my_func")) +}) + +test_that("as_tagged_function doesn't lose previous tags", { + fn1 <- as_tagged_function(mean) + fn2 <- as_tagged_function(fn1) + expect_identical(fn1, fn2) + expect_equal(attr(fn2, "tagged_expr"), rlang::expr(mean)) + + f_outer <- function(stat_outer) { + stat_outer <- as_tagged_function({{ stat_outer }}) + f_inner(stat_outer) + } + f_inner <- function(stat_inner) { + stat_inner <- as_tagged_function({{ stat_inner }}) + stat_inner + } + + # We don't want the tagged expressions to be stat_outer or stat_inner + my_function_name <- identity + f_inner(my_function_name) |> + attr("tagged_expr") |> + deparse() |> + expect_equal("my_function_name") + + f_outer(my_function_name) |> + attr("tagged_expr") |> + deparse() |> + expect_equal("my_function_name") + + # All the non-standard evaluation still provides a callable function + f_outer(my_function_name)(1:10) |> + expect_equal(1:10) +}) diff --git a/tests/testthat/test-ppc-scatterplots.R b/tests/testthat/test-ppc-scatterplots.R index 2640472e..9116b115 100644 --- a/tests/testthat/test-ppc-scatterplots.R +++ b/tests/testthat/test-ppc-scatterplots.R @@ -28,6 +28,15 @@ test_that("ppc_scatter_avg_grouped returns a ggplot object", { expect_gg(ppc_scatter_avg_grouped(y, yrep, as.integer(group))) }) +test_that("ppc_scatter_avg_data can take a custom fun_avg", { + # using the colMeans() and colSums() to avoid using apply(yrep, 2, fun) + # because apply() is used in ppc_scatter_avg_data() + means <- ppc_scatter_avg_data(y, yrep) + expect_equal(means$value, colMeans(yrep)) + sums <- ppc_scatter_avg_data(y, yrep, stat = "sum") + expect_equal(sums$value, colSums(yrep)) +}) + # Visual tests ------------------------------------------------------------