diff --git a/R/plotBounds.R b/R/plotBounds.R index 4e956771..4b5e9794 100644 --- a/R/plotBounds.R +++ b/R/plotBounds.R @@ -27,9 +27,18 @@ #' @example examples/plotBounds.R #' @export #' @keywords graphics -plotBounds <- function(z, area = TRUE, grid = TRUE, yt = "x", add = FALSE, - cols = c("green", "red", "darkgreen", "orange"), - lwds = c(3, 3), ltype = "l", lpch = 16, lcex = 1, gy = 20) { +plotBounds <- function( + z, + area = TRUE, + grid = TRUE, + yt = "x", + add = FALSE, + cols = c("green", "red", "darkgreen", "orange"), + lwds = c(3, 3), + ltype = "l", + lpch = 16, + lcex = 1, + gy = 20) { n <- nrow(z) nmin <- min(z$looks) nmax <- max(z$looks) @@ -51,40 +60,71 @@ plotBounds <- function(z, area = TRUE, grid = TRUE, yt = "x", add = FALSE, stop("yt can only be x or p") } if (add) { - graphics::lines(z$looks, z2, - lwd = lwds[1], col = cols[3], type = ltype, - pch = lpch, cex = lcex + graphics::lines( + z$looks, + z2, + lwd = lwds[1], + col = cols[3], + type = ltype, + pch = lpch, + cex = lcex ) - graphics::lines(z$looks, z1, - lwd = lwds[2], col = cols[4], type = ltype, - pch = lpch, cex = lcex + graphics::lines( + z$looks, + z1, + lwd = lwds[2], + col = cols[4], + type = ltype, + pch = lpch, + cex = lcex ) return(invisible()) } - graphics::plot(z$looks, rep(0, n), - xlim = c(0, max(z$looks)), ylim = c(0, yU), type = "n", - xlab = "n", ylab = ylabel + graphics::plot( + z$looks, + rep(0, n), + xlim = c(0, max(z$looks)), + ylim = c(0, yU), + type = "n", + xlab = "n", + ylab = ylabel ) if (grid) { graphics::abline(h = gridy, col = "gray") } if (area) { - graphics::polygon(c(z$looks, nmax, nmin), c(z2, yU, yU2), + graphics::polygon( + c(z$looks, nmax, nmin), + c(z2, yU, yU2), lwd = lwds[1], - col = cols[1], border = cols[1] + col = cols[1], + border = cols[1] ) - graphics::polygon(c(z$looks, nmax, nmin), c(z1, 0, 0), + graphics::polygon( + c(z$looks, nmax, nmin), + c(z1, 0, 0), lwd = lwds[2], - col = cols[2], border = cols[2] + col = cols[2], + border = cols[2] ) } else { - graphics::lines(z$looks, z2, - lwd = lwds[1], col = cols[1], type = ltype, - pch = lpch, cex = lcex + graphics::lines( + z$looks, + z2, + lwd = lwds[1], + col = cols[1], + type = ltype, + pch = lpch, + cex = lcex ) - graphics::lines(z$looks, z1, - lwd = lwds[2], col = cols[2], type = ltype, - pch = lpch, cex = lcex + graphics::lines( + z$looks, + z1, + lwd = lwds[2], + col = cols[2], + type = ltype, + pch = lpch, + cex = lcex ) } return(invisible()) diff --git a/design/design_doc_plotBounds.qmd b/design/design_doc_plotBounds.qmd new file mode 100644 index 00000000..6cc2a0eb --- /dev/null +++ b/design/design_doc_plotBounds.qmd @@ -0,0 +1,315 @@ +--- +title: "Design Doc for plotBounds" +format: revealjs +editor: visual +--- + +Statisticians can display boundaries of Go, Stop and GreyZone decisions graphically in terms of response rates required by desired samples sizes or looks. This is the purpose of the `plotBounds` function. Coordinates are the points where the polygon should be drawn and reflect the possible values for a go and stop decision respectively. + + Note that for the predictive probability case, decision 1 rules are chosen. See `ocPredprob` for rules. This design document aims to refactor the previous function, in particular to use `ggplot2` and reduce the length of the function. + +# Packages that aid + +```{r} +library(flextable) +library(ggplot2) +``` + +# Vintage function + +```{r} +vintage_plotBounds <- function( + z, + area = TRUE, + grid = TRUE, + yt = "x", + add = FALSE, + cols = c("#009E73", "#FF0046", "#7F55B1", "#F0A04B"), + lwds = c(3, 3), + ltype = "l", + lpch = 16, + lcex = 1, + gy = 20) { + n <- nrow(z) + nmin <- min(z$looks) + nmax <- max(z$looks) + if (yt == "x") { + z1 <- z$xL + z2 <- z$xU + yU <- nmax + yU2 <- nmin + ylabel <- "Number of Responses" + gridy <- seq(0, yU, by = floor(yU / gy)) + } else if (yt == "p") { + z1 <- z$pL + z2 <- z$pU + yU <- 1 + yU2 <- 1 + ylabel <- "Response Rate" + gridy <- seq(0, yU, by = yU / gy) + } else { + stop("yt can only be x or p") + } + if (add) { + graphics::lines( + z$looks, + z2, + lwd = lwds[1], + col = cols[3], + type = ltype, + pch = lpch, + cex = lcex + ) + graphics::lines( + z$looks, + z1, + lwd = lwds[2], + col = cols[4], + type = ltype, + pch = lpch, + cex = lcex + ) + return(invisible()) + } + graphics::plot( + z$looks, + rep(0, n), + xlim = c(0, max(z$looks)), + ylim = c(0, yU), + type = "n", + xlab = "n", + ylab = ylabel + ) + if (grid) { + graphics::abline(h = gridy, col = "gray") + } + if (area) { + graphics::polygon( + c(z$looks, nmax, nmin), + c(z2, yU, yU2), + lwd = lwds[1], + col = cols[1], + border = cols[1] + ) + graphics::polygon( + c(z$looks, nmax, nmin), + c(z1, 0, 0), + lwd = lwds[2], + col = cols[2], + border = cols[2] + ) + } else { + graphics::lines( + z$looks, + z2, + lwd = lwds[1], + col = cols[1], + type = ltype, + pch = lpch, + cex = lcex + ) + graphics::lines( + z$looks, + z1, + lwd = lwds[2], + col = cols[2], + type = ltype, + pch = lpch, + cex = lcex + ) + } + return(invisible()) +} +``` + +# Vintage example that should work with new function + +```{r} +plotBounds( + boundsPostprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tL = 0.10, + tU = 0.90, + parE = c(1, 1) + ), + yt = "p", + add = TRUE +) +``` + +# Vintage function output + +```{r} +vintage_plotBounds( + boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, + ), + yt = "x" +) +``` + +# New call function + +There are two good options to graph polygons to reflect the purpose of this function. The use of base r's `polygon()` is slightly more laborious; once minimum and maximum coordinates are established, approximation of these points are then required to draw the borders of this polygon. This step can be removed using the `geom_ribbon` call from the package `ggplot2`. Furthermore, the base r approach require the initial plot area to be created, and axes, axes labels, and points to be overlayed. There are additional steps that are also taken in the `ggplot2` approach, however done within a code chunk rather than separated calls. While it can be argued that these additional calls from the base r approach would anyway be encapsulated in one function, that is `plotbound`, the number of code lines is substantially reduced using the `ggplot` approach. + +```{r} +new_plotBounds <- function(coords, + go_colour = "#1B9E77", + stop_colour = "#E41A1C", + alpha = 1) { + assert_data_frame(coords, any.missing = FALSE, types = "numeric") + assert_character(go_colour) + assert_character(stop_colour) + assert_number(alpha, lower = 0, upper = 1) + + go_data <- data.frame( + n = coords$looks, + ymin_go = coords$xU, + ymax_go = coords$looks + ) + stop_data <- data.frame( + n = coords$looks, + ymin_stop = c(rep(0, length(coords$looks))), + ymax_stop = coords$xL + ) + plot <- ggplot2::ggplot() + + ggplot2::geom_ribbon( + data = stop_data, + ggplot2::aes(x = n, ymin = ymin_stop, ymax = ymax_stop), + fill = stop_colour, alpha = alpha + ) + + ggplot2::geom_ribbon( + data = go_data, + ggplot2::aes(x = n, ymin = ymin_go, ymax = ymax_go), + fill = go_colour, alpha = alpha + ) + + ggplot2::scale_x_continuous( + name = "n", + breaks = go_data$n + ) + + ggplot2::scale_y_continuous( + breaks = seq(from = 0, to = max(go_data$n), by = 2) + ) + + ggplot2::geom_point( + data = go_data, + ggplot2::aes(x = n, y = ymin_go), + size = 3, + shape = 16, + colour = "#16C47F", + ) + + ggplot2::geom_point( + data = stop_data, + ggplot2::aes(x = n, y = ymax_stop), + size = 3, + shape = 16, + colour = "#FF748B", # "#DC3C22",FF748B + ) + + ggplot2::xlab("Sample Size (n)") + + ggplot2::ylab("Number of Responders") + + ggplot2::theme_minimal() + + plot + ggplot2::ggtitle( + "Boundaries of Go and Stop Responders per sample size" + ) +} +``` + +# creating plot using new function with output from `boundsPredprob()` + +The following output show that important coordinates needed as input to create these plots. + +As a premise, we establish the criteria: + +Interim +Go : P(success) > 90 % +Stop : P(success) > 10 % + +Final +Go : P(RR > 0.20 | data) > 80 % +Stop : P(RR > 0.20 | data) < 10 % + +The criteria for interim analyses are from predictive probability calculation. + +The criteria for final analyses are from posterior probability calculation. + +which translates to boundaries : + +- When n = 10, 4 responders and above is a Go, 0 responders and below is a stop. +- When n = 20, 7 responders and above is a Go, 2 responders and below is a stop +- When n = 30, 9 responders and above is a Go, 5 responders and below is a stop +- When n = 40, 10 responders and above is a Go, 9 responders and below is a stop + +```{r} +coords <- boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, +) +print(coords) +``` + + +```{r} +coords <- boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, +) +new_plotBounds( + coords = coords, + go_colour = "blue", + alpha = 1 +) +``` + +# creating plots with new function with output from `boundsPostprob()` + +The following output show which inputs from a posterior probability design are needed to create these plots. + +As a premise, we establish the criteria: +Go : P(RR > 0.20 | data) > 90 % +Stop : P(RR > 0.20 | data) > 10 % +which translates to boundaries : + +- When n = 10, 4 responders and above is a Go, 3 responders and below is a stop. +- When n = 20, 7 responders and above is a Go, 6 responders and below is a stop +- When n = 30, 9 responders and above is a Go, 8 responders and below is a stop +- When n = 40, 12 responders and above is a Go, 11 responders and below is a stop + + +```{r} +coords1 <- boundsPostprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tL = 0.10, + tU = 0.90, + parE = c(1, 1) +) +new_plotBounds(coords1) +``` + ++ggplot2 vs base R ++https://r-graphics.org/CHAPTER-GGPLOT2.html + + + + + + + + + + + + + + diff --git a/examples/plotBounds.R b/examples/plotBounds.R index c39fc05b..4c84dbd5 100644 --- a/examples/plotBounds.R +++ b/examples/plotBounds.R @@ -1,16 +1,32 @@ # examples plotBounds( boundsPostprob( - looks = c(10, 20, 30, 40), p0 = 0.20, - tL = 0.10, tU = 0.90, parE = c(1, 1) + looks = c(10, 20, 30, 40), + p0 = 0.20, + tL = 0.10, + tU = 0.90, + parE = c(1, 1) ), - yt = "p", add = TRUE + yt = "p", + add = TRUE +) +plotBounds( + boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, + ), + yt = "x" +) +plotBounds( + boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, + ), + yt = "p" ) -plotBounds(boundsPredprob( - looks = c(10, 20, 30, 40), p0 = 0.20, tT = 0.80, - phiL = 0.10, phiU = 0.90, -), yt = "x") -plotBounds(boundsPredprob( - looks = c(10, 20, 30, 40), p0 = 0.20, tT = 0.80, - phiL = 0.10, phiU = 0.90, -), yt = "p") diff --git a/man/plotBounds.Rd b/man/plotBounds.Rd index 0caf508c..5b6ab385 100644 --- a/man/plotBounds.Rd +++ b/man/plotBounds.Rd @@ -58,18 +58,34 @@ and \code{\link{boundsPostprob}} # examples plotBounds( boundsPostprob( - looks = c(10, 20, 30, 40), p0 = 0.20, - tL = 0.10, tU = 0.90, parE = c(1, 1) + looks = c(10, 20, 30, 40), + p0 = 0.20, + tL = 0.10, + tU = 0.90, + parE = c(1, 1) ), - yt = "p", add = TRUE + yt = "p", + add = TRUE +) +plotBounds( + boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, + ), + yt = "x" +) +plotBounds( + boundsPredprob( + looks = c(10, 20, 30, 40), + p0 = 0.20, + tT = 0.80, + phiL = 0.10, + phiU = 0.90, + ), + yt = "p" ) -plotBounds(boundsPredprob( - looks = c(10, 20, 30, 40), p0 = 0.20, tT = 0.80, - phiL = 0.10, phiU = 0.90, -), yt = "x") -plotBounds(boundsPredprob( - looks = c(10, 20, 30, 40), p0 = 0.20, tT = 0.80, - phiL = 0.10, phiU = 0.90, -), yt = "p") } \keyword{graphics}