fitLinear() and fitLogistic() (except scale = 'coefficient') no longer fail with "We are unable to reconstruct the data" when the right-hand side of formula transforms a covariate, e.g., log(x). The fitted rows are now passed to emmeans directly, so it no longer re-evaluates the model call, where the analysis data are not visible. The same re-evaluation caused a test failure on R-devel for Windows.event_plot() now shows the trial as observed, i.e., cumulative counts up to the time of the last triggered milestone. Since 1.36.0 trial data hold the full generated follow-up of every patient, so without this cut the plot extended to the last generated event, far beyond the end of the trial.save_custom_data(), get(), bind()) is now strictly replicate-local: entries saved in an action function no longer leak into the next replicate, and data saved before run() does not reach the replicates. After run(), the last replicate's custom data remains available for inspection.save_custom_data() rejects a NULL value and requires name to be a non-empty character of length 1.trial() gains a read-only global_data argument: a named list of, e.g., design parameters or template objects of testing procedures, available in every replicate through trial$get_global_data(name). R6 objects and data.tables are copied at registration and on every read, so registered entries can never be modified; see vignette('actionFunctions').controller$run() refuses to start on a trial that has already been run and points to reset(), instead of failing at the first milestone with a misleading error.additional_followup no longer has a default in stop_followup(), Trials$stop_followup(), remove_arms() and Trials$remove_arms(). Existing calls must state it: additional_followup = 0 reproduces the previous behavior. Whether follow-up stops at the milestone or continues is a design decision that should be visible in the action function.remove_arms() and Trials$remove_arms() gain additional_followup (thanks, @gravesti, #21); 0 is the previous behavior. Patients of the removed arms are followed for that much longer after the milestone, or for the rest of the trial with Inf, e.g., to collect overall survival of a dose that stops enrolling. One value is shared by all arms removed in a call; call once per arm for different values. Events of a removed arm within its follow-up are counted by a later milestone only if the arm is listed in arms of its triggering condition; arms = NULL never counts removed arms.trial(duration = ...) is now an error stating that the argument was removed in 1.36.0. Previously duration fell into ... and was forwarded to the enroller and dropout generators, which ignore it, so a script written for an earlier version kept running with its follow-up no longer capped and nothing said so.arms argument of eventNumber() and enrollment() is now validated once, at the entry shared by the C++ and R implementations of the lock-time search, so both behave the same. A name that has never been an arm of the trial is an error; previously the default C++ path dropped it silently, so a misspelled arm only delayed the milestone. The set of known arms is every arm ever added in the current replicate, so an arm added and then removed within the trial is accepted; the R path previously checked against the current arms and the arms present when run() started, and rejected such an arm.arms that has been removed by remove_arms() before the milestone is evaluated now raises a warning unless the trial is silent (silent = TRUE in trial() or run()). Its patients are still counted as specified. Use arms = NULL to count on the arms in the trial only.?remove_arms now states what removing an arm does (no further randomization, out of the set of arms of the trial, data censored at removal but kept in locked data) and documents the three patterns for counting at later milestones after a data-driven removal: arms = NULL for the arms still in the trial, listing every arm of the design to include removed ones, and update_milestone() in the same action function for a subset that depends on which arm was removed. Examples show each pattern.enrollment(): after dose selection, arms = NULL would count on the remaining arms only and the total sample size could never be reached.trial() examples of ?controller and ?summarizeMilestoneTime no longer pass duration.duration is removed from trial(). A trial no longer has a pre-specified duration: it ends when its last milestone is triggered, and the timing of a milestone is determined by its triggering condition. Users of event-driven designs no longer need to guess a "sufficiently large" cap whose unit depends on the design. Existing code must drop the argument. For a trial whose milestones were all reachable within the old duration, results are unchanged for a given seed; a trial whose calendar-time milestone lay beyond the old duration now gets the full follow-up it was designed for instead of follow-up silently truncated at duration.set_duration() and Trials$set_duration() are removed. The design it served, extending a trial at an unblinded interim while patients enrolled by the interim keep their originally planned follow-up, is expressed within the action function of the interim as trial$stop_followup(additional_followup = planned_end - trial$get_current_time()) for the freeze, and trial$update_milestone('final', when = ...) for the new timing of the final analysis. The freeze point planned_end is the calendar time in the original triggering condition of the final analysis, which the user knows by design. Unlike set_duration(), this recipe does not re-randomize and regenerate patients not yet enrolled at the interim, so the random number stream is no longer advanced by the adaptation; the two are equivalent in distribution.Trials$censor_trial_data() no longer has a default for censor_at, the calendar time of administrative censoring; pass censor_at = Inf to apply dropout censoring only, without administrative censoring.lock_data()) and through adaptations (stop_followup(), remove_arms()). event_plot() extends its x axis to the time of the last triggered milestone.fitLogrank() is faster. It now computes the (stratified) log rank statistic directly from survival::survdiff() as observed-minus-expected events over its standard error, instead of fitting two Cox models per treatment arm (one for the sign of the effect and one with ties = "exact" for the score test). Its magnitude agrees with the previous exact-score statistic up to floating point rounding, while its sign now consistently follows the treatment log-rank score; with tied event times this can correct the previous sign when the Efron Cox coefficient pointed in the opposite direction. info and the tidy = FALSE columns are unchanged. When the statistic is undefined because its variance is zero (e.g., no event in the subset), a warning is issued and a simulation placeholder z = 0 with the corresponding p = 0.5 is returned instead of an error. On a three-arm dose-selection design with log rank tests at two milestones this removes roughly 40% of the per-replicate time, and roughly a third on an enrichment design driven by conditional power.regimen_trajectory to the switches that have happened by the lock time, and the n_switches count, are now computed for all switching patients at once (one strsplit() plus tabulate()), instead of a per-patient mapply() followed by a per-patient regular expression. Both columns are unchanged for names without the reserved characters below; on a 1000-patient three-arm design with crossover this removes roughly 14% of the per-replicate time.'@' and ';' are now reserved characters of the regimen_trajectory encoding: arm() rejects a name containing either, whether or not the trial uses a regimen (e.g., arm(name = 'dose@5mg') used to be accepted and now is an error), and what() of a regimen or of crossover() must not return a new_treatment containing either. Previously such names silently corrupted the trajectory and n_switches. Data locking also asserts that every trajectory with a switch parses cleanly and keeps its initial segment.tidy in controller$run() (default FALSE). With tidy = TRUE, the per-arm event count table (output column n_events_<milestone>_<arms>) is not saved at milestones; per-endpoint totals and milestone times are still saved, and the table remains available in the attributes of locked data, so event_plot() is unaffected. Saving that table is the most expensive part of the standard outputs: skipping it removes roughly 15% of the per-replicate time on a small two-arm design with trivial actions (on top of 1.35.5) and about 3% on a three-arm dose-selection design with log-rank tests at milestones. Unlike tidy in get_output(), which removes columns after the fact, this avoids the cost entirely.controller$run() collects the output of each replicate in a list and row-binds once at the end, instead of calling bind_rows() after every replicate (a fixed cost of roughly 1 ms per call). The same applies to the parallel path. Outputs are unchanged, including the partial output kept when a replicate fails. Removes roughly 10% of the per-replicate time on a small two-arm design with trivial actions and 3-5% on heavier designs.enroll_patients() now assembles the patient data of each arm directly from one generated pool, skipping the per-stratum bookkeeping (table(), merge(), split(), bind_rows()) that stratified randomization requires. Generator and dropout functions are called in exactly the same order and with the same sizes as before, so the random number stream and therefore all results are unchanged for a given seed (a regression test pins the patient data of a fixed-seed trial to reference values). On a small two-arm design with trivial actions this removes roughly 19% of the per-replicate time; the relative gain shrinks as the statistical work per milestone grows.arm(name, ...)). The criteria are now applied as a logical row mask with the semantics of dplyr::filter() (conditions combined with &, rows with NA dropped, .data/.env pronouns supported), instead of calling dplyr::filter() in every round of the rejection-sampling loop. The mask evaluation is shared with the lock-time fast path of 1.35.2 (internal filter_conditions_mask()). The human-readable criteria string used in error messages is also built once, when the arm is created, instead of on every generator call (deparsing quosures is not free). Results are unchanged; on a 1000-patient three-arm design with pfs <= os as inclusion criterion this removes roughly 9% of the per-replicate time.eventNumber(endpoint = 'pfs', n = 40, patient_id <= 70) or enrollment(n = 100, biomarker == 1), now take the C++ lock-time fast path. The subset conditions are reduced to a logical row mask with the semantics of dplyr::filter() (conditions combined with &, rows with NA dropped, .data/.env pronouns supported) and the existing C++ helpers are applied to the subset, instead of building per-endpoint event tables with dplyr for every endpoint in the trial. Results are unchanged; the pure-R path remains available through options(trialsimulator.use_cpp = FALSE). On an enrichment design whose milestones are all subgroup-filtered this removes roughly 11% of the per-replicate time.n_switches column of locked data is now computed on the subset of patients who switched treatment, instead of running a regular expression over the regimen_trajectory of every enrolled patient at every milestone. Results are unchanged; for a 1000-patient three-arm design with crossover this removes roughly 9% of the per-replicate time.trial$add_arms() deep-clones every arm it registers, and trial$add_regimen() deep-clones the regimen. Consequently, changes made to the caller's arm or regimen objects after registration no longer affect the trial, and adaptations within the trial (e.g., trial$update_generator(), trial$crossover()) no longer modify the caller's objects, so those objects can be safely reused, e.g., to build another trial. Complete the configuration of an arm before registering it; after registration, change it only through the trial's adaptation methods. The copy covers the arm, endpoint, and regimen objects themselves; a mutable environment or R6 object captured by a user-supplied function (generator, action, what/when/how) is shared by design of R closures and is not isolated.trial$add_arms() registration is now transactional: all incoming arms are validated (including endpoint-set consistency and re-use of the name of a previously removed arm) before any of them is installed, so a failing batch no longer leaves the trial partially modified. Re-adding an arm under the name of a removed arm is rejected explicitly; previously it failed halfway with the trial left partially modified.controller$run() now restores the listener's milestones to their as-designed state before the first replicate. Previously, reusing a listener from an earlier controller in the same session left its milestones in a triggered state, silently suppressing them in the new run.controller$run() shared its endpoint objects with the live arms, so a trial$update_generator() call in one replicate silently altered the data-generating process of all later replicates, and a failing replicate could not be reproduced from the seed reported in its error message. The snapshot now deep-clones endpoints, and every replicate starts from freshly cloned, as-designed arms.trial$update_generator() now accepts endpoint_name in any order. Endpoint names registered together in one endpoint() call are matched as a set, so, e.g., c('pfs', 'biomarker') and c('biomarker', 'pfs') are equivalent. Previously the names had to be given in registration order, and a reordered vector was rejected with a misleading error message. In addition, when endpoint_name covers only part of a registration, or mixes names from different registrations, the error message now spells out the exact endpoint_name vector(s) to use.trial$eventNumberReestimationFromConditionalPower() re-estimates the number of events at the final analysis for every treatment-vs-placebo comparison of a time-to-event endpoint: the smallest whole number of events, greater than the number observed at a triggered interim milestone, at which the conditional power of trial$conditionalPower() reaches a target. Conditional power can be evaluated at the interim trend or at a user-specified hazard ratio, and a practical cap can be imposed through D_cap. When no solution exists in the requested range, D and achieved_cp are NA and target_reached is FALSE. The search is exact even when conditional power is not monotone in the event number.trial$conditionalPower() and the new method.effect argument of trial$conditionalPower() no longer has a default value; it must be specified explicitly as 'trend', 'null', or a hazard ratio.trial$conditionalPower() computes conditional power at a triggered interim milestone for every treatment-vs-placebo comparison of a time-to-event endpoint, under a group sequential design with one interim and one final analysis. It pulls the milestone's locked data automatically and calls fitLogrank() internally to obtain the observed z statistic and number of events of each comparison. Conditional power can be evaluated at the interim trend (default), under the null, or at a user-specified hazard ratio, which is converted internally using the allocation ratio in effect at the milestone. Its alpha argument is the one-sided nominal significance level corresponding to the planned final critical boundary, not the total design alpha or alpha spent at the final look. The calculation assumes the trial continues as designed (constant allocation ratio of the compared arms, planned final statistic and boundary); it is users' responsibility to call it only when legitimate, e.g., not after the allocation ratio of the compared arms was adapted, as TrialSimulator does not verify this. Results are validated against rpact and gsDesign in unit tests.dry_run argument of controller$run() is now defunct.controller$run() can no longer be called twice without reset() in between. Previously, a second run() silently continued the already-executed trial (all milestones already triggered, snapshots partially overwritten) instead of starting a new simulation. To define a milestone whose triggering condition or action depends on interim results, register it upfront and revise it within an action function through update_milestone().save_milestone_time() only after the trial clock had been moved backwards and the locked snapshot stored; that check remains as an internal assertion, and set_current_time() gains a backstop against backward clock moves.listener$add_milestones() now raises an error when a milestone with the same name is already registered. Previously it warned and silently over-wrote the registered milestone. To modify a not-yet-triggered milestone within an action function, use update_milestone().Arms$get_number_endpoints(), Endpoints$get_readout() and Endpoints$get_type() are now private. The machinery methods of Arms (get_name(), get_endpoints_name(), has_endpoint() and update_endpoint_generator()) and of Endpoints (get_uid() and update_generator()), which stay public only because trials and arms invoke them, now carry a bold warning that users should not call them directly.Controllers, Arms and Endpoints now come with brief usage notes in the class description; exploratory methods (test_generator(), get_generator() and get_name() of Endpoints; generate_data() and get_endpoints() of Arms) are documented as helpful for understanding the classes but not needed in formal simulation.Controllers$get_trial(), Controllers$get_listener(), Controllers$mute(), Milestones$execute_action(), Milestones$get_type(), Milestones$get_trigger_condition(), Milestones$get_action(), Regimens$get_number_time_selector() and Regimens$get_number_data_modifier() are now private. They were used only internally by their own classes.Milestones and all public methods of Regimens are invoked on their objects by other components of the package (listeners and trials) and stay public only for that reason; they now carry a bold warning that users should not call them directly, in the same style as the Trials and Listeners classes.Listeners class: get_milestones() is now private. The machinery methods monitor(), mute() and reset(), which stay public only because the controller invokes them, now carry a bold warning that users should not call them directly, in the same style as the Trials class.trial$set_duration(), trial$resize(), trial$remove_arms(), trial$update_sample_ratio() and trial$update_generator() now raise an error when called before any milestone has been triggered, i.e., outside an action function.trial$update_milestone() (wrapper update_milestone()): update the triggering condition and/or the action of a not-yet-triggered milestone from within an action function. The update takes effect right after the current action function returns, and the as-designed milestone is restored between simulation replicates.action of a milestone must be a function; NULL is no longer accepted. Use doNothing if no action is intended.Trials class: 38 methods used only internally are now private (including censor_trial_data(), roll_back(), enroll_patients(), get_trial_data() and independentIncrement()), leaving 34 public members whose definitions and documentation are reorganized into sections: adaptation methods, methods callable within action functions (data access and manipulation, trial status queries, statistical testing), trial setup (add_regimen(), which must not be called within action functions), and internal machinery. Machinery methods that stay public only because other components of the package invoke them now carry a bold warning that users should not call them directly.make_snapshot() distinguishes private methods from data fields via bindingIsLocked() (R6 locks the binding of every member defined as a function) instead of a hardcoded name list, so snapshot/reset stay correct as methods are added..data$ in tidyselect contexts (rename()/select() in the correlated PFS/OS generators, PiecewiseConstantExponentialRNG(), and endpoint naming) with string literals, eliminating the tidyselect deprecation warnings that flooded test output. .data$ remains in data-masking contexts (filter(), mutate(), aes()), where it is still the recommended idiom; R CMD check stays clean.set_dropout() documentation no longer claims adaptive use: dropout times are generated at enrollment, so updating the generator within an action function would not apply to enrolled patients..gitignore and exclude .DS_Store from the build via .Rbuildignore.event_plot() now selects the endpoint column via all_of() (tidyselect external-vector deprecation), and plot.three_state_model() zooms with coord_cartesian() instead of xlim(), so the dashed guide segments anchored at the axes are clipped and rendered rather than dropped with a ggplot2 warning.Rplots.pdf appearing after test runs: summarizeDataFrame() restored par() settings via on.exit() after its png() device was already closed, which implicitly opened the default device. par() settings are local to the device and need no restore.make_snapshot() silently dropped NULL-valued private fields from the snapshot (assigning NULL with [[<- deletes a list entry); reset()'s explicit re-null list masked this for existing fields. The snapshot now preserves NULL fields, so any future field is restored across simulation replicates by construction.dunnettTest() that referenced an undefined variable.doseRanging vignette that compared a string literal instead of the decision column.doseRanging and fixedDesign vignettes under the 1.25.2 enrollment convention; the fixedDesign output now contains the 1,000 replicates its code states (previously 100).R CMD check GitHub Actions workflow (macOS/Windows/Ubuntu; R release/devel/oldrel).\value sections to exported help topics, a grouped reference index for the pkgdown site, and mention the newest adaptations in the actionFunctions vignette introduction.graphicalMCP entry from Suggests.trial$update_accrual_rate() (wrapper update_accrual_rate()): update the accrual rate of the recruitment curve at a milestone, e.g., to revise recruitment after dose selection or enrichment, or to pause it for a period after an interim decision. end_time of the new accrual_rate is measured from the milestone; patients not yet enrolled (and the enrollment reserves used by resize()) are re-planned and re-randomized under the new schedule, while enrolled patients are left unchanged. Like other adaptations, it must be called within an action function, after a milestone has been triggered.StaggeredRecruiter() now enrolls patient k when the planned cumulative accrual reaches k (previously k - 1): under a constant rate r the n-th patient enrolls exactly at n / r, so a milestone triggered by enrollment(n) occurs exactly at the planned accrual time. The first patient enrolls at 1 / piecewise_rate instead of time 0, and all enrollment times shift by one inter-arrival accordingly; simulation results under a fixed seed differ slightly from 1.25.1. The precomputed output of the adaptiveDesign vignette is regenerated under the new convention.sample() without stratification. The warning is suppressed when silent = TRUE.trial$stop_followup() (wrapper stop_followup()): stop follow-up of a subset of enrolled patients at a milestone, optionally after extra follow-up time (additional_followup). Patients are selected by conditions compatible with dplyr::filter; affected time-to-event endpoints are censored and non-time-to-event readouts are set to missing. This adaptation supports, e.g., treatment discontinuation and enrichment design. Like other adaptations, it must be called within an action function, after a milestone has been triggered.censor_trial_data() gains ... to further restrict the patients to be censored by conditions compatible with dplyr::filter, in addition to selected_arms and enrolled_before. Internal callers are unchanged and benchmark at parity with 1.24.0; simulation outputs are identical.controller$run() now displays a progress bar when silent = TRUE and n_workers = 1 if the simulation is expected to take more than 1 minute. Package cli is added to Imports for this feature.trial() now defaults enroller to StaggeredRecruiter and accepts no other enroller: any non-StaggeredRecruiter value is rejected with an informative error. The enroller argument is retained for backward compatibility, so existing code that passes enroller = StaggeredRecruiter explicitly is unaffected, and code that omits it now gets the default.test-enroller.R covering the default, explicit StaggeredRecruiter, rejection of other enrollers, and post-construction set_enroller() enforcement.StaggeredRecruiter() now supports recruitment pauses: a window with piecewise_rate = 0 enrolls no one while calendar time still advances, so accrual resumes at the window's end_time. Pauses may occur in the first window or span several consecutive windows (e.g., a safety hold, a site not yet activated, or a seasonal gap).StaggeredRecruiter() enrollment times are now the deterministic inverse of the cumulative accrual intensity: the cumulative accrual capacity increases by window length × piecewise_rate across each window (no per-window truncation), so an integer-capacity window holds exactly that many patients. Single open-ended schedules and integer-capacity windows are unchanged; schedules with fractional per-window capacity now yield slightly different (more accurate) times.StaggeredRecruiter() input validation is stricter: the last end_time must be Inf with a positive rate (so the schedule can always supply the patients the engine requests, including the inflated count used for adaptive resizing); a positive rate too low to enroll even one patient (window length × piecewise_rate < 1) is now an error pointing to piecewise_rate = 0; and n must be a positive integer.test-StaggeredRecruiter.R covering pauses (leading, middle, and consecutive), the per-window count property, equidistant spacing, the low-rate and schedule-shape validation errors, and n validation.GraphicalTesting$test() no longer errors with alpha_spent should be monotonically increasing when a hypothesis is first tested only at a later (or final) look. This happens when a hypothesis holds zero or very small allocated alpha at early looks and inherits alpha after another hypothesis is rejected: those looks yield a degenerate (flat, near-zero) reconstructed cumulative alpha-spent sequence. Such entries are now truncated and floored to a strictly increasing sequence before being passed to the group sequential boundary computation.computeCumulativeAlphaSpent treats Inf boundaries as zero increments.endpoint() now accepts type = "baseline" for a non-tte endpoint observed at randomization (e.g., a baseline covariate, biomarker, or subgroup indicator). Its readout is 0 by definition and must be omitted from readout. For forward compatibility, a non-tte endpoint observed at randomization can still be defined the existing way with readout = 0. Endpoints$new() continues to recognize only "tte" and "non-tte"; the conversion happens in endpoint().crossover().regimen()/crossover() examples and help pages to use the natural ifelse(condition, new_value, original) idiom in how() and to return only the switching patients in what(), rather than relying on NA to mark unchanged cells or non-switchers. The engine still accepts NA for backward compatibility.crossover() for milestone-triggered treatment crossover. Called inside a milestone's action function, it lets patients still in the trial switch treatment at (or after) the milestone, altering only their post-switch endpoint values. Regimens are unified under one contract keyed off an earliest crossover time; the classic enrollment regimen added via add_regimen() is the T = 0 case.how() may now only modify post-switch outcomes: returning a value that differs from the original for an endpoint whose readout/event is at or before switch_time (a pre-switch or already-observed outcome) raises an error. Guard such how() functions accordingly, e.g. os = ifelse(os > switch_time, new_os, os).Surv() and strata() from the survival package so that user-supplied formulas in action functions (e.g. Surv(os, os_event) ~ arm) work after library(TrialSimulator) alone, without requiring library(survival) or the survival:: prefix. This fixes errors in parallel runs (n_workers > 1) where each worker only attaches TrialSimulator (reported in #14).'or' would error out when not all of its branches could be reached, instead of resolving to the reachable branch.eventNumber() and enrollment() conditions. Wall-time reduction ranges from a few percent to ~20% depending on how condition-evaluation-heavy the design is, with no change to results. Set options(trialsimulator.use_cpp = FALSE) to fall back to the original R implementation.doseRanging.Rmd now loads pre-computed simulation output; simulatePfsAndOsGumbel.Rmd uses a smaller validation sample).CorrelatedPfsAndOs2() to simulate correlated PFS and OS using a Gumbel copula while targeting marginal PFS/OS medians and Kendall's tau between observed, uncensored PFS and OS times.dplyr::filter() call in Trials$get_event_tables() when no filter expression is provided, reducing total simulation wall time by roughly 5-10% in trials with frequent milestone condition checks.n_switches column to locked data returned by get_locked_data(), counting the number of treatment switches per patient within the data lock window.expandRegimen() to expand the regimen_trajectory column in locked data into a long-format data frame with one row per regimen segment per patient.trial() using argument stratification_factors.... in regimen() to pass arguments to what(), when() and how().regimen() to support both single or multiple switching.regimen().enrollment() to define milestone. This bug does not affect milestone triggering.tidy (default: TRUE) in Controllers$run() to stop computing and saving event count per arm per endpoint for 40% shorter run time.solvePiecewiseConstantExponentialDistribution() to compute event rates in time windows given survival probabilities at changepoints.qPiecewiseExponent(), the quantile function of piecewise exponential distribution. This function is useful to simulate time-to-event endpoint that is correlated to other endpoints using the copula method. For example, the simdata package needs marginal quantile functions.Controllers$run() through new argument n_workers. The package mirai is used. Although mirai advocates the use of L'Ecuyer-CMRG streams to maintain independence between multiple streams, however, TrialSimulator resets it to be Mersenne-Twister streams to force identical behavior between n_workers = 1 and n_workers > 1. This enables debuggability and reproduciability under single-process mode by setting seed that causes issues under multi-process mode.remove_arms(), add_arms(), update_sample_ratio(), set_duration(), resize() and update_generator().Trials$resize() that resizes an ongoing trial.get_output() by setting new argument tidy = TRUE.Trials$update_generator().enforce = TRUE is no longer needed when adding new arms to an existing trial with at least one arm through Trials$add_arms(). However, for backward compatibility, legacy codes with enforce = TRUE still behaves as expected and no need to update.milestone_name. Now action function only requires argument trial and supports optional arguments.milestone() now support ... to pass arguments to action functions.enrollment() now supports min_treatment_duration to ensure minimum treatment duration received by patients at a milestone. With its default value 0, milestone is triggered when a specific number of patients are enrolled.invisible(NULL) is no longer recommended as return value of action function.event_plot() for plot of cumulative events number when endpoint name is "ep". This is due to data masking in dplyr.update_sample_ratio. This test is probably broken by randomness (it is okay).summarizeMilestoneTime and its plot method to summarize triggering time of milestones.update_sample_ratio() now supports updating multiple arms simultaneously. When ratio is not a whole number, sample() is used to replace the permuted block algorithm to randomize patients to arms. This enable response-adaptive design.update_sample_ratio().-Add vignette of fixed design. -Add vignette of wrapper functions of commom statistical tests.
enroll_patients. This is useful when a milestone is triggered after all patients are recruited.... in eventNumber to count event in subset of trial data. This is useful in enrichment design when milestone is defined based on biomarker.get as alias of get_custom_data in the Trials class.trial$dunnettTest(..., planned_info = "default") triggers an error.Trials$dunnettTest to be compatible to one-sided logrank test.fitLogrank now supports formula. strata(...) can be in
formula. No covariate is accepted.fitLogistic.fitLogistic now supports scale = "coefficient" to compute regression coefficient as main effect of arm in the presence of covariates. It is also equivalent to scale = "log odds ratio" in the absence of covariates.fitLogistic.fitCoxph now supports formula to compute log hazard ratio or hazard ratio as main effect of arm. It will detect whether arm's main effect is specified in formula. It allows covariates and interaction between covariates and arm. However, only the main effect of arm is tested and returned.fitCoxph.fitLogistic now supports computing log odds ratio, odds ratio, risk ratio, and risk difference using emmeans contrast, in the presence of covariates.fitLogistic and fitLinear.fitLogistic now supports formula with covariates, and uses emmeans contrast to compute average treatment effect (ATE) on the logit scale.fitLinear now supports formula with covariates, and uses emmeans contrast to compute average treatment effect (ATE) on the mean scale.trigger_condition in the function milestone is deprecated and is replaced with when. Note that trigger_condition is still supported in the R6 class Milestones.trial$get_custom_data throws an error when n > 1 in controller$run(n) because custom data is wiped out in trial$reset.arm through the ... argument.rmarkdown.rmarkdown.controller() as per suggestion from CRAN team.message() so that Shiny app can display it properly.solveMixtureExponentialDistribution to compute median of exponential endpoint of subgroup or the overall population.endpoints, arm, trial, event, listener and controller for Endpoint$new, Arm$new, Trial$new, Event$new, Listener$new and Controller$new.Trial$set_duration.enroll_a_patient. Use enroll_patients only.n > 1 in Controller$run when an arm can possibly be removed adaptively during a trial.enrollment. This is useful to count randomized patients of all arms even if some are removed adaptively.GraphicalTesting and graphicalMCP to repository TrialSimulatorDocuments.Controller$run now can specify number of simulation replicates by newly added argument n. If n is greater than 1, simulation results can be accessed in Controller$get_output().StaggeredRecruiter to force the enrollment time of the first patient is zero. This is an known issue but I was too lazy to fix it. Earlier version may have overestimated time of events.fitFarringtonManning of Farrington-Manning test for rate difference.Trial$bind to row bind data frame in action functions. It is useful to prepare inputs of group sequential or graphical test.weibullDropout to compute parameters of Weibull distribution when using it for dropout distribution.1e-5 while no alpha should have been propagated.enrollment, eventNumber and calendarTime can be combined with & and |. Nested combination is supported by using parentheses.TriggerByEventNumbers and TriggerByCalendarTime are therefore deprecated.fitLogistic to fit logistic regression model.generator in Endpoint.GraphicalTesting when a hypothesis is tested multiple times at the same stage because more alpha is passed from other rejected hypothesis.do_nothing() if users have no intent to do anything at a triggered event. This function can be passed to the argument action when creating a new event, e.g., Event$new(name = 'interim', trigger_condition = TriggerByCalendarTime, action = do_nothing, calendar_time = 64).Controller$run() and insert error message into output (see Trial$get_output()$error_message). It helps to integrate TrialSimulator with targets.Trial$get_seed().GraphicalTesting based on simplified interface of GroupSequentialTest.