| name | clinical-trials |
| description | Clinical trial design and analysis methods in R, including randomization, estimands, multiplicity, and reporting. |
Clinical Trials Statistical Methods
Overview
Comprehensive clinical trial design and analysis methods in R covering sample size calculation, randomization, interim analyses, multiplicity adjustment, and regulatory-compliant statistical methods.
Sample Size Calculation
Two-Group Comparisons
library(pwr)
pwr.t.test(
d = 0.5,
sig.level = 0.05,
power = 0.80,
type = "two.sample",
alternative = "two.sided"
)
pwr.2p.test(
h = ES.h(p1 = 0.6, p2 = 0.4),
sig.level = 0.05,
power = 0.80
)
pwr.2p2n.test(
h = ES.h(p1 = 0.6, p2 = 0.4),
n1 = 100,
sig.level = 0.05
)
Survival Endpoints
library(gsDesign)
nSurv(
lambda1 = log(2)/12,
lambda2 = log(2)/18,
Ts = 24,
Tr = 12,
alpha = 0.025,
beta = 0.20,
ratio = 1
)
library(rpact)
getSampleSizeSurvival(
hazardRatio = 0.67,
lambda1 = log(2)/12,
accrualTime = 12,
followUpTime = 12,
alpha = 0.025,
beta = 0.20,
allocationRatioPlanned = 1
)
Non-Inferiority Trials
library(TrialSize)
TwoSampleProportion.NIS(
p = 0.80,
delta = 0.10,
alpha = 0.025,
power = 0.80
)
TwoSampleMean.NIS(
sigma = 10,
delta = 5,
alpha = 0.025,
power = 0.80
)
Randomization
Simple Randomization
set.seed(123)
n <- 100
treatment <- sample(c("A", "B"), n, replace = TRUE)
library(blockrand)
randomization <- blockrand(
n = 100,
num.levels = 2,
levels = c("Treatment", "Control"),
id.prefix = "PAT",
block.prefix = "BLK"
)
Stratified Block Randomization
library(blockrand)
strata <- expand.grid(
sex = c("Male", "Female"),
age_group = c("<65", ">=65")
)
rand_lists <- lapply(1:nrow(strata), function(i) {
blockrand(
n = 50,
num.levels = 2,
levels = c("Treatment", "Control"),
block.sizes = c(2, 4, 6),
stratum = paste(strata[i, ], collapse = "_")
)
})
full_list <- do.call(rbind, rand_lists)
Minimization
library(Minirand)
minirand(
covariates = data.frame(
sex = c("M", "F", "M"),
age = c("young", "old", "young"),
center = c("A", "A", "B")
),
treatment = c("A", "B"),
ratio = c(1, 1),
p = 0.85
)
Baseline Characteristics
For randomized trials, baseline tables should primarily describe the randomized groups and assess clinically meaningful imbalance. Routine baseline hypothesis tests are usually not appropriate because any baseline differences arise after randomization and p-values mostly reflect sample size.
library(gtsummary)
baseline_table <- adsl |>
dplyr::filter(ITTFL == "Y") |>
dplyr::select(TRT01P, AGE, SEX, BMI, BASELINE_SCORE) |>
tbl_summary(
by = TRT01P,
statistic = list(
all_continuous() ~ "{mean} ({sd})",
all_categorical() ~ "{n} ({p}%)"
)
) |>
modify_header(label = "**Characteristic**")
If the SAP requires standardized mean differences, report them as descriptive diagnostics rather than randomization tests.
Group Sequential Designs
gsDesign Package
library(gsDesign)
gs_design <- gsDesign(
k = 3,
test.type = 2,
alpha = 0.025,
beta = 0.20,
sfu = "OF",
timing = c(0.5, 0.75, 1)
)
gs_design
gs_design$upper$bound
gs_design$lower$bound
plot(gs_design)
Alpha Spending Functions
gs_pocock <- gsDesign(k = 3, sfu = "Pocock")
gs_hsd <- gsDesign(k = 3, sfu = sfHSD, sfupar = -4)
gs_power <- gsDesign(k = 3, sfu = sfPower, sfupar = 2)
gs_custom <- gsDesign(
k = 3,
sfu = sfPoints,
sfupar = c(0.01, 0.03, 0.025),
timing = c(0.5, 0.75, 1)
)
rpact Package
library(rpact)
design <- getDesignGroupSequential(
kMax = 3,
alpha = 0.025,
beta = 0.20,
sided = 1,
typeOfDesign = "OF",
informationRates = c(0.5, 0.75, 1)
)
sampleSize <- getSampleSizeMeans(
design = design,
alternative = 0.5,
stDev = 1
)
getAnalysisResults(
design,
dataInput = getDataset(
n = c(50, 75),
means = c(0.3, 0.4),
stDevs = c(1, 1)
)
)
Multiplicity Adjustment
P-value Adjustments
p.adjust(p_values, method = "bonferroni")
p.adjust(p_values, method = "holm")
p.adjust(p_values, method = "hochberg")
p.adjust(p_values, method = "BH")
p.adjust(p_values, method = "hommel")
Graphical Approaches
library(gMCP)
graph <- matrix2graph(
m = matrix(c(
0, 0.5, 0.5, 0,
0.5, 0, 0, 0.5,
0.5, 0, 0, 0.5,
0, 0.5, 0.5, 0
), nrow = 4, byrow = TRUE),
w = c(0.5, 0.5, 0, 0)
)
nodeNames(graph) <- c("H1_OS", "H2_OS", "H1_PFS", "H2_PFS")
plot(graph)
gMCP(
graph = graph,
pvalues = c(0.01, 0.03, 0.02, 0.04),
alpha = 0.025
)
Gatekeeping Procedures
library(multcomp)
serial_gate <- function(p_primary, p_secondary, alpha = 0.05) {
if (p_primary < alpha) {
return(c(primary = p_primary < alpha, secondary = p_secondary < alpha))
} else {
return(c(primary = FALSE, secondary = FALSE))
}
}
Missing Data
Mixed Models for Repeated Measures (MMRM)
library(mmrm)
mmrm_fit <- mmrm(
formula = change ~ treatment * visit + baseline + us(visit | subject),
data = long_data,
weights = NULL,
reml = TRUE
)
library(emmeans)
emmeans(mmrm_fit, ~ treatment | visit)
emmeans(mmrm_fit, pairwise ~ treatment | visit)
Multiple Imputation
library(mice)
imp <- mice(
data = df,
m = 20,
method = "pmm",
maxit = 10
)
analyses <- with(imp, lm(outcome ~ treatment + covariates))
pooled <- pool(analyses)
summary(pooled)
Tipping Point Analysis
library(rbmi)
draws <- draws(
data = data,
data_ice = ice_data,
method = method_bayes(),
vars = vars
)
impute(draws, references = c("Control" = "Control", "Treatment" = "Control"))
Subgroup Analysis
Forest Plots for Subgroups
library(forestplot)
subgroup_effects <- df |>
group_by(subgroup) |>
summarise(
n = n(),
effect = mean(outcome[trt == 1]) - mean(outcome[trt == 0]),
se = sqrt(var(outcome[trt == 1])/sum(trt == 1) +
var(outcome[trt == 0])/sum(trt == 0)),
lower = effect - 1.96 * se,
upper = effect + 1.96 * se
)
forestplot(
labeltext = subgroup_effects$subgroup,
mean = subgroup_effects$effect,
lower = subgroup_effects$lower,
upper = subgroup_effects$upper,
zero = 0,
xlab = "Treatment Effect (95% CI)"
)
Interaction Tests
interaction_model <- lm(outcome ~ treatment * subgroup, data = df)
anova(interaction_model)
library(QI)
qi_test(outcome ~ treatment | subgroup, data = df)
Regulatory Considerations
ICH E9 Estimands Framework
mmrm_fit <- mmrm(
change ~ treatment * visit + baseline + us(visit | subject),
data = data_all_randomized
)
Missing-data methods should follow the estimand and the plausible missingness mechanism. Prespecify primary handling and sensitivity analyses rather than defaulting to complete-case analysis.
CONSORT Diagram
library(consort)
consort_plot(
data = trial_data,
orders = c(
Assessed = "Assessed for eligibility",
Randomized = "Randomized",
Arm_A = "Allocated to Arm A",
Arm_B = "Allocated to Arm B",
Lost_A = "Lost to follow-up (Arm A)",
Lost_B = "Lost to follow-up (Arm B)",
Analyzed_A = "Analyzed (Arm A)",
Analyzed_B = "Analyzed (Arm B)"
),
side_box = c("Excluded", "Discontinued_A", "Discontinued_B"),
cex = 0.8
)
Key Packages Summary
| Package | Purpose |
|---|
| pwr | Power analysis |
| gsDesign | Group sequential designs |
| rpact | Adaptive designs |
| blockrand | Randomization |
| gMCP | Graphical multiplicity |
| mmrm | MMRM analysis |
| mice | Multiple imputation |
| rbmi | Reference-based imputation |
| emmeans | Least squares means |
| consort | CONSORT diagrams |