Code
library(ivdtools)Sample size is determined by effect size, variability, confidence level, power, and study design; it is not a fixed constant. This chapter covers the three sample-size problems implemented by ivdtools: Bland–Altman agreement precision/power, precision of a single-proportion confidence interval, and a one-arm test of a proportion against a target.
The functions do not add attrition, invalid samples, minimum numbers per stratum, design effects, clustering, or multiplicity adjustments. delta in a Bland–Altman calculation is the clinically acceptable limit, not the observed LoA. Confidence-interval methods and one- or two-sided directions must be prespecified. Preserve all assumptions and sensitivity analyses, not only the final integer.
library(ivdtools)| Function | Main purpose | Solving direction |
|---|---|---|
sample_size_bland_altman() |
Bland–Altman agreement evaluation | Solve for n from target power |
sample_size_proportion_ci() |
Single-proportion confidence interval | Solve for n from the target half-width |
sample_size_proportion() |
One-arm target-value test | Solve for n from the target alpha and power |
All three functions are parameter-driven and do not require raw data. They return structured results; the sample size is in result$n, and print() shows target and achieved quantities. Round-up, attrition, invalid samples, and minimum stratum counts must be handled separately.
sample_size_bland_altman(
power = 0.80, mu, sd, delta,
conf.level = 0.95, agree.level = 0.95,
method = "lu", n.min = 3, n.max = 1e5
)
sample_size_proportion_ci(
p, half_width, conf.level = 0.95,
interval = c("two.sided", "lower", "upper"),
method = "wilson", n.max = 1e6
)
sample_size_proportion(
p0, p1, alpha = 0.05, power = 0.80,
alternative = c("greater", "less", "two.sided"),
test = "exact", power_method = "binomial", n.max = 1e6
)For Bland–Altman, the target LoA are
\[ \mu\pm z_{(1+\gamma)/2}\sigma, \]
where $γ=agree.level. The function uses the Lu et al. noncentral-t calculation for the probability that both LoA confidence bounds remain within \(±\delta\) and searches for the smallest integer meeting target power. It requires \(δ>|\mu|+z_{(1+γ)/2}σ\).
For a proportion interval, the target is a half-width \(d\). For two-sided intervals,
\[\frac{U-L}{2}\le d,\]
and for one-sided intervals the distance from the expected proportion to the relevant limit is at most \(d\). The interval method affects the required sample size and must be stated in the protocol.
For a one-arm test, test determines the rejection region and power_method chooses continuous normal planning or discrete binomial enumeration. The final integer sample size is checked using actual alpha and achieved power; binomial planning additionally controls actual alpha rather than relying on an approximation.
n_ba <- sample_size_bland_altman(
power = 0.80, mu = 0.2, sd = 1, delta = 2.5
)
print(n_ba)Sample size for Bland-Altman agreement assessment
Method : Lu et al. (2016), noncentral-t approximation
Mean difference : 0.2000
SD of differences : 1.0000
Clinical limit : 2.5000
Confidence level : 0.9500
Agreement level : 0.9500
Sample size (n) : 201
Target power : 0.800000
Achieved power : 0.800300
Under these teaching parameters, approximately 201 pairs are needed for 80% power. mu is mean difference, sd is difference SD, and delta is the clinical agreement limit. conf.level and agree.level describe different layers: reliability of the LoA estimate versus coverage of the LoA itself.
n_pci <- sample_size_proportion_ci(p = 0.3, half_width = 0.05)
print(n_pci)Sample size for a single-proportion confidence interval
Method : wilson
Interval : two.sided
Anticipated proportion: 0.3000
Confidence level : 0.9500
Sample size (n) : 320
Target half-width : 0.050000
Achieved half-width : 0.049967
Target total width : 0.100000
Achieved total width : 0.099934
Planning interval : [0.252406, 0.352339]
Actual coverage : 0.956096
Actual alpha : 0.043904
For expected proportion 0.3 and a 95% Wilson half-width of 0.05, approximately 320 observations are needed. Available methods include Wilson, Wald, continuity-corrected Wald/Wilson, Agresti–Coull, Jeffreys, and Clopper–Pearson.
n_sp <- sample_size_proportion(
p0 = 0.2, p1 = 0.35, alpha = 0.05, power = 0.8,
alternative = "greater", test = "exact", power_method = "binomial"
)
print(n_sp)Sample size for a single-proportion target-value test
Test : Exact binomial test
Power calculation : Binomial enumeration
H0: p <= 0.2000
H1: p > 0.2000
Expected p : 0.3500
Sample size (n) : 56
Target alpha : 0.050000
Actual alpha : 0.043209
Alpha criterion : met
Target power : 0.800000
Achieved power : 0.806416 (binomial evaluation)
Critical successes : >= 17
alternative="greater" requires \(p_1>p_0\); use "less" or "two.sided" as appropriate. Exact, score, continuity-corrected score, Wald, and related test definitions are not interchangeable. Specify both test and power_method in the protocol.
The supplied examples compare the functions with PASS, SAS, and CMDE calculations. Small differences can arise from continuous normal planning versus discrete binomial enumeration, exact quantiles versus rounded handbook quantiles, interval definitions, and integer search rules. For example, a handbook may report a rounded approximate sample size while the package returns the first integer whose actual operating characteristics meet the stated constraint. Report both the selected method and achieved alpha/power.
PASS Example 1 fixes mu = 0.5, delta = 7, and conf.level = agree.level = 0.95, then crosses two power levels with three difference SDs. The comparison below keeps every row, including rows that do not agree, and reports both sample-size and achieved-power differences.
ba_manual <- data.frame(
target_power = c(0.8, 0.8, 0.8, 0.9, 0.9, 0.9),
sd = c(2.5, 2.6, 2.7, 2.5, 2.6, 2.7),
pass_n = c(60L, 82L, 118L, 78L, 108L, 156L),
pass_power = c(0.8059, 0.8019, 0.8024, 0.9014, 0.9003, 0.9002)
)
ba_results <- lapply(
seq_len(nrow(ba_manual)),
function(i) sample_size_bland_altman(
power = ba_manual$target_power[i],
mu = 0.5, sd = ba_manual$sd[i], delta = 7,
conf.level = 0.95, agree.level = 0.95
)
)| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| PASS BA Example 1 / power=0.8 / SD=2.5 | sample size | 60.0000 | 59.000000 |
| PASS BA Example 1 / power=0.8 / SD=2.5 | achieved power | 0.8059 | 0.803126 |
| PASS BA Example 1 / power=0.8 / SD=2.6 | sample size | 82.0000 | 82.000000 |
| PASS BA Example 1 / power=0.8 / SD=2.6 | achieved power | 0.8019 | 0.803997 |
| PASS BA Example 1 / power=0.8 / SD=2.7 | sample size | 118.0000 | 118.000000 |
| PASS BA Example 1 / power=0.8 / SD=2.7 | achieved power | 0.8024 | 0.803101 |
| PASS BA Example 1 / power=0.9 / SD=2.5 | sample size | 78.0000 | 78.000000 |
| PASS BA Example 1 / power=0.9 / SD=2.5 | achieved power | 0.9014 | 0.901856 |
| PASS BA Example 1 / power=0.9 / SD=2.6 | sample size | 108.0000 | 108.000000 |
| PASS BA Example 1 / power=0.9 / SD=2.6 | achieved power | 0.9003 | 0.900492 |
| PASS BA Example 1 / power=0.9 / SD=2.7 | sample size | 156.0000 | 156.000000 |
| PASS BA Example 1 / power=0.9 / SD=2.7 | achieved power | 0.9002 | 0.900235 |
For power = 0.8 and SD = 2.5, PASS reports 60, while the Lu noncentral-t formula as listed in the manual gives 59 as the first searched integer meeting the target. PASS does not publish its internal values at n = 59, so this comparison does not attribute the difference to rounding or force the package result upward. The power column is retained as reported: several small-n rows differ by approximately 0.00004–0.0028, with the difference shrinking as n increases.
PASS Example 2 uses mu = 0.2, SD = 1, and delta = 2.5 to validate the result in the Lu et al. paper. The paper table gives 203, while PASS and ivdtools both obtain 201 when searching for the first integer that reaches the target; this difference is between the paper’s approximate result and an integer search.
PASS two-sided cases specify total width, whereas the package takes half_width, so the comparison uses half_width = target_width / 2. Example 1 gives three widths and five expected proportions for 15 Clopper–Pearson combinations.
ci_manual <- expand.grid(
p = seq(0.1, 0.5, by = 0.1),
target_width = c(0.04, 0.06, 0.10)
)
ci_manual$pass_n <- c(
914L, 1585L, 2065L, 2353L, 2449L,
417L, 715L, 928L, 1056L, 1098L,
158L, 264L, 341L, 387L, 402L
)
ci_results <- lapply(
seq_len(nrow(ci_manual)),
function(i) sample_size_proportion_ci(
p = ci_manual$p[i],
half_width = ci_manual$target_width[i] / 2,
conf.level = 0.95,
interval = "two.sided",
method = "clopper-pearson"
)
)| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| PASS CI Example 1 / p=0.1 / width=0.04 | sample size | 914 | 914 |
| PASS CI Example 1 / p=0.2 / width=0.04 | sample size | 1585 | 1585 |
| PASS CI Example 1 / p=0.3 / width=0.04 | sample size | 2065 | 2065 |
| PASS CI Example 1 / p=0.4 / width=0.04 | sample size | 2353 | 2353 |
| PASS CI Example 1 / p=0.5 / width=0.04 | sample size | 2449 | 2449 |
| PASS CI Example 1 / p=0.1 / width=0.06 | sample size | 417 | 417 |
| PASS CI Example 1 / p=0.2 / width=0.06 | sample size | 715 | 715 |
| PASS CI Example 1 / p=0.3 / width=0.06 | sample size | 928 | 928 |
| PASS CI Example 1 / p=0.4 / width=0.06 | sample size | 1056 | 1056 |
| PASS CI Example 1 / p=0.5 / width=0.06 | sample size | 1098 | 1098 |
| PASS CI Example 1 / p=0.1 / width=0.10 | sample size | 158 | 158 |
| PASS CI Example 1 / p=0.2 / width=0.10 | sample size | 264 | 264 |
| PASS CI Example 1 / p=0.3 / width=0.10 | sample size | 341 | 341 |
| PASS CI Example 1 / p=0.4 / width=0.10 | sample size | 387 | 387 |
| PASS CI Example 1 / p=0.5 / width=0.10 | sample size | 402 | 402 |
The package also enumerates coverage at the final n; this is a diagnostic separate from the width condition used to select n in PASS.
ci_special <- data.frame(
case = c("PASS Example 2", "PASS Example 3", "PASS Example 4"),
design = c(
"one-sided lower limit, Clopper--Pearson",
"two-sided, Wilson continuity correction",
"one-sided upper limit, Clopper--Pearson, p=0"
),
pass_n = c(25L, 29L, 299L)
)
ci_special_results <- list(
sample_size_proportion_ci(
p = 0.92, half_width = 0.15104,
interval = "lower", method = "clopper-pearson"
),
sample_size_proportion_ci(
p = 0.034483, half_width = 0.1945 / 2,
interval = "two.sided", method = "wilson-cc"
),
sample_size_proportion_ci(
p = 0, half_width = 0.01,
interval = "upper", method = "clopper-pearson"
)
)| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| PASS Example 2 | sample size | 25 | 25 |
| PASS Example 3 | sample size | 29 | 29 |
| PASS Example 4 | sample size | 299 | 299 |
Example 3 shows that Wilson continuity correction must use the corrected score-inversion formula; mechanically adding or subtracting 1/(2n) from ordinary Wilson endpoints does not reproduce PASS. Example 4 validates a one-sided exact interval with p = 0, so the package allows expected proportions of 0 and 1 for interval planning.
The software options map to package parameters as follows.
| SAS/PASS option | ivdtools parameter | Meaning |
|---|---|---|
| SAS TEST=Z VAREST=NULL; PASS Z-test using S(P0) | test = “score” | p0 variance in the alpha term |
| SAS TEST=ADJZ; PASS S(P0) with C.C. | test = “score-cc” | S(P0) Z test with continuity correction |
| PASS Z-test using S(Phat) | test = “wald” | Wald approximation using p1 variance in the alpha term |
| METHOD=NORMAL; Normal Approximation | power_method = “normal” | Continuous normal approximation |
| Exact; Binomial Enumeration | power_method = “binomial” | Discrete binomial rejection region and power |
The comparison covers the uncorrected and corrected SAS Example 90.2 results, PASS Examples 9 and 11, and six effect sizes from PASS Example 2.
target_manual <- data.frame(
case = c(
"SAS 90.2 Z", "SAS 90.2 ADJZ", "PASS 9", "PASS 11",
paste0("PASS 2, p1=", sprintf("%.2f", seq(0.55, 0.80, 0.05)))
),
alternative = c(
"less", "less", "greater", "two.sided", rep("two.sided", 6)
),
test = c(
"score", "score-cc", "score", "wald", rep("score", 6)
),
p0 = c(0.30, 0.30, 0.50, 0.30, rep(0.50, 6)),
p1 = c(0.20, 0.20, 0.60, 0.50, seq(0.55, 0.80, 0.05)),
target_power = c(0.80, 0.80, 0.80, 0.80, rep(0.90, 6)),
manual_n = c(119L, 129L, 153L, 50L, 1047L, 259L, 113L, 62L, 38L, 25L),
manual_power = c(
0.80000, 0.80100, 0.80125, 0.80743,
0.90011, 0.90055, 0.90120, 0.90286, 0.90249, 0.90321
)
)
target_results <- lapply(
seq_len(nrow(target_manual)),
function(i) sample_size_proportion(
p0 = target_manual$p0[i], p1 = target_manual$p1[i],
alpha = 0.05, power = target_manual$target_power[i],
alternative = target_manual$alternative[i],
test = target_manual$test[i], power_method = "normal"
)
)| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| SAS 90.2 Z | sample size | 119.00000 | 119.00000 |
| SAS 90.2 Z | normal planning power | 0.80000 | 0.80032 |
| SAS 90.2 ADJZ | sample size | 129.00000 | 129.00000 |
| SAS 90.2 ADJZ | normal planning power | 0.80100 | 0.80094 |
| PASS 9 | sample size | 153.00000 | 153.00000 |
| PASS 9 | normal planning power | 0.80125 | 0.80125 |
| PASS 11 | sample size | 50.00000 | 50.00000 |
| PASS 11 | normal planning power | 0.80743 | 0.80743 |
| PASS 2, p1=0.55 | sample size | 1047.00000 | 1047.00000 |
| PASS 2, p1=0.55 | normal planning power | 0.90011 | 0.90011 |
| PASS 2, p1=0.60 | sample size | 259.00000 | 259.00000 |
| PASS 2, p1=0.60 | normal planning power | 0.90055 | 0.90055 |
| PASS 2, p1=0.65 | sample size | 113.00000 | 113.00000 |
| PASS 2, p1=0.65 | normal planning power | 0.90120 | 0.90120 |
| PASS 2, p1=0.70 | sample size | 62.00000 | 62.00000 |
| PASS 2, p1=0.70 | normal planning power | 0.90286 | 0.90286 |
| PASS 2, p1=0.75 | sample size | 38.00000 | 38.00000 |
| PASS 2, p1=0.75 | normal planning power | 0.90249 | 0.90249 |
| PASS 2, p1=0.80 | sample size | 25.00000 | 25.00000 |
| PASS 2, p1=0.80 | normal planning power | 0.90321 | 0.90321 |
Check the discrete operating characteristics at the final integer sample size:
| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| SAS 90.2 Z | binomial achieved power | NA | 0.80363 |
| SAS 90.2 Z | actual alpha | 0.05 | 0.04776 |
| SAS 90.2 ADJZ | binomial achieved power | NA | 0.79446 |
| SAS 90.2 ADJZ | actual alpha | 0.05 | 0.03595 |
| PASS 9 | binomial achieved power | NA | 0.80944 |
| PASS 9 | actual alpha | 0.05 | 0.05280 |
| PASS 11 | binomial achieved power | NA | 0.83888 |
| PASS 11 | actual alpha | 0.05 | 0.06532 |
| PASS 2, p1=0.55 | binomial achieved power | NA | 0.89681 |
| PASS 2, p1=0.55 | actual alpha | 0.05 | 0.04789 |
| PASS 2, p1=0.60 | binomial achieved power | NA | 0.89499 |
| PASS 2, p1=0.60 | actual alpha | 0.05 | 0.04656 |
| PASS 2, p1=0.65 | binomial achieved power | NA | 0.91362 |
| PASS 2, p1=0.65 | actual alpha | 0.05 | 0.05943 |
| PASS 2, p1=0.70 | binomial achieved power | NA | 0.91073 |
| PASS 2, p1=0.70 | actual alpha | 0.05 | 0.05590 |
| PASS 2, p1=0.75 | binomial achieved power | NA | 0.86830 |
| PASS 2, p1=0.75 | actual alpha | 0.05 | 0.03355 |
| PASS 2, p1=0.80 | binomial achieved power | NA | 0.89088 |
| PASS 2, p1=0.80 | actual alpha | 0.05 | 0.04329 |
binomial_power and actual_alpha are additional diagnostics at the final integer n; they do not change the normal-approximation sample size. Wald and some score designs can have actual alpha above 0.05; for example PASS Example 11 is approximately 0.06532. If the protocol requires actual alpha control, use power_method = “binomial”; the resulting n may differ from the manual’s continuous approximation because the design method is different.
| test | power_method = “normal” | power_method = “binomial” | Standard cross-check |
|---|---|---|---|
| exact | Supported; infinite-population planning equals score | Supported | PASS Examples 5 and 12 for discrete rejection; Example 13 for normal power |
| score | Supported | Supported | SAS 90.2; PASS 2/5/9 |
| score-cc | Supported | Supported | SAS 90.2 ADJZ; PASS 5 |
| wald | Supported | Supported | PASS 5/11 |
| wald-cc | Supported | Supported | PASS 5; no separate manual sample-size case |
| agresti-coull, jeffreys | Not supported | Supported | No matching PASS one-sample-test procedure found |
The testing layer also reproduces PASS Example 5: six fixed n values across five tests, for 30 discrete power values and 30 actual-alpha values. These validate rejection-region implementation and do not change the public sample-size interface.
The appendix uses the normal-approximation formula
\[ n=\frac{\left[Z_{1-\alpha/2}\sqrt{P_0(1-P_0)}+ Z_{1-\beta}\sqrt{P_T(1-P_T)}\right]^2}{(P_T-P_0)^2}. \]
The case uses two-sided alpha = 0.05 and power = 1 - beta = 0.80. The acceptable-standard/expected-value pairs are 85%/90% for positive agreement and 90%/94% for negative agreement. Using Z0.975 = 1.96 and Z0.80 = 0.84, the appendix reports 362 positive cases and 388 negative cases, for 750 total; after a 10% dropout allowance, at least 834 participants are enrolled.
sample_size_proportion(test = “score”, power_method = “normal”) performs the same continuous normal approximation. The package uses unrounded normal quantiles and rounds the theoretical result upward; actual alpha and power are then recalculated separately from the discrete binomial rejection region.
cmde_target_value <- data.frame(
group = c("Positive group", "Negative group"),
p0 = c(0.85, 0.90),
p1 = c(0.90, 0.94),
cmde_n = c(362L, 388L)
)
cmde_target_results <- lapply(
seq_len(nrow(cmde_target_value)),
function(i) {
sample_size_proportion(
p0 = cmde_target_value$p0[i],
p1 = cmde_target_value$p1[i],
alpha = 0.05,
power = 0.80,
alternative = "two.sided",
test = "score",
power_method = "normal"
)
}
)| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| CMDE target-value / Positive group | sample size | 362 | 363.00000 |
| CMDE target-value / Positive group | actual alpha | NA | 0.05552 |
| CMDE target-value / Negative group | sample size | 388 | 388.00000 |
| CMDE target-value / Negative group | actual alpha | NA | 0.05094 |
| CMDE target-value / Total | sample size | 750 | 751.00000 |
| CMDE target-value / Total | actual alpha | NA | NA |
The positive-group formula value is 362.7756, so the smallest integer meeting the target power is 363; the appendix writes 362 because it uses approximate quantiles and ordinary rounding. actual_alpha is a diagnostic obtained by placing the continuous-approximation sample size back into the discrete rejection region; it is not part of the CMDE formula’s sample-size search.
The appendix formula is
\[ n=\frac{Z_{1-\alpha/2}^2P(1-P)}{\Delta^2}. \]
The expected sensitivity is 85%, the expected specificity is 90%, and the allowable error is Delta = 0.05. This corresponds to sample_size_proportion_ci(method = “wald”). The package uses the exact qnorm(0.975) and rounds upward to the smallest integer satisfying the half-width requirement.
cmde_ci_precision <- data.frame(
group = c("Positive group (sensitivity)", "Negative group (specificity)"),
p = c(0.85, 0.90),
cmde_n = c(196L, 138L)
)
cmde_ci_precision$ivdtools_n <- vapply(
cmde_ci_precision$p,
function(p) sample_size_proportion_ci(
p = p, half_width = 0.05, conf.level = 0.95, method = "wald"
)[["n"]],
integer(1)
)| Standard location | Statistic | Standard | ivdtools |
|---|---|---|---|
| CMDE allowable-error / Positive group (sensitivity) | sample size | 196 | 196 |
| CMDE allowable-error / Negative group (specificity) | sample size | 138 | 139 |
| CMDE allowable-error / Total | sample size | 334 | 335 |
| CMDE allowable-error / 10% addition | enrolment | 371 | 373 |
The package gives 139 for the negative group while the appendix reports 138. The difference has two causes: the appendix substitutes the approximate value 1.96 and reports 138.30 as 138, whereas the package uses the exact quantile and rounds upward. The appendix then reports 371 after adding 10% to 334 cases; if “at least enroll” is handled conservatively, the package’s 335 analyzable cases require ceil(335 / 0.90) = 373. This is a quantile-precision and integer-rounding difference, not a difference in the CI method.
When the expected proportion is close to 100%, the guidance also warns that the normal approximation may be unsuitable. Use sample_size_proportion_ci(method = “clopper-pearson”) or another method for sensitivity analysis, but label the result as a supplementary analysis under a different interval method rather than as a recalculation of the appendix’s Wald formula.