Skip to contents

Simulates time-to-event trial data for one or two groups across many simulated trials, with piecewise accrual, piecewise-exponential survival and dropout, and optional subgroups defined by a prevalence specification. The entire generation pipeline (accrual, survival, dropout, derived columns, and two-group interleaving) runs in a single C++ kernel that materializes the output data frame once, avoiding intermediate R-level vector operations and copies. The random-number stream is consumed in the same order as a per-group reference implementation, so results are reproducible from seed.

Usage

simdata_fast(
  nsim = 1000,
  n,
  alloc = c(1, 1),
  a.time,
  a.rate = NULL,
  a.prop = NULL,
  e.hazard = NULL,
  e.median = NULL,
  e.time = NULL,
  d.hazard = NULL,
  d.median = NULL,
  d.time = NULL,
  seed = NULL,
  prevalence = NULL,
  fixed.alloc = FALSE,
  h01.hazard = NULL,
  h01.median = NULL,
  h01.time = NULL,
  h02.hazard = NULL,
  h02.median = NULL,
  h02.time = NULL,
  h12.hazard = NULL,
  h12.median = NULL,
  h12.time = NULL,
  switch.prop = NULL,
  h12.switch.hazard = NULL,
  h12.switch.median = NULL,
  h12.switch.time = NULL,
  switch.clock = "reset",
  stream = NULL
)

Arguments

nsim

Number of simulated trials.

n

Either a single total sample size (split by alloc), a length-two vector of per-group sample sizes, or, for a multi-arm trial, a vector of length greater than two giving the per-arm sample sizes (which requires a per-arm e.hazard or e.median list). When n is a per-arm vector, alloc is ignored.

alloc

A length-two allocation ratio (control, treatment), used when n is scalar. Supplying it explicitly requests a two-group simulation (see Details). The total is split in proportion to alloc and any rounding remainder goes to the group with the larger fractional share, so the two group sizes always add up to n.

a.time

A numeric vector of accrual-interval breakpoints.

a.rate

Absolute accrual rates (subjects per unit time), interpreted in one of two ways. With length length(a.time) - 1 the accrual period is fully specified and the rates must accrue exactly sum(n) subjects (an inconsistent total is an error). With length length(a.time) the final rate applies to an open last interval whose end time is computed so the total is sum(n). Supply exactly one of a.rate and a.prop.

a.prop

Accrual proportions, one per accrual interval (length length(a.time) - 1), giving the fraction of subjects enrolled in each interval. Values are normalized to sum to one and distribute the fixed total sum(n). Unlike a.rate this carries no rate, so the accrual period must be fully specified by a.time. Supply exactly one of a.rate and a.prop.

e.hazard

Survival hazard(s). A scalar or vector (piecewise, with e.time) shared by all groups and cells, a two-element list for two groups, or, with subgroups, a per-cell list (for one group, or as an element of the two-element list). See Details for how the number of groups is determined.

e.median

Survival median(s); an alternative to e.hazard.

e.time

Survival breakpoints for piecewise hazards (last element Inf).

d.hazard

Dropout hazard(s), same structure as e.hazard.

d.median

Dropout median(s); an alternative to d.hazard.

d.time

Dropout breakpoints for piecewise hazards.

seed

Optional integer seed for the dqrng generator. If NULL (default), the data are drawn from the current state of that generator, which set.seed() does not control: the state is initialized from R's random-number generator when dqrng is loaded, so repeated calls give different data, and processes forked from one R session (for example by parallel::mclapply()) start from the same state. Supply seed (and stream for parallel batches) for reproducible data.

prevalence

Optional subgroup prevalence specification (numeric vector, list of vectors, array, or a named control/treatment list for group-specific prevalence), for the single-endpoint and the illness-death models.

fixed.alloc

Logical; when TRUE subgroup sizes are deterministic rather than drawn. In a group of n subjects, each cell with probability p receives floor(n * p) subjects, and the remaining subjects are added one at a time to the cells in order, starting from the first cell.

h01.hazard

Transition hazard(s) for the non-terminal (intermediate) event (state 0 to state 1) in the illness-death model. A scalar or vector for one group, or a two-element list for two groups. Supplying any of h01.* or h02.* activates the illness-death model with two correlated endpoints, and is mutually exclusive with e.hazard / e.median.

h01.median

Median(s) for the intermediate event; an alternative to h01.hazard.

h01.time

Breakpoints for a piecewise h01.hazard (last element Inf).

h02.hazard

Transition hazard(s) for the terminal event without a prior intermediate event (state 0 to state 2). Same group and piecewise conventions as h01.hazard.

h02.median

Median(s) for the direct terminal event; an alternative to h02.hazard.

h02.time

Breakpoints for a piecewise h02.hazard.

h12.hazard

Transition hazard(s) for the terminal event after an intermediate event (state 1 to state 2) for subjects who do not switch. Defaults to h02.hazard, which gives the maximal-independence model of Fleischer, Gaschler-Markefski, and Bluhmki (2009): with constant hazards and no switching, the time to the intermediate event and the time to the terminal event are independent exponential variables, and their Theorem 1 gives the correlation of the two endpoints (PFS and OS) as the ratio of their medians. A different constant h12.hazard gives their more general model. With a piecewise h02.hazard the clock-reset h12 restarts the piecewise profile at the intermediate event.

h12.median

Median(s) for the post-event terminal event; an alternative to h12.hazard.

h12.time

Breakpoints for a piecewise h12.hazard, measured from the intermediate-event time (clock-reset).

switch.prop

Probability that a subject whose intermediate event is observed (on or before dropout) switches treatment at that event, a scalar or a two-element list (control, treatment). Defaults to zero (no switching); the treatment group is typically left at zero.

h12.switch.hazard

Transition hazard(s) from state 1 to state 2 for subjects who switch. Required when any switch.prop is positive.

h12.switch.median

Median(s) for the post-switch terminal event; an alternative to h12.switch.hazard.

h12.switch.time

Breakpoints for a piecewise h12.switch.hazard, measured from the switch (intermediate-event) time (clock-reset).

switch.clock

Time origin for the post-event hazards. Currently only "reset" (measured from the intermediate event) is implemented.

stream

Optional non-negative whole number selecting an independent dqrng random-number stream for the given seed; requires seed. A large simulation can be split into batches that are generated with the same seed and stream = 1, 2, ..., sequentially or in parallel, and the result of each batch does not depend on how the batches are distributed. With the default Xoroshiro128++ generator of dqrng, stream k starts k jumps of \(2^{64}\) draws ahead of the seeded state, so stream = 0 gives the same numbers as no stream. See the section on batches.

Value

A data.frame with nsim * sum(n) rows. The columns are sim, group, any subgroup columns, accrual_time, surv_time, dropout_time, tte, event, and calendar_time. In the illness-death model the columns are instead sim, group, any subgroup columns, accrual_time, e1_surv_time, e2_surv_time, dropout_time, e1_tte, e1_event, e2_tte, e2_event, e1_calendar_time, e2_calendar_time, intermediate, switched, and switch_time, where e1 is the first (state-0 exit) endpoint and e2 is the terminal endpoint. In oncology e1 is progression-free survival, e2 is overall survival, and intermediate flags progression. The column intermediate describes the latent process (an intermediate event before the terminal event, e1_surv_time < e2_surv_time), also when it would occur after dropout; the observed intermediate event is intermediate == 1 with e1_event == 1. A switch (switched == 1, at switch_time, the intermediate-event time) occurs only after an observed intermediate event.

Details

For each subject the observed time-to-event is tte = pmin(surv_time, dropout_time) and event is 1 when the survival time occurs first. The calendar time of the observed event is accrual_time + tte.

A hazard of zero in the last piece (for example a cure fraction, as in e.hazard = c(0.1, 0) with e.time = c(0, 24, Inf)) gives an infinite latent time to some subjects. A subject with neither a finite survival time nor a finite dropout time has tte = Inf and event = 0, and an analysis at a finite cutoff censors the subject at the cutoff.

The total enrolled is fixed at sum(n). With a.rate the rates are absolute (subjects per unit time): when the accrual period is fully specified the rates must accrue exactly sum(n), and when one extra rate is given the end of the final interval is solved so the total is met. With a.prop the values are relative proportions that distribute sum(n) across the fully specified intervals. Each accrual interval receives a deterministic number of subjects (the rate or proportion times the group total, rounded to keep the per-group total exact), placed uniformly within the interval.

Survival and dropout are exponential when a single hazard (or median) is supplied and piecewise-exponential when a vector is supplied together with the corresponding e.time or d.time breakpoints, whose last element must be Inf. Group-specific parameters are supplied as a two-element list (control first, treatment second).

When prevalence is supplied the trial has subgroups. A numeric vector defines a single factor; a list of numeric vectors defines several independent factors; a multi-dimensional array defines the joint distribution of correlated factors. Per-cell hazards may be supplied as a list with one element per cell (cells in column-major order, the first factor varying fastest). With fixed.alloc = TRUE the subgroup sizes are deterministic and the fixed labels are assigned to subjects in a random order within each simulated trial, so subgroup membership does not depend on the accrual time; otherwise subgroup membership is drawn from the prevalence distribution.

The number of groups is determined as follows. A length-two n always gives two groups. A scalar n gives two groups, with the total split by alloc, in any of these cases: alloc is supplied explicitly; the prevalence is group-specific; without subgroups, any of e.hazard, e.median, d.hazard, and d.median is a list; with subgroups, one of them is a length-two list with a list element (per-cell values within a group, as in list(list(0.10, 0.08, 0.06), 0.05)). Otherwise a scalar n gives one group, and with subgroups a list is read as one element per cell of that group. So with subgroups and a scalar n, e.hazard = list(0.10, 0.05) means per-cell hazards of a single group unless alloc is supplied, in which case it means per-group hazards. In a two-group simulation each specification is either shared by both groups (not a list) or a list of length two (control first), and each group's element may be a per-cell list when there are subgroups. Without subgroups, a length-two n requires at least one of the survival and dropout specifications to be such a list. The breakpoints e.time and d.time follow the same structure (per group in a two-group simulation, per cell in a one-group simulation with subgroups).

When n is a vector of length greater than two together with a per-arm survival list, the simulation is a multi-arm trial. Each arm is generated in turn with the validated single-group kernel over a common accrual window, and the arms are stacked into one data frame with a group column labeled 1 to length(n) in the order of n. Per-arm survival is supplied as an e.hazard or e.median list with one element per arm, and optional dropout as a shared value or a per-arm list through d.hazard or d.median. The arms share the master seed, so the result is reproducible. A multi-arm design is analyzed as a set of pairwise contrasts by subsetting the output to the control arm and one other arm and calling analysis_fast once per contrast, which pairwise_fast does for every experimental arm. Multi-arm mode does not support subgroups or the illness-death model, which remain two-group.

The illness-death model (activated by h01.* or h02.*) also accepts prevalence and fixed.alloc. The transition hazards (h01.*, h02.*, h12.*, h12.switch.*), switch.prop, and the dropout specification then follow the rules of e.hazard with subgroups: in a two-group simulation each is shared or a list with one element per group, and each group's element is shared or a list with one element per subgroup cell, for example h01.hazard = list(list(0.10, 0.06), 0.05); with a scalar n and no alloc, a list without list elements holds one element per cell of a single group. All subjects share one accrual process and the subgroup cells are assigned after accrual, so the subgroups enroll over the same calendar in proportion to their prevalence. This gives a mixture of illness-death models without generating the subgroups separately and adjusting their accrual rates. With a single cell (for example prevalence = 1) the data equal those without prevalence, with the subgroup column added.

Batches and parallel execution

The output has nsim * sum(n) rows, so memory rather than time limits the number of simulated trials in one call. A large study can be run in batches: each batch is generated with the same seed and its own stream, analyzed with analysis_fast, and only the analysis results are kept. Because each stream is an independent sequence of the dqrng generator, the data of batch b are the same whether the batches are run one after another or distributed over parallel workers (for example with parallel::mclapply() or the future framework), and whatever the number of workers. The simulation identifiers start at 1 in every batch, so they are renumbered before the batch results are combined. Functions that draw further random numbers, such as switch_fast, continue the same stream within a batch. The max-combo p-values of analysis_fast with four or more weights (or a two-sided test) are computed with R's own random-number generator, so a batch should also call set.seed() before the analysis for p-values that are identical across runs.

References

Fleischer, F., Gaschler-Markefski, B., & Bluhmki, E. (2009). A statistical model for the dependence between progression-free survival and overall survival. Statistics in Medicine, 28(21), 2669-2686.

Examples

# Batches from independent random-number streams: the simulations of each
# batch are renumbered before the analysis results are combined.
run_batch <- function(b, nsim = 20) {
  d <- simdata_fast(nsim = nsim, n = c(100, 100), a.time = c(0, 12),
                    a.rate = 200 / 12, e.median = list(12, 16),
                    seed = 2026, stream = b)
  r <- analysis_fast(d, control = 1, event.looks = 120)
  r$sim <- r$sim + (b - 1) * nsim
  r
}
res <- do.call(rbind, lapply(1:3, run_batch))
range(res$sim)
#> [1]  1 60

# One-group simulation, simple exponential, no dropout
df1 <- simdata_fast(
  nsim     = 100,
  n        = 50,
  a.time   = c(0, 12),
  a.rate   = 50 / 12,
  e.median = 18,
  seed     = 1
)
head(df1)
#>   sim group accrual_time surv_time dropout_time       tte event calendar_time
#> 1   1     1    0.3819659 71.464787          Inf 71.464787     1     71.846753
#> 2   1     1    4.3863151 50.157102          Inf 50.157102     1     54.543417
#> 3   1     1    1.8796625  1.363115          Inf  1.363115     1      3.242777
#> 4   1     1    4.1827192  4.388424          Inf  4.388424     1      8.571144
#> 5   1     1    2.6645346 27.617923          Inf 27.617923     1     30.282457
#> 6   1     1   10.4718355  9.350214          Inf  9.350214     1     19.822049

# Accrual rate with the final interval computed from the total: 20 per unit
# time for the first 12 units, then 30 per unit time until 500 are enrolled
df1b <- simdata_fast(
  nsim     = 100,
  n        = 500,
  a.time   = c(0, 12),
  a.rate   = c(20, 30),
  e.median = 18,
  seed     = 1
)
head(df1b)
#>   sim group accrual_time surv_time dropout_time      tte event calendar_time
#> 1   1     1    0.3819659  35.69947          Inf 35.69947     1      36.08143
#> 2   1     1    4.3863151  19.97617          Inf 19.97617     1      24.36248
#> 3   1     1    1.8796625  30.96883          Inf 30.96883     1      32.84849
#> 4   1     1    4.1827192  45.91519          Inf 45.91519     1      50.09791
#> 5   1     1    2.6645346  15.75625          Inf 15.75625     1      18.42079
#> 6   1     1   10.4718355  29.59420          Inf 29.59420     1      40.06603

# Accrual by proportion: 30% enrolled in [0, 6], 70% in [6, 12]
df1c <- simdata_fast(
  nsim     = 100,
  n        = 50,
  a.time   = c(0, 6, 12),
  a.prop   = c(0.3, 0.7),
  e.median = 18,
  seed     = 1
)
head(df1c)
#>   sim group accrual_time surv_time dropout_time       tte event calendar_time
#> 1   1     1    0.1909830 71.464787          Inf 71.464787     1     71.655770
#> 2   1     1    2.1931575 50.157102          Inf 50.157102     1     52.350260
#> 3   1     1    0.9398312  1.363115          Inf  1.363115     1      2.302946
#> 4   1     1    2.0913596  4.388424          Inf  4.388424     1      6.479784
#> 5   1     1    1.3322673 27.617923          Inf 27.617923     1     28.950190
#> 6   1     1    5.2359177  9.350214          Inf  9.350214     1     14.586131

# Two-group simulation, simple exponential, with dropout
df2 <- simdata_fast(
  nsim     = 100,
  n        = c(60, 60),
  a.time   = c(0, 6, 12),
  a.rate   = c(8, 12),
  e.median = list(18, 24),
  d.hazard = list(0.01, 0.01),
  seed     = 2
)
head(df2)
#>   sim group accrual_time surv_time dropout_time       tte event calendar_time
#> 1   1     1     3.356372 49.398104    338.23794 49.398104     1     52.754476
#> 2   1     1     4.642096 15.082166     68.86648 15.082166     1     19.724261
#> 3   1     1     1.722016 10.150280     34.10893 10.150280     1     11.872297
#> 4   1     1     2.639623 19.258733     52.33605 19.258733     1     21.898356
#> 5   1     1     1.618623  4.988974    111.28931  4.988974     1      6.607597
#> 6   1     1     4.107536 60.757715     46.11484 46.114837     0     50.222374

# One factor with three levels: single subgroup column
df3 <- simdata_fast(
  nsim       = 100,
  n          = c(150, 150),
  a.time     = c(0, 12),
  a.rate     = 300 / 12,
  e.hazard   = list(list(0.10, 0.08, 0.06), 0.05),
  prevalence = c(0.5, 0.3, 0.2),
  seed       = 3
)
head(df3)
#>   sim group subgroup accrual_time surv_time dropout_time       tte event
#> 1   1     1        1     1.777177  5.115282          Inf  5.115282     1
#> 2   1     1        2     6.189047 14.841849          Inf 14.841849     1
#> 3   1     1        1     2.772930 16.503950          Inf 16.503950     1
#> 4   1     1        3     5.795480  7.764437          Inf  7.764437     1
#> 5   1     1        3     6.756990 42.481282          Inf 42.481282     1
#> 6   1     1        2    11.895699  8.678879          Inf  8.678879     1
#>   calendar_time
#> 1       6.89246
#> 2      21.03090
#> 3      19.27688
#> 4      13.55992
#> 5      49.23827
#> 6      20.57458

# The same trial specified by the total sample size and the allocation ratio
df3b <- simdata_fast(
  nsim       = 100,
  n          = 300,
  alloc      = c(1, 1),
  a.time     = c(0, 12),
  a.rate     = 300 / 12,
  e.hazard   = list(list(0.10, 0.08, 0.06), 0.05),
  prevalence = c(0.5, 0.3, 0.2),
  seed       = 3
)
identical(df3, df3b)
#> [1] TRUE

# Two independent factors (2 x 2): columns subgroup1 and subgroup2.
# Four cells in column-major order: (1,1), (2,1), (1,2), (2,2).
df4 <- simdata_fast(
  nsim       = 100,
  n          = 200,
  a.time     = c(0, 12),
  a.rate     = 200 / 12,
  e.hazard   = list(0.10, 0.08, 0.07, 0.05),
  prevalence = list(c(0.5, 0.5), c(0.6, 0.4)),
  seed       = 4
)
head(df4)
#>   sim group subgroup1 subgroup2 accrual_time  surv_time dropout_time        tte
#> 1   1     1         1         1    9.1022179  5.9978017          Inf  5.9978017
#> 2   1     1         1         1    2.8634934  0.7745617          Inf  0.7745617
#> 3   1     1         1         2    7.8409159  4.3302474          Inf  4.3302474
#> 4   1     1         1         1   11.1231621 18.1107495          Inf 18.1107495
#> 5   1     1         2         1    0.1179968 21.9852009          Inf 21.9852009
#> 6   1     1         1         2    8.8916664 15.3635872          Inf 15.3635872
#>   event calendar_time
#> 1     1     15.100020
#> 2     1      3.638055
#> 3     1     12.171163
#> 4     1     29.233912
#> 5     1     22.103198
#> 6     1     24.255254

# Two correlated factors via a joint-distribution array (2 x 2)
df5 <- simdata_fast(
  nsim       = 100,
  n          = 200,
  a.time     = c(0, 12),
  a.rate     = 200 / 12,
  e.hazard   = 0.08,
  prevalence = array(c(0.40, 0.10, 0.15, 0.35), dim = c(2, 2)),
  seed       = 5
)
head(df5)
#>   sim group subgroup1 subgroup2 accrual_time surv_time dropout_time       tte
#> 1   1     1         1         2     2.799254 27.395432          Inf 27.395432
#> 2   1     1         2         1    11.012518  2.739611          Inf  2.739611
#> 3   1     1         1         1     9.539372 16.660831          Inf 16.660831
#> 4   1     1         1         2     8.875943 25.432814          Inf 25.432814
#> 5   1     1         1         1     6.182424  5.134396          Inf  5.134396
#> 6   1     1         1         1     7.800613 10.776088          Inf 10.776088
#>   event calendar_time
#> 1     1      30.19469
#> 2     1      13.75213
#> 3     1      26.20020
#> 4     1      34.30876
#> 5     1      11.31682
#> 6     1      18.57670

# Two correlated endpoints, no switching (the maximal-independence model of
# Fleischer et al., 2009, because h12 defaults to h02). In oncology e1 is PFS
# and e2 is OS; here the
# control has faster intermediate events and faster direct terminal events.
dfid <- simdata_fast(
  nsim       = 100,
  n          = c(150, 150),
  a.time     = c(0, 12),
  a.rate     = 300 / 12,
  h01.median = list(8, 12),
  h02.median = list(24, 30),
  seed       = 6
)
head(dfid)
#>   sim group accrual_time e1_surv_time e2_surv_time dropout_time    e1_tte
#> 1   1     1     3.210288     1.262139    86.027025          Inf  1.262139
#> 2   1     1     5.488111    13.830983    91.509522          Inf 13.830983
#> 3   1     1    10.250797     2.811514     6.206717          Inf  2.811514
#> 4   1     1     9.997081    10.769714    19.784649          Inf 10.769714
#> 5   1     1     6.210510    15.100728    56.583233          Inf 15.100728
#> 6   1     1     7.528979     7.054905    10.651538          Inf  7.054905
#>   e1_event    e2_tte e2_event e1_calendar_time e2_calendar_time intermediate
#> 1        1 86.027025        1         4.472427         89.23731            1
#> 2        1 91.509522        1        19.319094         96.99763            1
#> 3        1  6.206717        1        13.062311         16.45751            1
#> 4        1 19.784649        1        20.766795         29.78173            1
#> 5        1 56.583233        1        21.311238         62.79374            1
#> 6        1 10.651538        1        14.583884         18.18052            1
#>   switched switch_time
#> 1        0          NA
#> 2        0          NA
#> 3        0          NA
#> 4        0          NA
#> 5        0          NA
#> 6        0          NA

# With treatment switching: 40 percent of control subjects who reach the
# intermediate event switch and then follow a more favorable post-event hazard
dfsw <- simdata_fast(
  nsim              = 100,
  n                 = c(150, 150),
  a.time            = c(0, 12),
  a.rate            = 300 / 12,
  h01.median        = list(8, 12),
  h02.median        = list(24, 30),
  switch.prop       = list(0.4, 0),
  h12.switch.median = list(36, 36),
  seed              = 7
)
head(dfsw)
#>   sim group accrual_time e1_surv_time e2_surv_time dropout_time     e1_tte
#> 1   1     1     7.559415   14.4396084   36.8236195          Inf 14.4396084
#> 2   1     1     1.437074    0.6125605    0.6125605          Inf  0.6125605
#> 3   1     1     4.659691   61.4042624   61.4042624          Inf 61.4042624
#> 4   1     1     8.729861    0.6111453   83.4156438          Inf  0.6111453
#> 5   1     1     1.966126   10.6181068  101.0631930          Inf 10.6181068
#> 6   1     1    11.790063    3.0603097   87.5269704          Inf  3.0603097
#>   e1_event      e2_tte e2_event e1_calendar_time e2_calendar_time intermediate
#> 1        1  36.8236195        1        21.999024        44.383035            1
#> 2        1   0.6125605        1         2.049635         2.049635            0
#> 3        1  61.4042624        1        66.063953        66.063953            0
#> 4        1  83.4156438        1         9.341006        92.145505            1
#> 5        1 101.0631930        1        12.584233       103.029319            1
#> 6        1  87.5269704        1        14.850373        99.317034            1
#>   switched switch_time
#> 1        0          NA
#> 2        0          NA
#> 3        0          NA
#> 4        1   0.6111453
#> 5        0          NA
#> 6        0          NA

# Illness-death model with two subgroups (prevalence 0.4 and 0.6): the
# treatment effect on progression is larger in subgroup 2.
dfsg <- simdata_fast(
  nsim       = 100,
  n          = c(150, 150),
  a.time     = c(0, 12),
  a.rate     = 300 / 12,
  h01.median = list(8, list(10, 14)),
  h02.median = list(24, 30),
  prevalence = c(0.4, 0.6),
  seed       = 8
)
table(dfsg$group, dfsg$subgroup) / 100
#>    
#>         1     2
#>   1 59.63 90.37
#>   2 60.12 89.88

# Three-arm trial (one control and two treatment arms) analyzed as pairwise
# contrasts against the shared control.
dfk <- simdata_fast(
  nsim     = 100,
  n        = c(120, 120, 120),
  a.time   = c(0, 12),
  a.rate   = 360 / 12,
  e.median = list(12, 16, 20),
  seed     = 8
)
# Control arm (group 1) versus treatment arm 2, one-sided log-rank at month 24.
sub12 <- dfk[dfk$group %in% c(1, 2), ]
res12 <- analysis_fast(sub12, control = 1, time.looks = 24, side = 1)
head(res12)
#>   sim look look.value cutoff reached n.enrolled n.event n.dropout n.pipeline
#> 1   1    1         24     24    TRUE        240     145         0         95
#> 2   2    1         24     24    TRUE        240     137         0        103
#> 3   3    1         24     24    TRUE        240     149         0         91
#> 4   4    1         24     24    TRUE        240     149         0         91
#> 5   5    1         24     24    TRUE        240     142         0         98
#> 6   6    1         24     24    TRUE        240     137         0        103
#>    logrank.z logrank.chisq  logrank.p
#> 1 -1.4795251    2.18899443 0.06950002
#> 2 -1.6694771    2.78715368 0.04751144
#> 3 -0.9410187    0.88551621 0.17334764
#> 4 -2.1927002    4.80793424 0.01416449
#> 5  0.2683545    0.07201412 0.60578676
#> 6 -1.9520367    3.81044737 0.02546692