The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
This vignette demonstrates sample size calculation and power analysis for clinical trials with two co-primary continuous endpoints using asymptotic normal approximation methods. The methodology is based on Sozu et al. (2011).
In clinical trials, co-primary endpoints require demonstrating statistically significant treatment effects on all endpoints simultaneously. Unlike multiple primary endpoints (where success on any one endpoint is sufficient), co-primary endpoints require:
Co-primary continuous endpoints are common in:
Consider a two-arm parallel-group superiority trial comparing treatment (group 1) with control (group 2). Let \(n_{1}\) and \(n_{2}\) denote the sample sizes in the two groups (i.e., total sample size is \(N=n_{1}+n_{2}\)), and define the allocation ratio \(r = n_{1}/n_{2}\).
For subject \(i\) in group \(j\) (\(j = 1\): treatment, \(j = 2\): control), we observe two continuous outcomes:
Endpoint \(k\) (\(k = 1, 2\)): \[X_{i,j,k} \sim \text{N}(\mu_{j,k}, \sigma_{k}^{2})\]
where:
Within-subject correlation: The two outcomes are correlated within each subject: \[\text{Cor}(X_{i,j,1}, X_{i,j,2}) = \rho_{j}\]
We assume common correlation across groups: \(\rho_{1} = \rho_{2} = \rho\).
The treatment effect for endpoint \(k\) is measured by:
Absolute difference: \(\delta_{k} = \mu_{1,k} - \mu_{2,k}\)
Standardized effect size: \(\delta_{k}^{\ast} = \delta_{k} / \sigma_{k}\)
The standardized effect size is preferred as it is scale-free and facilitates comparison across studies.
For two co-primary endpoints, we test:
Null hypothesis: \(\text{H}_{0} = \text{H}_{01} \cup \text{H}_{02}\) (at least one null hypothesis is true)
where \(\text{H}_{0k}: \delta_{k} = 0\) for \(k = 1, 2\).
Alternative hypothesis: \(\text{H}_{1} = \text{H}_{11} \cap \text{H}_{12}\) (both alternative hypotheses are true)
where \(\text{H}_{1k}: \delta_{k} > 0\) for \(k = 1, 2\).
Decision rule: Reject \(\text{H}_{0}\) if and only if both \(\text{H}_{01}\) and \(\text{H}_{02}\) are rejected at significance level \(\alpha\).
For each endpoint \(k\), the test statistic is:
Known variance case: \[Z_{k} = \frac{\bar{X}_{1k} - \bar{X}_{2k}}{\sigma_{k}\sqrt{\frac{1}{n_{1}} + \frac{1}{n_{2}}}}\]
Unknown variance case: \[T_{k} = \frac{\bar{X}_{1k} - \bar{X}_{2k}}{s_{k}\sqrt{\frac{1}{n_{1}} + \frac{1}{n_{2}}}}\]
where \(s_{k}\) is the pooled sample standard deviation for endpoint \(k\).
Under \(\text{H}_{1}\), when variances are known, \((Z_{1}, Z_{2})\) asymptotically follows a bivariate normal distribution:
\[\begin{pmatrix} Z_{1} \\ Z_{2} \end{pmatrix} \sim \text{BN}\left(\begin{pmatrix} \omega_{1} \\ \omega_{2} \end{pmatrix}, \begin{pmatrix} 1 & \gamma \\ \gamma & 1 \end{pmatrix}\right)\]
where:
The overall power is:
\[1 - \beta = \Pr(Z_{1} > z_{1-\alpha} \text{ and } Z_{2} > z_{1-\alpha} \mid \text{H}_{1})\]
Using the bivariate normal CDF:
\[1 - \beta = \Phi_{2}(-z_{1-\alpha} + \omega_{1}, -z_{1-\alpha} + \omega_{2} \mid \rho)\]
where \(\Phi_{2}(\cdot, \cdot \mid \rho)\) is the bivariate normal CDF with correlation \(\rho\).
Calculate sample size for a balanced design (\(\kappa = 1\)) with known variance:
# Design parameters
result <- ss2Continuous(
delta1 = 0.5, # Effect size for endpoint 1
delta2 = 0.5, # Effect size for endpoint 2
sd1 = 1, # Standard deviation for endpoint 1
sd2 = 1, # Standard deviation for endpoint 2
rho = 0.5, # Correlation between endpoints
r = 1, # Balanced allocation
alpha = 0.025, # One-sided significance level
beta = 0.2, # Type II error (80% power)
known_var = TRUE
)
print(result)
#>
#> Sample size calculation for two continuous co-primary endpoints
#>
#> n1 = 79
#> n2 = 79
#> N = 158
#> delta = 0.5, 0.5
#> sd = 1, 1
#> rho = 0.5
#> allocation = 1
#> alpha = 0.025
#> beta = 0.2
#> known_var = TRUEExamine how correlation affects sample size:
# Calculate sample sizes for different correlations
correlations <- c(0, 0.3, 0.5, 0.8)
sample_sizes <- sapply(correlations, function(rho) {
ss2Continuous(
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = rho, r = 1,
alpha = 0.025, beta = 0.2,
known_var = TRUE
)$N
})
# Create summary table
correlation_table <- data.frame(
Correlation = correlations,
Total_N = sample_sizes,
Reduction = c(0, round((1 - sample_sizes[-1]/sample_sizes[1]) * 100, 1))
)
kable(correlation_table,
caption = "Sample Size vs Correlation (delta = 0.5, alpha = 0.025, power = 0.8)",
col.names = c("Correlation (rho)", "Total N", "Reduction (%)"))| Correlation (rho) | Total N | Reduction (%) |
|---|---|---|
| 0.0 | 166 | 0.0 |
| 0.3 | 162 | 2.4 |
| 0.5 | 158 | 4.8 |
| 0.8 | 148 | 10.8 |
Key finding: At \(\rho = 0.8\), approximately 11% reduction in sample size compared to \(\rho = 0\).
Visualize the relationship between correlation and sample size:
Visualize power contours for different effect sizes:
We replicate Table 1 from Sozu et al. (2011) using the
design_table() function. This table shows sample sizes per
group for various combinations of standardized effect sizes.
# Create parameter grid (delta1 <= delta2)
param_grid <- expand.grid(
delta1 = c(0.2, 0.25, 0.3, 0.35, 0.4),
delta2 = c(0.2, 0.25, 0.3, 0.35, 0.4),
sd1 = 1,
sd2 = 1
) %>%
arrange(delta1, delta2) %>%
filter(delta2 >= delta1)
# Calculate sample sizes for different correlations
result_table <- design_table(
param_grid = param_grid,
rho_values = c(0, 0.3, 0.5, 0.8),
r = 1,
alpha = 0.025,
beta = 0.2,
endpoint_type = "continuous"
) %>%
mutate_at(vars(starts_with("rho_")), ~ . / 2) # Per-group sample size
# Display table
kable(result_table,
caption = "Table 1: Sample Sizes Per Group (Sozu et al. 2011, alpha = 0.025, power = 0.8)",
digits = 2)| delta1 | delta2 | sd1 | sd2 | rho_0.0 | rho_0.3 | rho_0.5 | rho_0.8 |
|---|---|---|---|---|---|---|---|
| 0.20 | 0.20 | 1 | 1 | 516 | 503 | 490 | 458 |
| 0.20 | 0.25 | 1 | 1 | 432 | 424 | 417 | 401 |
| 0.20 | 0.30 | 1 | 1 | 402 | 399 | 397 | 393 |
| 0.20 | 0.35 | 1 | 1 | 394 | 394 | 393 | 393 |
| 0.20 | 0.40 | 1 | 1 | 393 | 393 | 393 | 393 |
| 0.25 | 0.25 | 1 | 1 | 330 | 322 | 314 | 294 |
| 0.25 | 0.30 | 1 | 1 | 284 | 278 | 272 | 260 |
| 0.25 | 0.35 | 1 | 1 | 263 | 260 | 257 | 253 |
| 0.25 | 0.40 | 1 | 1 | 254 | 253 | 253 | 252 |
| 0.30 | 0.30 | 1 | 1 | 230 | 224 | 218 | 204 |
| 0.30 | 0.35 | 1 | 1 | 201 | 197 | 192 | 183 |
| 0.30 | 0.40 | 1 | 1 | 186 | 183 | 181 | 176 |
| 0.35 | 0.35 | 1 | 1 | 169 | 165 | 160 | 150 |
| 0.35 | 0.40 | 1 | 1 | 150 | 147 | 143 | 136 |
| 0.40 | 0.40 | 1 | 1 | 129 | 126 | 123 | 115 |
Interpretation:
Calculate power for a specific sample size:
# Calculate power with n1 = n2 = 100
power_result <- power2Continuous(
n1 = 100, n2 = 100,
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = 0.5,
alpha = 0.025,
known_var = TRUE
)
print(power_result)
#>
#> Power calculation for two continuous co-primary endpoints
#>
#> n1 = 100
#> n2 = 100
#> delta = 0.5, 0.5
#> sd = 1, 1
#> rho = 0.5
#> alpha = 0.025
#> known_var = TRUE
#> power1 = 0.942438
#> power2 = 0.942438
#> powerCoprimary = 0.899732Verify that calculated sample size achieves target power:
# Calculate sample size
ss_result <- ss2Continuous(
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = 0.5, r = 1,
alpha = 0.025, beta = 0.2,
known_var = TRUE
)
# Verify power with calculated sample size
power_check <- power2Continuous(
n1 = ss_result$n1, n2 = ss_result$n2,
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = 0.5,
alpha = 0.025,
known_var = TRUE
)
cat("Calculated sample size per group:", ss_result$n2, "\n")
#> Calculated sample size per group: 79
cat("Target power: 0.80\n")
#> Target power: 0.80
cat("Achieved power:", round(power_check$powerCoprimary, 4), "\n")
#> Achieved power: 0.8042The package provides a unified interface similar to
power.prop.test():
# Sample size calculation mode
twoCoprimary2Continuous(
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = 0.5, power = 0.8, r = 1,
alpha = 0.025, known_var = TRUE
)
#>
#> Sample size calculation for two continuous co-primary endpoints
#>
#> n1 = 79
#> n2 = 79
#> N = 158
#> delta = 0.5, 0.5
#> sd = 1, 1
#> rho = 0.5
#> allocation = 1
#> alpha = 0.025
#> beta = 0.2
#> known_var = TRUE
# Power calculation mode
twoCoprimary2Continuous(
n1 = 100, n2 = 100,
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = 0.5,
alpha = 0.025, known_var = TRUE
)
#>
#> Power calculation for two continuous co-primary endpoints
#>
#> n1 = 100
#> n2 = 100
#> delta = 0.5, 0.5
#> sd = 1, 1
#> rho = 0.5
#> alpha = 0.025
#> known_var = TRUE
#> power1 = 0.942438
#> power2 = 0.942438
#> powerCoprimary = 0.899732When variances are unknown, use \(t\)-test with Monte Carlo simulation:
# Sample size calculation with unknown variance
ss_unknown <- ss2Continuous(
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = 0.5, r = 1,
alpha = 0.025, beta = 0.2,
known_var = FALSE,
nMC = 10000 # Number of Monte Carlo simulations
)
print(ss_unknown)
#>
#> Sample size calculation for two continuous co-primary endpoints
#>
#> n1 = 80
#> n2 = 80
#> N = 160
#> delta = 0.5, 0.5
#> sd = 1, 1
#> rho = 0.5
#> allocation = 1
#> alpha = 0.025
#> beta = 0.2
#> known_var = FALSE
#> nMC = 10000Note: The unknown variance case requires more computation time due to Monte Carlo simulation.
Methods to estimate correlation \(\rho\):
Conservative approach: Use lower correlation estimates to ensure adequate power.
Always perform sensitivity analysis:
# Test robustness to correlation misspecification
assumed_rho <- 0.5
true_rhos <- c(0, 0.3, 0.5, 0.7, 0.9)
# Calculate sample size assuming rho = 0.5
ss_assumed <- ss2Continuous(
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = assumed_rho, r = 1,
alpha = 0.025, beta = 0.2,
known_var = TRUE
)
# Calculate achieved power under different true correlations
sensitivity_results <- data.frame(
Assumed_rho = assumed_rho,
True_rho = true_rhos,
n_per_group = ss_assumed$n2,
Achieved_power = sapply(true_rhos, function(true_rho) {
power2Continuous(
n1 = ss_assumed$n1, n2 = ss_assumed$n2,
delta1 = 0.5, delta2 = 0.5,
sd1 = 1, sd2 = 1,
rho = true_rho,
alpha = 0.025,
known_var = TRUE
)$powerCoprimary
})
)
kable(sensitivity_results,
caption = "Sensitivity Analysis: Impact of Correlation Misspecification",
digits = 3,
col.names = c("Assumed rho", "True rho", "n per group", "Achieved Power"))| Assumed rho | True rho | n per group | Achieved Power |
|---|---|---|---|
| 0.5 | 0.0 | 79 | 0.777 |
| 0.5 | 0.3 | 79 | 0.791 |
| 0.5 | 0.5 | 79 | 0.804 |
| 0.5 | 0.7 | 79 | 0.821 |
| 0.5 | 0.9 | 79 | 0.846 |
Sozu, T., Sugimoto, T., & Hamasaki, T. (2011). Sample size determination in superiority clinical trials with multiple co-primary correlated endpoints. Journal of Biopharmaceutical Statistics, 21(4), 650-668.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.