diff --git a/NAMESPACE b/NAMESPACE index 821c48f6..824ae29f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -104,7 +104,9 @@ export(ppc_boxplot) export(ppc_data) export(ppc_dens) export(ppc_dens_overlay) +export(ppc_dens_overlay_grouped) export(ppc_ecdf_overlay) +export(ppc_ecdf_overlay_grouped) export(ppc_error_binned) export(ppc_error_hist) export(ppc_error_hist_grouped) diff --git a/NEWS.md b/NEWS.md index b99a0846..4dcf5d20 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,24 +8,30 @@ * Items for next release go here --> +* Added `ppc_dens_overlay_grouped()` and `ppc_ecdf_overlay_grouped()` for + plotting density and cumulative distributions of the posterior predictive + distribution (versus observed data) by group. (#212) + +* Fix bug in `color_scheme_view()` minimal theme (#213). + * On the y axis, `ppc_loo_pit_qq(..., compare = "normal")` now plots standard normal quantiles calculated from the PIT values (instead of the standardized PIT values). (#240, #243, @fweber144) * New plotting function `ppc_km_overlay()` for outcome variables that are - right-censored. Empirical CCDF estimates of `yrep` are compared with the + right-censored. Empirical CCDF estimates of `yrep` are compared with the Kaplan-Meier estimate of `y`. (#233, #234, @fweber144) -* CmdStanMCMC objects (from CmdStanR) can now be used with extractor - functions `nuts_params()`, `log_posterior()`, `rhat()`, and +* CmdStanMCMC objects (from CmdStanR) can now be used with extractor + functions `nuts_params()`, `log_posterior()`, `rhat()`, and `neff_ratio()`. (#227) * Added missing `facet_args` argument to `mcmc_rank_overlay()`. (#221, @hhau) -* Size of points and interval lines can set in - `mcmc_intervals(..., outer_size, inner_size, point_size)`. (#215, #228, #229) - -* `mcmc_areas()` tries to use less blank vertical blank space. (#218, #230) +* Size of points and interval lines can set in + `mcmc_intervals(..., outer_size, inner_size, point_size)`. (#215, #228, #229) + +* `mcmc_areas()` tries to use less blank vertical blank space. (#218, #230) * `ppc_loo_pit_overlay()` now uses a boundary correction for an improved kernel density estimation. The new argument `boundary_correction` defaults to TRUE but @@ -53,7 +59,7 @@ matrices also inheriting from "array" in R 4.0. examples. (#161, #183, #188) * Two new plots have been added for inspecting the distribution of ranks. - Rank histograms were introduced by the Stan team's [new paper on + Rank histograms were introduced by the Stan team's [new paper on MCMC diagnostics](https://arxiv.org/abs/1903.08008). (#178, #179) `mcmc_rank_hist()`: A traditional traceplot (`mcmc_trace()`) visualizes how @@ -61,21 +67,21 @@ matrices also inheriting from "array" in R 4.0. histogram (`mcmc_rank_hist()`) visualizes how the *ranks* of values from the chains mix together. An ideal plot would show the ranks mixing or overlapping in a uniform distribution. - + `mcmc_rank_overlay()`: Instead of drawing each chain's histogram in a separate panel, this plot draws the top edge of the chains' histograms in a single panel. - + * Added `mcmc_trace_data()`, which returns the data used for plotting the trace plots and rank histograms. (Advances #97) * [ColorBrewer](http://colorbrewer2.org) palettes are now available as color schemes via [`color_scheme_set()`](https://mc-stan.org/bayesplot/reference/bayesplot-colors.html). - For example, `color_scheme_set("brewer-Spectral")` will use the Spectral + For example, `color_scheme_set("brewer-Spectral")` will use the Spectral palette. (#177, #190) -* MCMC plots now also accept objects with an `as.array` method as +* MCMC plots now also accept objects with an `as.array` method as input (e.g., stanfit objects). (#175, #184) * [`mcmc_trace()`](https://mc-stan.org/bayesplot/reference/MCMC-traces.html) @@ -83,9 +89,9 @@ matrices also inheriting from "array" in R 4.0. from the first iteration after warmup. (#14, #155, @mcol) * [`mcmc_areas()`](https://mc-stan.org/bayesplot/reference/MCMC-intervals.html) - gains an argument `area_method` which controls how to draw the density - curves. The default `"equal area"` constrains the heights so that the curves - have the same area. As a result, a narrow interval will appear as a spike + gains an argument `area_method` which controls how to draw the density + curves. The default `"equal area"` constrains the heights so that the curves + have the same area. As a result, a narrow interval will appear as a spike of density, while a wide, uncertain interval is spread thin over the _x_ axis. Alternatively `"equal height"` will set the maximum height on each curve to the same value. This works well when the intervals are about the same width. @@ -112,12 +118,12 @@ matrices also inheriting from "array" in R 4.0. * The examples in [`?ppc_loo_pit_overlay()`](https://mc-stan.org/bayesplot/reference/PPC-loo.html) now work as expected. (#166, #167) - -* Added `"viridisD"` as an alternative name for `"viridis"` to the supported + +* Added `"viridisD"` as an alternative name for `"viridis"` to the supported colors. -* Added `"viridisE"` (the [cividis](https://github.com/marcosci/cividis) - version of viridis) to the supported colors. +* Added `"viridisE"` (the [cividis](https://github.com/marcosci/cividis) + version of viridis) to the supported colors. * `ppc_bars()` and `ppc_bars_grouped()` now allow negative integers as input. (#172, @jeffpollock9) @@ -160,7 +166,7 @@ matrices also inheriting from "array" in R 4.0. gains an argument `discrete`, which is `FALSE` by default, but can be used to make the Geom more appropriate for discrete data. (#145) -* [PPC intervals +* [PPC intervals plots](https://mc-stan.org/bayesplot/reference/PPC-intervals.html) and [LOO predictive checks](https://mc-stan.org/bayesplot/reference/PPC-loo.html) now draw both an outer and an inner probability interval, which can be diff --git a/R/bayesplot-colors.R b/R/bayesplot-colors.R index 31f76228..de06df9e 100644 --- a/R/bayesplot-colors.R +++ b/R/bayesplot-colors.R @@ -230,8 +230,9 @@ plot_scheme <- function(scheme = NULL) { legend_none() + xaxis_text( face = "bold", - margin = margin(t = -3, b = 10), + margin = margin(t = -3, b = 10, unit = "pt"), angle = 0, + vjust = 1, debug = FALSE ) } diff --git a/R/ppc-distributions.R b/R/ppc-distributions.R index 7f9a5ffd..0cc24c7d 100644 --- a/R/ppc-distributions.R +++ b/R/ppc-distributions.R @@ -33,7 +33,8 @@ #' `yrep` should therefore contain only a small number of rows. See the #' **Examples** section. #' } -#' \item{`ppc_dens_overlay(), ppc_ecdf_overlay()`}{ +#' \item{`ppc_ecdf_overlay(), ppc_dens_overlay(), +#' ppc_ecdf_overlay_grouped(), ppc_dens_overlay_grouped()`}{ #' Kernel density or empirical CDF estimates of each dataset (row) in #' `yrep` are overlaid, with the distribution of `y` itself on top #' (and in a darker shade). When using `ppc_ecdf_overlay()` with discrete @@ -58,6 +59,7 @@ #' yrep <- example_yrep_draws() #' dim(yrep) #' ppc_dens_overlay(y, yrep[1:25, ]) +#' #' \donttest{ #' # ppc_ecdf_overlay with continuous data (set discrete=TRUE if discrete data) #' ppc_ecdf_overlay(y, yrep[sample(nrow(yrep), 25), ]) @@ -85,6 +87,11 @@ #' ppc_freqpoly_grouped(y, yrep[1:3,], group, freq = FALSE) + yaxis_text() #' } #' +#' # density and distribution overlays by group +#' ppc_dens_overlay_grouped(y, yrep[1:25, ], group = group) +#' +#' ppc_ecdf_overlay_grouped(y, yrep[1:25, ], group = group) +#' #' # don't need to only use small number of rows for ppc_violin_grouped #' # (as it pools yrep draws within groups) #' color_scheme_set("gray") @@ -265,15 +272,18 @@ ppc_dens <- function(y, yrep, ..., trim = FALSE, size = 0.5, alpha = 1) { #' @rdname PPC-distributions #' @export #' @template args-density-controls -ppc_dens_overlay <- function(y, yrep, ..., - size = 0.25, - alpha = 0.7, - trim = FALSE, - bw = "nrd0", - adjust = 1, - kernel = "gaussian", - n_dens = 1024) { - +ppc_dens_overlay <- function( + y, + yrep, + ..., + size = 0.25, + alpha = 0.7, + trim = FALSE, + bw = "nrd0", + adjust = 1, + kernel = "gaussian", + n_dens = 1024 +) { check_ignored_arguments(...) data <- ppc_data(y, yrep) @@ -315,10 +325,46 @@ ppc_dens_overlay <- function(y, yrep, ..., yaxis_ticks(FALSE) } +#' @rdname PPC-distributions +#' @export +#' @template args-density-controls +ppc_dens_overlay_grouped <- function( + y, + yrep, + group, + ..., + size = 0.25, + alpha = 0.7, + trim = FALSE, + bw = "nrd0", + adjust = 1, + kernel = "gaussian", + n_dens = 1024 +) { + check_ignored_arguments(...) - - - + p_overlay <- ppc_dens_overlay( + y = y, + yrep = yrep, + ..., + size = size, + alpha = alpha, + trim = trim, + bw = bw, + adjust = adjust, + kernel = kernel, + n_dens = n_dens + ) + # Use + list(data) trick to replace the data in the plot. The layer-specific + # data in the y and yrep layers should be safe because they are + # specified using a function on the main plot data. + data <- ppc_data(y, yrep, group = group) + p_overlay <- p_overlay + list(data) + + p_overlay + + facet_wrap("group") + + force_axes_in_facets() +} #' @export #' @rdname PPC-distributions @@ -327,48 +373,90 @@ ppc_dens_overlay <- function(y, yrep, ..., #' passed to [ggplot2::stat_ecdf()]. If `discrete` is set to #' `TRUE` then `geom="step"` is used. #' @param pad A logical scalar passed to [ggplot2::stat_ecdf()]. -ppc_ecdf_overlay <- - function(y, - yrep, - ..., - discrete = FALSE, - pad = TRUE, - size = 0.25, - alpha = 0.7) { - check_ignored_arguments(...) - data <- ppc_data(y, yrep) +ppc_ecdf_overlay <- function( + y, + yrep, + ..., + discrete = FALSE, + pad = TRUE, + size = 0.25, + alpha = 0.7 +) { + check_ignored_arguments(...) + data <- ppc_data(y, yrep) - ggplot(data) + - aes_(x = ~ value) + - hline_at( - c(0, 0.5, 1), - size = c(0.2, 0.1, 0.2), - linetype = 2, - color = get_color("dh") - ) + - stat_ecdf( - data = function(x) dplyr::filter(x, !.data$is_y), - mapping = aes_(group = ~ rep_id, color = "yrep"), - geom = if (discrete) "step" else "line", - size = size, - alpha = alpha, - pad = pad - ) + - stat_ecdf( - data = function(x) dplyr::filter(x, .data$is_y), - mapping = aes_(color = "y"), - geom = if (discrete) "step" else "line", - size = 1, - pad = pad - ) + - scale_color_ppc_dist() + - xlab(y_label()) + - scale_y_continuous(breaks = c(0, 0.5, 1)) + - yaxis_title(FALSE) + - xaxis_title(FALSE) + - yaxis_ticks(FALSE) + + ggplot(data) + + aes_(x = ~ value) + + hline_at( + 0.5, + size = 0.1, + linetype = 2, + color = get_color("dh") + ) + + hline_at( + c(0, 1), + size = 0.2, + linetype = 2, + color = get_color("dh") + ) + + stat_ecdf( + data = function(x) dplyr::filter(x, !.data$is_y), + mapping = aes_(group = ~ rep_id, color = "yrep"), + geom = if (discrete) "step" else "line", + size = size, + alpha = alpha, + pad = pad + ) + + stat_ecdf( + data = function(x) dplyr::filter(x, .data$is_y), + mapping = aes_(color = "y"), + geom = if (discrete) "step" else "line", + size = 1, + pad = pad + ) + + scale_color_ppc_dist() + + xlab(y_label()) + + scale_y_continuous(breaks = c(0, 0.5, 1)) + + yaxis_title(FALSE) + + xaxis_title(FALSE) + + yaxis_ticks(FALSE) + bayesplot_theme_get() - } +} + +#' @export +#' @rdname PPC-distributions +ppc_ecdf_overlay_grouped <- function( + y, + yrep, + group, + ..., + discrete = FALSE, + pad = TRUE, + size = 0.25, + alpha = 0.7 +) { + check_ignored_arguments(...) + + p_overlay <- ppc_ecdf_overlay( + y = y, + yrep = yrep, + ..., + discrete = discrete, + pad = pad, + size = size, + alpha = alpha + ) + + # Use + list(data) trick to replace the data in the plot + data <- ppc_data(y, yrep, group = group) + p_overlay <- p_overlay + list(data) + + p_overlay + + facet_wrap("group") + + force_axes_in_facets() +} + + #' @export #' @rdname PPC-distributions diff --git a/man/PPC-distributions.Rd b/man/PPC-distributions.Rd index ab203a13..2961468a 100644 --- a/man/PPC-distributions.Rd +++ b/man/PPC-distributions.Rd @@ -9,7 +9,9 @@ \alias{ppc_freqpoly_grouped} \alias{ppc_dens} \alias{ppc_dens_overlay} +\alias{ppc_dens_overlay_grouped} \alias{ppc_ecdf_overlay} +\alias{ppc_ecdf_overlay_grouped} \alias{ppc_violin_grouped} \title{PPC distributions} \usage{ @@ -55,6 +57,20 @@ ppc_dens_overlay( n_dens = 1024 ) +ppc_dens_overlay_grouped( + y, + yrep, + group, + ..., + size = 0.25, + alpha = 0.7, + trim = FALSE, + bw = "nrd0", + adjust = 1, + kernel = "gaussian", + n_dens = 1024 +) + ppc_ecdf_overlay( y, yrep, @@ -65,6 +81,17 @@ ppc_ecdf_overlay( alpha = 0.7 ) +ppc_ecdf_overlay_grouped( + y, + yrep, + group, + ..., + discrete = FALSE, + pad = TRUE, + size = 0.25, + alpha = 0.7 +) + ppc_violin_grouped( y, yrep, @@ -171,7 +198,7 @@ variable for \code{y} and each dataset (row) in \code{yrep}. For this plot \code{yrep} should therefore contain only a small number of rows. See the \strong{Examples} section. } -\item{\verb{ppc_dens_overlay(), ppc_ecdf_overlay()}}{ +\item{\verb{ppc_ecdf_overlay(), ppc_dens_overlay(), ppc_ecdf_overlay_grouped(), ppc_dens_overlay_grouped()}}{ Kernel density or empirical CDF estimates of each dataset (row) in \code{yrep} are overlaid, with the distribution of \code{y} itself on top (and in a darker shade). When using \code{ppc_ecdf_overlay()} with discrete @@ -193,6 +220,7 @@ y <- example_y_data() yrep <- example_yrep_draws() dim(yrep) ppc_dens_overlay(y, yrep[1:25, ]) + \donttest{ # ppc_ecdf_overlay with continuous data (set discrete=TRUE if discrete data) ppc_ecdf_overlay(y, yrep[sample(nrow(yrep), 25), ]) @@ -220,6 +248,11 @@ ppc_freqpoly_grouped(y, yrep[1:3,], group) + yaxis_text() ppc_freqpoly_grouped(y, yrep[1:3,], group, freq = FALSE) + yaxis_text() } +# density and distribution overlays by group +ppc_dens_overlay_grouped(y, yrep[1:25, ], group = group) + +ppc_ecdf_overlay_grouped(y, yrep[1:25, ], group = group) + # don't need to only use small number of rows for ppc_violin_grouped # (as it pools yrep draws within groups) color_scheme_set("gray") diff --git a/tests/figs/aesthetics/color-scheme-view-brewer-palette.svg b/tests/figs/aesthetics/color-scheme-view-brewer-palette.svg index 81d3fa7b..9c5c07fe 100644 --- a/tests/figs/aesthetics/color-scheme-view-brewer-palette.svg +++ b/tests/figs/aesthetics/color-scheme-view-brewer-palette.svg @@ -28,6 +28,6 @@ -brewer-Spectral +brewer-Spectral color_scheme_view (brewer palette) diff --git a/tests/figs/aesthetics/color-scheme-view-default.svg b/tests/figs/aesthetics/color-scheme-view-default.svg index df8e4aaa..b2304c26 100644 --- a/tests/figs/aesthetics/color-scheme-view-default.svg +++ b/tests/figs/aesthetics/color-scheme-view-default.svg @@ -28,6 +28,6 @@ -blue +blue color_scheme_view (default) diff --git a/tests/figs/aesthetics/color-scheme-view-mixed-scheme.svg b/tests/figs/aesthetics/color-scheme-view-mixed-scheme.svg index c18a7b09..2634ac93 100644 --- a/tests/figs/aesthetics/color-scheme-view-mixed-scheme.svg +++ b/tests/figs/aesthetics/color-scheme-view-mixed-scheme.svg @@ -28,6 +28,6 @@ -mix-red-blue +mix-red-blue color_scheme_view (mixed scheme) diff --git a/tests/figs/aesthetics/color-scheme-view-scheme-specified.svg b/tests/figs/aesthetics/color-scheme-view-scheme-specified.svg index 4728a30a..d89200c4 100644 --- a/tests/figs/aesthetics/color-scheme-view-scheme-specified.svg +++ b/tests/figs/aesthetics/color-scheme-view-scheme-specified.svg @@ -28,6 +28,6 @@ -red +red color_scheme_view (scheme specified) diff --git a/tests/figs/ppc-distributions/ppc-dens-overlay-grouped-alpha-size.svg b/tests/figs/ppc-distributions/ppc-dens-overlay-grouped-alpha-size.svg new file mode 100644 index 00000000..dd90e502 --- /dev/null +++ b/tests/figs/ppc-distributions/ppc-dens-overlay-grouped-alpha-size.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +C + + + + + + + + + + +D + + + + + + + + + + +A + + + + + + + + + + +B + + + + + + + + + +-2 +0 +2 + + + + +-2 +0 +2 + + + + + + +y +y +r +e +p +ppc_dens_overlay_grouped (alpha, size) + diff --git a/tests/figs/ppc-distributions/ppc-dens-overlay-grouped-default.svg b/tests/figs/ppc-distributions/ppc-dens-overlay-grouped-default.svg new file mode 100644 index 00000000..5b2b6180 --- /dev/null +++ b/tests/figs/ppc-distributions/ppc-dens-overlay-grouped-default.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +C + + + + + + + + + + +D + + + + + + + + + + +A + + + + + + + + + + +B + + + + + + + + + +-2 +0 +2 + + + + +-2 +0 +2 + + + + + + +y +y +r +e +p +ppc_dens_overlay_grouped (default) + diff --git a/tests/figs/ppc-distributions/ppc-ecdf-overlay-default.svg b/tests/figs/ppc-distributions/ppc-ecdf-overlay-default.svg index 0975d0c2..90a9d067 100644 --- a/tests/figs/ppc-distributions/ppc-ecdf-overlay-default.svg +++ b/tests/figs/ppc-distributions/ppc-ecdf-overlay-default.svg @@ -17,8 +17,8 @@ - + diff --git a/tests/figs/ppc-distributions/ppc-ecdf-overlay-discrete-size-alpha.svg b/tests/figs/ppc-distributions/ppc-ecdf-overlay-discrete-size-alpha.svg index 8b5e4afb..352dae40 100644 --- a/tests/figs/ppc-distributions/ppc-ecdf-overlay-discrete-size-alpha.svg +++ b/tests/figs/ppc-distributions/ppc-ecdf-overlay-discrete-size-alpha.svg @@ -17,8 +17,8 @@ - + diff --git a/tests/figs/ppc-distributions/ppc-ecdf-overlay-grouped-default.svg b/tests/figs/ppc-distributions/ppc-ecdf-overlay-grouped-default.svg new file mode 100644 index 00000000..a9401a66 --- /dev/null +++ b/tests/figs/ppc-distributions/ppc-ecdf-overlay-grouped-default.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + +2 + + + + + + + + + + + + +0 +1 +2 +3 +4 +5 + + + + + + + +0 +1 +2 +3 +4 +5 + +0.0 +0.5 +1.0 + + + + + + + +y +y +r +e +p +ppc_ecdf_overlay_grouped (default) + diff --git a/tests/figs/ppc-distributions/ppc-ecdf-overlay-grouped-discrete-size-alpha.svg b/tests/figs/ppc-distributions/ppc-ecdf-overlay-grouped-discrete-size-alpha.svg new file mode 100644 index 00000000..74d5a838 --- /dev/null +++ b/tests/figs/ppc-distributions/ppc-ecdf-overlay-grouped-discrete-size-alpha.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 + + + + + + + + + + +2 + + + + + + + + + + + + +0 +1 +2 +3 +4 +5 + + + + + + + +0 +1 +2 +3 +4 +5 + +0.0 +0.5 +1.0 + + + + + + + +y +y +r +e +p +ppc_ecdf_overlay_grouped (discrete, size, alpha) + diff --git a/tests/testthat/test-ppc-distributions.R b/tests/testthat/test-ppc-distributions.R index 469fb603..0c2a4f44 100644 --- a/tests/testthat/test-ppc-distributions.R +++ b/tests/testthat/test-ppc-distributions.R @@ -122,8 +122,34 @@ test_that("ppc_ecdf_overlay renders correctly", { vdiff_yrep2, discrete = TRUE, size = 2, - alpha = .2) - vdiffr::expect_doppelganger("ppc_ecdf_overlay (discrete, size, alpha)", p_custom) + alpha = .2 + ) + + vdiffr::expect_doppelganger( + "ppc_ecdf_overlay (discrete, size, alpha)", + p_custom + ) +}) + +test_that("ppc_ecdf_overlay_grouped renders correctly", { + testthat::skip_on_cran() + + p_base <- ppc_ecdf_overlay_grouped(vdiff_y2, vdiff_yrep2, vdiff_group2) + vdiffr::expect_doppelganger("ppc_ecdf_overlay_grouped (default)", p_base) + + p_custom <- ppc_ecdf_overlay_grouped( + vdiff_y2, + vdiff_yrep2, + vdiff_group2, + discrete = TRUE, + size = 2, + alpha = .2 + ) + + vdiffr::expect_doppelganger( + "ppc_ecdf_overlay_grouped (discrete, size, alpha)", + p_custom + ) }) test_that("ppc_km_overlay renders correctly", { @@ -161,6 +187,26 @@ test_that("ppc_dens_overlay renders correctly", { vdiffr::expect_doppelganger("ppc_dens_overlay (alpha, size)", p_custom) }) +test_that("ppc_dens_overlay_grouped renders correctly", { + testthat::skip_on_cran() + + p_base <- ppc_dens_overlay_grouped(vdiff_y, vdiff_yrep, vdiff_group) + vdiffr::expect_doppelganger("ppc_dens_overlay_grouped (default)", p_base) + + p_custom <- ppc_dens_overlay_grouped( + vdiff_y, + vdiff_yrep, + vdiff_group, + size = 1, + alpha = 0.2 + ) + + vdiffr::expect_doppelganger( + "ppc_dens_overlay_grouped (alpha, size)", + p_custom + ) +}) + test_that("ppc_violin_grouped renders correctly", { testthat::skip_on_cran() testthat::skip_if_not(getRversion() >= "3.6.0")