From 1b89d1ef8d4e66b10d7c6fe355dab58f8800c531 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 23 Jan 2026 17:12:59 -0600 Subject: [PATCH 01/12] Improve plot.TOSTt and add plot_htest_est function - plot.TOSTt type='simple' improvements: - Raw estimate plot now appears on top (was on bottom) - Decision text and equivalence bounds displayed at top - Added 'layout' parameter: "stacked" (default) or "combined" - Added plot_htest_est() for plotting estimates from any htest object - Displays point estimate with confidence interval - Handles null values as reference lines - Auto-converts two-sample t-test to mean difference - Added tests for new functionality Co-Authored-By: Claude Opus 4.5 --- NAMESPACE | 1 + NEWS.md | 8 ++ R/htest_helpers.R | 155 ++++++++++++++++++++++++++++++++++++ R/methods.TOSTt.R | 85 +++++++++++++++++--- man/TOSTt-methods.Rd | 5 +- man/as_htest.Rd | 1 + man/htest-helpers.Rd | 1 + man/plot_htest_est.Rd | 66 +++++++++++++++ man/simple_htest.Rd | 6 +- tests/testthat/test-htest.R | 46 +++++++++++ tests/testthat/test-tTOST.R | 39 +++++++++ 11 files changed, 399 insertions(+), 14 deletions(-) create mode 100644 man/plot_htest_est.Rd diff --git a/NAMESPACE b/NAMESPACE index d2f491d8..75f5e014 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -68,6 +68,7 @@ export(log_TOST) export(np_ses) export(perm_t_test) export(plot_cor) +export(plot_htest_est) export(plot_pes) export(plot_smd) export(powerTOSTone) diff --git a/NEWS.md b/NEWS.md index 196a1b73..35553ea3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,14 @@ NEWS - Added `perm_t_test` function to allow for permutation tests for equivalence using TOST - Update `brunner_munzel` function to allow TOST directly - Update functions to disallow `paired = TRUE` when formula method utilized. +- Improved `plot.TOSTt` for `type = "simple"`: + - Raw estimate plot now appears on top (was on bottom) + - Decision text and equivalence bounds now displayed at top of plot + - Added `layout` parameter: "stacked" (default) or "combined" for a single faceted plot +- Added `plot_htest_est()` function to create simple estimate plots from any `htest` object + - Displays point estimate with confidence interval + - Handles null values (single or equivalence bounds) as reference lines + - Automatically handles two-sample t-test estimates by computing mean difference # TOSTER v0.8.7 - Update documentation to make it clear what the "eqb" argument does within the `wilcox_TOST` function. diff --git a/R/htest_helpers.R b/R/htest_helpers.R index 4ddb076b..658a8e38 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -404,6 +404,7 @@ rounder_stat = function(number, printable_pval = function(pval, digits = 3){ + cutoff = 1*10^(-1*digits) if(pval < cutoff){ pval = paste0("p < ",cutoff) @@ -413,3 +414,157 @@ printable_pval = function(pval, return(pval) } + +#' @title Plot Estimate from 'htest' Object +#' +#' @description +#' `r lifecycle::badge('stable')` +#' +#' Creates a simple point estimate plot with confidence interval from any 'htest' object +#' that contains an estimate and confidence interval. This provides a visual representation +#' of the effect size and its uncertainty, similar to a forest plot. +#' +#' @param htest An S3 object of class 'htest' containing at minimum an `estimate` and +#' `conf.int` component. Examples include output from `t.test()`, `cor.test()`, +#' or TOSTER functions converted with `as_htest()`. +#' @param alpha Significance level for determining the confidence level label. +#' +#' @details +#' The function creates a horizontal point-range plot showing: +#' \itemize{ +#' \item Point estimate (black dot) +#' \item Confidence interval (horizontal line) +#' \item Null value(s) as dashed vertical reference line(s) +#' } +#' +#' For two-sample t-tests, R's `t.test()` returns both group means as the estimate + +#' rather +#' than their difference. This function automatically computes the difference to display +#' a single meaningful estimate with its confidence interval. +#' +#' If the 'htest' object contains equivalence bounds (two values in `null.value`), +#' both bounds are displayed as dashed vertical lines. +#' +#' @return A `ggplot` object that can be further customized using ggplot2 functions. +#' +#' @examples +#' # Standard t-test +#' t_result <- t.test(extra ~ group, data = sleep) +#' plot_htest_est(t_result) +#' +#' # One-sample t-test +#' t_one <- t.test(sleep$extra, mu = 0) +#' plot_htest_est(t_one) +#' +#' # Correlation test +#' cor_result <- cor.test(mtcars$mpg, mtcars$wt) +#' plot_htest_est(cor_result) +#' +#' # TOST result converted to htest +#' tost_res <- t_TOST(extra ~ group, data = sleep, eqb = 1) +#' plot_htest_est(as_htest(tost_res)) +#' +#' @import ggplot2 +#' @import ggdist +#' @family htest +#' @export +plot_htest_est <- function(htest, alpha = NULL) { + + + if (!inherits(htest, "htest")) { + stop("Input must be an object of class +'htest'") + } + + if (is.null(htest$estimate)) { + stop("Cannot create estimate plot: htest object has no estimate") + } + + if (is.null(htest$conf.int)) { + stop("Cannot create estimate plot: htest object has no confidence interval") + } + + # Handle two-sample t-test case where estimate contains both group means + estimate <- htest$estimate + estimate_name <- names(estimate) + + if (grepl("two sample t-test", htest$method, ignore.case = TRUE) && + length(estimate) > 1) { + estimate <- estimate[1] - estimate[2] + estimate_name <- "mean difference" + } else if (length(estimate) > 1) { + # For other cases with multiple estimates, warn and use first + warning("htest object has multiple estimates; using first estimate only") + estimate <- estimate[1] + estimate_name <- names(htest$estimate)[1] + } + + # Get confidence interval + ci_lower <- min(htest$conf.int) + ci_upper <- max(htest$conf.int) + conf_level <- attr(htest$conf.int, "conf.level") + + if (is.null(conf_level)) { + conf_level <- 0.95 + if (is.null(alpha)) { + message("No confidence level found in htest object. Defaulting to 95%.") + } + } + + # Create data frame for plotting + df_plot <- data.frame( + estimate = unname(estimate), + lower.ci = ci_lower, + upper.ci = ci_upper, + stringsAsFactors = FALSE + ) + + # Determine label for facet + if (is.null(estimate_name) || length(estimate_name) == 0) { + facet_label <- "Estimate" + } else { + # Capitalize first letter + facet_label <- paste0(toupper(substr(estimate_name, 1, 1)), + substr(estimate_name, 2, nchar(estimate_name))) + } + + # Build the plot + p <- ggplot(df_plot, + aes(x = estimate, + y = 1, + xmin = lower.ci, + xmax = upper.ci)) + + geom_pointrange() + + facet_grid(~facet_label) + + theme_tidybayes() + + labs(caption = paste0(conf_level * 100, "% Confidence Interval"), + subtitle = htest$method) + + theme(strip.text = element_text(face = "bold", size = 10), + plot.subtitle = element_text(size = 10), + axis.title.x = element_blank(), + axis.title.y = element_blank(), + axis.text.y = element_blank(), + axis.ticks.y = element_blank()) + + # Add null value reference line(s) + if (!is.null(htest$null.value)) { + null_vals <- unname(htest$null.value) + + if (length(null_vals) == 1) { + # Single null value (standard hypothesis test) + p <- p + geom_vline(xintercept = null_vals, linetype = "dashed") + } else if (length(null_vals) == 2) { + # Two null values (equivalence bounds) + p <- p + + geom_vline(xintercept = null_vals[1], linetype = "dashed") + + geom_vline(xintercept = null_vals[2], linetype = "dashed") + + scale_x_continuous(sec.axis = dup_axis( + breaks = round(null_vals, 3), + name = "" + )) + } + } + + return(p) +} diff --git a/R/methods.TOSTt.R b/R/methods.TOSTt.R index 5ca56eac..3cd9c894 100644 --- a/R/methods.TOSTt.R +++ b/R/methods.TOSTt.R @@ -4,10 +4,11 @@ #' #' @param x object of class `TOSTt`. #' @param digits Number of digits to print for p-values -#' @param type Type of plot to produce. Default is a consonance density plot "cd". Consonance plots (type = "cd") and null distribution plots (type = "tnull") can also be produced. Note: null distribution plots only available for estimates = "raw". +#' @param type Type of plot to produce. Default is "simple" which shows point estimates with confidence intervals. Other options include consonance plots ("c"), consonance density plots ("cd"), and null distribution plots ("tnull"). Note: null distribution plots only available for estimates = "raw". #' @param ci_lines Confidence interval lines for plots. Default is 1-alpha*2 (e.g., alpha = 0.05 is 90%) #' @param ci_shades Confidence interval shades when plot type is "cd". #' @param estimates indicator of what estimates to plot; options include "raw" or "SMD". Default is is both: c("raw","SMD"). +#' @param layout Layout for displaying multiple estimates. Options are "stacked" (default, separate plots stacked vertically) or "combined" (single faceted plot with shared legend). Only applies when both "raw" and "SMD" are in estimates. #' @param ... further arguments passed through, see description of return value for details.. #' #' @return @@ -119,8 +120,10 @@ plot.TOSTt <- function(x, estimates = c("raw","SMD"), ci_lines, ci_shades, + layout = c("stacked", "combined"), ...){ type = match.arg(type) + layout = match.arg(layout) low_eqd = x$eqb$low_eq[2] high_eqd = x$eqb$high_eq[2] @@ -177,12 +180,75 @@ plot.TOSTt <- function(x, ci_print = x$effsize$conf.level[1] + # Build subtitle with decision text and equivalence bounds + eqb_text = paste0("Equivalence bounds: [", + round(low_eqt, 3), ", ", + round(high_eqt, 3), "] (raw)") + subtitle_text = paste0(x$decision$TOST, "\n", + x$decision$ttest, "\n", + eqb_text) + # Get estimates for mean ---- df_t = x$effsize[1,] # Get estimates for SMD ---- df_d = x$effsize[2,] + # Check if we should use combined layout + both_estimates = "SMD" %in% estimates && "raw" %in% estimates + + if(both_estimates && layout == "combined"){ + # Combined faceted plot ---- + # Create combined data frame with scale indicator + df_combined = rbind( + data.frame( + estimate = df_t$estimate, + lower.ci = df_t$lower.ci, + upper.ci = df_t$upper.ci, + type = paste0(x_label, " (raw)"), + low_eq = low_eqt, + high_eq = high_eqt, + stringsAsFactors = FALSE + ), + data.frame( + estimate = df_d$estimate, + lower.ci = df_d$lower.ci, + upper.ci = df_d$upper.ci, + type = paste0(x$smd$smd_label, " (standardized)"), + low_eq = low_eqd, + high_eq = high_eqd, + stringsAsFactors = FALSE + ) + ) + # Preserve order: raw first, then SMD + df_combined$type = factor(df_combined$type, + levels = c(paste0(x_label, " (raw)"), + paste0(x$smd$smd_label, " (standardized)"))) + + plts <- ggplot(df_combined, + aes(x = estimate, + y = 1, + xmin = lower.ci, + xmax = upper.ci)) + + geom_pointrange() + + geom_vline(aes(xintercept = low_eq), linetype = "dashed") + + geom_vline(aes(xintercept = high_eq), linetype = "dashed") + + facet_wrap(~type, scales = "free_x") + + theme_tidybayes() + + labs(title = subtitle_text, + caption = paste0(ci_print*100, "% Confidence Interval")) + + theme(strip.text = element_text(face = "bold", size = 10), + plot.title = element_text(size = 10), + axis.title.x = element_blank(), + axis.title.y = element_blank(), + axis.text.y = element_blank(), + axis.ticks.y = element_blank()) + + return(plts) + } + + # Stacked layout (default) ---- + # Raw plot (now shown on top with subtitle) t_plot <- ggplot(df_t, aes(x=estimate, @@ -198,15 +264,16 @@ plot.TOSTt <- function(x, facet_grid(~as.character(x_label)) + theme_tidybayes() + labs(caption = paste0(ci_print*100,"% Confidence Interval"), - subtitle = paste0(x$decision$TOST, " \n", x$decision$ttest)) + + title = subtitle_text) + theme(strip.text = element_text(face = "bold", size = 10), - plot.subtitle = element_text(size = 10), + plot.title = element_text(size = 10), axis.title.x = element_blank(), axis.title.y=element_blank(), axis.text.y=element_blank(), axis.ticks.y=element_blank()) + # SMD plot (now shown on bottom) d_plot <- ggplot(df_d, aes(x=estimate, @@ -229,14 +296,10 @@ plot.TOSTt <- function(x, axis.text.y=element_blank(), axis.ticks.y=element_blank()) - - - # add the legend to the row we made earlier. Give it one-third of - # the width of one plot (via rel_widths). - - if("SMD" %in% estimates && "raw" %in% estimates){ - plts = plot_grid(d_plot, - t_plot, + # Stack plots: raw on top, SMD on bottom + if(both_estimates){ + plts = plot_grid(t_plot, + d_plot, ncol = 1) } diff --git a/man/TOSTt-methods.Rd b/man/TOSTt-methods.Rd index 3990f965..d9c01713 100644 --- a/man/TOSTt-methods.Rd +++ b/man/TOSTt-methods.Rd @@ -16,6 +16,7 @@ estimates = c("raw", "SMD"), ci_lines, ci_shades, + layout = c("stacked", "combined"), ... ) @@ -30,13 +31,15 @@ describe(x, ...) \item{...}{further arguments passed through, see description of return value for details..} -\item{type}{Type of plot to produce. Default is a consonance density plot "cd". Consonance plots (type = "cd") and null distribution plots (type = "tnull") can also be produced. Note: null distribution plots only available for estimates = "raw".} +\item{type}{Type of plot to produce. Default is "simple" which shows point estimates with confidence intervals. Other options include consonance plots ("c"), consonance density plots ("cd"), and null distribution plots ("tnull"). Note: null distribution plots only available for estimates = "raw".} \item{estimates}{indicator of what estimates to plot; options include "raw" or "SMD". Default is is both: c("raw","SMD").} \item{ci_lines}{Confidence interval lines for plots. Default is 1-alpha*2 (e.g., alpha = 0.05 is 90\%)} \item{ci_shades}{Confidence interval shades when plot type is "cd".} + +\item{layout}{Layout for displaying multiple estimates. Options are "stacked" (default, separate plots stacked vertically) or "combined" (single faceted plot with shared legend). Only applies when both "raw" and "SMD" are in estimates.} } \value{ \itemize{ diff --git a/man/as_htest.Rd b/man/as_htest.Rd index eb99eeda..58d8bae7 100644 --- a/man/as_htest.Rd +++ b/man/as_htest.Rd @@ -57,6 +57,7 @@ as_htest(res2) \seealso{ Other htest: \code{\link{htest-helpers}}, +\code{\link{plot_htest_est}()}, \code{\link{simple_htest}()} } \concept{htest} diff --git a/man/htest-helpers.Rd b/man/htest-helpers.Rd index 8e56aeda..836e2dc2 100644 --- a/man/htest-helpers.Rd +++ b/man/htest-helpers.Rd @@ -84,6 +84,7 @@ describe_htest(cor_result) \seealso{ Other htest: \code{\link{as_htest}()}, +\code{\link{plot_htest_est}()}, \code{\link{simple_htest}()} } \concept{htest} diff --git a/man/plot_htest_est.Rd b/man/plot_htest_est.Rd new file mode 100644 index 00000000..a20113be --- /dev/null +++ b/man/plot_htest_est.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/htest_helpers.R +\name{plot_htest_est} +\alias{plot_htest_est} +\title{Plot Estimate from 'htest' Object} +\usage{ +plot_htest_est(htest, alpha = NULL) +} +\arguments{ +\item{htest}{An S3 object of class 'htest' containing at minimum an \code{estimate} and +\code{conf.int} component. Examples include output from \code{t.test()}, \code{cor.test()}, +or TOSTER functions converted with \code{as_htest()}.} + +\item{alpha}{Significance level for determining the confidence level label.} +} +\value{ +A \code{ggplot} object that can be further customized using ggplot2 functions. +} +\description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} + +Creates a simple point estimate plot with confidence interval from any 'htest' object +that contains an estimate and confidence interval. This provides a visual representation +of the effect size and its uncertainty, similar to a forest plot. +} +\details{ +The function creates a horizontal point-range plot showing: +\itemize{ +\item Point estimate (black dot) +\item Confidence interval (horizontal line) +\item Null value(s) as dashed vertical reference line(s) +} + +For two-sample t-tests, R's \code{t.test()} returns both group means as the estimate +rather +than their difference. This function automatically computes the difference to display +a single meaningful estimate with its confidence interval. + +If the 'htest' object contains equivalence bounds (two values in \code{null.value}), +both bounds are displayed as dashed vertical lines. +} +\examples{ +# Standard t-test +t_result <- t.test(extra ~ group, data = sleep) +plot_htest_est(t_result) + +# One-sample t-test +t_one <- t.test(sleep$extra, mu = 0) +plot_htest_est(t_one) + +# Correlation test +cor_result <- cor.test(mtcars$mpg, mtcars$wt) +plot_htest_est(cor_result) + +# TOST result converted to htest +tost_res <- t_TOST(extra ~ group, data = sleep, eqb = 1) +plot_htest_est(as_htest(tost_res)) + +} +\seealso{ +Other htest: +\code{\link{as_htest}()}, +\code{\link{htest-helpers}}, +\code{\link{simple_htest}()} +} +\concept{htest} diff --git a/man/simple_htest.Rd b/man/simple_htest.Rd index a8909594..47716644 100644 --- a/man/simple_htest.Rd +++ b/man/simple_htest.Rd @@ -171,11 +171,13 @@ Other TOST: Other htest: \code{\link{as_htest}()}, -\code{\link{htest-helpers}} +\code{\link{htest-helpers}}, +\code{\link{plot_htest_est}()} Other htest: \code{\link{as_htest}()}, -\code{\link{htest-helpers}} +\code{\link{htest-helpers}}, +\code{\link{plot_htest_est}()} } \concept{TOST} \concept{htest} diff --git a/tests/testthat/test-htest.R b/tests/testthat/test-htest.R index 124ccb17..d5b5a5ab 100644 --- a/tests/testthat/test-htest.R +++ b/tests/testthat/test-htest.R @@ -656,3 +656,49 @@ test_that("All other htests",{ expect_equal(htest$p.value,df1$p.value) }) + +test_that("plot_htest_est works correctly", { + + # Standard two-sample t-test (should auto-convert to difference) + t_two <- t.test(extra ~ group, data = sleep) + p1 <- plot_htest_est(t_two) + expect_s3_class(p1, "ggplot") + + # One-sample t-test + t_one <- t.test(sleep$extra, mu = 0) + p2 <- plot_htest_est(t_one) + expect_s3_class(p2, "ggplot") + + # Correlation test + cor_res <- cor.test(mtcars$mpg, mtcars$wt) + p3 <- plot_htest_est(cor_res) + expect_s3_class(p3, "ggplot") + + # TOST converted to htest (equivalence bounds) + tost_res <- t_TOST(extra ~ group, data = sleep, eqb = 1) + htest_tost <- as_htest(tost_res) + p4 <- plot_htest_est(htest_tost) + expect_s3_class(p4, "ggplot") + + # Error cases + expect_error(plot_htest_est("not_htest"), + "Input must be an object of class") + + # htest without estimate + htest_no_est <- t_two + htest_no_est$estimate <- NULL + expect_error(plot_htest_est(htest_no_est), + "htest object has no estimate") + + # htest without conf.int + htest_no_ci <- t_two + htest_no_ci$conf.int <- NULL + expect_error(plot_htest_est(htest_no_ci), + "htest object has no confidence interval") + + # Wilcoxon test with CI + wilcox_res <- wilcox.test(extra ~ group, data = sleep, conf.int = TRUE) + p5 <- plot_htest_est(wilcox_res) + expect_s3_class(p5, "ggplot") + +}) diff --git a/tests/testthat/test-tTOST.R b/tests/testthat/test-tTOST.R index 93e5b2f0..e042a419 100644 --- a/tests/testthat/test-tTOST.R +++ b/tests/testthat/test-tTOST.R @@ -1137,6 +1137,45 @@ test_that("plot generic function",{ }) +test_that("plot.TOSTt simple type layout options", { + # Test the new layout parameter and updated simple plot behavior + + test1 = t_TOST(extra ~ group, data = sleep, eqb = 2) + + # Test default stacked layout + p_stacked = plot(test1, type = "simple") + expect_true(inherits(p_stacked, c("gg", "ggplot")) || inherits(p_stacked, "gtable")) + + # Test combined layout + p_combined = plot(test1, type = "simple", layout = "combined") + expect_s3_class(p_combined, "ggplot") + + # Test stacked layout explicitly + p_stacked2 = plot(test1, type = "simple", layout = "stacked") + expect_true(inherits(p_stacked2, c("gg", "ggplot")) || inherits(p_stacked2, "gtable")) + + # Test with only raw estimates + p_raw = plot(test1, type = "simple", estimates = "raw") + expect_s3_class(p_raw, "ggplot") + + # Test with only SMD estimates + p_smd = plot(test1, type = "simple", estimates = "SMD") + expect_s3_class(p_smd, "ggplot") + + # Test combined layout with only one estimate type (should just return single plot) + p_raw_combined = plot(test1, type = "simple", estimates = "raw", layout = "combined") + expect_s3_class(p_raw_combined, "ggplot") + + # Test one-sample case + test_one = t_TOST(x = sleep$extra, eqb = 1) + p_one = plot(test_one, type = "simple") + expect_true(inherits(p_one, c("gg", "ggplot")) || inherits(p_one, "gtable")) + + p_one_combined = plot(test_one, type = "simple", layout = "combined") + expect_s3_class(p_one_combined, "ggplot") + +}) + test_that("Ensure paired output correct", { test1 = tsum_TOST(n1 = 23, From 26f5203726529d08d2e73feb160f158008494e22 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 23 Jan 2026 17:20:41 -0600 Subject: [PATCH 02/12] Add (raw) and (standardized) labels to stacked layout facets Co-Authored-By: Claude Opus 4.5 --- R/methods.TOSTt.R | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/R/methods.TOSTt.R b/R/methods.TOSTt.R index 3cd9c894..a5783a58 100644 --- a/R/methods.TOSTt.R +++ b/R/methods.TOSTt.R @@ -248,6 +248,10 @@ plot.TOSTt <- function(x, } # Stacked layout (default) ---- + # Build facet labels with scale indicator + raw_facet_label = paste0(x_label, " (raw)") + smd_facet_label = paste0(x$smd$smd_label, " (standardized)") + # Raw plot (now shown on top with subtitle) t_plot <- ggplot(df_t, @@ -261,7 +265,7 @@ plot.TOSTt <- function(x, scale_x_continuous(sec.axis = dup_axis(breaks=c(round(low_eqt,round_t), round(high_eqt,round_t)), name = "")) + - facet_grid(~as.character(x_label)) + + facet_grid(~as.character(raw_facet_label)) + theme_tidybayes() + labs(caption = paste0(ci_print*100,"% Confidence Interval"), title = subtitle_text) + @@ -286,7 +290,7 @@ plot.TOSTt <- function(x, scale_x_continuous(sec.axis = dup_axis(breaks=c(round(low_eqd,round_t), round(high_eqd,round_t)), name = "")) + - facet_grid(~as.character(x$smd$smd_label)) + + facet_grid(~as.character(smd_facet_label)) + labs(caption = paste0(ci_print*100,"% Confidence Interval")) + theme_tidybayes() + theme(strip.text = element_text(face = "bold", From 1e170088ef44aceacdfe8491ca91dcab6129c8b4 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 23 Jan 2026 17:23:59 -0600 Subject: [PATCH 03/12] Fix plot_htest_est facet error with geom_vline Add facet_label to df_plot and use inherit.aes = FALSE for geom_vline to prevent faceting issues when null values are plotted. Co-Authored-By: Claude Opus 4.5 --- R/htest_helpers.R | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/R/htest_helpers.R b/R/htest_helpers.R index 658a8e38..c2a56cc9 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -512,14 +512,6 @@ plot_htest_est <- function(htest, alpha = NULL) { } } - # Create data frame for plotting - df_plot <- data.frame( - estimate = unname(estimate), - lower.ci = ci_lower, - upper.ci = ci_upper, - stringsAsFactors = FALSE - ) - # Determine label for facet if (is.null(estimate_name) || length(estimate_name) == 0) { facet_label <- "Estimate" @@ -529,6 +521,15 @@ plot_htest_est <- function(htest, alpha = NULL) { substr(estimate_name, 2, nchar(estimate_name))) } + # Create data frame for plotting (include facet_label in the data) + df_plot <- data.frame( + estimate = unname(estimate), + lower.ci = ci_lower, + upper.ci = ci_upper, + facet_label = facet_label, + stringsAsFactors = FALSE + ) + # Build the plot p <- ggplot(df_plot, aes(x = estimate, @@ -548,17 +549,21 @@ plot_htest_est <- function(htest, alpha = NULL) { axis.ticks.y = element_blank()) # Add null value reference line(s) + # Use inherit.aes = FALSE to avoid facet issues with geom_vline if (!is.null(htest$null.value)) { null_vals <- unname(htest$null.value) if (length(null_vals) == 1) { # Single null value (standard hypothesis test) - p <- p + geom_vline(xintercept = null_vals, linetype = "dashed") + p <- p + geom_vline(xintercept = null_vals, linetype = "dashed", + inherit.aes = FALSE) } else if (length(null_vals) == 2) { # Two null values (equivalence bounds) p <- p + - geom_vline(xintercept = null_vals[1], linetype = "dashed") + - geom_vline(xintercept = null_vals[2], linetype = "dashed") + + geom_vline(xintercept = null_vals[1], linetype = "dashed", + inherit.aes = FALSE) + + geom_vline(xintercept = null_vals[2], linetype = "dashed", + inherit.aes = FALSE) + scale_x_continuous(sec.axis = dup_axis( breaks = round(null_vals, 3), name = "" From 2954d193393ccbaa3429c939c9f95b6997a744f2 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 23 Jan 2026 17:27:57 -0600 Subject: [PATCH 04/12] Small update changing vignettes. Making requested changes to plotting functions. --- vignettes/IntroTOSTt.R | 2 +- vignettes/IntroTOSTt.Rmd | 2 +- vignettes/IntroTOSTt.html | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vignettes/IntroTOSTt.R b/vignettes/IntroTOSTt.R index 0db2069c..217fffd6 100644 --- a/vignettes/IntroTOSTt.R +++ b/vignettes/IntroTOSTt.R @@ -163,7 +163,7 @@ print(res1) print(res1b) ## ----fig.width=6, fig.height=6------------------------------------------------ -plot(res1, type = "simple") +plot(res1, type = "simple", layout = "combined") ## ----fig.width=6, fig.height=6, eval=TRUE------------------------------------- # Shade the 90% and 95% CI areas diff --git a/vignettes/IntroTOSTt.Rmd b/vignettes/IntroTOSTt.Rmd index 86c3a39f..7a526f20 100644 --- a/vignettes/IntroTOSTt.Rmd +++ b/vignettes/IntroTOSTt.Rmd @@ -251,7 +251,7 @@ One of the advantages of `t_TOST` is its built-in visualization capabilities. Th This is the default plot type, showing the point estimate and confidence intervals relative to the equivalence bounds: ```{r fig.width=6, fig.height=6} -plot(res1, type = "simple") +plot(res1, type = "simple", layout = "combined") ``` This plot clearly shows where our observed difference (with confidence intervals) falls in relation to our equivalence bounds (dashed vertical lines). diff --git a/vignettes/IntroTOSTt.html b/vignettes/IntroTOSTt.html index e13b6983..a3694a09 100644 --- a/vignettes/IntroTOSTt.html +++ b/vignettes/IntroTOSTt.html @@ -585,8 +585,8 @@

Visualizing Results

1. Simple Dot-and-Whisker Plot

This is the default plot type, showing the point estimate and confidence intervals relative to the equivalence bounds:

-
plot(res1, type = "simple")
-

+
plot(res1, type = "simple", layout = "combined")
+

This plot clearly shows where our observed difference (with confidence intervals) falls in relation to our equivalence bounds (dashed vertical lines).

@@ -973,7 +973,7 @@

Working with Summary Statistics Only

The same visualization and description methods work with tsum_TOST:

plot(res_tsum)
-

+

describe(res_tsum)
 #> [1] "Using the One-sample t-test, a null hypothesis significance test (NHST), and a equivalence test, via two one-sided tests (TOST), were performed with an alpha-level of 0.05. These tested the null hypotheses that true mean is equal to 0 (NHST), and true mean is more extreme than 5.5 and 8.5 (TOST). The equivalence test was significant, t(149) = 5.078, p < 0.001 (mean = 5.843 90% C.I.[5.731, 5.955]; Hedges's g = 7.021 90% C.I.[6.327, 7.691]). At the desired error rate, it can be stated that the true mean is between 5.5 and 8.5."
From 89a9987de01143639421c3d7984dcdc41d529844 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 26 Jan 2026 13:38:32 -0600 Subject: [PATCH 05/12] Add describe argument to plot_htest_est When describe=TRUE (default), the plot now includes: - Title: the test method name - Subtitle: test statistic, p-value, estimate, and CI on two lines Also removed unnecessary inherit.aes parameter from geom_vline calls. Co-Authored-By: Claude Opus 4.5 --- R/htest_helpers.R | 73 ++++++++++++++++++++++++++++++------- man/plot_htest_est.Rd | 14 +++++-- tests/testthat/test-htest.R | 18 +++++++++ 3 files changed, 89 insertions(+), 16 deletions(-) diff --git a/R/htest_helpers.R b/R/htest_helpers.R index c2a56cc9..c3cec8ba 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -428,6 +428,8 @@ printable_pval = function(pval, #' `conf.int` component. Examples include output from `t.test()`, `cor.test()`, #' or TOSTER functions converted with `as_htest()`. #' @param alpha Significance level for determining the confidence level label. +#' @param describe Logical. If TRUE (default), includes a concise statistical description +#' in the plot subtitle showing the test statistic, p-value, estimate, and confidence interval. #' #' @details #' The function creates a horizontal point-range plot showing: @@ -438,14 +440,15 @@ printable_pval = function(pval, #' } #' #' For two-sample t-tests, R's `t.test()` returns both group means as the estimate - -#' rather -#' than their difference. This function automatically computes the difference to display +#' rather than their difference. This function automatically computes the difference to display #' a single meaningful estimate with its confidence interval. #' #' If the 'htest' object contains equivalence bounds (two values in `null.value`), #' both bounds are displayed as dashed vertical lines. #' +#' When `describe = TRUE`, the plot includes a subtitle with the test statistic, p-value, +#' and estimate with confidence interval. The method name appears as the plot title. +#' #' @return A `ggplot` object that can be further customized using ggplot2 functions. #' #' @examples @@ -465,11 +468,14 @@ printable_pval = function(pval, #' tost_res <- t_TOST(extra ~ group, data = sleep, eqb = 1) #' plot_htest_est(as_htest(tost_res)) #' +#' # Without description +#' plot_htest_est(t_result, describe = FALSE) +#' #' @import ggplot2 #' @import ggdist #' @family htest #' @export -plot_htest_est <- function(htest, alpha = NULL) { +plot_htest_est <- function(htest, alpha = NULL, describe = TRUE) { if (!inherits(htest, "htest")) { @@ -530,6 +536,49 @@ plot_htest_est <- function(htest, alpha = NULL) { stringsAsFactors = FALSE ) + # Build description for subtitle if requested + if (describe) { + # Build concise description similar to describe_htest but shorter + desc_parts <- c() + + # Add test statistic if available + if (!is.null(htest$statistic)) { + stat_name <- names(htest$statistic) + stat_val <- rounder_stat(unname(htest$statistic), digits = 3) + + if (!is.null(htest$parameter)) { + par_val <- rounder_stat(unname(htest$parameter), digits = 2) + stat_str <- paste0(stat_name, "(", par_val, ") = ", stat_val) + } else { + stat_str <- paste0(stat_name, " = ", stat_val) + } + desc_parts <- c(desc_parts, stat_str) + } + + # Add p-value if available + if (!is.null(htest$p.value)) { + desc_parts <- c(desc_parts, printable_pval(htest$p.value, digits = 3)) + } + + # Build first line: test statistic and p-value + line1 <- paste(desc_parts, collapse = ", ") + + # Build second line: estimate and CI + est_str <- paste0(estimate_name, " = ", + rounder_stat(unname(estimate), digits = 3)) + ci_str <- paste0(round(conf_level * 100), "% CI [", + rounder_stat(ci_lower, digits = 3), ", ", + rounder_stat(ci_upper, digits = 3), "]") + line2 <- paste(est_str, ci_str, sep = ", ") + + # Combine with line break + subtitle_text <- paste(line1, line2, sep = "\n") + title_text <- htest$method + } else { + subtitle_text <- NULL + title_text <- htest$method + } + # Build the plot p <- ggplot(df_plot, aes(x = estimate, @@ -540,30 +589,28 @@ plot_htest_est <- function(htest, alpha = NULL) { facet_grid(~facet_label) + theme_tidybayes() + labs(caption = paste0(conf_level * 100, "% Confidence Interval"), - subtitle = htest$method) + + title = title_text, + subtitle = subtitle_text) + theme(strip.text = element_text(face = "bold", size = 10), - plot.subtitle = element_text(size = 10), + plot.title = element_text(size = 11), + plot.subtitle = element_text(size = 9), axis.title.x = element_blank(), axis.title.y = element_blank(), axis.text.y = element_blank(), axis.ticks.y = element_blank()) # Add null value reference line(s) - # Use inherit.aes = FALSE to avoid facet issues with geom_vline if (!is.null(htest$null.value)) { null_vals <- unname(htest$null.value) if (length(null_vals) == 1) { # Single null value (standard hypothesis test) - p <- p + geom_vline(xintercept = null_vals, linetype = "dashed", - inherit.aes = FALSE) + p <- p + geom_vline(xintercept = null_vals, linetype = "dashed") } else if (length(null_vals) == 2) { # Two null values (equivalence bounds) p <- p + - geom_vline(xintercept = null_vals[1], linetype = "dashed", - inherit.aes = FALSE) + - geom_vline(xintercept = null_vals[2], linetype = "dashed", - inherit.aes = FALSE) + + geom_vline(xintercept = null_vals[1], linetype = "dashed") + + geom_vline(xintercept = null_vals[2], linetype = "dashed") + scale_x_continuous(sec.axis = dup_axis( breaks = round(null_vals, 3), name = "" diff --git a/man/plot_htest_est.Rd b/man/plot_htest_est.Rd index a20113be..21c9e352 100644 --- a/man/plot_htest_est.Rd +++ b/man/plot_htest_est.Rd @@ -4,7 +4,7 @@ \alias{plot_htest_est} \title{Plot Estimate from 'htest' Object} \usage{ -plot_htest_est(htest, alpha = NULL) +plot_htest_est(htest, alpha = NULL, describe = TRUE) } \arguments{ \item{htest}{An S3 object of class 'htest' containing at minimum an \code{estimate} and @@ -12,6 +12,9 @@ plot_htest_est(htest, alpha = NULL) or TOSTER functions converted with \code{as_htest()}.} \item{alpha}{Significance level for determining the confidence level label.} + +\item{describe}{Logical. If TRUE (default), includes a concise statistical description +in the plot subtitle showing the test statistic, p-value, estimate, and confidence interval.} } \value{ A \code{ggplot} object that can be further customized using ggplot2 functions. @@ -32,12 +35,14 @@ The function creates a horizontal point-range plot showing: } For two-sample t-tests, R's \code{t.test()} returns both group means as the estimate -rather -than their difference. This function automatically computes the difference to display +rather than their difference. This function automatically computes the difference to display a single meaningful estimate with its confidence interval. If the 'htest' object contains equivalence bounds (two values in \code{null.value}), both bounds are displayed as dashed vertical lines. + +When \code{describe = TRUE}, the plot includes a subtitle with the test statistic, p-value, +and estimate with confidence interval. The method name appears as the plot title. } \examples{ # Standard t-test @@ -56,6 +61,9 @@ plot_htest_est(cor_result) tost_res <- t_TOST(extra ~ group, data = sleep, eqb = 1) plot_htest_est(as_htest(tost_res)) +# Without description +plot_htest_est(t_result, describe = FALSE) + } \seealso{ Other htest: diff --git a/tests/testthat/test-htest.R b/tests/testthat/test-htest.R index d5b5a5ab..1d400f9d 100644 --- a/tests/testthat/test-htest.R +++ b/tests/testthat/test-htest.R @@ -701,4 +701,22 @@ test_that("plot_htest_est works correctly", { p5 <- plot_htest_est(wilcox_res) expect_s3_class(p5, "ggplot") + # Test describe argument + p_desc <- plot_htest_est(t_two, describe = TRUE) + expect_s3_class(p_desc, "ggplot") + expect_true(!is.null(p_desc$labels$subtitle)) + expect_true(grepl("t\\(", p_desc$labels$subtitle)) # Should contain t statistic + + p_no_desc <- plot_htest_est(t_two, describe = FALSE) + expect_s3_class(p_no_desc, "ggplot") + expect_true(is.null(p_no_desc$labels$subtitle)) + + # Test with equivalence test (two null values) + res_eq <- simple_htest(x = sleep$extra[sleep$group == 1], + y = sleep$extra[sleep$group == 2], + paired = TRUE, mu = 1, alternative = "e") + p_eq <- plot_htest_est(res_eq) + expect_s3_class(p_eq, "ggplot") + expect_true(!is.null(p_eq$labels$subtitle)) + }) From 15cdb52d1ace6cf68f6725cd0c782667054abbc5 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 26 Jan 2026 15:38:03 -0600 Subject: [PATCH 06/12] Add one-sided rejection regions to tnull plots and null hypothesis to plot_htest_est - Improve plot.TOSTt for type="tnull" to show one-sided rejection regions - Equivalence tests: lower bound shows right tail, upper bound shows left tail - MET tests: lower bound shows left tail, upper bound shows right tail - Fix hypothesis detection to use grepl instead of exact match - Add null hypothesis line to plot_htest_est describe subtitle - Shows null hypothesis statement based on alternative type - Handles single null values and equivalence bounds Co-Authored-By: Claude Opus 4.5 --- NEWS.md | 4 ++ R/htest_helpers.R | 65 +++++++++++++++++-- R/methods.TOSTt.R | 148 +++++++++++++++++++++++++----------------- man/plot_htest_est.Rd | 12 +++- 4 files changed, 161 insertions(+), 68 deletions(-) diff --git a/NEWS.md b/NEWS.md index 35553ea3..45284d63 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,10 @@ NEWS - Raw estimate plot now appears on top (was on bottom) - Decision text and equivalence bounds now displayed at top of plot - Added `layout` parameter: "stacked" (default) or "combined" for a single faceted plot +- Improved `plot.TOSTt` for `type = "tnull"`: + - Now shows only one-sided rejection regions appropriate to the test type + - Equivalence tests: lower bound shows right tail, upper bound shows left tail + - Minimal effect tests: lower bound shows left tail, upper bound shows right tail - Added `plot_htest_est()` function to create simple estimate plots from any `htest` object - Displays point estimate with confidence interval - Handles null values (single or equivalence bounds) as reference lines diff --git a/R/htest_helpers.R b/R/htest_helpers.R index c3cec8ba..e243a41a 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -429,7 +429,8 @@ printable_pval = function(pval, #' or TOSTER functions converted with `as_htest()`. #' @param alpha Significance level for determining the confidence level label. #' @param describe Logical. If TRUE (default), includes a concise statistical description -#' in the plot subtitle showing the test statistic, p-value, estimate, and confidence interval. +#' in the plot subtitle showing the test statistic, p-value, estimate, confidence interval, +#' and the null hypothesis. #' #' @details #' The function creates a horizontal point-range plot showing: @@ -446,8 +447,13 @@ printable_pval = function(pval, #' If the 'htest' object contains equivalence bounds (two values in `null.value`), #' both bounds are displayed as dashed vertical lines. #' -#' When `describe = TRUE`, the plot includes a subtitle with the test statistic, p-value, -#' and estimate with confidence interval. The method name appears as the plot title. +#' When `describe = TRUE`, the plot includes a three-line subtitle: +#' \enumerate{ +#' \item Test statistic and p-value +#' \item Point estimate and confidence interval +#' \item Null hypothesis statement +#' } +#' The method name appears as the plot title. #' #' @return A `ggplot` object that can be further customized using ggplot2 functions. #' @@ -571,8 +577,57 @@ plot_htest_est <- function(htest, alpha = NULL, describe = TRUE) { rounder_stat(ci_upper, digits = 3), "]") line2 <- paste(est_str, ci_str, sep = ", ") - # Combine with line break - subtitle_text <- paste(line1, line2, sep = "\n") + # Build third line: null hypothesis + line3 <- NULL + if (!is.null(htest$null.value) && !is.null(htest$alternative)) { + null_name <- names(htest$null.value) + if (is.null(null_name) || length(null_name) == 0) { + null_name <- estimate_name + } + + if (length(htest$null.value) == 1) { + # Standard hypothesis test - show null based on alternative + null_rel <- switch(htest$alternative, + two.sided = "is equal to", + less = "is greater than or equal to", + greater = "is less than or equal to", + "is equal to") + line3 <- paste0("null: ", null_name, " ", null_rel, " ", + rounder_stat(unname(htest$null.value), digits = 3)) + } else if (length(htest$null.value) == 2) { + # Equivalence or minimal effect test + null_vals <- sort(unname(htest$null.value)) + if (htest$alternative == "equivalence") { + line3 <- paste0("null: ", null_name, " < ", rounder_stat(null_vals[1], digits = 3), + " or > ", rounder_stat(null_vals[2], digits = 3)) + } else if (htest$alternative == "minimal.effect") { + line3 <- paste0("null: ", rounder_stat(null_vals[1], digits = 3), + " < ", null_name, " < ", rounder_stat(null_vals[2], digits = 3)) + } else { + # Fallback for other cases with two bounds + line3 <- paste0("null: ", null_name, " in [", + rounder_stat(null_vals[1], digits = 3), ", ", + rounder_stat(null_vals[2], digits = 3), "]") + } + } + } else if (!is.null(htest$null.value)) { + # No alternative specified, just show null value + null_name <- names(htest$null.value) + if (is.null(null_name) || length(null_name) == 0) { + null_name <- estimate_name + } + if (length(htest$null.value) == 1) { + line3 <- paste0("null: ", null_name, " = ", + rounder_stat(unname(htest$null.value), digits = 3)) + } + } + + # Combine lines + if (!is.null(line3)) { + subtitle_text <- paste(line1, line2, line3, sep = "\n") + } else { + subtitle_text <- paste(line1, line2, sep = "\n") + } title_text <- htest$method } else { subtitle_text <- NULL diff --git a/R/methods.TOSTt.R b/R/methods.TOSTt.R index a5783a58..214d0977 100644 --- a/R/methods.TOSTt.R +++ b/R/methods.TOSTt.R @@ -662,23 +662,14 @@ plot.TOSTt <- function(x, warning("Multiple CI lines provided; only first element will be used.") } - if("Equilvalence" %in% x$hypothesis){ - METhyp = TRUE - } else { - METhyp = FALSE - } - points = data.frame( - type = x_label, - mu = c(x$effsize$estimate[1]), - param = c(round(unname(x$TOST$df[1]), 0)), - sigma = c(x$TOST$SE[1]), - lambda = c(0), - est = c(x$effsize$estimate[1]), - low = c(x$eqb$low_eq[1]), - high = c(x$eqb$high_eq[1]), - alpha = c(x$alpha), - stringsAsFactors = FALSE - ) + # Determine if this is equivalence or MET hypothesis + is_equivalence = grepl("Equivalence", x$hypothesis, ignore.case = TRUE) + + # Common parameters + se = x$TOST$SE[1] + df_t = round(unname(x$TOST$df[1]), 0) + + # Data for point estimate and CI points = data.frame( x_label = x_label, point = x$effsize$estimate[1], @@ -686,53 +677,88 @@ plot.TOSTt <- function(x, ci_low = x$effsize$upper.ci[1], stringsAsFactors = FALSE ) + points_l = data.frame( - mu = c(x$eqb$low_eq[1]), - param = c(round(unname(x$TOST$df[1]), 0)), - sigma = c(x$TOST$SE[1]), + mu = c(low_eqt), + param = c(df_t), + sigma = c(se), lambda = c(0), stringsAsFactors = FALSE ) points_u = data.frame( - mu = c(x$eqb$high_eq[1]), - param = c(round(unname(x$TOST$df[1]), 0)), - sigma = c(x$TOST$SE[1]), + mu = c(high_eqt), + param = c(df_t), + sigma = c(se), lambda = c(0), stringsAsFactors = FALSE ) - x_l = c(low_eqt - qnorm(1-x$alpha)*points_l$sigma, - low_eqt + qnorm(1-x$alpha)*points_l$sigma) - x_u = c(high_eqt - qnorm(1-x$alpha)*points_l$sigma, - high_eqt + qnorm(1-x$alpha)*points_l$sigma) + # Calculate one-sided critical values + # For equivalence: lower bound uses right tail (greater), upper bound uses left tail (less) + # For MET: lower bound uses left tail (less), upper bound uses right tail (greater) + if (is_equivalence) { + crit_l_right = low_eqt + qnorm(1 - x$alpha) * se + crit_u_left = high_eqt - qnorm(1 - x$alpha) * se + } else { + crit_l_left = low_eqt - qnorm(1 - x$alpha) * se + crit_u_right = high_eqt + qnorm(1 - x$alpha) * se + } + # Build plot with one-sided rejection regions t_plot = ggplot(data = points, - aes_string(y = 0)) + - stat_dist_slab(data = points_l, - aes(fill = stat(x < x_l[1] | x > x_l[2]), - dist = dist_student_t( - mu = mu, - df = param, - sigma = sigma, - ncp = lambda - )), - alpha = .5, - # fill = NA, - slab_color = "black", - slab_size = .5) + - stat_dist_slab(data = points_u, - aes(fill = stat(x < x_u[1] | x > x_u[2]), - dist = dist_student_t( - mu = mu, - df = param, - sigma = sigma, - ncp = lambda - )), - - alpha = .5, - # fill = NA, - slab_color = "black", - slab_size = .5) + + aes_string(y = 0)) + + if (is_equivalence) { + t_plot = t_plot + + stat_dist_slab(data = points_l, + aes(fill = after_stat(x > crit_l_right), + dist = dist_student_t( + mu = mu, + df = param, + sigma = sigma, + ncp = lambda + )), + alpha = .5, + slab_color = "black", + slab_size = .5) + + stat_dist_slab(data = points_u, + aes(fill = after_stat(x < crit_u_left), + dist = dist_student_t( + mu = mu, + df = param, + sigma = sigma, + ncp = lambda + )), + alpha = .5, + slab_color = "black", + slab_size = .5) + } else { + t_plot = t_plot + + stat_dist_slab(data = points_l, + aes(fill = after_stat(x < crit_l_left), + dist = dist_student_t( + mu = mu, + df = param, + sigma = sigma, + ncp = lambda + )), + alpha = .5, + slab_color = "black", + slab_size = .5) + + stat_dist_slab(data = points_u, + aes(fill = after_stat(x > crit_u_right), + dist = dist_student_t( + mu = mu, + df = param, + sigma = sigma, + ncp = lambda + )), + alpha = .5, + slab_color = "black", + slab_size = .5) + } + + t_plot = t_plot + geom_point(data = data.frame(y = -.1, x = points$point), aes(x = x, y = y), @@ -742,15 +768,16 @@ plot.TOSTt <- function(x, xend = points$ci_high, y = -.1, yend = -.1, size = 1.5, - colour = "black")+ - # set palettes need true false + colour = "black") + scale_fill_manual(values = c("gray85", "green")) + geom_vline(aes(xintercept = low_eqt), linetype = "dashed") + geom_vline(aes(xintercept = high_eqt), linetype = "dashed") + - facet_wrap( ~ x_label) + - labs(caption = "Note: green indicates rejection region for null equivalence and MET hypotheses")+ + facet_wrap(~ x_label) + + labs(caption = paste0("Note: green indicates one-sided rejection region (", + ifelse(is_equivalence, "equivalence", "minimal effect"), + " test)")) + theme_tidybayes() + theme( legend.position = "none", @@ -764,14 +791,15 @@ plot.TOSTt <- function(x, axis.text.x = element_text(face = "bold", size = 11), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), - panel.background = element_rect(fill = "transparent",colour = NA), - plot.background = element_rect(fill = "transparent",colour = NA), - legend.background = element_rect(fill = "transparent",colour = NA) + panel.background = element_rect(fill = "transparent", colour = NA), + plot.background = element_rect(fill = "transparent", colour = NA), + legend.background = element_rect(fill = "transparent", colour = NA) ) + scale_x_continuous(sec.axis = dup_axis(breaks = c( round(low_eqt, round_t), round(high_eqt, round_t) ))) + return(t_plot) } diff --git a/man/plot_htest_est.Rd b/man/plot_htest_est.Rd index 21c9e352..c0650f6e 100644 --- a/man/plot_htest_est.Rd +++ b/man/plot_htest_est.Rd @@ -14,7 +14,8 @@ or TOSTER functions converted with \code{as_htest()}.} \item{alpha}{Significance level for determining the confidence level label.} \item{describe}{Logical. If TRUE (default), includes a concise statistical description -in the plot subtitle showing the test statistic, p-value, estimate, and confidence interval.} +in the plot subtitle showing the test statistic, p-value, estimate, confidence interval, +and the null hypothesis.} } \value{ A \code{ggplot} object that can be further customized using ggplot2 functions. @@ -41,8 +42,13 @@ a single meaningful estimate with its confidence interval. If the 'htest' object contains equivalence bounds (two values in \code{null.value}), both bounds are displayed as dashed vertical lines. -When \code{describe = TRUE}, the plot includes a subtitle with the test statistic, p-value, -and estimate with confidence interval. The method name appears as the plot title. +When \code{describe = TRUE}, the plot includes a three-line subtitle: +\enumerate{ +\item Test statistic and p-value +\item Point estimate and confidence interval +\item Null hypothesis statement +} +The method name appears as the plot title. } \examples{ # Standard t-test From 036b2aad7c43e0d8c2371a7935110951b5f41ed7 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 27 Jan 2026 10:07:22 -0600 Subject: [PATCH 07/12] Update R-CMD-check.yaml Should fix Check error. fingers crossed. --- .github/workflows/R-CMD-check.yaml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 7be9b5d4..a010cb10 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -8,6 +8,8 @@ on: name: R-CMD-check +permissions: read-all + jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} @@ -18,18 +20,16 @@ jobs: fail-fast: false matrix: config: - - {os: macOS-latest, r: 'release'} + - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - #- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - #- {os: ubuntu-latest, r: 'oldrel-1'} + - {os: ubuntu-latest, r: 'release'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 @@ -41,6 +41,10 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: rcmdcheck + cache-version: 2 + extra-packages: any::rcmdcheck + needs: check - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true From 76e0bffd152515fcbe9d539a478aeb067c18e7d6 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 27 Jan 2026 10:29:44 -0600 Subject: [PATCH 08/12] Update R/htest_helpers.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/htest_helpers.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/htest_helpers.R b/R/htest_helpers.R index e243a41a..a55ba8d4 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -485,8 +485,7 @@ plot_htest_est <- function(htest, alpha = NULL, describe = TRUE) { if (!inherits(htest, "htest")) { - stop("Input must be an object of class -'htest'") + stop("Input must be an object of class 'htest'") } if (is.null(htest$estimate)) { From bf8f3e4e8b16d833f30807b1daca53da39ede709 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 27 Jan 2026 10:30:46 -0600 Subject: [PATCH 09/12] Update R/htest_helpers.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/htest_helpers.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/htest_helpers.R b/R/htest_helpers.R index a55ba8d4..1449ab7b 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -517,8 +517,10 @@ plot_htest_est <- function(htest, alpha = NULL, describe = TRUE) { conf_level <- attr(htest$conf.int, "conf.level") if (is.null(conf_level)) { - conf_level <- 0.95 - if (is.null(alpha)) { + if (!is.null(alpha)) { + conf_level <- 1 - alpha + } else { + conf_level <- 0.95 message("No confidence level found in htest object. Defaulting to 95%.") } } From d50e942d16f016027cb1e87f30f04d4a0f2bbe1a Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 27 Jan 2026 10:30:54 -0600 Subject: [PATCH 10/12] Update R/htest_helpers.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/htest_helpers.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/htest_helpers.R b/R/htest_helpers.R index 1449ab7b..0680e86c 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -483,7 +483,6 @@ printable_pval = function(pval, #' @export plot_htest_est <- function(htest, alpha = NULL, describe = TRUE) { - if (!inherits(htest, "htest")) { stop("Input must be an object of class 'htest'") } From 89851ce7d27826820306420d5a15000981664106 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 27 Jan 2026 10:31:00 -0600 Subject: [PATCH 11/12] Update R/htest_helpers.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/htest_helpers.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/htest_helpers.R b/R/htest_helpers.R index 0680e86c..4c8ecf11 100644 --- a/R/htest_helpers.R +++ b/R/htest_helpers.R @@ -404,7 +404,6 @@ rounder_stat = function(number, printable_pval = function(pval, digits = 3){ - cutoff = 1*10^(-1*digits) if(pval < cutoff){ pval = paste0("p < ",cutoff) From 27351157aa9ad9da97350e438f94c8591006abdc Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 27 Jan 2026 10:31:18 -0600 Subject: [PATCH 12/12] Update R/methods.TOSTt.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/methods.TOSTt.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/methods.TOSTt.R b/R/methods.TOSTt.R index 214d0977..0f63a797 100644 --- a/R/methods.TOSTt.R +++ b/R/methods.TOSTt.R @@ -706,7 +706,7 @@ plot.TOSTt <- function(x, # Build plot with one-sided rejection regions t_plot = ggplot(data = points, - aes_string(y = 0)) + aes(y = 0)) if (is_equivalence) { t_plot = t_plot +