Bug in function power.TOST() [🇷 for BE/BA]
THX to ElMaestro for detecting a bug in power.TOST(). Say, you dosed N subjects and are interested in post hoc power of a fraction f of this sample size (e.g., of eligible subjects). Calling power.TOST(..., n = f * N) might give a lower than the correct power.Example for
CV = 0.3, N = 90, f = 0.7 (using all the defaults of a 2×2 design):
power.TOST(CV = 0.3, n = 63) # eligible subjects
Unbalanced design. n(i)=32/31 assumed.
[1] 0.9453907 # correct (given the assumption)
power.TOST(CV = 0.3, n = c(32, 31)) # explicit subjects / sequence
[1] 0.9453907 # correct
power.TOST(CV = 0.3, n = 0.7 * 90) # eligible subjects calculated
[1] 0.9424189 # wrong (too low)
power.TOST(CV = 0.3, n = round(0.7 * 90)) # quick fix
Unbalanced design. n(i)=32/31 assumed.
[1] 0.9453907 # correct (given the assumption)
Why is that so? in the vectorization of the total sample size (to get subjects / sequence),
trunc(n) is used. That is correct for n = 63 but not for n = 0.7 * 90.trunc(63); trunc(0.7 * 90)
[1] 63
[1] 62
63; round(0.7 * 90)
[1] 63
[1] 63
PowerTOST and the planned new function:new.fun <- function(n, grps) {
n.int <- as.integer(round(n))
quotient <- n.int %/% grps
remainder <- n.int %% grps
nv <- c(rep.int(quotient + 1L, remainder),
rep.int(quotient, grps - remainder))
return(as.integer(nv))
}
res <- data.frame(Approach =c("Explicit sequences", "Total sample size",
"f * N", "Workaround", "New function"),
N = c(NA, 63, 0.7 * 90, round(0.7 * 90), 0.7 * 90),
n1 = c(32L, rep(NA_integer_, 4)),
n2 = c(31L, rep(NA_integer_, 4)),
n = c(32L + 31L, rep(NA_integer_, 4)), correct = TRUE,
df = c(63L - 2L, rep(NA_integer_, 4)))
for (j in 2:nrow(res)) {
if (j < nrow(res)) {
res[j, 3:4] <- PowerTOST:::nvec(res$N[j], 2)
} else {
res[j, 3:4] <- new.fun(res$N[j], 2)
}
res[j, 5] <- sum(res[j, 3:4])
res$df[j] <- res[j, 5] - 2
if (!res$df[j] == res$df[1]) res$correct[j] <- FALSE
}
print(res, row.names = FALSE, right = FALSE)
Approach N n1 n2 n correct df
Explicit sequences NA 32 31 63 TRUE 61
Total sample size 63 32 31 63 TRUE 61
f * N 63 31 31 62 FALSE 60
Workaround 63 32 31 63 TRUE 61
New function 63 32 31 63 TRUE 61
Not for the first time we are a victim of floating-point arithmetic.
print(63); print(0.7 * 90) # by default 7 significant digits are shown
[1] 63
[1] 63 # they look the same
63 == 0.7 * 90; identical(63, 0.7 * 90) # are they the same?
[1] FALSE
[1] FALSE # obviously not
print(63, digits = 16); print(0.7 * 90, digits = 16); print(round(0.7 * 90), digits = 16)
[1] 63
[1] 62.99999999999999 # gotcha!
[1] 63 # the workaround
63 == round(0.7 * 90); identical(63, round(0.7 * 90))
[1] TRUE
[1] TRUE
For details see the issue at GitHub. Until it is resolved and the library updated, please use
power.TOST(n = round(f * N)).
However, best is to give the subjects / sequence explicitly. The functions of PowerTOST don’t have a crystal ball and can only guess while keeping the sequences as balanced as possible. Only you know the actual values. If the sample size is divisible by the number of sequence, power.TOST() assumes balanced sequences. Quoting the note in the man-page of power.TOST():If n is given as scalar (total sample size) and this number is not divisible by the number of (sequence) groups of the design an unbalanced design with small imbalance is assumed. A corresponding message is thrown showing the assumed numbers of subjects in (sequence) groups.
n <- 40
nseq <- 2
n %% nseq # modulo
[1] 0 # remainder zero, power.TOST() assumes balanced sequences
power.TOST(CV = 0.3, n = n)
[1] 0.8158453 # in good faith
power.TOST(CV = 0.3, n = c(24, 16))
[1] 0.8002523 # actually quite imbalanced, lower power
The internal function
nvec(n, grps) of PowerTOST is called from its following functions:CVfromCI(), power.HVNTID(), power.NTID(), power.RatioF(), power.RSABE(), power.scABEL(), power.TOST(), power.TOST.sds(), power.TOST.sim(), pvalues.TOST(), scABEL.ad().
n (assuming almost balanced sequences), the observed sequences as a vector (preferred), or use the workaround with rounding. Sorry for the inconvenience.Dif-tor heh smusma 🖖🏼 Довге життя Україна!
![[image]](https://static.bebac.at/pics/Blue_and_yellow_ribbon_UA.png)
Helmut Schütz
![[image]](https://static.bebac.at/img/CC by.png)
The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
Complete thread:
- Bug in function power.TOST()Helmut 2025-08-22 10:28 [🇷 for BE/BA]
- Bug in function power.TOST() ElMaestro 2025-08-23 09:46
- Bug in function power.TOST() Helmut 2025-08-23 11:01
- Bug in function power.TOST() fixed d_labes 2025-10-06 09:59
- Bug in function power.TOST() Helmut 2025-08-23 11:01
- Bug in function power.TOST() ElMaestro 2025-08-23 09:46
