Two Continuous Co-Primary Endpoints

Overview

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).

library(twoCoprimary)
library(dplyr)
library(tidyr)
library(knitr)

Background and Motivation

What are Co-Primary Endpoints?

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:

  1. Rejecting all null hypotheses at level \(\alpha\)
  2. No multiplicity adjustment needed for Type I error control
  3. Correlation consideration can improve efficiency

Clinical Examples

Co-primary continuous endpoints are common in:

Statistical Framework

Model and Assumptions

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\).

Effect Size Parameterization

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.

Hypothesis Testing

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\).

Test Statistics

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\).

Joint Distribution

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:

Power Formula

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\).

Sample Size Calculation

Basic Example

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 = TRUE

Impact of Correlation

Examine 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 (%)"))
Sample Size vs Correlation (delta = 0.5, alpha = 0.025, power = 0.8)
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\).

Visualization with plot()

Visualize the relationship between correlation and sample size:

# Use plot method to visualize sample size vs correlation
plot(result, type = "sample_size_rho")

Visualize power contours for different effect sizes:

# Create contour plot for effect sizes
plot(result, type = "effect_contour")

Replicating Sozu et al. (2011) Table 1

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)
Table 1: Sample Sizes Per Group (Sozu et al. 2011, alpha = 0.025, power = 0.8)
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:

Power Calculation

Power for a Given Sample Size

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.899732

Power Verification

Verify 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.8042

Unified Interface

The 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.899732

Unknown Variance Case

When 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 = 10000

Note: The unknown variance case requires more computation time due to Monte Carlo simulation.

Practical Considerations

Correlation Estimation

Methods to estimate correlation \(\rho\):

  1. Pilot studies: Small preliminary studies
  2. Historical data: Previous trials in the same disease area
  3. Literature review: Published studies with similar endpoints
  4. Expert opinion: Clinical judgment when data are unavailable

Conservative approach: Use lower correlation estimates to ensure adequate power.

Sensitivity Analysis

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"))
Sensitivity Analysis: Impact of Correlation Misspecification
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

References

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.