This example demonstrates a simplified Seamless Phase II/III Adaptive Dose-Selection Design.
The trial consists of two stages:
The confirmatory hypothesis is:
\[ H_0: p_{\text{selected}} \leq p_{\text{control}} \]
versus
\[ H_1: p_{\text{selected}} > p_{\text{control}} \]
A one-sided significance level of
\[ \alpha = 0.025 \]
is used.
set.seed(123)
alpha <- 0.025
doses <- c(
"50mg",
"100mg",
"200mg"
)
n_doses <- length(doses)
# Stage 1 sample size per arm
n_stage1_per_arm <- 100
# Stage 2 sample size per arm
n_stage2_per_arm <- 150
These probabilities are used only to generate simulated trial data.
response_prob <- c(
"Control" = 0.40,
"50mg" = 0.48,
"100mg" = 0.60,
"200mg" = 0.65
)
tox_prob <- c(
"Control" = 0.05,
"50mg" = 0.07,
"100mg" = 0.10,
"200mg" = 0.30
)
Stage 1 includes four treatment groups:
stage1_arms <- c(
"Control",
doses
)
stage1_data <- data.frame()
for (arm in stage1_arms) {
response <- rbinom(
n_stage1_per_arm,
1,
response_prob[arm]
)
toxicity <- rbinom(
n_stage1_per_arm,
1,
tox_prob[arm]
)
tmp <- data.frame(
stage = 1,
treatment = arm,
response = response,
toxicity = toxicity
)
stage1_data <- rbind(
stage1_data,
tmp
)
}
Calculate the observed response rate and toxicity rate for each treatment group.
stage1_summary <- aggregate(
cbind(response, toxicity) ~ treatment,
data = stage1_data,
mean
)
print(stage1_summary)
## treatment response toxicity
## 1 100mg 0.61 0.10
## 2 200mg 0.64 0.27
## 3 50mg 0.45 0.09
## 4 Control 0.40 0.05
control_stage1 <- subset(
stage1_data,
treatment == "Control"
)
rate_control_stage1 <- mean(
control_stage1$response
)
rate_control_stage1
## [1] 0.4
For each dose, test:
\[ H_0: p_{\text{dose}} \leq p_{\text{control}} \]
versus
\[ H_1: p_{\text{dose}} > p_{\text{control}} \]
A one-sided two-sample proportion test is used.
dose_results <- data.frame()
for (d in doses) {
dat_dose <- subset(
stage1_data,
treatment == d
)
rate_response <- mean(
dat_dose$response
)
rate_toxicity <- mean(
dat_dose$toxicity
)
test <- prop.test(
x = c(
sum(dat_dose$response),
sum(control_stage1$response)
),
n = c(
nrow(dat_dose),
nrow(control_stage1)
),
alternative = "greater",
correct = FALSE
)
dose_results <- rbind(
dose_results,
data.frame(
dose = d,
response_rate = rate_response,
toxicity_rate = rate_toxicity,
p_value_raw = test$p.value
)
)
}
print(dose_results)
## dose response_rate toxicity_rate p_value_raw
## 1 50mg 0.45 0.09 0.2372431856
## 2 100mg 0.61 0.10 0.0014890134
## 3 200mg 0.64 0.27 0.0003408551
A dose is considered acceptable if:
\[ \text{Response Rate} > \text{Control Response Rate} \]
and
\[ \text{Toxicity Rate} < 0.20 \]
acceptable <- subset(
dose_results,
response_rate > rate_control_stage1 &
toxicity_rate < 0.20
)
print(acceptable)
## dose response_rate toxicity_rate p_value_raw
## 1 50mg 0.45 0.09 0.237243186
## 2 100mg 0.61 0.10 0.001489013
If no dose meets both criteria, the trial stops for futility or safety.
if (nrow(acceptable) == 0) {
stop(
"No acceptable dose. Trial stops for futility/safety."
)
}
Among acceptable doses:
acceptable <- acceptable[
order(
-acceptable$response_rate,
acceptable$toxicity_rate
),
]
selected_dose <- acceptable$dose[1]
cat(
"\n====================================\n"
)
##
## ====================================
cat(
"Selected dose:",
selected_dose,
"\n"
)
## Selected dose: 100mg
cat(
"====================================\n"
)
## ====================================
p1_raw <- dose_results$p_value_raw[
dose_results$dose == selected_dose
]
p1_raw
## [1] 0.001489013
Because three doses were examined before selecting one, a simple Bonferroni adjustment is applied:
$$ p_{1,} =
(1, 3p_1) $$
More generally:
$$ p_{1,} =
(1, Kp_1) $$
where (K) is the number of candidate doses.
p1_adjusted <- min(
1,
n_doses * p1_raw
)
cat(
"\nStage 1 raw p-value =",
round(p1_raw, 5),
"\n"
)
##
## Stage 1 raw p-value = 0.00149
cat(
"Stage 1 multiplicity-adjusted p-value =",
round(p1_adjusted, 5),
"\n"
)
## Stage 1 multiplicity-adjusted p-value = 0.00447
Only the selected dose and Control continue into Stage 2.
Stage 2 uses newly enrolled patients.
response_selected_stage2 <- rbinom(
n_stage2_per_arm,
1,
response_prob[selected_dose]
)
response_control_stage2 <- rbinom(
n_stage2_per_arm,
1,
response_prob["Control"]
)
Create the Stage 2 dataset.
stage2_data <- data.frame(
stage = 2,
treatment = c(
rep(
selected_dose,
n_stage2_per_arm
),
rep(
"Control",
n_stage2_per_arm
)
),
response = c(
response_selected_stage2,
response_control_stage2
)
)
stage2_summary <- aggregate(
response ~ treatment,
data = stage2_data,
mean
)
print(stage2_summary)
## treatment response
## 1 100mg 0.5933333
## 2 Control 0.3400000
The Stage 2 hypothesis is:
\[ H_0: p_{\text{selected}} \leq p_{\text{control}} \]
versus
\[ H_1: p_{\text{selected}} > p_{\text{control}} \]
test_stage2 <- prop.test(
x = c(
sum(response_selected_stage2),
sum(response_control_stage2)
),
n = c(
n_stage2_per_arm,
n_stage2_per_arm
),
alternative = "greater",
correct = FALSE
)
p2 <- test_stage2$p.value
cat(
"\nStage 2 p-value =",
round(p2, 5),
"\n"
)
##
## Stage 2 p-value = 1e-05
Rather than simply pooling Stage 1 and Stage 2 data, evidence from both stages is combined using an inverse-normal combination test.
First, convert the stage-specific p-values into Z statistics:
\[ Z_1 = \Phi^{-1} \left( 1-p_{1,\text{adjusted}} \right) \]
and
$$ Z_2 =
^{-1} ( 1-p_2 ) $$
where (^{-1}) is the standard normal quantile function.
z1 <- qnorm(
1 - p1_adjusted
)
z2 <- qnorm(
1 - p2
)
z1
## [1] 2.614566
z2
## [1] 4.397645
Stage 1 contributes:
\[ 100 + 100 = 200 \]
patients from the selected-dose and Control groups.
Stage 2 contributes:
\[ 150 + 150 = 300 \]
patients.
Weights are defined as:
\[ w_1 = \sqrt{ \frac{I_1} {I_1 + I_2} } \]
and
\[ w_2 = \sqrt{ \frac{I_2} {I_1 + I_2} } \]
so that:
\[ w_1^2 + w_2^2 = 1 \]
In this simplified example, sample size is used as a proxy for statistical information.
info_stage1 <-
2 * n_stage1_per_arm
info_stage2 <-
2 * n_stage2_per_arm
w1 <- sqrt(
info_stage1 /
(info_stage1 + info_stage2)
)
w2 <- sqrt(
info_stage2 /
(info_stage1 + info_stage2)
)
cat(
"\nStage 1 weight =",
round(w1, 3),
"\n"
)
##
## Stage 1 weight = 0.632
cat(
"Stage 2 weight =",
round(w2, 3),
"\n"
)
## Stage 2 weight = 0.775
The combined test statistic is:
\[ Z_{\text{comb}} = w_1 Z_1 + w_2 Z_2 \]
z_comb <- (
w1 * z1 +
w2 * z2
)
z_comb
## [1] 5.059998
The final one-sided combined p-value is:
\[ p_{\text{comb}} = 1-\Phi \left( Z_{\text{comb}} \right) \]
p_comb <- 1 - pnorm(
z_comb
)
p_comb
## [1] 2.096299e-07
The trial is considered successful if:
\[ p_{\text{comb}} < 0.025 \]
if (p_comb < alpha) {
decision <- paste(
"SUCCESS:",
selected_dose,
"demonstrated superiority to Control."
)
} else {
decision <- paste(
"NOT SUCCESSFUL:",
selected_dose,
"did not demonstrate superiority to Control."
)
}
cat(
"\nFinal Decision:",
decision,
"\n"
)
##
## Final Decision: SUCCESS: 100mg demonstrated superiority to Control.
cat(
"\n====================================\n"
)
##
## ====================================
cat(
"Seamless Phase II/III Trial Summary\n"
)
## Seamless Phase II/III Trial Summary
cat(
"====================================\n"
)
## ====================================
cat(
"Selected dose:",
selected_dose,
"\n"
)
## Selected dose: 100mg
cat(
"Stage 1 adjusted p-value:",
round(p1_adjusted, 5),
"\n"
)
## Stage 1 adjusted p-value: 0.00447
cat(
"Stage 2 p-value:",
round(p2, 5),
"\n"
)
## Stage 2 p-value: 1e-05
cat(
"Stage 1 weight:",
round(w1, 3),
"\n"
)
## Stage 1 weight: 0.632
cat(
"Stage 2 weight:",
round(w2, 3),
"\n"
)
## Stage 2 weight: 0.775
cat(
"Combined Z statistic:",
round(z_comb, 3),
"\n"
)
## Combined Z statistic: 5.06
cat(
"Combined p-value:",
round(p_comb, 5),
"\n"
)
## Combined p-value: 0
cat(
"Final decision:",
decision,
"\n"
)
## Final decision: SUCCESS: 100mg demonstrated superiority to Control.
This simplified seamless design illustrates the following workflow:
\[ \text{Stage 1 Dose Finding} \rightarrow \text{Dose Selection} \rightarrow \text{Stage 2 Confirmation} \rightarrow \text{Combination Test} \]
Stage 1 data contribute to the final confirmatory analysis rather than being discarded.
However, because the selected dose is chosen based on interim data, the final analysis cannot simply treat the selected dose as if it had been prespecified from the beginning.
Multiplicity and adaptive selection must therefore be addressed.
In this example:
For a real confirmatory seamless Phase II/III trial, the adaptation rules, multiplicity procedure, combination-test weights, stopping rules, and final testing strategy should be prespecified. Extensive simulation would generally be required to evaluate operating characteristics such as Type I error, power, dose-selection probability, estimation bias, and expected sample size.