Intro to Clinical Trial Simulator
Source:vignettes/intro-clinical-trial-simulator.Rmd
intro-clinical-trial-simulator.Rmd#Introduction This vignette demonstrates how to use the clinicalTrialSimulator package to simulate a simple clinical trial from start to finish. We will simulate patient demographics, randomize patients to treatment arms, generate time-to-event outcomes (e.g., survival), simulate dropouts (including some related to adverse events), produce longitudinal repeated measures, and finally compile the subject-level analysis dataset (ADSL).
Simulating Patient Characteristics
First, we simulate the baseline patient demographics and site information. In this example, we simulate 100 patients across 2 countries (with 5 sites per country). Each patient has an ID, is assigned to a country and site, and has attributes like age and sex.
# Simulate baseline data for 100 patients in 2 countries with 5 sites each
patients <- simulate_patients(n = 100, countries = 2, sites_per_country = 5,
mean_age = 50, sd_age = 10, prop_female = 0.5)
# Inspect first few patients
head(patients, 10)## ID Country Site Age Sex
## 1 1 Country1 Site3 54 Female
## 2 2 Country1 Site3 46 Male
## 3 3 Country2 Site10 56 Male
## 4 4 Country1 Site2 48 Female
## 5 5 Country2 Site6 53 Male
## 6 6 Country1 Site5 61 Male
## 7 7 Country1 Site4 54 Male
## 8 8 Country2 Site6 47 Male
## 9 9 Country2 Site9 61 Male
## 10 10 Country2 Site10 60 Male
Each row represents a patient with their demographic information. We see columns for ID, Country, Site, Age, and Sex. These will serve as the starting point for further simulations.
##Treatment Allocation
Next, we randomize patients into treatment groups. Let’s assume a two-arm trial with equal allocation between a placebo and an active drug.
# Randomly allocate patients to two arms: Placebo vs Drug (1:1 ratio)
patients <- simulate_allocation(patients, arm_names = c("Placebo", "Drug"), ratio = c(1, 1))
table(patients$Arm)##
## Placebo Drug
## 49 51
head(patients, 5)## ID Country Site Age Sex Arm
## 1 1 Country1 Site3 54 Female Drug
## 2 2 Country1 Site3 46 Male Drug
## 3 3 Country2 Site10 56 Male Placebo
## 4 4 Country1 Site2 48 Female Drug
## 5 5 Country2 Site6 53 Male Drug
The output shows the distribution of patients between the “Placebo” and “Drug” arms and the updated data frame now includes an Arm column indicating each patient’s assigned group.
##Simulating Survival (Time-to-Event) Outcome
Suppose the primary endpoint is a time-to-event outcome (e.g., overall survival). We will simulate survival times with a specified median for the placebo group and a hazard ratio for the drug group. Here we assume the placebo median survival is 24 months and the drug is expected to improve survival with a hazard ratio of 0.6 (i.e., 40% reduction in hazard). We also define a maximum study follow-up of 36 months.
# Simulate survival times (e.g., overall survival) with median 24 for placebo and HR 0.6 for drug
patients <- simulate_survival(patients, median_time_control = 24, hr = 0.6, study_duration = 36)
# Check a summary of event status by arm
with(patients, table(Arm, EventStatus))## EventStatus
## Arm 0 1
## Placebo 21 28
## Drug 24 27
## ID Arm EventTime EventStatus
## 1 1 Drug 31.079753 1
## 2 2 Drug 36.000000 0
## 3 3 Placebo 10.405513 1
## 4 4 Drug 17.747074 1
## 5 5 Drug 36.000000 0
## 6 6 Placebo 21.626910 1
## 7 7 Drug 5.427236 1
## 8 8 Placebo 13.279536 1
## 9 9 Drug 10.094406 1
## 10 10 Placebo 36.000000 0
The EventTime column represents either the time of event (if the event occurred) or the censoring time (if the patient did not have an event by end of follow-up). EventStatus is 1 if the event occurred, or 0 if the time is a censoring time (e.g., the patient was event-free at study end or dropped out before event as we will simulate next). The contingency table above shows how many events (1) and censored observations (0) occurred in each arm; we expect fewer events in the Drug arm due to the hazard reduction.
##Simulating Dropouts Patients may drop out of the trial (for reasons like loss to follow-up or withdrawal due to adverse events). We simulate dropouts such that about 30% of patients withdraw by 36 months. We will also illustrate an original feature: increasing dropout risk for patients who experience a serious adverse event (AE). Let’s say 20% of patients will have a simulated serious AE, and those patients have twice the hazard of dropout.
# Simulate dropouts: 30% dropout rate by 36 months
# 20% of patients have a serious AE which doubles their dropout hazard
patients <- simulate_dropout(patients, dropout_rate = 0.3, study_duration = 36,
p_ae = 0.2, dropout_hr_ae = 2)
# Inspect dropout and AE columns
with(patients, table(Arm, Dropped))## Dropped
## Arm FALSE TRUE
## Placebo 36 13
## Drug 35 16
## Dropped
## SeriousAE FALSE TRUE
## FALSE 59 20
## TRUE 12 9
## ID Arm SeriousAE DropoutTime Dropped
## 1 1 Drug FALSE 18.147944 TRUE
## 2 2 Drug TRUE 6.225057 TRUE
## 3 3 Placebo FALSE NA FALSE
## 4 4 Drug FALSE NA FALSE
## 5 5 Drug FALSE 21.314232 TRUE
## 6 6 Placebo FALSE 5.307033 TRUE
## 7 7 Drug FALSE NA FALSE
## 8 8 Placebo FALSE NA FALSE
## 9 9 Drug FALSE NA FALSE
## 10 10 Placebo FALSE NA FALSE
We can see the SeriousAE flag indicating which patients had the adverse event and the DropoutTime and Dropped columns indicating if and when patients left the study. The first table shows the number of dropouts (Dropped=TRUE) by arm, and the second table illustrates that patients with serious AEs tend to have a higher dropout rate (rows where SeriousAE=TRUE likely show more dropouts). Note: Patients who died (had the event) before they could drop out are treated as not dropped. The simulation logic censors the survival time at dropout if dropout occurs first, or ignores the dropout if an event occurred first. Thus, each patient’s timeline is appropriately handled.
Simulating Longitudinal Outcomes
Now let’s simulate a longitudinal outcome, for example a biomarker measured at clinic visits over time. We’ll assume visits at baseline and every 6 months up to 24 months (so 0, 6, 12, 18, 24). We also incorporate a treatment effect on this biomarker: perhaps the biomarker decreases over time in the treatment group compared to placebo. We set up a scenario where the placebo group stays roughly constant on average, while the treatment group sees an improvement (decrease) of about 1 unit every 6 months. We also include a random site effect to demonstrate multi-level data (each site has a random offset on the biomarker).
# Define planned visit times (months)
visit_schedule <- c(0, 6, 12, 18, 24)
# Generate the schedule of visits for each patient (accounting for dropouts and deaths)
visits <- simulate_visits(patients, visit_times = visit_schedule)
# Simulate longitudinal biomarker outcomes:
# baseline mean = 100, control group slope ~ 0 per month, treatment slope = -0.2 per month (improvement),
# measurement error SD = 5, and random site intercept SD = 3.
visit_data <- simulate_longitudinal_outcomes(visits, patients,
baseline_mean = 100, baseline_sd = 10,
control_slope = 0, treatment_slope = -0.2,
error_sd = 5,
cluster_var = "Site", cluster_effect_sd = 3)
# Look at some longitudinal data points
head(visit_data, 10)## ID Time Arm Site Country Outcome
## 1 1 0 Drug Site3 Country1 98.27484
## 2 1 6 Drug Site3 Country1 111.83072
## 3 1 12 Drug Site3 Country1 95.44381
## 4 1 18 Drug Site3 Country1 98.60257
## 5 2 0 Drug Site3 Country1 99.38935
## 6 2 6 Drug Site3 Country1 101.99665
## 7 3 0 Placebo Site10 Country2 98.83889
## 8 3 6 Placebo Site10 Country2 104.58937
## 9 4 0 Drug Site2 Country1 96.50052
## 10 4 6 Drug Site2 Country1 100.27258
In the visit_data, each patient has multiple rows (one per visit) with an Outcome value for the biomarker. The Arm, Site, and Country columns provide context for each measurement. Patients who dropped out or died before certain visits will have no entries for those later times (e.g., if a patient dropped at month 18, you will not see a 24-month record for them). We can examine the treatment effect by comparing the biomarker values at baseline and 24 months for each arm:
# Baseline and final visit outcomes by treatment arm
baseline_data <- subset(visit_data, Time == 0)
final_data <- subset(visit_data, Time == 24)
cat("Mean outcome at baseline by arm:\n")## Mean outcome at baseline by arm:
## Placebo Drug
## 99.19378 99.88680
cat("Mean outcome at 24 months by arm:\n")## Mean outcome at 24 months by arm:
## Placebo Drug
## 97.94677 98.08435
We expect the baseline means to be roughly equal (by design), and by 24 months the Drug arm has a lower mean outcome (indicating improvement) compared to Placebo. The random site effect and error add variability, so these are average trends.
Simulating a Binary Outcome
For illustration, let’s also simulate a binary endpoint, such as an overall response (Yes/No) at end of study. We will assume, for example, a 40% response rate in the placebo group and a 60% response rate in the drug group. We’ll add this as a single outcome per patient.
# Simulate a binary outcome (e.g., treatment response)
patients <- simulate_outcomes(patients, outcome_type = "binary",
outcome_prob_control = 0.4, outcome_prob_treatment = 0.6)
# Response rates by arm
with(patients, table(Arm, Outcome))## Outcome
## Arm No Yes
## Placebo 9 4
## Drug 5 11
## ID Arm Outcome
## 1 1 Drug <NA>
## 2 2 Drug <NA>
## 3 3 Placebo <NA>
## 4 4 Drug <NA>
## 5 5 Drug <NA>
## 6 6 Placebo <NA>
## 7 7 Drug <NA>
## 8 8 Placebo <NA>
## 9 9 Drug <NA>
## 10 10 Placebo No
The output shows the number of responders (“Yes”) vs non-responders (“No”) in each arm. The simulation assigns “Yes”/“No” taking into account the specified probabilities. Note that patients who dropped out or died have their Outcome set to NA (missing), as they did not complete the study to have a measured response.
Creating the ADSL Dataset
Finally, we compile all the simulated data into a single subject-level analysis dataset (ADSL). This will include one row per patient with their baseline info, treatment, and outcomes.
adsl <- export_adam_format(patients, study_id = "SIMTRIAL01")
# Examine the first few records of ADSL
head(adsl, 5)## STUDYID USUBJID ID Country Site Arm Age Sex SeriousAE
## 1 SIMTRIAL01 SIMTRIAL01-1 1 Country1 Site3 Drug 54 Female FALSE
## 2 SIMTRIAL01 SIMTRIAL01-2 2 Country1 Site3 Drug 46 Male TRUE
## 3 SIMTRIAL01 SIMTRIAL01-3 3 Country2 Site10 Placebo 56 Male FALSE
## 4 SIMTRIAL01 SIMTRIAL01-4 4 Country1 Site2 Drug 48 Female FALSE
## 5 SIMTRIAL01 SIMTRIAL01-5 5 Country2 Site6 Drug 53 Male FALSE
## DropoutTime Dropped EventTime EventStatus Outcome
## 1 18.147944 TRUE 18.147944 0 <NA>
## 2 6.225057 TRUE 6.225057 0 <NA>
## 3 NA FALSE 10.405513 1 <NA>
## 4 NA FALSE 17.747074 1 <NA>
## 5 21.314232 TRUE 21.314232 0 <NA>
The adsl data frame contains columns like STUDYID (study identifier), USUBJID (unique subject ID), treatment Arm, demographic variables (Age, Sex, etc.), and the various outcomes: - EventTime and EventStatus (time-to-event outcome and censoring status), - DropoutTime and Dropped (dropout information), - Outcome (our binary response outcome in this case). Each patient’s row aggregates all their relevant information. For example, if a patient died during the trial, EventStatus=1 and EventTime is when it happened; if a patient dropped out, Dropped=TRUE and DropoutTime is given (and their Outcome may be NA due to missing data from dropout).
Conclusion
We have simulated a complete clinical trial dataset. The clinicalTrialSimulator package allowed us to generate: - Demographics and multi-level site data, - Treatment assignments, - Time-to-event outcomes with treatment effect, - Dropouts including those triggered by adverse events, - Longitudinal repeated measures with treatment and site effects, - A binary endpoint, - A compiled ADSL dataset for analysis.