---
title: "Package MKpower"
author: "Matthias Kohl"
date: "`r Sys.Date()`"
output:
rmarkdown::html_vignette:
toc: true
bibliography: MKpower.bib
vignette: >
%\VignetteIndexEntry{Package MKpower}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{utf8}
---
## Introduction
The package includes functions for power analysis and sample size calculation
for Welch and Hsu [@Hedderich2018] t tests including Monte-Carlo simulations
of empirical power and type-I-error. In addition, power and sample size calculation
for Wilcoxon rank sum and signed rank tests via Monte-Carlo simulations can
be performed. Moreover, power and sample size required for the evaluation of a
diagnostic test(-system) [@Flahault2005; @Dobbin2007] as well as for a single
proportion [@Fleiss2003], comparing two negative binomial rates [@Zhu2014],
ANCOVA [@Shieh2020], reference ranges [@Jennen2005], and multiple primary
endpoints [@Sozu2015].
We first load the package.
```{r}
library(MKpower)
```
## Single Proportion
The computation of the required sample size for investigating a single proportion
can be determined via the respective test or confidence interval [@Fleiss2003].
First, we consider the asymptotic test.
```{r}
## with continuity correction
power.prop1.test(p1 = 0.4, power = 0.8)
## without continuity correction
power.prop1.test(p1 = 0.4, power = 0.8, cont.corr = FALSE)
```
Next, we compute the sample size via the respective asymptotic confidence interval.
```{r}
## with continuity correction
ssize.prop.ci(prop = 0.4, width = 0.14)
## without continuity correction
ssize.prop.ci(prop = 0.4, width = 0.14, method = "wald")
```
## Welch Two-Sample t-Test
For computing the sample size of the Welch t-test, we only consider the situation
of equal group size (balanced design).
```{r}
## identical results as power.t.test, since sd = sd1 = sd2 = 1
power.welch.t.test(n = 20, delta = 1)
power.welch.t.test(power = .90, delta = 1)
power.welch.t.test(power = .90, delta = 1, alternative = "one.sided")
## sd1 = 0.5, sd2 = 1
power.welch.t.test(delta = 1, sd1 = 0.5, sd2 = 1, power = 0.9)
```
## Hsu Two-Sample t-Test
For computing the sample size of the Hsu t-test [@Hedderich2018], we only consider
the situation of equal group size (balanced design).
```{r}
## slightly more conservative than Welch t-test
power.hsu.t.test(n = 20, delta = 1)
power.hsu.t.test(power = .90, delta = 1)
power.hsu.t.test(power = .90, delta = 1, alternative = "one.sided")
## sd1 = 0.5, sd2 = 1
power.welch.t.test(delta = 0.5, sd1 = 0.5, sd2 = 1, power = 0.9)
power.hsu.t.test(delta = 0.5, sd1 = 0.5, sd2 = 1, power = 0.9)
```
## ANCOVA
With function power.ancova one can compute power and sample size in ANCOVA
designs [@Shieh2020]. The default matrix of contrasts used in the function is
```{r}
## 3 groups
cbind(rep(1,2), -diag(2))
## 4 groups
cbind(rep(1,3), -diag(3))
```
We use the example provided in Table 9.7 of @Maxwell2004.
```{r}
## Example from Maxwell and Delaney (2004) according to Shieh (2020)
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8)
power.ancova(n = rep(45/3, 3), mu=c(7.5366, 11.9849, 13.9785), var = 29.0898)
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.9)
power.ancova(n = rep(57/3, 3), mu=c(7.5366, 11.9849, 13.9785), var = 29.0898)
```
Based on the reported adjusted group means and error variance, a (total) sample size
of 45 is required to achieve a power of at least 80%. The calculated power is
82.2%. With a sample size of 57 the power will be at least 90%, where the
calculated power is 91.2%.
Introducing additional covariates (random covariate model) will increase the required
sample size.
```{r}
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8,
nr.covs = 2)
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8,
nr.covs = 3)
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8,
nr.covs = 5)
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8,
nr.covs = 10)
```
We can also calculate the per group sample sizes for an imbalanced design.
```{r}
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8,
group.ratio = c(1, 1.25, 1.5))
power.ancova(n = c(13, 16, 19), mu=c(7.5366, 11.9849, 13.9785), var = 29.0898,
group.ratio = c(1, 1.25, 1.5))
power.ancova(mu=c(7.5366, 11.9849, 13.9785), var = 29.0898, power = 0.8,
group.ratio = c(1, 0.8, 2/3))
power.ancova(n = c(17, 14, 12), mu=c(7.5366, 11.9849, 13.9785), var = 29.0898,
group.ratio = c(1, 0.8, 2/3))
```
We get a smaller total sample size with an imbalanced design (43 instead of 45).
## Wilcoxon Rank Sum and Signed Rank Tests
For computing the sample size of these tests, we offer a function that
performs Monte-Carlo simulations to determine their (empirical) power.
```{r}
rx <- function(n) rnorm(n, mean = 0, sd = 1)
ry <- function(n) rnorm(n, mean = 0.5, sd = 1)
## two-sample
sim.ssize.wilcox.test(rx = rx, ry = ry, n.max = 100, iter = 1000)
sim.ssize.wilcox.test(rx = rx, ry = ry, n.min = 65, n.max = 70,
step.size = 1, iter = 1000, BREAK = FALSE)
power.t.test(delta = 0.5, power = 0.8)
## one-sample
sim.ssize.wilcox.test(rx = ry, n.max = 100, iter = 1000, type = "one.sample")
sim.ssize.wilcox.test(rx = ry, type = "one.sample", n.min = 33,
n.max = 38, step.size = 1, iter = 1000, BREAK = FALSE)
power.t.test(delta = 0.5, power = 0.8, type = "one.sample")
## two-sample
rx <- function(n) rgamma(n, scale = 2, shape = 1.5)
ry <- function(n) rgamma(n, scale = 4, shape = 1.5) # equal shape
## two-sample
sim.ssize.wilcox.test(rx = rx, ry = ry, n.max = 100, iter = 1000)
sim.ssize.wilcox.test(rx = rx, ry = ry, n.min = 25, n.max = 30,
step.size = 1, iter = 1000, BREAK = FALSE)
```
For practical applications we recommend to use a higher number of iterations.
For more detailed examples we refer to the help page of the function.
## Two Negative Binomial Rates
When we consider two negative binomial rates [@Zhu2014], we can compute sample
size or power applying function power.nb.test.
```{r}
## examples from Table III in Zhu and Lakkis (2014)
power.nb.test(mu0 = 5.0, RR = 2.0, theta = 1/0.5, duration = 1, power = 0.8, approach = 1)
power.nb.test(mu0 = 5.0, RR = 2.0, theta = 1/0.5, duration = 1, power = 0.8, approach = 2)
power.nb.test(mu0 = 5.0, RR = 2.0, theta = 1/0.5, duration = 1, power = 0.8, approach = 3)
```
## Diagnostic Test
Given an expected sensitivity and specificity we can compute sample size,
power (assurance probability), delta or significance level (error probability)
of diagnostic test [@Flahault2005]. The calculation is based on a one-sided
confidence interval.
```{r}
## see n2 on page 1202 of Chu and Cole (2007)
ssize.sens.ci(sens = 0.99, delta = 0.14, power = 0.95) # 40
ssize.sens.ci(sens = 0.99, delta = 0.13, power = 0.95) # 43
ssize.spec.ci(spec = 0.99, delta = 0.12, power = 0.95) # 47
```
Given an expected AUC we can compute sample size, power (assurance probability),
delta or significance level (error probability) of the AUC based on a one-sided
confidence interval.
```{r}
ssize.auc.ci(AUC = 0.9, delta = 0.1, power = 0.95)
```
The sample size planning for developing binary classifiers in case of high
dimensional data, we can apply function ssize.pcc, which is based on the
probability of correct classification (PCC) [@Dobbin2007].
```{r}
## see Table 2 of Dobbin et al. (2008)
g <- 0.1
fc <- 1.6
ssize.pcc(gamma = g, stdFC = fc, nrFeatures = 22000)
```
## Reference Range
We can apply function ssize.reference.range to determine the sample size required
for a study planned to establish a reference range. The parametric approach
assumes a normal distribution whereas the non-parametric approach only assumes
a continuous distribution.
```{r}
ssize.reference.range(delta = 0.01, ref.prob = 0.95, conf.prob = 0.9,
method = "parametric", exact = TRUE)
ssize.reference.range(delta = 0.01, ref.prob = 0.95, conf.prob = 0.9,
method = "parametric", exact = FALSE)
ssize.reference.range(delta = 0.01, ref.prob = 0.95, conf.prob = 0.9,
method = "nonparametric", exact = TRUE)
ssize.reference.range(delta = 0.01, ref.prob = 0.95, conf.prob = 0.9,
method = "nonparametric", exact = FALSE)
```
We can also calculate the sample size for one-sided reference ranges.
```{r}
ssize.reference.range(delta = 0.015, ref.prob = 0.95, conf.prob = 0.9,
method = "parametric", exact = TRUE,
alternative = "one.sided")
```
## Multiple Primary Endpoints (MPE)
We demonstrate how to calculate the sample size for a trial with two co-primary
endpoints with known covariance.
```{r}
Sigma <- matrix(c(1, 0.8, 0.8, 1), nrow = 2)
power.mpe.known.var(K = 2, delta = c(0.25, 0.4), Sigma = Sigma,
sig.level = 0.025, power = 0.8)
## equivalent: specify SDs and correlation rho
power.mpe.known.var(K = 2, delta = c(0.25, 0.4), SD = c(1,1), rho = 0.8,
sig.level = 0.025, power = 0.8)
```
Next, we show how to calculate the sample size for a trial with two
co-primary endpoints with unknown covariance. Here, we follow three steps to
determine the sample size.
+ Step 1: As we need starting values for our algorithm that computes the sample
size in this case, we first act as if the covariance would be known and compute
the sample size by applying our function `power.mpe.known.var`.
+ Step 2: The resulting value of `n` is considered as lower bound for the sample
size in case of unknown covariance and is used as `n.min` in function
`power.mpe.unkown.var`. Moreover, we specify a reasonable `n.max`, which must be
larger than `n.min`.
+ Step 3: Finally, by using the arguments from the step 2, we can compute the
sample size for the situation with unknown covariance.
```{r}
## Step 1:
power.mpe.known.var(K = 2, delta = c(0.5, 0.4), Sigma = Sigma,
sig.level = 0.025, power = 0.8)
## Step 2 + 3:
Sigma <- matrix(c(1, 0.5, 0.5, 1), nrow = 2)
power.mpe.unknown.var(K = 2, delta = c(0.5, 0.4), Sigma = Sigma,
sig.level = 0.025, power = 0.8,
n.min = 105, n.max = 120)
## equivalent: specify SDs and correlation rho
power.mpe.unknown.var(K = 2, delta = c(0.5, 0.4), SD = c(1,1), rho = 0.5,
sig.level = 0.025, power = 0.8,
n.min = 105, n.max = 120)
```
We finally demonstrate how to calculate the sample size for a trial with two primary
endpoints with known covariance, where the trial is designed to find a significant
difference for at least one endpoint.
```{r}
Sigma <- matrix(c(1, 0.3, 0.3, 1), nrow = 2)
power.mpe.atleast.one(K = 2, delta = c(0.2, 0.3), Sigma = Sigma,
power = 0.8, sig.level = 0.025)
## equivalent: specify SDs and correlation rho
power.mpe.atleast.one(K = 2, delta = c(0.2, 0.3), SD = c(1,1), rho = 0.3,
power = 0.8, sig.level = 0.025)
```
## Power and Type-I-Error Simulations
There are quite some discussions and various proposals, which test is the best
under which circumstances when we want to compare the location (mean, median)
of two groups [@Fagerland2009; @Fagerland2012; @Sezer2017]. In addition, it is
questioned whether pre-testing of assumptions is appropriate/useful at least from
a practical point of view [@Zimmerman2004; @Rasch2011; @Rochon2012; @Hoekstra2012].
We provide functions to simulate power and type-I-error of classical [@student1908],
Welch [@Welch1947] and Hsu [@Hsu1938] t-tests as well as of Wilcoxon-Mann-Whitney
tests [@Wilcoxon1945; @Mann1947].
```{r}
## functions to simulate the data
## group 1
rx <- function(n) rnorm(n, mean = 0, sd = 1)
rx.H0 <- rx
## group 2
ry <- function(n) rnorm(n, mean = 3, sd = 3)
ry.H0 <- function(n) rnorm(n, mean = 0, sd = 3)
## theoretical results
power.welch.t.test(n = 10, delta = 3, sd1 = 1, sd2 = 3)
power.hsu.t.test(n = 10, delta = 3, sd1 = 1, sd2 = 3)
## simulation
res.t <- sim.power.t.test(nx = 10, rx = rx, rx.H0 = rx.H0,
ny = 10, ry = ry, ry.H0 = ry.H0)
res.t
res.w <- sim.power.wilcox.test(nx = 10, rx = rx, rx.H0 = rx.H0,
ny = 10, ry = ry, ry.H0 = ry.H0)
res.w
```
For further investigation of the results, we provide some diagnostic plots.
```{r, fig.width=7, fig.height=7}
## t-tests
hist(res.t)
qqunif(res.t, alpha = 0.05)
volcano(res.t, hex = TRUE)
## Wilcoxon-Mann-Whitney tests
hist(res.w)
qqunif(res.w, alpha = 0.05)
```
We can also generate a volcano plot for the Wilcoxon-Mann-Whitney test, but this
would require setting argument conf.int to TRUE, which would
lead to a much higher computation time, hence we omitted it here. Furthermore,
it is also possible to compute an approximate version of the test by setting
argument approximate to TRUE [@Hothorn2008] again by the cost
of an increased computation time.
## sessionInfo
```{r}
sessionInfo()
```
## References