From cb079b782c39315714b0c406fe257fd492162655 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 16 Mar 2024 13:04:49 +0100 Subject: [PATCH 1/3] Draft plotting simulated residuals (#329) * Draft plotting simulated residuals * draft plot for check_residuals * fix * docs * include in check_model * version * lintr * fix * add transform argument * use latest performance * fix * fix * fix if qqplotr is missing --- DESCRIPTION | 4 +- NAMESPACE | 2 + R/data_plot.R | 5 ++ R/plot.check_model.R | 40 ++++++--- R/plot.parameters_model.R | 44 +++++----- R/plot.performance_simres.R | 133 +++++++++++++++++++++++++++++ man/geom_violindot.Rd | 29 +------ man/geom_violinhalf.Rd | 63 +++----------- man/plot.see_performance_simres.Rd | 71 +++++++++++++++ 9 files changed, 280 insertions(+), 111 deletions(-) create mode 100644 R/plot.performance_simres.R create mode 100644 man/plot.see_performance_simres.Rd diff --git a/DESCRIPTION b/DESCRIPTION index b2d51ad1c..6d071261a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: see Title: Model Visualisation Toolbox for 'easystats' and 'ggplot2' -Version: 0.8.2.4 +Version: 0.8.2.5 Authors@R: c(person(given = "Daniel", family = "Lüdecke", @@ -75,6 +75,7 @@ Imports: Suggests: brms, curl, + DHARMa, emmeans, factoextra, ggdist, @@ -118,3 +119,4 @@ Config/Needs/website: r-lib/pkgdown, easystats/easystatstemplate Config/rcmdcheck/ignore-inconsequential-notes: true +Remotes: easystats/performance#643 diff --git a/NAMESPACE b/NAMESPACE index c4027f24d..4efe799b3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -38,6 +38,7 @@ S3method(plot,see_check_model) S3method(plot,see_check_normality) S3method(plot,see_check_outliers) S3method(plot,see_check_overdisp) +S3method(plot,see_check_residuals) S3method(plot,see_compare_parameters) S3method(plot,see_compare_performance) S3method(plot,see_effectsize_table) @@ -64,6 +65,7 @@ S3method(plot,see_parameters_sem) S3method(plot,see_parameters_simulate) S3method(plot,see_performance_pp_check) S3method(plot,see_performance_roc) +S3method(plot,see_performance_simres) S3method(plot,see_point_estimate) S3method(plot,see_rope) S3method(plot,see_si) diff --git a/R/data_plot.R b/R/data_plot.R index c88c21d5a..d7ce90069 100644 --- a/R/data_plot.R +++ b/R/data_plot.R @@ -148,6 +148,11 @@ add_plot_attributes <- function(x) { obj_name <- attr(x, "object_name", exact = TRUE) dat <- NULL + # for simulated residuals, we save all necessary information in the object + if (inherits(x, "performance_simres")) { + return(x$fittedModel) + } + if (!is.null(obj_name)) { # first try, parent frame dat <- tryCatch(get(obj_name, envir = parent.frame()), error = function(e) NULL) diff --git a/R/plot.check_model.R b/R/plot.check_model.R index 97acb31a7..95436aaf2 100644 --- a/R/plot.check_model.R +++ b/R/plot.check_model.R @@ -165,19 +165,33 @@ plot.see_check_model <- function(x, } if ("QQ" %in% names(x) && !is.null(x$QQ) && any(c("qq", "all") %in% check)) { - p$QQ <- .plot_diag_qq( - x$QQ, - size_point, - size_line, - alpha_level = alpha_level, - detrend = detrend, - theme_style = style, - colors = colors, - dot_alpha_level = dot_alpha_level, - show_dots = TRUE, # qq-plots w/o dots makes no sense - model_info = model_info, - model_class = model_class - ) + if (inherits(x$QQ, "performance_simres")) { + p$QQ <- plot( + x$QQ, + size_line = size_line, + size_point = size_point, + alpha = alpha_level, + dot_alpha = dot_alpha_level, + colors = colors, + detrend = detrend, + style = style + + ) + } else { + p$QQ <- .plot_diag_qq( + x$QQ, + size_point, + size_line, + alpha_level = alpha_level, + detrend = detrend, + theme_style = style, + colors = colors, + dot_alpha_level = dot_alpha_level, + show_dots = TRUE, # qq-plots w/o dots makes no sense + model_info = model_info, + model_class = model_class + ) + } } if ("NORM" %in% names(x) && !is.null(x$NORM) && any(c("normality", "all") %in% check)) { diff --git a/R/plot.parameters_model.R b/R/plot.parameters_model.R index 7cbba6ecb..6206c4066 100644 --- a/R/plot.parameters_model.R +++ b/R/plot.parameters_model.R @@ -595,28 +595,28 @@ plot.see_parameters_model <- function(x, .meta_measure <- function(meta_measure) { switch(meta_measure, - "MD" = "Raw Mean Difference", - "SMDH" = , - "SMD" = "Standardized Mean Difference", - "ROM" = "Log transformed Ratio of Means", - "D2ORL" = , - "D2ORN" = "Transformed Standardized Mean Difference", - "UCOR" = , - "COR" = "Raw Correlation Coefficient", - "ZCOR" = "Z transformed Correlation Coefficient", - "PHI" = "Phi Coefficient", - "RR" = "Log Risk Ratio", - "OR" = "Log Odds Ratio", - "RD" = "Risk Difference", - "AS" = "Root transformed Risk Difference", - "PETO" = "Peto's Log Odds Ratio", - "PBIT" = "Standardized Mean Difference (Probit-transformed)", - "OR2DL" = , - "OR2DN" = "Standardized Mean Difference (Odds Ratio-transformed)", - "IRR" = "Log Incidence Rate Ratio", - "IRD" = "Incidence Rate Difference", - "IRSD" = "Square Root transformed Incidence Rate Difference", - "GEN" = "Generic Estimate", + MD = "Raw Mean Difference", + SMDH = , + SMD = "Standardized Mean Difference", + ROM = "Log transformed Ratio of Means", + D2ORL = , + D2ORN = "Transformed Standardized Mean Difference", + UCOR = , + COR = "Raw Correlation Coefficient", + ZCOR = "Z transformed Correlation Coefficient", + PHI = "Phi Coefficient", + RR = "Log Risk Ratio", + OR = "Log Odds Ratio", + RD = "Risk Difference", + AS = "Root transformed Risk Difference", + PETO = "Peto's Log Odds Ratio", + PBIT = "Standardized Mean Difference (Probit-transformed)", + OR2DL = , + OR2DN = "Standardized Mean Difference (Odds Ratio-transformed)", + IRR = "Log Incidence Rate Ratio", + IRD = "Incidence Rate Difference", + IRSD = "Square Root transformed Incidence Rate Difference", + GEN = "Generic Estimate", "Estimate" ) } diff --git a/R/plot.performance_simres.R b/R/plot.performance_simres.R new file mode 100644 index 000000000..24ac0853f --- /dev/null +++ b/R/plot.performance_simres.R @@ -0,0 +1,133 @@ +#' Plot method for check model for (non-)normality of residuals +#' +#' The `plot()` method for the `performance::check_residuals()` resp. +#' `performance::simulate_residuals()` function. +#' +#' @param transform Function to transform the residuals. If `NULL` (default), +#' no transformation is applied and uniformly distributed residuals are expected. +#' See argument `quantileFuntion` in `?DHARMa:::residuals.DHARMa` for more details. +#' +#' @inheritParams plot.see_check_normality +#' @inheritParams plot.see_check_model +#' +#' @return A ggplot2-object. +#' +#' @seealso See also the vignette about [`check_model()`](https://easystats.github.io/performance/articles/check_model.html). +#' +#' @examplesIf insight::check_if_installed("performance", "0.10.9.7") && require("glmmTMB") && require("qqplotr") && require("DHARMa") +#' data(Salamanders, package = "glmmTMB") +#' model <- glmmTMB::glmmTMB( +#' count ~ mined + spp + (1 | site), +#' family = poisson(), +#' data = Salamanders +#' ) +#' simulated_residuals <- performance::simulate_residuals(model) +#' plot(simulated_residuals) +#' +#' # or +#' simulated_residuals <- performance::simulate_residuals(model) +#' result <- performance::check_residuals(simulated_residuals) +#' plot(result) +#' +#' @export +plot.see_performance_simres <- function(x, + size_line = 0.8, + size_point = 1, + alpha = 0.2, + dot_alpha = 0.8, + colors = c("#3aaf85", "#1b6ca8"), + detrend = FALSE, + transform = NULL, + style = theme_lucid, + ...) { + dp <- list(min = 0, max = 1, lower.tail = TRUE, log.p = FALSE) + + # need DHARMa to be installed + insight::check_if_installed("DHARMa") + + # extract data, if from check_residuals + if (inherits(x, "see_check_residuals")) { + x <- attributes(x)$data + } + + if (is.null(transform)) { + res <- stats::residuals(x) + } else { + res <- stats::residuals(x, quantileFunction = transform) + } + + # base plot information + gg_init <- ggplot2::ggplot( + data.frame(scaled_residuals = res), + ggplot2::aes(sample = .data$scaled_residuals) + ) + + # when we have package qqplotr, we can add confidence bands + if (requireNamespace("qqplotr", quietly = TRUE)) { + qq_stuff <- list( + qqplotr::stat_qq_band( + distribution = "unif", + dparams = list(min = 0, max = 1), + alpha = alpha, + detrend = detrend + ), + qqplotr::stat_qq_line( + distribution = "unif", + dparams = dp, + size = size_line, + colour = colors[1], + detrend = detrend + ), + qqplotr::stat_qq_point( + distribution = "unif", + dparams = dp, + size = size_point, + alpha = dot_alpha, + colour = colors[2], + detrend = detrend + ) + ) + if (detrend) { + y_lab <- "Sample Quantile Deviations" + } else { + y_lab <- "Sample Quantiles" + } + } else { + insight::format_alert("For confidence bands, please install `qqplotr`.") + qq_stuff <- list( + ggplot2::geom_qq( + shape = 16, + stroke = 0, + distribution = stats::qunif, + dparams = dp, + size = size_point, + colour = colors[2] + ), + ggplot2::geom_qq_line( + linewidth = size_line, + colour = colors[1], + na.rm = TRUE, + distribution = stats::qunif, + dparams = dp + ) + ) + y_lab <- "Sample Quantiles" + } + + gg_init + + qq_stuff + + ggplot2::labs( + title = "Uniformity of Residuals", + subtitle = "Dots should fall along the line", + x = "Standard Uniform Distribution Quantiles", + y = y_lab + ) + + style( + base_size = 10, + plot.title.space = 3, + axis.title.space = 5 + ) +} + +#' @export +plot.see_check_residuals <- plot.see_performance_simres diff --git a/man/geom_violindot.Rd b/man/geom_violindot.Rd index b7cdeee8c..b02b4b4cf 100644 --- a/man/geom_violindot.Rd +++ b/man/geom_violindot.Rd @@ -68,31 +68,10 @@ Defaults to 1/30 of the range of the data} \item{position_dots}{Position adjustment for dots, either as a string, or the result of a call to a position adjustment function.} -\item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}'s \code{params} argument. These -arguments broadly fall into one of 4 categories below. Notably, further -arguments to the \code{position} argument, or aesthetics that are required -can \emph{not} be passed through \code{...}. Unknown arguments that are not part -of the 4 categories below are ignored. -\itemize{ -\item Static aesthetics that are not mapped to a scale, but are at a fixed -value and apply to the layer as a whole. For example, \code{colour = "red"} -or \code{linewidth = 3}. The geom's documentation has an \strong{Aesthetics} -section that lists the available options. The 'required' aesthetics -cannot be passed on to the \code{params}. -\item When constructing a layer using -a \verb{stat_*()} function, the \code{...} argument can be used to pass on -parameters to the \code{geom} part of the layer. An example of this is -\code{stat_density(geom = "area", outline.type = "both")}. The geom's -documentation lists which parameters it can accept. -\item Inversely, when constructing a layer using a -\verb{geom_*()} function, the \code{...} argument can be used to pass on parameters -to the \code{stat} part of the layer. An example of this is -\code{geom_area(stat = "density", adjust = 0.5)}. The stat's documentation -lists which parameters it can accept. -\item The \code{key_glyph} argument of \code{\link[ggplot2:layer]{layer()}} may also be passed on through -\code{...}. This can be one of the functions described as -\link[ggplot2:draw_key]{key glyphs}, to change the display of the layer in the legend. -}} +\item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are +often aesthetics, used to set an aesthetic to a fixed value, like +\code{colour = "red"} or \code{size = 3}. They may also be parameters +to the paired geom/stat.} \item{size_dots, dots_size}{Size adjustment for dots.} diff --git a/man/geom_violinhalf.Rd b/man/geom_violinhalf.Rd index 8bf3734d2..82b68ae8e 100644 --- a/man/geom_violinhalf.Rd +++ b/man/geom_violinhalf.Rd @@ -38,31 +38,15 @@ the plot data. The return value must be a \code{data.frame}, and will be used as the layer data. A \code{function} can be created from a \code{formula} (e.g. \code{~ head(.x, 10)}).} -\item{stat}{The statistical transformation to use on the data for this layer. -When using a \verb{geom_*()} function to construct a layer, the \code{stat} -argument can be used the override the default coupling between geoms and -stats. The \code{stat} argument accepts the following: -\itemize{ -\item A \code{Stat} ggproto subclass, for example \code{StatCount}. -\item A string naming the stat. To give the stat as a string, strip the -function name of the \code{stat_} prefix. For example, to use \code{stat_count()}, -give the stat as \code{"count"}. -\item For more information and other ways to specify the stat, see the -\link[ggplot2:layer_stats]{layer stat} documentation. -}} - -\item{position}{A position adjustment to use on the data for this layer. This -can be used in various ways, including to prevent overplotting and -improving the display. The \code{position} argument accepts the following: -\itemize{ -\item The result of calling a position function, such as \code{position_jitter()}. -This method allows for passing extra arguments to the position. -\item A string naming the position adjustment. To give the position as a -string, strip the function name of the \code{position_} prefix. For example, -to use \code{position_jitter()}, give the position as \code{"jitter"}. -\item For more information and other ways to specify the position, see the -\link[ggplot2:layer_positions]{layer position} documentation. -}} +\item{stat}{The statistical transformation to use on the data for this +layer, either as a \code{ggproto} \code{Geom} subclass or as a string naming the +stat stripped of the \code{stat_} prefix (e.g. \code{"count"} rather than +\code{"stat_count"})} + +\item{position}{Position adjustment, either as a string naming the adjustment +(e.g. \code{"jitter"} to use \code{position_jitter}), or the result of a call to a +position adjustment function. Use the latter if you need to change the +settings of the adjustment.} \item{trim}{If \code{TRUE} (default), trim the tails of the violins to the range of the data. If \code{FALSE}, don't trim the tails.} @@ -88,31 +72,10 @@ rather than combining with them. This is most useful for helper functions that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. \code{\link[ggplot2:borders]{borders()}}.} -\item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}'s \code{params} argument. These -arguments broadly fall into one of 4 categories below. Notably, further -arguments to the \code{position} argument, or aesthetics that are required -can \emph{not} be passed through \code{...}. Unknown arguments that are not part -of the 4 categories below are ignored. -\itemize{ -\item Static aesthetics that are not mapped to a scale, but are at a fixed -value and apply to the layer as a whole. For example, \code{colour = "red"} -or \code{linewidth = 3}. The geom's documentation has an \strong{Aesthetics} -section that lists the available options. The 'required' aesthetics -cannot be passed on to the \code{params}. -\item When constructing a layer using -a \verb{stat_*()} function, the \code{...} argument can be used to pass on -parameters to the \code{geom} part of the layer. An example of this is -\code{stat_density(geom = "area", outline.type = "both")}. The geom's -documentation lists which parameters it can accept. -\item Inversely, when constructing a layer using a -\verb{geom_*()} function, the \code{...} argument can be used to pass on parameters -to the \code{stat} part of the layer. An example of this is -\code{geom_area(stat = "density", adjust = 0.5)}. The stat's documentation -lists which parameters it can accept. -\item The \code{key_glyph} argument of \code{\link[ggplot2:layer]{layer()}} may also be passed on through -\code{...}. This can be one of the functions described as -\link[ggplot2:draw_key]{key glyphs}, to change the display of the layer in the legend. -}} +\item{...}{Other arguments passed on to \code{\link[ggplot2:layer]{layer()}}. These are +often aesthetics, used to set an aesthetic to a fixed value, like +\code{colour = "red"} or \code{size = 3}. They may also be parameters +to the paired geom/stat.} } \description{ Create a half-violin plot. diff --git a/man/plot.see_performance_simres.Rd b/man/plot.see_performance_simres.Rd new file mode 100644 index 000000000..6520442bc --- /dev/null +++ b/man/plot.see_performance_simres.Rd @@ -0,0 +1,71 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot.performance_simres.R +\name{plot.see_performance_simres} +\alias{plot.see_performance_simres} +\title{Plot method for check model for (non-)normality of residuals} +\usage{ +\method{plot}{see_performance_simres}( + x, + size_line = 0.8, + size_point = 1, + alpha = 0.2, + dot_alpha = 0.8, + colors = c("#3aaf85", "#1b6ca8"), + detrend = FALSE, + transform = NULL, + style = theme_lucid, + ... +) +} +\arguments{ +\item{x}{An object.} + +\item{size_line}{Numeric value specifying size of line geoms.} + +\item{size_point}{Numeric specifying size of point-geoms.} + +\item{alpha}{Numeric value specifying alpha level of the confidence bands.} + +\item{dot_alpha}{Numeric value specifying alpha level of the point geoms.} + +\item{colors}{Character vector of length two, indicating the colors (in +hex-format) for points and line.} + +\item{detrend}{Logical that decides if Q-Q and P-P plots should be de-trended +(also known as \emph{worm plots}).} + +\item{transform}{Function to transform the residuals. If \code{NULL} (default), +no transformation is applied and uniformly distributed residuals are expected. +See argument \code{quantileFuntion} in \code{?DHARMa:::residuals.DHARMa} for more details.} + +\item{style}{A ggplot2-theme.} + +\item{...}{Arguments passed to or from other methods.} +} +\value{ +A ggplot2-object. +} +\description{ +The \code{plot()} method for the \code{performance::check_residuals()} resp. +\code{performance::simulate_residuals()} function. +} +\examples{ +\dontshow{if (insight::check_if_installed("performance", "0.10.9.7") && require("glmmTMB") && require("qqplotr") && require("DHARMa")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +data(Salamanders, package = "glmmTMB") +model <- glmmTMB::glmmTMB( + count ~ mined + spp + (1 | site), + family = poisson(), + data = Salamanders +) +simulated_residuals <- performance::simulate_residuals(model) +plot(simulated_residuals) + +# or +simulated_residuals <- performance::simulate_residuals(model) +result <- performance::check_residuals(simulated_residuals) +plot(result) +\dontshow{\}) # examplesIf} +} +\seealso{ +See also the vignette about \href{https://easystats.github.io/performance/articles/check_model.html}{\code{check_model()}}. +} From ec5e643c082dcae8f3f9bd99884059a3308541bd Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 16 Mar 2024 13:28:26 +0100 Subject: [PATCH 2/3] Update NEWS.md --- NEWS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NEWS.md b/NEWS.md index 8238601ea..60777122a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,17 @@ # see (development version) +## Minor Changes + +- Minor changes regarding the latest update of _ggplot2_. + # see 0.8.2 +## Major changes + +- New `plot()` method for simulated residuals (implemented in the _performance_ package). + +- `plot()` for `check_model()` was revised and now includes more accurate Q-Q plots for non-Gaussian models. + ## Minor Changes * `plot.n_factors()` now shows a dashed line over the bars, indicating the From cdf65f995d41a88aeef2e47f764a426fc8d686fa Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 16 Mar 2024 13:29:10 +0100 Subject: [PATCH 3/3] Update NEWS.md --- NEWS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 60777122a..2b9bbd2b7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,5 @@ # see (development version) -## Minor Changes - -- Minor changes regarding the latest update of _ggplot2_. - -# see 0.8.2 - ## Major changes - New `plot()` method for simulated residuals (implemented in the _performance_ package). @@ -14,6 +8,12 @@ ## Minor Changes +- Minor changes regarding the latest update of _ggplot2_. + +# see 0.8.2 + +## Minor Changes + * `plot.n_factors()` now shows a dashed line over the bars, indicating the cumulate explained variance by the number of factors.