AHTauDesign connects a clinically preferred average-hazard (AH) horizon to the trial design expected to support it. Its prospective workflow is calibrate–freeze–audit–decide–reverse-design.
Here is a synthetic example illustrating the main package functions. We consider two designs under the same working event-time model. The first shows that a clinical horizon may be supported but have insufficient power. The second shows how Stage 2 is activated after a support failure. This vignette is based on precomputed simulation results (base seed 202610).
The example is illustrative, not a clinical recommendation. All support-rule thresholds require calibration or validation for the intended trial setting.
Suppose a two-arm trial uses equal allocation, 48 months of uniform accrual, independent exponential event times, and administrative censoring. The working control-arm 36-month survival probability is 0.60, and the treatment hazard is 0.55 times the control hazard. The preferred AH horizon is \(\tau_{\mathrm{clin}}=36\) months; a 24-month horizon is an acceptable planning alternative only if Stage 1 fails.
Our two illustrative information states are:
| Design | Sample size | Additional follow-up | Clinical horizon |
|---|---|---|---|
| Reference design | 160 | 36 months | 36 months |
| Short-follow-up design | 160 | 6 months | 36 months |
A user may specify a different trial generator. This one is used only for the synthetic example:
trial_generator <- function(N, A, F, rate_C, rate_T) {
arm <- rep(c(0, 1), each = N / 2)
entry <- runif(N, 0, A)
event_time <- rexp(N, ifelse(arm == 0, rate_C, rate_T))
censor_time <- A + F - entry
data.frame(arm = arm, X = pmin(event_time, censor_time),
status = as.numeric(event_time <= censor_time))
}
null_generator <- function(N, A, F, rate_C) {
trial_generator(N, A, F, rate_C, rate_C)
}
rate_C <- -log(0.60) / 36
rate_T <- 0.55 * rate_CCalibration evaluates null-model Type I error, 95% Wald coverage, and support availability over a prespecified design-and-horizon region. Each simulated trial is evaluated at both horizons. Reliability classification is based on repeated-sampling behavior, not on the risk-set cutoffs being calibrated.
The example compares a relative-support-only candidate with one that also requires adequate absolute mean risk-set size. It includes a small, immature calibration setting deliberately, so the candidates can be distinguished.
calibration_designs <- data.frame(
scenario_id = c("small_early", "medium_early", "large_mature", "large_later"),
N = c(60, 120, 240, 360),
A = 48, F = c(0, 6, 24, 36), rate_C = -log(0.60) / 36
)
rules <- data.frame(
rule_id = c("relative_only", "relative_plus_count"),
rule_family = c("A", "B"),
p_risk_threshold = c(0.10, 0.10),
n_risk_threshold = c(NA, 30),
p_support_threshold = c(0.95, 0.95),
use_absolute_n = c(FALSE, TRUE)
)
cal <- simulate_calibration_null(
design_space = calibration_designs,
trial_generator = null_generator,
tau_grid = c(24, 36), n_sim = 10000, seed = 202610,
rules = rules,
reliability_criteria = list(type1_tolerance = 0.06,
coverage_tolerance = 0.94,
support_tolerance = 0.95)
)
# Review the candidate comparison before explicitly freezing a rule.
cal$rule_evaluation$performance
frozen <- freeze_support_rule(
diagnostic = cal$diagnostic, rules = rules,
rule_id = "relative_plus_count",
rationale = "Selected from qualifying rules after UPR/APR review.",
calibration_scope = list(designs = calibration_designs,
tau_grid = c(24, 36),
null_model = "equal exponential event hazards"),
provenance = list(source = "synthetic vignette simulation",
inference_conditioning = "support_valid"),
simulation_settings = cal$settings
)| n_total | n_reliable | n_unreliable | n_indeterminate |
|---|---|---|---|
| 8 | 6 | 1 | 1 |
| rule_id | n_pass | pass_reliable | pass_unreliable | pass_indeterminate | UPR_conservative | APR_confirmed |
|---|---|---|---|---|---|---|
| relative_only | 8 | 6 | 1 | 1 | 0.25 | 1.000 |
| relative_plus_count | 4 | 4 | 0 | 0 | 0.00 | 0.667 |
| selected_rule | p_risk_min | n_risk_min | p_support_min |
|---|---|---|---|
| relative_plus_count | 0.1 | 30 | 0.95 |
The candidate rule uses p_support_threshold, while
calibration reliability uses support_tolerance for support
availability. The displayed reliability labels and rule-performance
measures come from the precomputed null simulations; no label was
assigned by hand.
At the reference design, Stage 1 checks whether the fixed 36-month horizon satisfies the frozen rule. Overall empirical power is calculated separately, with unavailable analyses counted as nonrejections. Passing Stage 1 does not imply that a formal power target is met.
raw_reference <- simulate_design(
trial_generator = trial_generator,
n_sim = 1000,
tau_grid = c(24, 36),
trial_args = list(N = 160, A = 48, F = 36,
rate_C = rate_C, rate_T = rate_T),
scenario_id = "reference",
seed = 202611
)
audit_reference <- stage1_audit(
raw_reference, thresholds = frozen$thresholds
)| tau | n_sim | min_mean_p_risk | min_mean_n_risk | p_support | Stage1_pass | power_overall |
|---|---|---|---|---|---|---|
| 36 | 1000 | 0.598 | 47.845 | 1 | TRUE | 0.702 |
In this example, the clinical horizon is retained because it passes the support audit, even though its empirical power is below the illustrative 0.80 target. This is why supportability and power must be distinguished. No Stage 2 selection is made for the reference design.
Now consider the same sample size and event-time model, but with only six months of additional follow-up. The clinical horizon is audited first. If it fails, the candidate set is limited to Stage 1-supported horizons inside the prespecified clinical window. The candidate with the largest finite, positive utility is selected; independent simulations then evaluate the two fixed horizons.
raw_short <- simulate_design(
trial_generator = trial_generator,
n_sim = 1000,
tau_grid = c(24, 36),
trial_args = list(N = 160, A = 48, F = 6,
rate_C = rate_C, rate_T = rate_T),
scenario_id = "short_followup",
seed = 202612
)
audit_short <- stage1_audit(raw_short, thresholds = frozen$thresholds)
selection <- stage2_select(audit_short$scenario_summary,
tau_clin = 36, clinical_window = c(24, 36))
# Generate fresh trials; the selected horizon is held fixed.
raw_eval <- simulate_design(
trial_generator = trial_generator,
n_sim = 1000,
tau_grid = c(36, selection$final_decision_summary$tau_star),
trial_args = list(N = 160, A = 48, F = 6,
rate_C = rate_C, rate_T = rate_T),
scenario_id = "independent_evaluation",
seed = 202613
)
audit_eval <- stage1_audit(raw_eval, thresholds = frozen$thresholds)
independent_eval <- stage2_evaluate(selection, audit_eval$scenario_summary)
independent_eval$power_difference| tau | min_mean_n_risk | p_support | Stage1_pass | within_clinical_window | utility_U | selected |
|---|---|---|---|---|---|---|
| 24 | 35.621 | 1 | TRUE | TRUE | 1.548 | TRUE |
| 36 | 17.927 | 1 | FALSE | TRUE | 1.716 | FALSE |
| clinical_horizon | chosen_horizon | decision |
|---|---|---|
| 36 | 24 | Utility-driven alternative |
| role | tau | Stage1_pass | p_support | power_overall |
|---|---|---|---|---|
| tau_clin | 36 | FALSE | 1 | 0.559 |
| tau_star | 24 | TRUE | 1 | 0.514 |
| power_difference |
|---|
| -0.045 |
The reported power_difference retains its sign and
compares power at two different AH estimands; it is not
a precision comparison for one estimand.
Changing \(\tau\) changes the estimand, not just the precision. The Stage 2 selected horizon is a planning alternative, not an automatic clinical replacement for the preferred horizon.
Part II addresses the resource question for the same 36-month estimand: what sample size would meet both Stage 1 and an illustrative power target of 0.80? The sample-size search uses a coarse grid, a finer local grid, and independent validation. Only validated overall power receives isotonic stabilization; Stage 1 support diagnostics remain unsmoothed.
part2_12 <- find_n_req(
F_followup = 12, tau = 36, thresholds = frozen$thresholds,
trial_generator = trial_generator,
trial_args = list(A = 48, rate_C = rate_C, rate_T = rate_T),
N_lower = 160, N_upper = 600, coarse_step = 20, fine_step = 10,
n_coarse = 200, n_fine = 400, n_validate = 2000,
power_target = 0.80,
search_seed = 202614, validation_seed = 202615
)
part2_36 <- find_n_req(
F_followup = 36, tau = 36, thresholds = frozen$thresholds,
trial_generator = trial_generator,
trial_args = list(A = 48, rate_C = rate_C, rate_T = rate_T),
N_lower = 160, N_upper = 600, coarse_step = 20, fine_step = 10,
n_coarse = 200, n_fine = 400, n_validate = 2000,
power_target = 0.80,
search_seed = 202616, validation_seed = 202617
)| F_followup | tau | N_req | status | power_target | raw_power | isotonic_power |
|---|---|---|---|---|---|---|
| 12 | 36 | 270 | validated_boundary | 0.8 | 0.800 | 0.800 |
| 36 | 36 | 230 | validated_boundary | 0.8 | 0.816 | 0.816 |
These are simulation-based planning estimates on a 10-participant grid, not exact analytical minima. The illustrative simulation counts are too modest for definitive sample-size recommendations in real trials.
planning_report() combines completed results and their
provenance. It does not simulate trials, rerank
horizons, or rerun the sample-size search.
report_reference <- planning_report(
trial_spec = list(scenario_id = "reference", N = 160, A = 48, F = 36,
tau_clin = 36, clinical_window = c(24, 36),
power_target = 0.80, survival_model = "exponential"),
frozen_rule = frozen,
stage1_result = audit_reference,
part2_results = list(F12 = part2_12, F36 = part2_36),
simulation_diagnostics = list(base_seed = 202610,
stage1_seed = 202611)
)
report_alternative <- planning_report(
trial_spec = list(scenario_id = "short_followup", N = 160, A = 48,
F = 6, tau_clin = 36, clinical_window = c(24, 36),
survival_model = "exponential"),
frozen_rule = frozen,
stage1_result = audit_short,
stage2_result = selection,
stage2_evaluation = independent_eval,
simulation_diagnostics = list(selection_seed = 202612,
independent_evaluation_seed = 202613)
)| design | Stage1_pass | power_overall | Stage2_decision | final_horizon |
|---|---|---|---|---|
| Reference | TRUE | 0.702 | Not entered: Stage 1 Pass | 36 |
| Short follow-up | FALSE | 0.523 | Utility-driven alternative | 24 |
The full report objects contain frozen calibration evidence, support and power diagnostics, conditional selection records, independent evaluation, Part II validation traces, and simulation metadata. The tables above display only the key planning results rather than thousands of replicate-level rows.