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.

Simulation Workflow for OLE Phase

Lei Shi, Matthew Secrest

2026-08-20

OLE study

This vignette demonstrates Monte Carlo simulation for evaluating the difference-in-differences (DID) estimators proposed in Zhou et al. (2024) for the open-label extension (OLE) phase.

The simulated data has four outcome columns (y1, y2, y3, y4) and a crossover point T_cross = 2. This means:

In other words, T_cross is the last index of the placebo-controlled phase in outcome_col_name.

head(SyntheticData)
#>   x1 x2 x3 x4       x5 A S T_cross         y1         y2         y3        y4
#> 1  1  1  1  9 54.59836 1 1       2  3.4512377 -0.7642287 -2.4713591  3.935466
#> 2  0  1  0  8 33.08006 1 1       2  0.4518106  6.3516296  4.5231869 -0.198674
#> 3  1  1  1  7 48.51653 0 1       2  3.0532714 -2.0453190  5.9064870 -1.374919
#> 4  1  1  1 15 31.68766 1 1       2 -9.1183948  0.2304339  4.7858172  8.490757
#> 5  1  1  1 12 29.98495 0 1       2 -1.4270057  1.5878794  3.7006101  9.449632
#> 6  1  1  0  7 46.08991 0 1       2 -2.6967072 -0.6130288  0.7482786 -2.413717

1. Simulate a dataset for OLE study

# Initialize an empty data list
set.seed(2023)

data_matrix_list <- list()
# a small ntrial keeps this vignette fast to build; use hundreds to
# thousands of trials for stable operating characteristic estimates
ntrial <- 20

# Specify the significance level alpha
alpha <- 0.05

# Specify the true effect size at the end of the OLE study
true_effect_long <- 0

# Specify column names
covariates_col_name <- c("x1", "x2", "x3", "x4", "x5")
outcome_col_name <- c("y1", "y2", "y3", "y4")
treatment_col_name <- "A"
trial_status_col_name <- "S"
# Sequentially adding in datasets
for (trial_iter in 1:ntrial) {
  # simulate 300 sample
  normal <- copula::normalCopula(param = c(0.8), dim = 4, dispstr = "ar1")

  # ========== generate internal covariates =============
  X_int <- simulate_X_copula(
    n = 200,
    p = 4,
    cp = normal, # copula
    margins = c("binom", "binom", "binom", "exp"), # specify marginal distributions
    paramMargins = list(
      list(size = 1, prob = 0.7), # specify parameters for marginals
      list(size = 1, prob = 0.9),
      list(size = 1, prob = 0.3),
      list(rate = 1 / 10)
    )
  )

  X_int$x4 <- round(X_int$x4) + 1
  X_int$x5 <- 30 + 10 * X_int$x1 + (7) * X_int$x2 + (-6) * X_int$x3 + (-0.5) * X_int$x4 + rnorm(200, mean = 0, sd = 10)

  varnames <- c("1", paste0("x", 1:5))

  # ============ generate external covariates ==============
  X_ext <- simulate_X_copula(
    n = 100,
    p = 4,
    cp = normal, # copula
    margins = c("binom", "binom", "binom", "exp"), # specify marginal distributions
    paramMargins = list(
      list(size = 1, prob = 0.7), # specify parameters for marginals
      list(size = 1, prob = 0.9),
      list(size = 1, prob = 0.3),
      list(rate = 1 / 10)
    )
  )

  X_ext$x4 <- round(X_ext$x4) + 1
  X_ext$x5 <- 50 + 10 * X_ext$x1 + (2) * X_ext$x2 + (-1) * X_ext$x3 + (-0.3) * X_ext$x4 + rnorm(100, mean = 0, sd = 10)

  varnames <- c("1", paste0("x", 1:5))

  # ============ Specify outcome models ==============
  model_form_x_t1 <- setNames(c(10.0, 0.05, -1.5, -1.0, -0.2, -0.1), varnames) # 1.5*A, sigma = 4.0
  model_form_x_t2 <- setNames(c(6.0, 0.5, -0.5, -1.0, -0.3, -0.06), varnames) # 1.8*A, sigma = 4.0
  model_form_x_t3 <- setNames(c(5.0, 1.9, 1.4, -1.3, -0.4, -0.15), varnames) # 1.6*A, sigma = 4.0
  model_form_x_t4 <- setNames(c(1.2, 1.0, 2.0, -0.5, -0.4, -0.10), varnames) # 2.5*A, sigma = 5.0

  outcome_model_specs <- list(
    list(
      effect = 0, model_form_x = model_form_x_t1, # from data: true_effect = 1.5
      noise_mean = 0, noise_sd = 4
    ), # model form for the first time point, given by model_form_x_t1
    list(
      effect = 0, model_form_x = model_form_x_t2, # from data: true_effect = 1.8
      noise_mean = 0, noise_sd = 4
    ), # model form for the second time point, given by model_form_x_t2
    list(
      effect = 0, model_form_x = model_form_x_t3, # from data: true_effect = 1.6
      noise_mean = 0, noise_sd = 4
    ), # model form for the third time point, given by model_form_x_t3
    list(
      effect = true_effect_long, model_form_x = model_form_x_t4, # from data: true_effect = 2.5
      noise_mean = 0, noise_sd = 4
    ) # model form for the fourth time point, given by model_form_x_t4
  )

  # =========== generate trial data ============
  Data <- simulate_trial(X_int,
    X_ext,
    num_treated = 150,
    OLE_flag = TRUE,
    T_cross = 2,
    outcome_model_specs
  )


  data_matrix_list[[trial_iter]] <- Data
}

2 Bootstrap inference

model_form_mu <- c(
  "y1 ~ x1 + x2 + x3 + x4 + x5",
  "y2 ~ x1 + x2 + x3 + x4 + x5",
  "y3 ~ x1 + x2 + x3 + x4 + x5",
  "y4 ~ x1 + x2 + x3 + x4 + x5"
)

method_IPW_DID <- did_ec_ipw(
  ps_formula = "S ~ x1 + x2 + x3 + x4 + x5",
  trt_formula = "A ~ x1 + x2 + x3 + x4 + x5",
  bootstrap = 50,
  bootstrap_ci_type = "perc"
)

method_AIPW_DID <- did_ec_aipw(
  ps_formula = "S ~ x1 + x2 + x3 + x4 + x5",
  trt_formula = "A ~ x1 + x2 + x3 + x4 + x5",
  outcome_formula = model_form_mu,
  bootstrap = 50,
  bootstrap_ci_type = "perc"
)

method_OR_DID <- did_ec_or(
  outcome_formula_ext = model_form_mu,
  outcome_formula_rct_ctrl = model_form_mu,
  outcome_formula_rct_trt = model_form_mu,
  bootstrap = 50,
  bootstrap_ci_type = "perc"
)

method_obj_list <- list(
  method_IPW_DID,
  method_AIPW_DID,
  method_OR_DID
)
# create a simulation object for primary analysis
simulation_OLE_obj <- setup_simulation_OLE(
  data_matrix_list = data_matrix_list, # two scenarios
  trial_status_col_name = trial_status_col_name,
  treatment_col_name = treatment_col_name,
  outcome_col_name = outcome_col_name,
  covariates_col_name = covariates_col_name,
  method_obj_list = method_obj_list,
  true_effect = true_effect_long,
  T_cross = 2,
  alpha = alpha,
  method_description = c(
    "IPW, DID",
    "AIPW, DID",
    "OR, DID"
  )
)
simulation_report <- run_simulation(simulation_OLE_obj, quiet = TRUE)
simulation_report
#>   method_description        bias variance       mse coverage type_I_error
#> 1           IPW, DID -0.28180693 3.881071 3.9604863     0.90         0.10
#> 2          AIPW, DID  0.23441801 3.885825 3.9407764     0.95         0.05
#> 3            OR, DID  0.05582604 0.609393 0.6125095     1.00         0.00

References

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.