Design-informed truncation time selection

Xinyue Zhao, Haitao Pan

Introduction

This vignette contains two kinds of output, and it is important to distinguish them.

  1. A quick toy example, executed when this vignette is built. It uses n_sim = 10, n_boot = 10 and a coarse tau_grid so that the vignette builds in seconds. It illustrates the workflow and function usage only. Its numerical results are based on far too few simulated trials and bootstrap resamples to be stable, and should not be interpreted as simulation or inferential results.

  2. Precomputed full-setting results, using 100 simulated trials and 100 bootstrap resamples per trial. The complete code producing each result is shown but not evaluated at build time; the stored output is displayed instead. All substantive statements in the text below refer to these results.

Every precomputed result is reproducible after a single set.seed() call with seed 202608. Precomputed blocks are marked Precomputed; the toy block is marked Toy example.

Quick start: a runnable toy example

Toy example. n_sim = 10, n_boot = 10, coarse tau_grid. These settings exist so the vignette builds quickly. The feasibility classifications and selected truncation time produced here are unstable and are not the results discussed in the rest of this vignette.

set.seed(202608)
toy_result <- twostage_tau(
  N = 60, A = 24, F_followup = 6,
  tau_clin = 24,
  tau_grid = seq(6, 24, by = 6),
  effect_model = list(type = "ph", lambda0 = -log(0.5) / 24, HR = 0.5),
  delta = 12, epsilon = 0.05,
  n_sim = 10, n_boot = 10)

toy_result
#> Two-Stage Truncation-Time Selection
#> -----------------------------------
#> Stage 1 (tau_clin = 24): Fail
#> Final selection: tau* = 12
#> 
#> Selected tau* = 12, replacing the clinically proposed tau_clin =
#> 24. tau_clin did not meet the feasibility criteria (Stage 1 status:
#> Fail), so the best feasible candidate within the clinical window
#> was selected.

For real design work, use the package defaults (n_sim = 100, n_boot = 100) or larger, with a finer tau_grid. Everything below uses those settings.

The planned design

We use a single running example: a two-arm trial with 60 participants, 24 months of accrual, and 6 additional months of follow-up, under a proportional-hazards effect with a hazard ratio of 0.5.

effect_model <- list(type = "ph", lambda0 = -log(0.5) / 24, HR = 0.5)

Stage 1: assessing the clinically proposed truncation time

The clinical team has proposed a truncation time of 24 months.

set.seed(202608)
assess_tau(
  N = 60, A = 24, F_followup = 6,
  tau = 24,
  effect_model = effect_model,
  n_sim = 100, n_boot = 100)
#> Truncation-Time Feasibility Assessment
#> --------------------------------------
#> Proposed truncation time: tau = 24
#> Feasibility status:       Fail
#> 
#>                                       metric  value pass_threshold
#>          Expected total risk-set size at tau    8.9             30
#>    Expected control-arm risk-set size at tau    3.8             10
#>  Expected treatment-arm risk-set size at tau    5.1             10
#>                   Risk-set proportion at tau 0.1477            0.1
#>                     Estimability probability 0.9700            0.9
#>                                  Feasibility   Fail               
#>  fail_threshold
#>              20
#>               5
#>               5
#>             0.1
#>             0.8
#> 

Precomputed (100 simulations, 100 bootstrap resamples).

This output can be summarized as follows:

\(\quad\) For a trial with (N = 60), a 24-month accrual period, and only 6 months of additional follow-up, the statistic at 24 months could be successfully computed in all simulation runs. However, only approximately nine patients in total were expected to remain in the risk set at that time point, with about four to five patients in each treatment arm. This provides severely insufficient statistical support. Therefore, the clinically specified truncation time, (\(\tau_{clin} = 24\)) months, was deemed infeasible. Importantly, this failure was driven primarily by the limited follow-up design, rather than by an inadequate treatment effect or a failure of the computational procedure.

How can (\(\tau = 24\)) months be made feasible?

There are three main ways to improve the feasibility of using \(\tau = 24\) months.

Approach 1: increase the additional follow-up period

The most direct solution is to increase the additional follow-up period. For example, if \[F = 6 \] is increased to approximately \[F = 24 ,\] then even the last enrolled patient would have the opportunity to complete 24 months of follow-up.

\(\quad\) Under this design, nearly all patients could potentially contribute to the risk set at 24 months. The expected total risk-set size would therefore increase substantially, from approximately 9 patients to approximately 36 patients: \[30 \times 0.5 + 30 \times 0.707 \approx 36.2.\]

\(\quad\) Thus, extending the additional follow-up period is the most direct and efficient way to make (\(\tau = 24\)) months feasible.

set.seed(202608)
assess_tau(N = 60, A = 24, F_followup = 24, tau = 24,
           effect_model = effect_model,
           n_sim = 100, n_boot = 100)
#> Truncation-Time Feasibility Assessment
#> --------------------------------------
#> Proposed truncation time: tau = 24
#> Feasibility status:       Pass
#> 
#>                                       metric  value pass_threshold
#>          Expected total risk-set size at tau   36.3             30
#>    Expected control-arm risk-set size at tau   15.2             10
#>  Expected treatment-arm risk-set size at tau   21.1             10
#>                   Risk-set proportion at tau 0.6043            0.1
#>                     Estimability probability 1.0000            0.9
#>                                  Feasibility   Pass               
#>  fail_threshold
#>              20
#>               5
#>               5
#>             0.1
#>             0.8
#> 

Precomputed (100 simulations, 100 bootstrap resamples).

Approach 2: select an earlier truncation time

Another solution is to use an earlier truncation time.

Under the current design:

Therefore, optimize_tau() would likely identify a feasible or optimal truncation time substantially earlier than 24 months, possibly some where within the range of 12 to 18 months.

set.seed(202608)
assess_tau(N = 60, A = 24, F_followup = 6, tau = 12,
           effect_model = effect_model,
           n_sim = 100, n_boot = 100)
#> Truncation-Time Feasibility Assessment
#> --------------------------------------
#> Proposed truncation time: tau = 12
#> Feasibility status:       Pass
#> 
#>                                       metric  value pass_threshold
#>          Expected total risk-set size at tau   34.7             30
#>    Expected control-arm risk-set size at tau   15.8             10
#>  Expected treatment-arm risk-set size at tau   19.0             10
#>                   Risk-set proportion at tau 0.5785            0.1
#>                     Estimability probability 1.0000            0.9
#>                                  Feasibility   Pass               
#>  fail_threshold
#>              20
#>               5
#>               5
#>             0.1
#>             0.8
#> 

Precomputed (100 simulations, 100 bootstrap resamples).

This approach improves statistical support at the cost of evaluating treatment effects over a shorter time horizon.

Approach 3: increase the sample size

Increasing the sample size also increases the number of patients remaining in the risk set at 24 months.

\(\quad\) Under the current design, the expected proportion of patients remaining in the risk set at 24 months is only approximately \[0.15.\]

\(\quad\) Therefore, to obtain an expected risk-set of approximately 30 patients, the required total sample size would be roughly \[N \approx \frac{30}{0.15} \approx 200.\]

set.seed(202608)
assess_tau(N = 200, A = 24, F_followup = 6, tau = 24,
           effect_model = effect_model,
           n_sim = 100, n_boot = 100)
#> Truncation-Time Feasibility Assessment
#> --------------------------------------
#> Proposed truncation time: tau = 24
#> Feasibility status:       Borderline
#> 
#>                                       metric      value pass_threshold
#>          Expected total risk-set size at tau       29.9             30
#>    Expected control-arm risk-set size at tau       12.4             10
#>  Expected treatment-arm risk-set size at tau       17.4             10
#>                   Risk-set proportion at tau     0.1492            0.1
#>                     Estimability probability     1.0000            0.9
#>                                  Feasibility Borderline               
#>  fail_threshold
#>              20
#>               5
#>               5
#>             0.1
#>             0.8
#> 

Precomputed (100 simulations, 100 bootstrap resamples).

\(\quad\) The rough calculation suggests that approximately (\(N = 200\)) patients would be needed to obtain an expected risk-set size near 30 at (\(\tau = 24\)) months. In the Monte Carlo assessment, (\(N = 200\)) produced an expected total risk-set size of 29.9 and was classified as Borderline. This near-threshold result illustrates that increasing sample size can improve support, but sample-size increase alone may be inefficient when the fundamental limitation is that few patients have sufficient follow-up to contribute information at the 24-month truncation time.

Customizing the hard-failure rules

The built-in rules can be inspected directly:

default_fail_thresholds()
#> $total_risk
#> [1] 20
#> 
#> $arm_risk
#> [1] 5
#> 
#> $riskset_prop
#> [1] 0.1
#> 
#> $estimability
#> [1] 0.8

A user running an exploratory pilot may accept weaker support, and can override any subset of them:

set.seed(202608)
assess_tau(N = 60, A = 24, F_followup = 6, tau = 24,
           effect_model = effect_model,
           fail_thresholds = list(total_risk = 5, arm_risk = 2),
           n_sim = 100, n_boot = 100)
#> Truncation-Time Feasibility Assessment
#> --------------------------------------
#> Proposed truncation time: tau = 24
#> Feasibility status:       Borderline
#> 
#>                                       metric      value pass_threshold
#>          Expected total risk-set size at tau        8.9             30
#>    Expected control-arm risk-set size at tau        3.8             10
#>  Expected treatment-arm risk-set size at tau        5.1             10
#>                   Risk-set proportion at tau     0.1477            0.1
#>                     Estimability probability     0.9700            0.9
#>                                  Feasibility Borderline               
#>  fail_threshold
#>               5
#>               2
#>               2
#>             0.1
#>             0.8
#> 

Precomputed (100 simulations, 100 bootstrap resamples).

Unspecified elements keep their defaults. Relaxing a rule changes only the classification, not the underlying statistical support.

Function “optimize_tau()”: feasibility-constrained optimization

set.seed(202608)
optimize_tau(
  N = 60, A = 24, F_followup = 6,
  tau_clin = 24,
  tau_grid = seq(6, 36, by = 3),
  effect_model = effect_model,
  delta = 12, epsilon = 0.05,
  n_sim = 100, n_boot = 100)
#> Feasibility-Constrained Truncation-Time Optimization
#> ----------------------------------------------------
#> Stage 1 (tau_clin = 24): Fail
#> Number of admissible candidates: 1
#> Admissible candidate values:     12
#> Final selection:                 tau* = 12
#> 
#> Selected tau* = 12, replacing the clinically proposed tau_clin =
#> 24. tau_clin did not meet the feasibility criteria (Stage 1 status:
#> Fail), so the best feasible candidate within the clinical window
#> was selected.
#> 
#> Use $candidate_summary for the full tau-by-tau table.

Precomputed (100 simulations, 100 bootstrap resamples).

\(\quad\) The clinically proposed truncation time of 24 months failed the prespecified feasibility criteria because of inadequate risk-set support. Among the candidate truncation times satisfying both the feasibility requirements and the prespecified 12-month clinical-distance constraint, 12 months was the only admissible candidate and was therefore selected as the final truncation time.

The complete two-stage workflow

set.seed(202608)
result <- twostage_tau(
  N = 60, A = 24, F_followup = 6,
  tau_clin = 24,
  tau_grid = seq(6, 36, by = 3),
  effect_model = effect_model,
  delta = 12, epsilon = 0.05,
  n_sim = 100, n_boot = 100)

summary(result)
#> Two-Stage Truncation-Time Selection
#> -----------------------------------
#> Stage 1 (tau_clin = 24): Fail
#> Final selection: tau* = 12
#> 
#> Selected tau* = 12, replacing the clinically proposed tau_clin =
#> 24. tau_clin did not meet the feasibility criteria (Stage 1 status:
#> Fail), so the best feasible candidate within the clinical window
#> was selected.
#> 
#> Candidate-level selection table:
#>  tau E_Y_total E_Y_ctr E_Y_trt p_riskset p_estimable mean_log_AH_ratio
#>    6      52.8    25.2    27.6    0.8797        0.95             -0.71
#>    9      43.4    20.1    23.2    0.7232        0.99             -0.86
#>   12      34.7    15.8    19.0    0.5787        1.00             -0.87
#>   15      27.7    12.3    15.4    0.4613        1.00             -0.85
#>   18      20.7     8.9    11.8    0.3445        1.00             -0.85
#>   21      15.0     6.3     8.7    0.2503        1.00             -0.82
#>   24       9.4     4.0     5.5    0.1573        0.98             -0.78
#>   27       4.5     1.9     2.6    0.0748        0.73             -0.78
#>   30       0.0     0.0     0.0    0.0000        0.00                NA
#>   33       0.0     0.0     0.0    0.0000        0.00                NA
#>   36       0.0     0.0     0.0    0.0000        0.00                NA
#>  empirical_SD mean_boot_SE power_overall feasibility dist_to_clin within_window
#>          0.83         0.74          0.07        Pass           18         FALSE
#>          0.74         0.70          0.25        Pass           15         FALSE
#>          0.62         0.65          0.26        Pass           12          TRUE
#>          0.63         0.62          0.30  Borderline            9          TRUE
#>          0.61         0.61          0.40  Borderline            6          TRUE
#>          0.61         0.61          0.37        Fail            3          TRUE
#>          0.57         0.59          0.24        Fail            0          TRUE
#>          0.65         0.61          0.09        Fail            3          TRUE
#>            NA           NA          0.00        Fail            6          TRUE
#>            NA           NA          0.00        Fail            9          TRUE
#>            NA           NA          0.00        Fail           12          TRUE
#>  utility admissible selected
#>     0.85      FALSE    FALSE
#>     1.16      FALSE    FALSE
#>     1.41       TRUE     TRUE
#>     1.36      FALSE    FALSE
#>     1.39      FALSE    FALSE
#>     1.36      FALSE    FALSE
#>     1.38      FALSE    FALSE
#>     1.21      FALSE    FALSE
#>       NA      FALSE    FALSE
#>       NA      FALSE    FALSE
#>       NA      FALSE    FALSE
#> 
#> Final decision summary:
#>                                   quantity            result
#>                   Clinical time (tau_clin)                24
#>            Stage 1 status of clinical time              Fail
#>        Clinical-distance tolerance (delta)                12
#>                         Pass candidate set          6, 9, 12
#>            Number of admissible candidates                 1
#>                Admissible candidate values                12
#>  Preliminary optimal candidate (tau_tilde)                12
#>              Retention tolerance (epsilon)              0.05
#>                     Clinical time retained    Not applicable
#>                 Final selected time (tau*)                12
#>                              Decision type Constraint-driven
#>                        Absolute power gain                NA
#>               Relative utility improvement                NA
#> 
#> Independent evaluation table:
#>  tau_role tau E_Y_total E_Y_ctr E_Y_trt p_riskset p_estimable mean_log_AH_ratio
#>  tau_star  12      35.2    16.2    19.0    0.5863        0.99             -0.73
#>  tau_clin  24       8.9     3.8     5.1    0.1485        0.98             -0.80
#>  empirical_SD mean_boot_SE utility power_overall feasibility
#>          0.71         0.65    1.03          0.24        Pass
#>          0.60         0.60    1.34          0.24        Fail

Precomputed (100 simulations, 100 bootstrap resamples).

\(\quad\) Under the planned design with 60 participants, 24 months of accrual, and 6 additional months of follow-up, the clinically proposed truncation time of 24 months failed the Stage 1 feasibility assessment because of inadequate risk-set support. The truncation times of 6, 9, and 12 months passed the feasibility criteria, but only 12 months fell within the prespecified clinical-distance tolerance of 12 months from the proposed clinical time. Thus, 12 months was the only admissible candidate and was selected as the final truncation time. An independent evaluation confirmed that the selected 12-month time remained feasible, whereas the original 24-month clinical time remained infeasible.

plot(result)

Why is one table called the independent evaluation table?

\(\quad\) \(\tau^{\star}\) is chosen as the best of many candidates, so its performance in the selection table is subject to the winner’s curse. The independent evaluation therefore re-simulates fresh trials and evaluates only the clinical time and the already-fixed \(\tau^{\star}\), which gives unbiased estimates for the design that was selected.