Nothing
# @file Plots.R
#
# Copyright 2022 Observational Health Data Sciences and Informatics
#
# This file is part of EmpiricalCalibration
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#' Create a forest plot
#'
#' @description
#' \code{plotForest} creates a forest plot of effect size estimates.
#'
#' @details
#' Creates a forest plot of effect size estimates (ratios). Estimates that are significantly different
#' from 1 (alpha = 0.05) are marked in orange, others are marked in blue.
#'
#'
#' @param logRr A numeric vector of effect estimates on the log scale
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the standard
#' error = (log(<lower bound 95 percent confidence interval>) - log(<effect
#' estimate>))/qnorm(0.025)
#' @param names A vector containing the names of the drugs or outcomes
#' @param xLabel The label on the x-axis: the name of the effect estimate
#' @param title Optional: the main title for the plot
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'. See the
#' function \code{ggsave} in the ggplot2 package for supported file formats.
#'
#' @examples
#' data(sccs)
#' negatives <- sccs[sccs$groundTruth == 0, ]
#' plotForest(negatives$logRr, negatives$seLogRr, negatives$drugName)
#'
#' @export
plotForest <- function(logRr, seLogRr, names, xLabel = "Relative risk", title, fileName = NULL) {
breaks <- c(0.25, 0.5, 1, 2, 4, 6, 8, 10)
theme <- ggplot2::element_text(colour = "#000000", size = 6)
themeRA <- ggplot2::element_text(colour = "#000000", size = 5, hjust = 1)
col <- c(rgb(0, 0, 0.8, alpha = 1), rgb(0.8, 0.4, 0, alpha = 1))
colFill <- c(rgb(0, 0, 1, alpha = 0.5), rgb(1, 0.4, 0, alpha = 0.5))
data <- data.frame(
DRUG_NAME = as.factor(names),
logRr = logRr,
logLb95Rr = logRr + qnorm(0.025) *
seLogRr, logUb95Rr = logRr + qnorm(0.975) * seLogRr
)
data$significant <- data$logLb95Rr > 0 | data$logUb95Rr < 0
data$DRUG_NAME <- factor(data$DRUG_NAME, levels = rev(levels(data$DRUG_NAME)))
plot <- ggplot2::ggplot(
data,
ggplot2::aes(
x = .data$DRUG_NAME,
y = exp(.data$logRr),
ymin = exp(.data$logLb95Rr),
ymax = exp(.data$logUb95Rr),
colour = .data$significant,
fill = .data$significant
)
) +
ggplot2::geom_hline(yintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.2) +
ggplot2::geom_hline(yintercept = 1, size = 0.5) +
ggplot2::geom_pointrange(shape = 23) +
ggplot2::scale_colour_manual(values = col) +
ggplot2::scale_fill_manual(values = colFill) +
ggplot2::coord_flip(ylim = c(0.25, 10)) +
ggplot2::scale_y_continuous(xLabel, trans = "log10", breaks = breaks, labels = breaks) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_line(colour = "#EEEEEE"),
axis.ticks = ggplot2::element_blank(),
axis.title.y = ggplot2::element_blank(),
axis.title.x = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
plot.title = ggplot2::element_text(hjust = 0.5),
legend.key = ggplot2::element_blank(),
strip.text.x = theme,
strip.background = ggplot2::element_blank(),
legend.position = "none"
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 5, height = 2.5 + length(logRr) * 0.8, dpi = 400)
}
return(plot)
}
logRrtoSE <- function(logRr, alpha, mu, sigma) {
phi <- (mu - logRr)^2 / qnorm(alpha / 2)^2 - sigma^2
phi[phi < 0] <- 0
se <- sqrt(phi)
return(se)
}
#' Plot the effect of the calibration
#'
#' @description
#' \code{plotCalibrationEffect} creates a plot showing the effect of the calibration.
#'
#' @details
#' Creates a plot with the effect estimate on the x-axis and the standard error on the y-axis.
#' Negative controls are shown as blue dots, positive controls as yellow diamonds. The area below the
#' dashed line indicated estimates with p < 0.05. The orange area indicates estimates with calibrated
#' p < 0.05.
#'
#' @param logRrNegatives A numeric vector of effect estimates of the negative controls on the log
#' scale.
#' @param seLogRrNegatives The standard error of the log of the effect estimates of the negative
#' controls.
#' @param logRrPositives Optional: A numeric vector of effect estimates of the positive controls on the log
#' scale.
#' @param seLogRrPositives Optional: The standard error of the log of the effect estimates of the positive
#' controls.
#' @param null An object representing the fitted null distribution as created by the
#' \code{fitNull} or \code{fitMcmcNull} functions. If not provided, a null
#' will be fitted before plotting.
#' @param alpha The alpha for the hypothesis test.
#' @param xLabel The label on the x-axis: the name of the effect estimate.
#' @param title Optional: the main title for the plot
#' @param showCis Show 95 percent credible intervals for the calibrated p = alpha boundary.
#' @param showExpectedAbsoluteSystematicError Show the expected absolute systematic error. If \code{null} is of
#' type \code{mcmcNull} the 95 percent credible interval will also be shown.
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'.
#' See the function \code{ggsave} in the ggplot2 package for supported file
#' formats.
#' @param xLimits Vector of length 2 for limits of the plot x axis - defaults to 0.25, 10
#' @param yLimits Vector of length 2 for size limits of the y axis - defaults to 0, 1.5
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' data(sccs)
#' negatives <- sccs[sccs$groundTruth == 0, ]
#' positive <- sccs[sccs$groundTruth == 1, ]
#' plotCalibrationEffect(negatives$logRr, negatives$seLogRr, positive$logRr, positive$seLogRr)
#'
#' @export
plotCalibrationEffect <- function(logRrNegatives,
seLogRrNegatives,
logRrPositives = NULL,
seLogRrPositives = NULL,
null = NULL,
alpha = 0.05,
xLabel = "Relative risk",
title,
showCis = FALSE,
showExpectedAbsoluteSystematicError = FALSE,
fileName = NULL,
xLimits = c(0.25, 10),
yLimits = c(0, 1.5)) {
if (is.null(null)) {
if (showCis) {
null <- fitMcmcNull(logRrNegatives, seLogRrNegatives)
} else {
null <- fitNull(logRrNegatives, seLogRrNegatives)
}
}
if (showCis && is(null, "null")) {
stop("Cannot show credible intervals when using asymptotic null. Please use 'fitMcmcNull' to fit the null")
}
if (!is.vector(xLimits) | !length(xLimits) >= 2) {
stop("xLimits must be a vector of length 2")
}
if (!is.vector(yLimits) | !length(yLimits) >= 2) {
stop("yLimits must be a vector of length 2")
}
x <- exp(seq(log(xLimits[1]), log(xLimits[2]), by = 0.01))
if (is(null, "null")) {
y <- logRrtoSE(log(x), alpha, null[1], null[2])
} else {
chain <- attr(null, "mcmc")$chain
matrix <- apply(chain, 1, function(null) logRrtoSE(log(x), alpha, null[1], 1 / sqrt(null[2])))
ys <- apply(matrix, 1, function(se) quantile(se, c(0.025, 0.50, 0.975), na.rm = TRUE))
rm(matrix)
y <- ys[2, ]
yLb <- ys[1, ]
yUb <- ys[3, ]
}
seTheoretical <- sapply(x, FUN = function(x) {
abs(log(x)) / qnorm(1 - alpha / 2)
})
breaks <- c(0.25, 0.5, 1, 2 * (1:ceiling(xLimits[2] / 2.0)))
if (xLimits[1] < 1) {
breaks <- c(0.125, breaks)
}
checkWithinLimits(yLimits, c(seLogRrNegatives, seLogRrPositives), "yLimits")
checkWithinLimits(log(xLimits), c(logRrNegatives, logRrPositives), "xLimits")
theme <- ggplot2::element_text(colour = "#000000", size = 12)
themeRA <- ggplot2::element_text(colour = "#000000", size = 12, hjust = 1)
plot <- ggplot2::ggplot(
data.frame(x, y, seTheoretical),
ggplot2::aes(x = .data$x, y = .data$y)
) +
ggplot2::geom_vline(xintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.5) +
ggplot2::geom_vline(xintercept = 1, size = 1) +
ggplot2::geom_area(
fill = rgb(1, 0.5, 0, alpha = 0.5),
color = rgb(1, 0.5, 0),
size = 1,
alpha = 0.5
)
if (showCis) {
plot <- plot +
ggplot2::geom_ribbon(ggplot2::aes(
ymin = yLb,
ymax = yUb
), fill = rgb(0.8, 0.2, 0.2), alpha = 0.3) +
ggplot2::geom_line(ggplot2::aes(y = yLb),
colour = rgb(0.8, 0.2, 0.2, alpha = 0.2),
size = 1
) +
ggplot2::geom_line(ggplot2::aes(y = yUb),
colour = rgb(0.8, 0.2, 0.2, alpha = 0.2),
size = 1
)
}
if (showExpectedAbsoluteSystematicError) {
ease <- computeExpectedAbsoluteSystematicError(null)
if (is(null, "null")) {
label <- sprintf("Expected absolute systematic error = %0.2f", ease)
} else {
label <- sprintf(
"Expected absolute systematic error = %0.2f (%0.2f - %0.2f)",
ease$ease,
ease$ciLb,
ease$ciUb
)
}
dummy <- data.frame(text = label)
plot <- plot + ggplot2::geom_label(x = log10(0.26), y = 1.49, hjust = "left", vjust = "top", alpha = 0.9, ggplot2::aes(label = .data$text), data = dummy, size = 3.5)
}
plot <- plot +
ggplot2::geom_area(ggplot2::aes(y = .data$seTheoretical),
fill = rgb(0, 0, 0),
colour = rgb(0, 0, 0, alpha = 0.1),
alpha = 0.1
) +
ggplot2::geom_line(ggplot2::aes(y = .data$seTheoretical),
colour = rgb(0, 0, 0),
linetype = "dashed",
size = 1,
alpha = 0.5
) +
ggplot2::geom_point(
shape = 16,
ggplot2::aes(x, y),
data = data.frame(x = exp(logRrNegatives), y = seLogRrNegatives),
size = 2,
alpha = 0.5,
color = rgb(0, 0, 0.8)
) +
ggplot2::geom_hline(yintercept = 0) +
ggplot2::scale_x_continuous(xLabel,
trans = "log10",
limits = xLimits,
breaks = breaks,
labels = breaks
) +
ggplot2::scale_y_continuous("Standard Error") +
ggplot2::coord_cartesian(ylim = yLimits) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
legend.key = ggplot2::element_blank(),
plot.title = ggplot2::element_text(hjust = 0.5),
strip.text.x = theme,
strip.background = ggplot2::element_blank(),
legend.position = "none"
)
if (!missing(logRrPositives)) {
plot <- plot + ggplot2::geom_point(
shape = 23,
ggplot2::aes(x = .data$x, y = .data$y),
data = data.frame(
x = exp(logRrPositives),
y = seLogRrPositives
),
size = 4,
fill = rgb(1, 1, 0),
alpha = 0.8
)
}
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 6, height = 4.5, dpi = 400)
}
return(plot)
}
checkWithinLimits <- function(limits, values, label) {
values <- values[!is.na(values)]
if (length(values) > 0) {
if (limits[1] > min(values) || limits[2] < max(values)) {
warning(sprintf("Values are outside plotted range. Consider adjusting %s parameter", label))
}
}
}
#' Create a calibration plot
#'
#' @description
#' \code{plotCalibration} creates a plot showing the calibration of our calibration procedure
#'
#' @details
#' Creates a calibration plot showing the number of effects with p < alpha for every level of alpha.
#' The empirical calibration is performed using a leave-one-out design: The p-value of an effect is
#' computed by fitting a null using all other negative controls. Ideally, the calibration line should
#' approximate the diagonal. The plot shows both theoretical (traditional) and empirically calibrated
#' p-values.
#'
#' @param logRr A numeric vector of effect estimates on the log scale
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the
#' standard error = (log(<lower bound 95 percent confidence interval>) -
#' log(<effect estimate>))/qnorm(0.025)
#' @param useMcmc Use MCMC to estimate the calibrated P-value?
#' @param legendPosition Where should the legend be positioned? ("none", "left", "right", "bottom",
#' "top")
#' @param title Optional: the main title for the plot
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'. See
#' the function \code{ggsave} in the ggplot2 package for supported file
#' formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' data(sccs)
#' negatives <- sccs[sccs$groundTruth == 0, ]
#' plotCalibration(negatives$logRr, negatives$seLogRr)
#'
#' @export
plotCalibration <- function(logRr,
seLogRr,
useMcmc = FALSE,
legendPosition = "right",
title,
fileName = NULL) {
if (any(is.infinite(seLogRr))) {
warning("Estimate(s) with infinite standard error detected. Removing before fitting null distribution")
logRr <- logRr[!is.infinite(seLogRr)]
seLogRr <- seLogRr[!is.infinite(seLogRr)]
}
if (any(is.infinite(logRr))) {
warning("Estimate(s) with infinite logRr detected. Removing before fitting null distribution")
seLogRr <- seLogRr[!is.infinite(logRr)]
logRr <- logRr[!is.infinite(logRr)]
}
if (any(is.na(seLogRr))) {
warning("Estimate(s) with NA standard error detected. Removing before fitting null distribution")
logRr <- logRr[!is.na(seLogRr)]
seLogRr <- seLogRr[!is.na(seLogRr)]
}
if (any(is.na(logRr))) {
warning("Estimate(s) with NA logRr detected. Removing before fitting null distribution")
seLogRr <- seLogRr[!is.na(logRr)]
logRr <- logRr[!is.na(logRr)]
}
data <- data.frame(logRr = logRr, SE = seLogRr)
data$Z <- data$logRr / data$SE
data$P <- 2 * pmin(pnorm(data$Z), 1 - pnorm(data$Z)) # 2-sided p-value
data$Y <- sapply(data$P, function(x) {
sum(data$P < x) / nrow(data)
})
data$calibratedP <- vector(length = nrow(data))
for (i in 1:nrow(data)) {
dataLeaveOneOut <- data[seq(1, nrow(data)) != i, ]
if (useMcmc) {
null <- fitMcmcNull(dataLeaveOneOut$logRr, dataLeaveOneOut$SE)
} else {
null <- fitNull(dataLeaveOneOut$logRr, dataLeaveOneOut$SE)
}
data$calibratedP[i] <- calibrateP(null, data$logRr[i], data$SE[i], pValueOnly = TRUE)
}
data <- data[!is.na(data$calibratedP), ]
data$AdjustedY <- sapply(data$calibratedP, function(x) {
sum(data$calibratedP < x) / nrow(data)
})
catData <- data.frame(
x = c(data$P, data$calibratedP),
y = c(data$Y, data$AdjustedY),
label = factor(c(
rep("Theoretical", times = nrow(data)),
rep("Empirical", times = nrow(data))
))
)
catData$label <- factor(catData$label, levels = c("Empirical", "Theoretical"))
names(catData) <- c("x", "y", "P-value calculation")
breaks <- c(0, 0.25, 0.5, 0.75, 1)
theme <- ggplot2::element_text(colour = "#000000", size = 10)
themeRA <- ggplot2::element_text(colour = "#000000", size = 10, hjust = 1)
plot <- ggplot2::ggplot(
catData,
ggplot2::aes(
x = .data$x,
y = .data$y,
colour = .data$`P-value calculation`,
linetype = .data$`P-value calculation`
)
) +
ggplot2::geom_vline(
xintercept = breaks,
colour = "#AAAAAA",
lty = 1,
size = 0.3
) +
ggplot2::geom_vline(xintercept = 0.05, colour = "#888888", linetype = "dashed", size = 1) +
ggplot2::geom_hline(yintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.3) +
ggplot2::geom_abline(colour = "#AAAAAA", lty = 1, size = 0.3) +
ggplot2::geom_step(direction = "hv", size = 1) +
ggplot2::scale_colour_manual(values = c(rgb(0, 0, 0), rgb(0, 0, 0), rgb(0.5, 0.5, 0.5))) +
ggplot2::scale_linetype_manual(values = c("solid", "twodash")) +
ggplot2::scale_x_continuous(expression(alpha), limits = c(0, 1), breaks = c(breaks, 0.05), labels = c("", ".25", ".50", ".75", "1", ".05")) +
ggplot2::scale_y_continuous(expression(paste("Fraction with p < ", alpha)), limits = c(0, 1), breaks = breaks, labels = c("0", ".25", ".50", ".75", "1")) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_blank(),
plot.title = ggplot2::element_text(hjust = 0.5),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
strip.text.x = theme,
strip.background = ggplot2::element_blank(),
legend.position = legendPosition
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 6, height = 4.5, dpi = 400)
}
return(plot)
}
#' Create a confidence interval calibration plot
#'
#' @description
#' \code{plotCalibration} creates a plot showing the calibration of our confidence interval
#' calibration procedure
#'
#' @details
#' Creates a calibration plot showing the fraction of effects within the confidence interval. The
#' empirical calibration is performed using a leave-one-out design: The confidence interval of an
#' effect is computed by fitting a null using all other controls. Ideally, the calibration line should
#' approximate the diagonal. The plot shows the coverage for both theoretical (traditional) and
#' empirically calibrated confidence intervals.
#'
#' @param logRr A numeric vector of effect estimates on the log scale.
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the
#' standard error = (log(<lower bound 95 percent confidence interval>) -
#' log(<effect estimate>))/qnorm(0.025).
#' @param trueLogRr The true log relative risk.
#' @param strata Variable used to stratify the plot. Set \code{strata = NULL} for no
#' stratification.
#' @param legacy If true, a legacy error model will be fitted, meaning standard
#' deviation is linear on the log scale. If false, standard deviation
#' is assumed to be simply linear.
#' @param evaluation A data frame as generated by the \code{\link{evaluateCiCalibration}}
#' function. If provided, the logRr, seLogRr, trueLogRr, strata, and legacy
#' arguments will be ignored.
#' @param crossValidationGroup What should be the unit for the cross-validation? By default the unit
#' is a single control, but a different grouping can be provided, for
#' example linking a negative control to synthetic positive controls
#' derived from that negative control.
#' @param legendPosition Where should the legend be positioned? ("none", "left", "right",
#' "bottom", "top").
#' @param title Optional: the main title for the plot
#' @param fileName Name of the file where the plot should be saved, for example
#' 'plot.png'. See the function \code{ggsave} in the ggplot2 package for
#' supported file formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' \dontrun{
#' data <- simulateControls(n = 50 * 3, mean = 0.25, sd = 0.25, trueLogRr = log(c(1, 2, 4)))
#' plotCiCalibration(data$logRr, data$seLogRr, data$trueLogRr)
#' }
#' @export
plotCiCalibration <- function(logRr,
seLogRr,
trueLogRr,
strata = as.factor(trueLogRr),
crossValidationGroup = 1:length(logRr),
legacy = FALSE,
evaluation,
legendPosition = "top",
title,
fileName = NULL) {
if (missing(evaluation) || is.null(evaluation)) {
evaluation <- evaluateCiCalibration(
logRr = logRr,
seLogRr = seLogRr,
trueLogRr = trueLogRr,
strata = strata,
crossValidationGroup = crossValidationGroup,
legacy = legacy
)
}
vizData <- evaluation[evaluation$label == "Within confidence interval", ]
breaks <- c(0, 0.25, 0.5, 0.75, 1)
theme <- ggplot2::element_text(colour = "#000000", size = 10)
themeRA <- ggplot2::element_text(colour = "#000000", size = 10, hjust = 1)
plot <- ggplot2::ggplot(
vizData,
ggplot2::aes(
x = .data$ciWidth,
y = .data$coverage,
colour = .data$`Confidence interval calculation`,
linetype = .data$`Confidence interval calculation`
)
) +
ggplot2::geom_vline(
xintercept = breaks,
colour = "#AAAAAA",
lty = 1,
size = 0.3
) +
ggplot2::geom_vline(xintercept = 0.95, colour = "#888888", linetype = "dashed", size = 1) +
ggplot2::geom_hline(yintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.3) +
ggplot2::geom_abline(colour = "#AAAAAA", lty = 1, size = 0.3) +
ggplot2::geom_line(size = 1) +
ggplot2::scale_colour_manual(values = c(rgb(0, 0, 0), rgb(0, 0, 0), rgb(0.5, 0.5, 0.5))) +
ggplot2::scale_linetype_manual(values = c("solid", "twodash")) +
ggplot2::scale_x_continuous("Width of CI", limits = c(0, 1), breaks = c(breaks, 0.95), labels = c("0", ".25", ".50", ".75", "", ".95")) +
ggplot2::scale_y_continuous("Coverage", limits = c(0, 1), breaks = breaks, labels = c("0", ".25", ".50", ".75", "1")) +
ggplot2::facet_grid(. ~ trueRr) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
plot.title = ggplot2::element_text(hjust = 0.5),
strip.text.x = theme,
strip.background = ggplot2::element_blank(),
legend.position = legendPosition
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
width <- 1 + 2 * length(levels(evaluation$trueRr))
ggplot2::ggsave(fileName, plot, width = width, height = 3.5, dpi = 400)
}
return(plot)
}
#' Plot true and observed values
#'
#' @description
#' Plot true and observed values, for example from a simulation study.
#'
#' @details
#' Creates a forest plot of effect size estimates (ratios). Estimates that are significantly different
#' from the true value (alpha = 0.05) are marked in orange, others are marked in blue.
#'
#' @param logRr A numeric vector of effect estimates on the log scale.
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the standard
#' error = (log(<lower bound 95 percent confidence interval>) - log(<effect
#' estimate>))/qnorm(0.025).
#' @param trueLogRr A vector of the true effect sizes.
#' @param xLabel The label on the x-axis: the name of the effect estimate.
#' @param title Optional: the main title for the plot
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'. See the
#' function \code{ggsave} in the ggplot2 package for supported file formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' data <- simulateControls(n = 50 * 3, mean = 0.25, sd = 0.25, trueLogRr = log(c(1, 2, 4)))
#' plotTrueAndObserved(data$logRr, data$seLogRr, data$trueLogRr)
#'
#' @export
plotTrueAndObserved <- function(logRr,
seLogRr,
trueLogRr,
xLabel = "Relative risk",
title,
fileName = NULL) {
breaks <- c(0.25, 0.5, 1, 2, 4, 6, 8, 10)
theme <- ggplot2::element_text(colour = "#000000", size = 6)
col <- c(rgb(0, 0, 0.8, alpha = 1), rgb(0.8, 0.4, 0, alpha = 1))
colFill <- c(rgb(0, 0, 1, alpha = 0.5), rgb(1, 0.4, 0, alpha = 0.5))
data <- data.frame(
logRr = logRr,
logLb95Rr = logRr + qnorm(0.025) * seLogRr,
logUb95Rr = logRr + qnorm(0.975) * seLogRr,
trueLogRr = trueLogRr,
trueRr = round(exp(trueLogRr), 2)
)
data$significant <- data$logLb95Rr > data$trueLogRr | data$logUb95Rr < data$trueLogRr
data <- data[order(data$trueLogRr, data$logRr), ]
data$order <- 1:nrow(data)
coverage <- aggregate(!significant ~ trueRr, data = data, mean)
names(coverage)[2] <- "coverage"
plot <- ggplot2::ggplot(
data,
ggplot2::aes(
x = exp(.data$logRr),
y = .data$order,
xmin = exp(.data$logLb95Rr),
xmax = exp(.data$logUb95Rr),
colour = .data$significant,
fill = .data$significant
)
) +
ggplot2::geom_vline(xintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.2) +
ggplot2::geom_errorbarh(ggplot2::aes(xmax = .data$trueRr, xmin = .data$trueRr), height = 1, color = rgb(0, 0, 0), size = 1) +
ggplot2::geom_errorbarh(height = 0) +
ggplot2::geom_point(shape = 21, size = 1.5) +
ggplot2::scale_colour_manual(values = col) +
ggplot2::scale_fill_manual(values = colFill) +
ggplot2::coord_cartesian(xlim = c(0.25, 10)) +
ggplot2::scale_x_continuous(xLabel, trans = "log10", breaks = breaks, labels = breaks) +
ggplot2::facet_grid(trueRr ~ ., scales = "free_y", space = "free") +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_line(colour = "#EEEEEE"),
axis.ticks = ggplot2::element_blank(),
axis.title.y = ggplot2::element_blank(),
axis.title.x = ggplot2::element_blank(),
axis.text.y = ggplot2::element_blank(),
plot.title = ggplot2::element_text(hjust = 0.5),
axis.text.x = theme, legend.key = ggplot2::element_blank(),
strip.text.y = ggplot2::element_blank(),
strip.background = ggplot2::element_blank(),
legend.position = "none"
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 5, height = 7, dpi = 400)
}
return(plot)
}
#' Plot the MCMC trace
#'
#' @details
#' Plot the trace of the MCMC for diagnostics purposes.
#'
#' @param mcmcNull An object of type \code{mcmcNull} as generated using the \code{fitMcmcNull}
#' function.
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'. See the
#' function \code{ggsave} in the ggplot2 package for supported file formats.
#'
#' @examples
#' \dontrun{
#' data(sccs)
#' negatives <- sccs[sccs$groundTruth == 0, ]
#' null <- fitMcmcNull(negatives$logRr, negatives$seLogRr)
#' plotMcmcTrace(null)
#' }
#' @export
plotMcmcTrace <- function(mcmcNull, fileName = NULL) {
mcmc <- attr(mcmcNull, "mcmc")
dataMean <- data.frame(x = 1:nrow(mcmc$chain), trace = as.numeric(ts(mcmc$chain[
,
1
])), var = "Mean")
dataPrecision <- data.frame(x = 1:nrow(mcmc$chain), trace = as.numeric(ts(mcmc$chain[
,
2
])), var = "Precision")
data <- rbind(dataMean, dataPrecision)
plot <- ggplot2::ggplot(data, ggplot2::aes(x = .data$x, y = .data$trace)) +
ggplot2::geom_line(alpha = 0.7) +
ggplot2::scale_x_continuous("Iterations") +
ggplot2::facet_grid(var ~ ., scales = "free") +
ggplot2::theme(axis.title.y = ggplot2::element_blank())
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 5, height = 3.5, dpi = 400)
}
return(plot)
}
#' Create a confidence interval coverage plot
#'
#' @description
#' \code{plotCiCoverage} creates a plot showing the coverage before and after confidence interval
#' calibration at various widths of the confidence interval.
#'
#' @details
#' Creates a plot showing the fraction of effects above, within, and below the confidence interval. The
#' empirical calibration is performed using a leave-one-out design: The confidence interval of an
#' effect is computed by fitting a null using all other controls. The plot shows the coverage for
#' both theoretical (traditional) and empirically calibrated confidence intervals.
#'
#' @param logRr A numeric vector of effect estimates on the log scale.
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the
#' standard error = (log(<lower bound 95 percent confidence interval>) -
#' log(<effect estimate>))/qnorm(0.025).
#' @param trueLogRr The true log relative risk.
#' @param strata Variable used to stratify the plot. Set \code{strata = NULL} for no
#' stratification.
#' @param legacy If true, a legacy error model will be fitted, meaning standard
#' deviation is linear on the log scale. If false, standard deviation
#' is assumed to be simply linear.
#' @param evaluation A data frame as generated by the \code{\link{evaluateCiCalibration}}
#' function. If provided, the logRr, seLogRr, trueLogRr, strata, and legacy
#' arguments will be ignored.
#' @param crossValidationGroup What should be the unit for the cross-validation? By default the unit
#' is a single control, but a different grouping can be provided, for
#' example linking a negative control to synthetic positive controls
#' derived from that negative control.
#' @param legendPosition Where should the legend be positioned? ("none", "left", "right",
#' "bottom", "top").
#' @param title Optional: the main title for the plot
#' @param fileName Name of the file where the plot should be saved, for example
#' 'plot.png'. See the function \code{ggsave} in the ggplot2 package for
#' supported file formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' \dontrun{
#' data <- simulateControls(n = 50 * 3, mean = 0.25, sd = 0.25, trueLogRr = log(c(1, 2, 4)))
#' plotCiCoverage(data$logRr, data$seLogRr, data$trueLogRr)
#' }
#' @export
plotCiCoverage <- function(logRr,
seLogRr,
trueLogRr,
strata = as.factor(trueLogRr),
crossValidationGroup = 1:length(logRr),
legacy = FALSE,
evaluation,
legendPosition = "top",
title,
fileName = NULL) {
if (missing(evaluation) || is.null(evaluation)) {
evaluation <- evaluateCiCalibration(
logRr = logRr,
seLogRr = seLogRr,
trueLogRr = trueLogRr,
strata = strata,
crossValidationGroup = crossValidationGroup,
legacy = legacy
)
}
evaluation$label <- factor(evaluation$label, levels = rev(c("Below confidence interval", "Within confidence interval", "Above confidence interval")))
evaluation$`Confidence interval calculation` <- factor(evaluation$`Confidence interval calculation`, levels = rev(c("Calibrated", "Uncalibrated")))
evaluation$trueRr <- paste0("True effect size = ", evaluation$trueRr)
breaks <- c(0, 0.25, 0.5, 0.75, 1)
theme <- ggplot2::element_text(colour = "#000000", size = 12)
themeRA <- ggplot2::element_text(colour = "#000000", size = 12, hjust = 1)
plot <- ggplot2::ggplot(
evaluation,
ggplot2::aes(
x = .data$ciWidth,
y = .data$coverage,
colour = .data$label,
fill = .data$label,
group = .data$label
)
) +
ggplot2::geom_vline(
xintercept = breaks,
colour = "#AAAAAA",
lty = 1,
size = 0.3
) +
ggplot2::geom_hline(yintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.3) +
ggplot2::geom_area(position = "stack", alpha = 0.6) +
ggplot2::geom_abline(colour = "#000000", size = 1, intercept = 0.5, slope = 0.5, linetype = "dashed") +
ggplot2::geom_abline(colour = "#000000", size = 1, intercept = 0.5, slope = -0.5, linetype = "dashed") +
ggplot2::scale_colour_manual(values = c(rgb(0.8, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0.8))) +
ggplot2::scale_fill_manual(values = c(rgb(0.8, 0, 0), rgb(0.75, 0.75, 0.75), rgb(0, 0, 0.8))) +
ggplot2::scale_x_continuous("Width of the confidence interval", limits = c(0, 1), breaks = breaks, labels = c("0", ".25", ".50", ".75", "1")) +
ggplot2::scale_y_continuous("Fraction", limits = c(0, 1), breaks = breaks, labels = c("0", ".25", ".50", ".75", "1")) +
ggplot2::facet_grid(`Confidence interval calculation` ~ trueRr) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_blank(),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
plot.title = ggplot2::element_text(hjust = 0.5),
strip.text.x = theme,
strip.text.y = theme,
strip.background = ggplot2::element_blank(),
legend.position = legendPosition,
legend.title = ggplot2::element_blank(),
legend.text = theme
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
width <- 1 + 1.8 * length(levels(as.factor(evaluation$trueRr)))
ggplot2::ggsave(fileName, plot, width = width, height = 5, dpi = 400)
}
return(plot)
}
#' Plot the systematic error model
#'
#' @description
#' \code{plotErrorModel} creates a plot showing the systematic error model.
#'
#' @details
#' Creates a plot with the true effect size on the x-axis, and the mean plus and minus the standard
#' deviation shown on the y-axis. Also shown are simple error models fitted at each true relative
#' risk in the input.
#'
#' @param logRr A numeric vector of effect estimates on the log scale.
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the
#' standard error = (log(<lower bound 95 percent confidence interval>) -
#' log(<effect estimate>))/qnorm(0.025).
#' @param trueLogRr The true log relative risk.
#' @param legacy If true, a legacy error model will be fitted, meaning standard
#' deviation is linear on the log scale. If false, standard deviation
#' is assumed to be simply linear.
#' @param title Optional: the main title for the plot
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'.
#' See the function \code{ggsave} in the ggplot2 package for supported file
#' formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' data <- simulateControls(n = 50 * 3, mean = 0.25, sd = 0.25, trueLogRr = log(c(1, 2, 4)))
#' plotErrorModel(data$logRr, data$seLogRr, data$trueLogRr)
#' @export
plotErrorModel <- function(logRr, seLogRr, trueLogRr, title, legacy = FALSE, fileName = NULL) {
model <- fitSystematicErrorModel(
logRr = logRr,
seLogRr = seLogRr,
trueLogRr = trueLogRr,
legacy = legacy
)
fitSimpleModel <- function(selectedTrueLogRr) {
idx <- trueLogRr == selectedTrueLogRr
return(fitNull(logRr[idx], seLogRr[idx]))
}
simpleModels <- as.data.frame(t(sapply(unique(trueLogRr), fitSimpleModel)))
simpleModels$trueRr <- exp(unique(trueLogRr))
simpleModels$y <- exp(simpleModels$mean)
simpleModels$ymin <- exp(simpleModels$mean - simpleModels$sd)
simpleModels$ymax <- exp(simpleModels$mean + simpleModels$sd)
breaks <- c(0.25, 0.5, 1, 2, 4, 6, 8, 10)
x <- exp(seq(log(0.25), log(10), by = 0.01))
y <- exp(model[1] + model[2] * log(x))
if (legacy) {
ymin <- exp(log(y) - exp(model[3] + model[4] * log(x)))
ymax <- exp(log(y) + exp(model[3] + model[4] * log(x)))
} else {
ymin <- exp(log(y) - (model[3] + model[4] * abs(log(x))))
ymax <- exp(log(y) + (model[3] + model[4] * abs(log(x))))
}
data <- data.frame(x = x, y = y, ymin = ymin, ymax = ymax)
plot <- ggplot2::ggplot(
data,
ggplot2::aes(
x = .data$x,
y = .data$y,
ymin = .data$ymin,
ymax = .data$ymax
)
) +
ggplot2::geom_vline(xintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.2) +
ggplot2::geom_hline(yintercept = breaks, colour = "#AAAAAA", lty = 1, size = 0.2) +
ggplot2::geom_vline(xintercept = 1, size = 1) +
ggplot2::geom_hline(yintercept = 1, size = 1) +
ggplot2::geom_ribbon(fill = rgb(0, 0, 0.8), alpha = 0.3) +
ggplot2::geom_line(color = rgb(0, 0, 0.8)) +
ggplot2::geom_errorbar(ggplot2::aes(x = .data$trueRr), width = 0.1, size = 1, color = rgb(0, 0, 0.8), data = simpleModels) +
ggplot2::scale_x_continuous("True effect size",
trans = "log10",
breaks = breaks,
labels = breaks
) +
ggplot2::scale_y_continuous("Systematic error mean (plus and minus one SD)",
trans = "log10",
breaks = breaks,
labels = breaks
) +
ggplot2::coord_cartesian(xlim = c(0.25, 10), ylim = c(0.25, 10)) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
plot.title = ggplot2::element_text(hjust = 0.5),
legend.position = "none"
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 6, height = 4.5, dpi = 400)
}
return(plot)
}
plotIsobars <- function(null, alpha, xLabel = "Relative risk", seLogRrPositives) {
if (is(null, "mcmcNull")) {
null <- c(null[1], 1 / sqrt(null[2]))
}
x <- exp(seq(log(0.25), log(10), by = 0.01))
y <- sapply(x, FUN = function(x) abs(log(x)) / qnorm(1 - alpha / 2))
thresholds <- c(0.05, 0.25, 0.5, 0.75)
isoBars <- lapply(thresholds, function(threshold) {
data.frame(
x = x,
y = logRrtoSE(log(x), threshold, null[1], null[2]),
ymax = y,
threshold = threshold
)
})
breaks <- c(0.25, 0.5, 1, 2, 4, 6, 8, 10)
theme <- ggplot2::element_text(colour = "#000000", size = 12)
themeRA <- ggplot2::element_text(colour = "#000000", size = 12, hjust = 1)
plot <- ggplot2::ggplot(
data.frame(x, y),
ggplot2::aes(x = .data$x, y = .data$y)
) +
ggplot2::geom_vline(xintercept = breaks, colour = rgb(0, 0, 0), lty = 1, size = 0.5, alpha = 0.2) +
ggplot2::geom_hline(yintercept = 0:4 / 4, colour = rgb(0, 0, 0), lty = 1, size = 0.5, alpha = 0.2) +
ggplot2::geom_vline(xintercept = 1, size = 1) +
ggplot2::geom_line(
colour = rgb(0, 0, 0),
linetype = "dashed",
size = 1,
alpha = 0.5
) +
ggplot2::scale_x_continuous(xLabel,
trans = "log10",
breaks = breaks,
labels = breaks
) +
ggplot2::scale_y_continuous("Standard Error") +
ggplot2::coord_cartesian(xlim = c(0.25, 10), ylim = c(0, 1)) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
legend.key = ggplot2::element_blank(),
strip.text.x = theme,
strip.background = ggplot2::element_blank(),
legend.position = "none"
)
for (isoBar in isoBars) {
isoBarLeft <- isoBar[isoBar$x < exp(null[1]), ]
isoBarRight <- isoBar[isoBar$x > exp(null[1]), ]
labelData <- data.frame(
x = c(
isoBarLeft$x[which.min(abs(isoBarLeft$y - 0.625))],
isoBarRight$x[which.min(abs(isoBarRight$y - 0.625))]
),
y = 0.625,
label = paste0(100 * isoBar$threshold[1], "%")
)
plot <- plot + ggplot2::geom_ribbon(ggplot2::aes(
ymin = .data$y,
ymax = .data$ymax
),
fill = rgb(1, 0.5, 0, alpha = 0.2),
data = isoBar[isoBar$y < isoBar$ymax, ]
) +
ggplot2::geom_line(
color = rgb(1, 0.5, 0),
size = 1,
alpha = 0.5,
data = isoBar
) +
ggplot2::geom_label(ggplot2::aes(label = .data$label),
color = rgb(1, 0.5, 0),
data = labelData
) +
ggplot2::geom_text(ggplot2::aes(label = .data$label),
data = labelData
)
}
if (!missing(seLogRrPositives)) {
plot <- plot + ggplot2::geom_hline(yintercept = seLogRrPositives)
}
return(plot)
}
#' Plot the expected type 1 error as a function of standard error
#'
#' @description
#' \code{plotExpectedType1Error} creates a plot showing the expected type 1 error as a function of standard error.
#'
#' @details
#' Creates a plot with the standard error on the x-axis and the expected type 1 error on the y-axis. The
#' red line indicates the expected type 1 error given the estimated empirical null distribution if no
#' calibration is performed. The dashed line indicated the nominal expected type 1 error rate, assuming
#' the theoretical null distribution.
#'
#' If standard errors are provided for non-negative estimates these will be plotted on the red line as
#' yellow diamonds.
#'
#' @param logRrNegatives A numeric vector of effect estimates of the negative controls on the log
#' scale.
#' @param seLogRrNegatives The standard error of the log of the effect estimates of the negative
#' controls.
#' @param seLogRrPositives The standard error of the log of the effect estimates of the positive
#' controls.
#' @param alpha The alpha (nominal type 1 error) to be used.
#' @param null An object representing the fitted null distribution as created by the
#' \code{fitNull} function. If not provided, a null will be fitted before
#' plotting.
#' @param xLabel If showing effect sizes, what label should be used for the effect size axis?
#' @param title Optional: the main title for the plot
#' @param showCis Show 95 percent credible intervals for the expected type 1 error.
#' @param showEffectSizes Show the expected effect sizes alongside the expected type 1 error?
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'.
#' See the function \code{ggsave} in the ggplot2 package for supported file
#' formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' data(sccs)
#' negatives <- sccs[sccs$groundTruth == 0, ]
#' positive <- sccs[sccs$groundTruth == 1, ]
#' plotExpectedType1Error(negatives$logRr, negatives$seLogRr, positive$seLogRr)
#'
#' @export
plotExpectedType1Error <- function(logRrNegatives,
seLogRrNegatives,
seLogRrPositives,
alpha = 0.05,
null = NULL,
xLabel = "Relative risk",
title,
showCis = FALSE,
showEffectSizes = FALSE,
fileName = NULL) {
if (is.null(null)) {
if (showCis) {
null <- fitMcmcNull(logRrNegatives, seLogRrNegatives)
} else {
null <- fitNull(logRrNegatives, seLogRrNegatives)
}
}
if (showCis && is(null, "null")) {
stop("Cannot show credible intervals when using asymptotic null. Please use 'fitMcmcNull' to fit the null")
}
se <- (0:100) / 100
if (is(null, "null")) {
mean <- null[1]
sd <- sqrt(se^2 + null[2]^2)
type1Error <- pnorm(qnorm(1 - alpha / 2, 0, se), mean, sd, lower.tail = FALSE) +
pnorm(qnorm(alpha / 2, 0, se), mean, sd, lower.tail = TRUE)
} else {
computeExpected <- function(se, chain) {
mean <- chain[, 1]
sd <- sqrt(se^2 + (1 / sqrt(chain[, 2]))^2)
type1Error <- pnorm(qnorm(1 - alpha / 2, 0, se), mean, sd, lower.tail = FALSE) +
pnorm(qnorm(alpha / 2, 0, se), mean, sd, lower.tail = TRUE)
return(quantile(type1Error, c(0.025, 0.5, 0.975)))
}
mcmc <- attr(null, "mcmc")
estimates <- sapply(se, computeExpected, chain = mcmc$chain)
type1Error <- estimates[2, ]
lb <- estimates[1, ]
ub <- estimates[3, ]
}
breaks <- 0:4 / 4
theme <- ggplot2::element_text(colour = "#000000", size = 12)
themeRA <- ggplot2::element_text(colour = "#000000", size = 12, hjust = 1)
plot <- ggplot2::ggplot(
data.frame(se, type1Error),
ggplot2::aes(x = .data$se, y = .data$type1Error)
) +
ggplot2::geom_vline(xintercept = breaks, colour = rgb(0, 0, 0), lty = 1, size = 0.5, alpha = 0.2) +
ggplot2::geom_hline(yintercept = breaks, colour = rgb(0, 0, 0), lty = 1, size = 0.5, alpha = 0.2) +
ggplot2::geom_hline(
yintercept = alpha,
colour = rgb(0, 0, 0),
linetype = "dashed",
size = 1,
alpha = 0.5
) +
ggplot2::geom_line(
color = rgb(0.8, 0, 0),
size = 1,
alpha = 0.5
)
if (showCis) {
plot <- plot +
ggplot2::geom_ribbon(ggplot2::aes(
ymin = lb,
ymax = ub
), fill = rgb(0.8, 0.2, 0.2), alpha = 0.3) +
ggplot2::geom_line(ggplot2::aes(y = lb),
colour = rgb(0.8, 0.2, 0.2, alpha = 0.2),
size = 1
) +
ggplot2::geom_line(ggplot2::aes(y = ub),
colour = rgb(0.8, 0.2, 0.2, alpha = 0.2),
size = 1
)
}
plot <- plot + ggplot2::geom_label(
label = paste("alpha ==", alpha),
data = data.frame(
se = 0.875 + showEffectSizes * 0.125,
type1Error = alpha + showEffectSizes * 0.04
),
parse = TRUE
) +
ggplot2::scale_x_continuous("Standard error",
limits = c(0, 1),
breaks = breaks,
labels = breaks
) +
ggplot2::scale_y_continuous("Expected type 1 error",
limits = c(0, 1),
breaks = breaks,
labels = breaks
) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_rect(fill = "#FAFAFA", colour = NA),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
legend.key = ggplot2::element_blank(),
plot.title = ggplot2::element_text(hjust = 0.5),
strip.text.x = theme,
strip.background = ggplot2::element_blank(),
legend.position = "none"
)
if (!missing(seLogRrPositives)) {
yPos <- sapply(seLogRrPositives, FUN = function(x) {
type1Error[which.min(abs(x - se))]
})
posData <- data.frame(
x = seLogRrPositives,
y = yPos
)
plot <- plot + ggplot2::geom_vline(ggplot2::aes(xintercept = .data$x),
data = posData
) +
ggplot2::geom_point(
shape = 23,
ggplot2::aes(x = .data$x, y = .data$y),
data = posData,
size = 4,
fill = rgb(1, 1, 0),
alpha = 0.8
)
}
if (showEffectSizes) {
plot <- plot + ggplot2::coord_flip()
plot <- plot + ggplot2::theme(
axis.text.y = ggplot2::element_blank(),
axis.title.y = ggplot2::element_blank()
)
plot2 <- plotIsobars(null = null, alpha = alpha, xLabel = xLabel, seLogRrPositives = seLogRrPositives)
plots <- list(plot2, plot)
grobs <- heights <- list()
for (i in 1:length(plots)) {
grobs[[i]] <- ggplot2::ggplotGrob(plots[[i]])
heights[[i]] <- grobs[[i]]$heights[6:9]
}
maxHeight <- do.call(grid::unit.pmax, heights)
for (i in 1:length(grobs)) {
grobs[[i]]$heights[6:9] <- as.list(maxHeight)
}
if (missing(title)) {
title <- NULL
}
plot <- gridExtra::grid.arrange(grobs[[1]], grobs[[2]], nrow = 1, ncol = 2, widths = c(2, 1), top = title)
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 10, height = 5, dpi = 400)
}
} else {
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 5, height = 5, dpi = 400)
}
}
return(plot)
}
#' Plot the effect of the CI calibration
#'
#' @description
#' Creates a plot with the effect estimate on the x-axis and the standard error on the y-axis. The plot
#' is trellised by true effect size. Negative and positive controls are shown as blue dots. The area below the
#' dashed line indicated estimates that are statistically significant different from the true effect size (p < 0.05).
#' The orange area indicates estimates with calibrated p < 0.05.
#'
#' @param logRr A numeric vector of effect estimates on the log scale.
#' @param seLogRr The standard error of the log of the effect estimates. Hint: often the standard
#' error = (log(<lower bound 95 percent confidence interval>) - log(<effect
#' estimate>))/qnorm(0.025).
#' @param trueLogRr A vector of the true effect sizes.
#' @param legacy If true, a legacy error model will be fitted, meaning standard
#' deviation is linear on the log scale. If false, standard deviation
#' is assumed to be simply linear.
#' @param model The fitted systematic error model. If not provided, it will be fitted on the
#' provided data.
#' @param xLabel The label on the x-axis: the name of the effect estimate.
#' @param title Optional: the main title for the plot
#' @param fileName Name of the file where the plot should be saved, for example 'plot.png'. See the
#' function \code{ggsave} in the ggplot2 package for supported file formats.
#'
#' @return
#' A Ggplot object. Use the \code{ggsave} function to save to file.
#'
#' @examples
#' data <- simulateControls(n = 50 * 3, mean = 0.25, sd = 0.25, trueLogRr = log(c(1, 2, 4)))
#' plotCiCalibrationEffect(data$logRr, data$seLogRr, data$trueLogRr)
#'
#' @export
plotCiCalibrationEffect <- function(logRr,
seLogRr,
trueLogRr,
legacy = FALSE,
model = NULL,
xLabel = "Relative risk",
title,
fileName = NULL) {
alpha <- 0.05
if (is.null(model)) {
model <- fitSystematicErrorModel(
logRr = logRr,
seLogRr = seLogRr,
trueLogRr = trueLogRr,
estimateCovarianceMatrix = FALSE,
legacy = legacy
)
} else {
legacy <- (names(model)[3] == "logSdIntercept")
}
d <- data.frame(
logRr = logRr,
seLogRr = seLogRr,
trueLogRr = trueLogRr,
trueRr = exp(trueLogRr),
logCi95lb = logRr + qnorm(0.025) * seLogRr,
logCi95ub = logRr + qnorm(0.975) * seLogRr
)
d <- d[!is.na(d$logRr), ]
d <- d[!is.na(d$seLogRr), ]
if (nrow(d) == 0) {
return(NULL)
}
d$Group <- as.factor(d$trueRr)
d$Significant <- d$logCi95lb > d$trueLogRr | d$logCi95ub < d$trueLogRr
temp1 <- aggregate(Significant ~ trueRr, data = d, length)
temp2 <- aggregate(Significant ~ trueRr, data = d, mean)
temp1$nLabel <- paste0(formatC(temp1$Significant, big.mark = ","), " estimates")
temp1$Significant <- NULL
temp2$meanLabel <- paste0(
formatC(100 * (1 - temp2$Significant), digits = 1, format = "f"),
"% of CIs includes ",
temp2$trueRr
)
temp2$Significant <- NULL
dd <- merge(temp1, temp2)
breaks <- c(0.1, 0.25, 0.5, 1, 2, 4, 6, 8, 10)
theme <- ggplot2::element_text(colour = "#000000", size = 10)
themeRA <- ggplot2::element_text(colour = "#000000", size = 10, hjust = 1)
d$Group <- paste("True", tolower(xLabel), "=", d$trueRr)
dd$Group <- paste("True", tolower(xLabel), "=", dd$trueRr)
x <- seq(log(0.1), log(10), by = 0.01)
calBounds <- data.frame()
for (i in 1:nrow(dd)) {
mu <- model[1] + model[2] * log(dd$trueRr[i])
if (legacy) {
sigma <- exp(model[3] + model[4] * log(dd$trueRr[i]))
} else {
sigma <- model[3] + model[4] * abs(log(dd$trueRr[i]))
}
calBounds <- rbind(
calBounds,
data.frame(
logRr = x,
seLogRr = logRrtoSE(x, alpha, mu, sigma),
Group = dd$Group[i]
)
)
}
plot <- ggplot2::ggplot(d, ggplot2::aes(x = .data$logRr, y = .data$seLogRr)) +
ggplot2::geom_vline(xintercept = log(breaks), colour = "#AAAAAA", lty = 1, size = 0.5) +
ggplot2::geom_area(
fill = rgb(1, 0.5, 0, alpha = 0.5),
color = rgb(1, 0.5, 0),
size = 1,
alpha = 0.5, data = calBounds
) +
ggplot2::geom_abline(ggplot2::aes(intercept = (-log(.data$trueRr)) / qnorm(0.025), slope = 1 / qnorm(0.025)), colour = rgb(0, 0, 0), linetype = "dashed", size = 1, alpha = 0.5, data = dd) +
ggplot2::geom_abline(ggplot2::aes(intercept = (-log(.data$trueRr)) / qnorm(0.975), slope = 1 / qnorm(0.975)), colour = rgb(0, 0, 0), linetype = "dashed", size = 1, alpha = 0.5, data = dd) +
ggplot2::geom_point(
shape = 16,
size = 2,
alpha = 0.5,
color = rgb(0, 0, 0.8)
) +
ggplot2::geom_hline(yintercept = 0) +
ggplot2::geom_label(x = log(0.15), y = 0.95, alpha = 1, hjust = "left", ggplot2::aes(label = .data$nLabel), size = 3.5, data = dd) +
ggplot2::geom_label(x = log(0.15), y = 0.8, alpha = 1, hjust = "left", ggplot2::aes(label = .data$meanLabel), size = 3.5, data = dd) +
ggplot2::scale_x_continuous(xLabel, limits = log(c(0.1, 10)), breaks = log(breaks), labels = breaks) +
ggplot2::scale_y_continuous("Standard Error") +
ggplot2::coord_cartesian(ylim = c(0, 1)) +
ggplot2::facet_grid(. ~ Group) +
ggplot2::theme(
panel.grid.minor = ggplot2::element_blank(),
panel.background = ggplot2::element_blank(),
panel.grid.major = ggplot2::element_blank(),
axis.ticks = ggplot2::element_blank(),
axis.text.y = themeRA,
axis.text.x = theme,
axis.title = theme,
legend.key = ggplot2::element_blank(),
plot.title = ggplot2::element_text(hjust = 0.5),
strip.text.x = theme,
strip.text.y = theme,
strip.background = ggplot2::element_blank(),
legend.position = "none"
)
if (!missing(title)) {
plot <- plot + ggplot2::ggtitle(title)
}
if (!is.null(fileName)) {
ggplot2::ggsave(fileName, plot, width = 2 + 3.5 * nrow(dd), height = 2.8, dpi = 400)
}
return(plot)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.