Create a class of trial.
Methods
Method new()
initialize a trial
Usage
Trials$new(
name,
n_patients,
duration,
description = name,
seed = NULL,
enroller,
dropout = NULL,
silent = FALSE,
...
)
Arguments
name
character. Name of trial.
n_patients
integer. Maximum number of patients could be enrolled to the trial.
duration
Numeric. Trial duration.
description
character. Optional for description of the trial. By default it is set to be trial's
name
.seed
random seed. If
NULL
,set.seed()
will not be called, which uses seed set outside.enroller
a function returning a vector enrollment time for patients. Its first argument is the number of enrolled patients.
dropout
a function returning a vector of dropout time for patients. Its first argument is the number of enrolled patients.
silent
logical.
TRUE
to mute messages....
arguments of
enroller
anddropout
.
Method get_trial_data()
return trial data of enrolled patients at the time of this function is called
Method set_duration()
set trial duration in an adaptive designed trial. All patients enrolled before resetting the duration are truncated (non-tte endpoints) or censored (tte endpoints) at the original duration. Remaining patients are re-randomized. Now new duration must be longer than the old one.
Method set_dropout()
set distribution of drop out time. This can be done when initialize a trial, or when updating a trial in adaptive design.
Method roll_back()
roll back data to current time of trial. By doing so,
Trial$trial_data
will be cut at current time, and data after then
are deleted. However, Trial$enroll_time
after current time are
kept unchanged because that is planned enrollment curve.
Method remove_arms()
remove arms from a trial. enroll_patients()
will be always called
at the end to enroll all remaining patients after
Trial$get_current_time()
. This function may be used with futility
analysis, dose selection, enrichment analysis (sub-population) or
interim analysis (early stop for efficacy)
Method update_sample_ratio()
update sample ratio of an arm. This could happen after an arm is added or removed. We may want to update sample ratio of unaffected arms as well. This function can only update sample ratio for one arm at a time. Once sample ratio is updated, trial data should be rolled back with updated randomization queue. Data of unenrolled patients should be re-sampled as well.
Method add_arms()
add one or more arms to the trial. enroll_patients()
will be
called at the end to enroll all remaining patients in
private$randomization_queue
. This function can be used in two
scenarios.
(1) add arms right after a trial is created (i.e., Trial$new(...)
).
sample_ratio
and arms added through ...
should be of same
length.
(2) add arms to a trial already with arm(s)
Arguments
sample_ratio
integer vector. Sample ratio for permuted block randomization. It will be appended to existing sample ratio in the trial.
...
one or more objects of class
Arm
. One exception in...
is an argumentenforce
. Whenenforce = TRUE
, sample ratio of newly added arm. It rolls back all patients afterTrial$get_current_time()
, i.e. redo randomization for those patients. This can be useful to add arms one by one when creating a trial. Note that we can runTrial$add_arm(sample_ratio1, arm1)
followed byTrial$add_arm(sample_ratio2, enforce = TRUE, arm2)
. We would expected similar result withTrial$add_arms(c(sample_ratio1, sample_ratio2), arm1, arm2)
. Note that these two method won't return exactly the same trial because randomization_queue were generated twice in the first approach but only once in the second approach. But statistically, they are equivalent and of the same distribution.
Method get_sample_ratio()
return current sample ratio of the trial. The ratio can probably change during the trial (e.g., arm is removed or added)
Method get_randomization_queue()
return randomization queue of planned but not yet enrolled patients. This function does not update randomization_queue, just return its value for debugging purpose.
Method get_enroll_time()
return enrollment time of planned but not yet enrolled patients. This function does not update enroll_time, just return its value for debugging purpose.
Method enroll_patients()
assign new patients to pre-planned randomization queue at pre-specified enrollment time.
Method set_current_time()
set current time of a trial. Any data collected before could not be changed. private$now should be set after a milestone is triggered (through Milestones class, futility, interim, etc), an arm is added or removed at a milestone
Method get_event_tables()
count accumulative number of events (for TTE) or non-missing samples (otherwise) over calendar time (enroll time + tte for TTE, or enroll time + readout otherwise)
Method get_data_lock_time_by_event_number()
given a set of endpoints and target number of events, determine the data lock time for a milestone (futility, interim, final, etc.). This function does not change trial object (e.g. rolling back not yet randomized patients after the found data lock time).
Usage
Trials$get_data_lock_time_by_event_number(
endpoints,
arms,
target_n_events,
type = c("all", "any")
)
Arguments
endpoints
character vector. Data lock time is determined by a set of endpoints.
arms
a vector of arms' name on which number of events will be counted.
target_n_events
target number of events for each of the
endpoints
.type
all
if all target number of events are reached.any
if the any target number of events is reached.
Method get_data_lock_time_by_calendar_time()
given the calendar time to lock the data, return it with event counts of each of the endpoints.
Method lock_data()
lock data at specific calendar time.
For time-to-event endpoints, their event indicator *_event
should be
updated accordingly. Locked data should be stored separately.
DO NOT OVERWRITE/UPDATE private$trial_data! which can lose actual
time-to-event information. For example, a patient may be censored at
the first data lock. However, he may have event being observed in a
later data lock.
Method censor_trial_data()
censor trial data at calendar time
Arguments
censor_at
time of censoring. It is set to trial duration if
NULL
.selected_arms
censoring is applied to selected arms (e.g., removed arms) only. If
NULL
, it will be set to all available arms in trial data. Otherwise, censoring is applied to user-specified arms only. This is necessary because number of events/sample size in removed arms should be fixed unchanged since corresponding milestone is triggered. In that case, one can update trial data by something likecensor_trial_data(censor_at = milestone_time, selected_arms = removed_arms)
.enrolled_before
censoring is applied to patients enrolled before specific time. This argument would be used when trial duration is updated by
set_duration
. Adaptation happens whenset_duration
is called so we fix duration for patients enrolled before adaptation to maintain independent increment. This should work when trial duration is updated for multiple times.
Method save()
save a single value or a one-row data frame to trial's output for further analysis/summary later.
Arguments
value
value to be saved. It can be a vector (of length 1) or a data frame (of one row).
name
character to name the saved object. It will be used to name a column in trial's output if
value
is a vector. Ifvalue
is a data frame,name
will be the prefix pasted with the column name ofvalue
in trial's output. If user want to usevalue
's column name as is in trial's output, setname
to be''
as default. Otherwise, column name would be, e.g.,"{name}_<{names(value)}>"
.overwrite
logic.
TRUE
if overwriting existing entries with warning, otherwise, throwing an error and stop.
Method bind()
row bind a data frame to existing data frame. If name
is not
existing in Trial
, then it is equivalent to Trial$save
.
Extra columns in value
are ignored. Columns in
Trial$custom_data[[name]]
but not in value
are filled
with NA
.
Method save_custom_data()
save arbitrary (number of) objects into a trial so that users can use those to control the workflow. Most common use case is to store simulation parameters to be used in action functions.
Method independentIncrement()
calculate independent increments from a given set of milestones
Arguments
endpoint
character. Name of time-to-event endpoint in trial's locked data.
placebo
character. String of placebo in trial's locked data.
milestones
a character vector of milestone names in the trial, e.g.,
listener$get_milestone_names()
.planned_info
a vector of planned accumulative number of event of time-to-event endpoint. Note:
planned_info
can also be a character"oracle"
so that planned number of events are set to be observed number of events, in that case inverse normal z statistics equal to one-sided logrank statistics. This is for the purpose of debugging only. In formal simulation,"oracle"
should not be used if adaptation is present. Pre-fixedplanned_info
should be used to create weights in combination test that controls the family-wise error rate in the strong sense....
subset condition that is compatible with
dplyr::filter
.survdiff
will be fitted on this subset only to compute one-sided logrank statistics. It could be useful when a trial consists of more than two arms. By default it is not specified, all data will be used to fit the model.
Returns
This function returns a data frame with columns:
p_inverse_normal
one-sided p-value for inverse normal test based on logrank test (alternative hypothesis: risk is higher in placebo arm). Accumulative data is used.
z_inverse_normal
z statistics of
p_inverse_normal
. Accumulative data is used.p_lr
one-sided p-value for logrank test (alternative hypothesis: risk is higher in placebo arm). Accumulative data is used.
z_lr
z statistics of
p_lr
. Accumulative data is used.info
observed accumulative event number.
planned_info
planned accumulative event number.
info_pbo
observed accumulative event number in placebo.
info_trt
observed accumulative event number in treatment arm.
wt
weights in
z_inverse_normal
.
Method dunnettTest()
carry out closed test based on Dunnett method under group sequential design.
Arguments
endpoint
character of an endpoint for Dunnett test.
placebo
character. Name of placebo arm.
treatments
character vector. Name of treatment arms to be used in comparison.
milestones
character vector. Names of triggered milestones at which either adaptation is applied or statistical testing for endpoint is performed. Milestones in
milestones
does not need to be sorted by their triggering time.planned_info
a data frame of planned number of events of time-to-event endpoint in each stage and each arm. Milestone names, i.e.,
milestones
are row names ofplanned_info
, and arm names, i.e.,c(placebo, treatments)
are column names. Note that it is not the accumulative but stage-wise event numbers. It is usually not easy to determine these numbers in practice, simulation may be used to get estimates. Note:planned_info
can also be a character"default"
so thatplanned_info
are set to be number of newly randomized patients in the control arm in each of the stages. This assumes that event rate do not change over time and/or sample ratio between placebo and a treatment arm does not change as well, which may not be true. It is for the purpose of debugging or rapid implementation only. Using simulation to pickplanned_info
is recommended in formal simulation study. Another issue withplanned_info
set to be"default"
is that it is possible patient recruitment is done before a specific stage, as a result,planned_info
is zero which can crash the program....
subset condition that is compatible with
dplyr::filter
.survdiff
will be fitted on this subset only to compute one-sided logrank statistics. It could be useful when comparison is made on a subset of treatment arms. By default it is not specified, all data (placebo plus one treatment arm at a time) in the locked data are used to fit the model.
Details
This function computes stage-wise p-values for each of the intersection
hypotheses based on Dunnett test. If only one treatment arm is present,
it is equivalent to compute the stage-wise p-values of elemental
hypotheses. This function also computes inverse normal combination
test statistics at each of the stages.
The choice of planned_info
can affect the calculation of
stage-wise p-values. Specifically, it is used to compute
the columns observed_info
and p_inverse_normal
in returned
data frame, which will be used in Trial$closedTest()
.
The choice of planned_info
can affect the result of
Trial$closedTest()
so user should chose it with caution.
Note that in Trial$closedTest()
,
observed_info
, which is derived from planned_info
, will
lead to the same closed testing results up to a constant. This is because
the closed test uses information fraction
observed_info/sum(observed_info)
. As a result, setting
planned_info
to, e.g., 10 * planned_info
should give same
closed test results.
Based on numerical study, setting planned_info = "default"
leads
to a much higher power (roughly 10%) than setting planned_info
to
median of event numbers at stages, which can be determined by simulation.
I am not sure if regulator would support such practice. For example,
if a milestone (e.g., interim analysis) is triggered at a pre-specified
calendar time, the number of randomized patients is random and is unknown
when planning the trial. If I understand it correctly, regulator may want
the information fraction in closed test (combined with Dunnett test) to
be pre-fixed. In addition, this choice for planned_info
assumes
that the event rates does not change over time which is obviously not
true. It is recommended to always use pre-fixed planned_info
for
restrict control of family-wise error rate. It should be pointed out
that the choice of pre-fixed planned_info
can affect statistical
power significantly so fine-tuning may be required.
Returns
a list with element names like arm_name
,
arm1_name|arm2_name
, arm1_name|arm2_name|arm3_name
, etc.,
i.e., all possible combination of treatment arms in comparison. Each
element is a data frame, with its column names self-explained. Specifically,
the columns p_inverse_normal
, observed_info
,
is_final
can be used with GroupSequentialTest
to perform
significance test.
Method closedTest()
perform closed test based on Dunnett test
Usage
Trials$closedTest(
dunnett_test,
treatments,
milestones,
alpha,
alpha_spending = c("asP", "asOF")
)
Arguments
dunnett_test
object returned by
Trial$dunnettTest()
.treatments
character vector. Name of treatment arms to be used in comparison.
milestones
character vector. Names of triggered milestones at which significance testing for endpoint is performed in closed test. Milestones in
milestones
does not need to be sorted by their triggering time.alpha
numeric. Allocated alpha.
alpha_spending
alpha spending function. It can be
"asP"
or"asOF"
. Note that theoretically it can be"asUser"
, but it is not tested. It may be supported in the future.
Method make_snapshot()
make a snapshot before running a trial. This can be useful when resetting a trial. This is only called when initializing a `Trial` object, when arms have not been added yet.
Method reset()
reset a trial to its snapshot taken before it was executed. Seed will be reassigned with a new one. Enrollment time are re-generated. If the trial already have arms when this function is called, they are added back to recruit patients again.
Examples
# Instead of using Trial$new, please use trial(), a user-friendly
# wrapper. See examples in ?trial.
## ------------------------------------------------
## Method `Trials$get_data_lock_time_by_event_number`
## ------------------------------------------------
## trial$get_data_lock_time_by_event_number(c('pfs','orr'), c(200,500), 'any')
## ------------------------------------------------
## Method `Trials$get_data_lock_time_by_calendar_time`
## ------------------------------------------------
## trial$get_data_lock_time_by_calendar_time(20)
## ------------------------------------------------
## Method `Trials$independentIncrement`
## ------------------------------------------------
if (FALSE) { # \dontrun{
trial$independentIncrement('pfs', 'pbo', listener$get_milestone_names(), 'oracle')
} # }
## ------------------------------------------------
## Method `Trials$dunnettTest`
## ------------------------------------------------
if (FALSE) { # \dontrun{
trial$dunnettTest('pfs', 'pbo', c('high dose', 'low dose'),
listener$get_milestone_names(), 'default')
} # }
## ------------------------------------------------
## Method `Trials$closedTest`
## ------------------------------------------------
if (FALSE) { # \dontrun{
dt <- trial$dunnettTest(
'pfs', 'pbo', c('high dose', 'low dose'),
listener$get_milestone_names(), 'default')
trial$closedTest(dt, c('high dose', 'low dose'),
c('pfs interim', 'pfs final'),
0.025, 'asOF')
} # }