Package {whatifbandit}


Title: Analyzing Randomized Experiments Using Multi-Arm Bandits
Version: 1.0.2
Description: Simulates response-adaptive experimental trials using Multi-Arm Bandits. Adaptive robust estimators defined in Hadad et al. (2021) <doi:10.1073/pnas.2014602118> and Offer-Westort et al. (2021) <doi:10.1111/ajps.12597> are used to robustly estimate conditional expectations and treatment effects. Provides significant simulation customization options for imperfect information, non-stationary bandits, and increased exploration strategies for assignments.
License: GPL (≥ 3)
URL: https://github.com/Noch05/whatifbandit
BugReports: https://github.com/Noch05/whatifbandit/issues
Depends: R (≥ 4.1.0)
Imports: bandit, clubSandwich, data.table (≥ 1.18.0), dplyr, estimatr, furrr, lubridate, methods, purrr, randomizr, rlang, tibble, tidyr
Suggests: future, ggplot2, knitr, rmarkdown, testthat (≥ 3.0.0)
VignetteBuilder: knitr
Config/testthat/edition: 3
Config/testthat/parallel: true
Encoding: UTF-8
LazyData: true
RoxygenNote: 8.0.0
NeedsCompilation: no
Packaged: 2026-07-22 23:17:28 UTC; noah
Author: Noah Ochital ORCID iD [aut, cre, cph], Ryan T. Moore ORCID iD [ctb, cph]
Maintainer: Noah Ochital <no9857a@american.edu>
Repository: CRAN
Date/Publication: 2026-07-23 10:50:02 UTC

whatifbandit: Analyzing Randomized Experiments Using Multi-Arm Bandits

Description

Simulates response-adaptive experimental trials using Multi-Arm Bandits. Adaptive robust estimators defined in Hadad et al. (2021) doi:10.1073/pnas.2014602118 and Offer-Westort et al. (2021) doi:10.1111/ajps.12597 are used to robustly estimate conditional expectations and treatment effects. Provides significant simulation customization options for imperfect information, non-stationary bandits, and increased exploration strategies for assignments.

Author(s)

Maintainer: Noah Ochital no9857a@american.edu (ORCID) [copyright holder]

Authors:

Other contributors:

See Also

Useful links:


Adaptively Assign Treatments in a Period

Description

Assigns new treatments for an assignment wave based on the assignment probabilities provided from compute_bandit(). Assignments are made randomly with the given probabilities using randomizr::block_ra(), randomizr::complete_ra(), randomizr::cluster_ra(), or randomizr::block_and_cluster_ra() depending on whether blocking and/or clustering are used.

Usage

assign_treatments(
  current_data,
  probs,
  blocking = NULL,
  clustering = NULL,
  conditions,
  condition_col = NULL,
  cluster_col = NULL,
  sim_type
)

build_ra_args(
  current_data,
  probs = NULL,
  conditions,
  blocking,
  clustering,
  cluster_col = NULL,
  dt
)

compute_assignments(
  current_data,
  probs,
  conditions,
  blocking,
  clustering,
  cluster_col = NULL
)

## S3 method for class 'data.frame'
assign_treatments(
  current_data,
  probs,
  blocking,
  clustering,
  conditions,
  condition_col = NULL,
  cluster_col = NULL,
  sim_type
)

## S3 method for class 'data.table'
assign_treatments(
  current_data,
  probs,
  blocking,
  clustering,
  conditions,
  condition_col = NULL,
  cluster_col = NULL,
  sim_type
)

Arguments

current_data

A data.frame or data.table with only observations from the current sampling period.

probs

Named numeric vector; probability of assignment for each treatment condition.

blocking

Logical; Whether or not treatment blocking is occurring

clustering

Logical; Whether or not treatment clustering is occurring

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

condition_col

Column name of current_data which holds original treatment assignments.

cluster_col

Column name of current_data which holds cluster assignments.

sim_type

String; Type of simulation to conduct, either "resim", "param", or "test", for a resimulated rct, simulation from population parameters, or simulation for the randomization joint test.

dt

Logical. Whether current_data is a data.table.

Value

Updated tibble or data.table with the new treatment conditions for each observation, and whether imputation is required. If this treatment is different from the original experiment, impute_req = 1, otherwise it is 0.

A list with fn (the randomizr function) and args (its arguments).

Character vector of length nrow(current_data) with treatment assignments.

Functions

See Also

Other assign: compute_bandit(), compute_prior()


Recover Block-Specific Success and Total Counts for Bootstrap Null

Description

Recovers the number of successes and total observations within each group for use in constructing group-specific Beta posteriors for the parametric bootstrap joint test.

Usage

boot_null_counts(data, success_col, group = NULL)

## S3 method for class 'data.frame'
boot_null_counts(data, success_col, group = NULL)

## S3 method for class 'data.table'
boot_null_counts(data, success_col, group = NULL)

Arguments

data

Data holding the appropriate outcomes

success_col

Column holding the outcomes.

group

Column to group by, or NULL for the whole dataset.

Value

An aggregated data.frame or data.table with the appropriate counts.


Build Proper Arguments for RCT Bootstrap Joint Test

Description

Build Proper Arguments for RCT Bootstrap Joint Test

Usage

build_rct_clusters(mab, col_names)

build_p_cols(args)

boot_build_p(data, success_col, group_col, cols)

build_time_model_args(mab, args, col_names)

Arguments

mab

A single_rct_mab object.

col_names

Named list of column name strings.

args

The processed args list from joint_base_args().

data

Input data.

success_col

Name of the success column.

group_col

Column to group by, or NULL for the whole dataset.

cols

Named numeric vector of column proportions (from build_p_cols()).

Value

A named numeric vector, named list of vectors, or NULL.

A named numeric vector of proportions, or a scalar 1.

A list with elements cols, s (successes), and n (totals), all named and sorted consistently.

A list with elements time_model (function or NULL) and args (list of additional arguments for the time model).

Functions


Checking Clusters Do Not Persist Across Periods

Description

Checks to ensure that each cluster only exists within a single simulation period, because if this is the case a true clustered design is no longer specified. See details.

Usage

check_clusters(data, cluster_col)

## S3 method for class 'data.frame'
check_clusters(data, cluster_col)

## S3 method for class 'data.table'
check_clusters(data, cluster_col)

cluster_throw(check_clusters, cluster_col)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

cluster_col

String; name of column with cluster indices.

check_clusters

Object created in check_clusters() with all of the clusters which persist across periods.

Details

The assignment algorithm in mab_loop() assumes that clusters do not persist across periods. For a true clustered design, if a cluster persisted across periods, all observations within it would have to be assigned to the same treatment as in the previous period. In an adaptive experiment this results in no adaptation, thus this is not implemented into the algorithm. Instead, the assumption is verified here.

Value

Nothing; Throws a warning if any clusters persist across multiple periods.

Nothing; Throws a warning if clusters persist across periods.

Functions


Argument Check Helper Functions

Description

This set of functions is common across the main argument checkers, and they each check a clear condition on a set of arguments, such as ensuring the proper data type.

Usage

check_logical(...)

check_prop(...)

check_posint(...)

check_sum1(...)

check_string(arg, valid, name)

check_names(...)

Arguments

...

Arguments to check

arg

Argument to check.

valid

vector of valid arguments.

name

name of the argument.

Value

Throws an error if any input is not TRUE or FALSE

Throws an error if any input is not a valid proportion between 0 and 1.

Throws an error if any input is not a valid positive integer.

Nothing; Throws an error if a numeric vector does not sum to 1.

Nothing; Throws an error if the string argument is invalid.

Nothing; Throws an if an argument does not have names attribute.

Functions

See Also

Other checks: check_rct_args()


Perform Validation Checks for simulate_mab()

Description

This function checks to ensure that all required arguments have been properly passed to simulate_mab() before continuing with the simulation. When errors are thrown, user-friendly messages are provided to indicate which argument was misspecified. Additionally, when verbose = TRUE, additional warning messages may be shown if unnecessary arguments are passed.

Usage

check_mab_sim(
  n,
  t,
  p,
  blocks = NULL,
  clusters = NULL,
  control_augment,
  random_assign_prop,
  assignment_dates,
  delayed_feedback,
  time_model = NULL,
  period_sizes = NULL,
  prior_periods = NULL,
  discount_rate,
  dt,
  ndraws = 5000,
  r,
  keep_data,
  keep_models,
  verbose
)

check_p_colnames(p, expected)

Arguments

n

A positive integer. Total number of units to simulate.

t

Total number of assignment periods. Positive integer. Default is t = n for pure sequential (one unit per period) assignment. The sizes of each period will be equal as n %/% t, except for the last period which will be n %/% t + n %% t, when period_sizes = NULL.

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

blocks

A named numeric vector of block membership probabilities (must sum to 1), where names(blocks) are the block labels. Units are assigned to blocks via randomizr::complete_ra(). Pass NULL (default) for no blocking.

clusters

Cluster membership probabilities. Can be:

Numeric vector

A named vector where names(clusters) are the cluster labels e. g. ⁠cC1 = 0.4, C2 = 0.6)⁠. Used when there is not blocking.

Named list of vectors

A named list where names(clusters) are block labels, and each element is a named vector of per-block cluster proportions, e.g. list(B1 = c(C1 = 0.4, C2=0.6), B2 = c(C3 = 0.2, C4 = 0.8)) Clusters are accessed as clusters[[block]][cluster]. Inside each block, cluster proportions must sum to 1, and the same cluster cannot appear in multiple blocks.

Units are assigned to clusters via randomizr::complete_ra(). Pass NULL (default) for no clustering.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

assignment_dates

An optional Date vector of dates representing when units are assigned. If shorter than n it is recycled and sorted. If NULL (default) no assignment dates are recorded.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

period_sizes

Numeric vector of length(t), with the specific number of units to be assigned in each period. Used when it is required to assign different numbers of units to treatment across the periods of the trial.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

dt

Logical. If TRUE returns a data.table::data.table(); otherwise returns a tibble::tibble(). Default FALSE.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

r

Positive integer; number of replications (under different random seed). Replications of the MAB procedure on a fixed dataset provides important diagnostic information on the stochasticity/variance of the re-simulation method. Replications can be conducted in parallel, by setting an appropriate future::plan(). See details below.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

expected

Expected set of group labels.

Value

Nothing; Throws an error if all checks are not met.

Nothing; Throws an error if colnames(p) doesn't match provided labels.

Functions


Validates Inputs For mab_from_rct()

Description

This function checks to ensure that all required arguments have been properly passed to mab_from_rct() before continuing with the simulation. When errors are thrown, user-friendly messages are provided to indicate which argument was misspecified. Additionally, when verbose = TRUE, additional warning messages may be shown if unnecessary arguments are passed.

Usage

check_rct_args(
  data,
  control_augment,
  random_assign_prop,
  period_method,
  time_unit,
  period_length,
  prior_periods,
  discount_rate,
  delayed_feedback,
  whole_experiment,
  col_names,
  verbose,
  ndraws,
  r,
  keep_data,
  keep_models,
  blocking
)

check_cols(
  period_method,
  time_unit,
  delayed_feedback,
  col_names,
  data,
  verbose,
  blocking
)

check_data(
  data,
  col_names,
  period_method,
  period_length,
  time_unit,
  delayed_feedback
)

check_period_method(period_method, time_unit, verbose, period_length)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

period_method

A character string; one of "date", "batch", or "individual", to define the assignment into treatment waves. When using "batch" or "individual", ensure your dataset is pre-arranged in the proper order observations should be considered so that groups are assigned correctly. For "date", observations will be considered in chronological order. "individual" assignment can be computationally intensive for larger datasets.

time_unit

A character string specifying the unit of time for assigning periods when period_method ="date". Acceptable values are ⁠"day", "week",⁠ or "month". "month" does not require an additional column with the months of each observation, but it can accept a separate month_col. If month_col is specified, the periods follow the calendar months strictly, and when it is not specified months are simply used as the time interval. For example if a dataset has dates starting on July 26th, under month based assignment and a specified month_col the dates July 26th and August 3rd would be in different periods, but if the month_col was not specified, they would be in the same period because the dates are less than one month apart.

period_length

A positive integer; represents the length of each treatment period. If period_method is "date", this length refers the number of units specified in time_unit. (i.e., if "day", 10 would be 10 days). If period_method = "batch", this refers to the number of units in each batch.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

col_names

List holding the columns required from the provided data as strings and symbols.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

r

Positive integer; number of replications (under different random seed). Replications of the MAB procedure on a fixed dataset provides important diagnostic information on the stochasticity/variance of the re-simulation method. Replications can be conducted in parallel, by setting an appropriate future::plan(). See details below.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

blocking

Logical; Whether or not treatment blocking is occurring

Value

Throws an error if an argument is missing or misspecified.

Throws an error if columns which are required have not been declared or are not present in the data, or are the wrong primitive data type. Additionally throws warning messages, if unnecessary columns have been provided, only when verbose = TRUE.

Nothing; Throws an error if the provided dataset does not meet the specifications of the trial based on user settings.

Throws an error if the user is missing necessary arguments to assign treatments or passes invalid ones.

Functions

See Also

Other checks: check_helpers


Check Conflicts with Internal Columns

Description

Identifies column name conflicts between internal reserved columns and input data for mab_from_rct()'s internal procedures

Usage

col_conflict_check(data)

reserved_cols()

Value

throws an error under any conflict

a named character vector of reserved column names.

See Also

mab_from_rct()


Ends Multi-Arm Bandit Trial

Description

Condenses output from mab_loop() into manageable structure. Contains methods for data.frame and data.table.

Usage

collect_mab_results(
  data,
  bandits,
  algorithm,
  periods,
  conditions,
  num_conditions,
  ndraws
)

## S3 method for class 'data.frame'
collect_mab_results(
  data,
  bandits,
  algorithm,
  periods,
  conditions,
  num_conditions,
  ndraws
)

## S3 method for class 'data.table'
collect_mab_results(
  data,
  bandits,
  algorithm,
  periods,
  conditions,
  num_conditions,
  ndraws
)

Arguments

data

Finalized data from mab_loop().

bandits

Finalized bandits list of matrices from mab_loop().

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

periods

Total number of periods in Multi-Arm-Bandit trial.

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

num_conditions

Number of conditions, equivalent to length(conditions).

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

Value

A named list containing:

See Also

Other simulation: mab_loop(), run_mab()


Calculate Multi-Arm Bandit Decision Based on Algorithm

Description

Calculates the best treatment for a given period using either a UCB1 or Thompson Sampling algorithm. Thompson sampling is done using bandit::best_binomial_bandit() from the bandit package and UCB1 values are calculated using the well-defined formula that can be found in Auer et al. (2002).

Usage

compute_bandit(
  past_results = NULL,
  algorithm,
  num_conditions,
  conditions,
  current_period,
  control_augment = 0,
  random_assign_prop = 0,
  ndraws
)

compute_bandit.thompson(past_results, conditions, current_period, ndraws)

bandit_invalid(bandit)

compute_bandit.ucb1(past_results, conditions, current_period = NULL)

Arguments

past_results

A tibble/data.table containing summary of prior periods, with successes, number of observations, and success rates, which is created by compute_prior().

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

num_conditions

Number of conditions, equivalent to length(conditions).

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

current_period

Numeric value of length 1; current period of the adaptive trial simulation.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

bandit

a numeric vector of Thompson Sampling probabilities.

Details

Control augmentation adjustment is always done last, to ensure proper probability floor.

The Thompson assignment_probabilities are the same as the bandit vector except when control_augment or random_assign_prop are greater than 0, as these arguments will alter the probabilities of assignment.

Thompson sampling is calculated using the bandit package but the direct calculation can result in errors or overflow. If this occurs, a simulation based method from the same package is used instead to estimate the posterior distribution. If this occurs a warning will be presented. ndraws specifies the number of iterations for the simulation based method, and the default value is 5000.

The UCB1 algorithm only selects 1 treatment at each period, with no probability matching so assignment_probabilities will always have 1 element equal to 1, and the rest equal to 0, unless control_augment or random_assign_prop are greater than 0, which will alter the probabilities of assignment. For example, if the original vector is ⁠(0, 0, 1)⁠, and control_augment = 0.2, the new vector is ⁠(0.2, 0, 0.8)⁠ assuming the first element is control. If instead the 3rd element were the control group the resulting vector would not be changed because it already meets the control group threshold. Under ties, the assignment probability is split evenly among tied arms, so ⁠(0, 1, 1)⁠ would become ⁠(0, 0.5, 0.5)⁠.

Value

A list of length 2 containing:

Logical; TRUE if the vector is invalid, FALSE if valid

Functions

References

Auer, Peter, Nicolò Cesa-Bianchi, and Paul Fischer. 2002. "Finite-Time Analysis of the Multiarmed Bandit Problem." Machine Learning 47 (2): 235–56. doi:10.1023/A:1013689704352.

Kuleshov, Volodymyr, and Doina Precup. 2014. "Algorithms for Multi-Armed Bandit Problems." arXiv. doi:10.48550/arXiv.1402.6028.

Loecher, Thomas Lotze and Markus. 2022. "Bandit: Functions for Simple a/B Split Test and Multi-Armed Bandit Analysis." https://cran.r-project.org/package=bandit.

Thompson, William R. 1933. "On the Likelihood That One Unknown Probability Exceeds Another in View of the Evidence of Two Samples." Biometrika 25 (3/4): 285–94. doi:10.2307/2332286

See Also

Other assign: assign_treatments(), compute_prior()


Calculate Observation Level AW-AIPW For Each Treatment Condition

Description

Calculates the augmented inverse probability weighted estimate (AW-AIPW) of treatment success for each observation and treatment (i.e. on the level of a single unit).

Usage

compute_iaipw(data, assignment_probs, periods, conditions)

## S3 method for class 'data.frame'
compute_iaipw(data, assignment_probs, periods, conditions)

## S3 method for class 'data.table'
compute_iaipw(data, assignment_probs, periods, conditions)

iaipw(conditions_vec, success_vec, mhat, prob, condition)

Arguments

data

tibble or data.table holding necessary information to complete the simulation. Also used to store simulation outputs.

assignment_probs

A tibble/data.table containing the probabilities of being assigned each treatment at a given period.

periods

Total periods in the simulation.

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

Details

The specification for the individual AW-AIPW estimates can be found in Hadad et al. (2021). The formulas in equation 5, formed the basis for this function's calculations. Here the regression adjustment used is the grouped mean of success by treatment, up until the current period of estimation (so at period 5, the grouped mean would be calculated using the results from periods 1 through 4).

Value

A named list with the individual aipw estimate vectors for each treatment condition.

Functions

References

Hadad, Vitor, David A. Hirshberg, Ruohan Zhan, Stefan Wager, and Susan Athey. 2021. "Confidence Intervals for Policy Evaluation in Adaptive Experiments." Proceedings of the National Academy of Sciences of the United States of America 118 (15): e2014602118. doi:10.1073/pnas.2014602118.

See Also

Other estimation: estimate_aw_aipw(), estimate_lm(), inference_helpers, lin_contrast


Compute exact period sizes

Description

Compute exact period sizes

Usage

compute_period_sizes(data)

## S3 method for class 'data.frame'
compute_period_sizes(data)

## S3 method for class 'data.table'
compute_period_sizes(data)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

Value

Numeric vector of length(max(period_nummber)) with each element representing the number of units in each period.


Gather Past Results for Given Assignment Period

Description

Summarizes results of prior periods to update assignment probabilities in the current period. This function calculates the number of successes under each treatment and the total number of observations assigned to each treatment which are used to calculate UCB1 values or Thompson sampling probabilities. These values are weighted by the discount_rate provided.

Usage

compute_prior(
  current_data,
  prior_data,
  delayed_feedback,
  assignment_date_col = NULL,
  discount_rate,
  conditions,
  current_period
)

## S3 method for class 'fast'
compute_prior(
  current_data,
  prior_data,
  delayed_feedback,
  assignment_date_col = NULL,
  discount_rate,
  conditions,
  current_period
)

## S3 method for class 'data.frame'
compute_prior(
  current_data,
  prior_data,
  delayed_feedback,
  assignment_date_col = NULL,
  discount_rate,
  conditions,
  current_period
)

## S3 method for class 'data.table'
compute_prior(
  current_data,
  prior_data,
  delayed_feedback,
  assignment_date_col = NULL,
  discount_rate,
  conditions,
  current_period
)

finalize_prior_list(prior_list, conditions)

Arguments

current_data

A data.frame or data.table with only observations from the current sampling period.

prior_data

A data.frame or data.table with only the observations from the prior index.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

assignment_date_col

Bare column in data; contains original dates treatments were assigned to observations. Only necessary when delayed_feedback = TRUE. Used to simulate imperfect observation of outcomes in the simulation. Must be of type Date, not a character string.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

conditions

Character vector of all treatment conditions in the trial.

current_period

The current period number.

prior_list

Named list with elements mab_condition, successes, n, produced by converting a summarized data.frame/data.table via as.list().

Details

When delayed_feedback = TRUE, the maximum value from the specified assignment_date_col in the current data is taken as the last possible date the researchers conducting the experiment could have learned about a treatment outcome. All successes that occur past this date are masked and are treated as failures for the purposes this period's assignments.

Value

A list containing 2 named vectors: the weighted number of successes, and the weighted number of assignments, where the names of each vector correspond to the treatment condition.

A named list with elements successes, n, each a named numeric vector of length length(conditions).

Functions

See Also

Other assign: assign_treatments(), compute_bandit()


Condenses results of repeated simulations.

Description

Condenses results of repeated simulations.

Usage

condense_results(dt, keep_data, keep_models, mabs)

Arguments

dt

Logical; Whether to output data.tables or tibbles. When r * number_of_periods > 100000, dt = TRUE, even if the user passed data is not a data.table.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

mabs

List of outputs from repeated run_mab() calls.

Details

This function iterates over every element in mabs and extracts the required element to place in a condensed list for the final output.

Value

A named list containing:


Constructs mab and its other class variants

Description

Simple constructor for proper mab subclasses as output to simulate_mab() and mab_from_rct().

Usage

construct_mab(mab, type, multi)

Arguments

mab

Named list output of simulate_mab() or mab_from_rct().

type

Type of simulated trial, either "rct" or "param" to denote whether it was an RCT re-simulation or an simulation from population parameters.

multi

Logical; TRUE denotes multiple trials.

Value

Input mab with appropriate S3 class, restructured for output


Create Treatment Wave Cutoffs

Description

Used to assign each observation a new treatment assignment period, based on user-supplied specifications, and user supplied data from date_col and month_col in col_names, and the period_length. Creates a new column indicating with period each observation belongs to.

Usage

create_cutoff(data, col_names, period_length = NULL, period_method, time_unit)

create_cutoff.date(data, time_unit, date_col, month_col, period_length)

create_cutoff.individual(data)

create_cutoff.batch(data, period_length)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

col_names

List holding the columns required from the provided data as strings and symbols.

period_length

A positive integer; represents the length of each treatment period. If period_method is "date", this length refers the number of units specified in time_unit. (i.e., if "day", 10 would be 10 days). If period_method = "batch", this refers to the number of units in each batch.

period_method

A character string; one of "date", "batch", or "individual", to define the assignment into treatment waves. When using "batch" or "individual", ensure your dataset is pre-arranged in the proper order observations should be considered so that groups are assigned correctly. For "date", observations will be considered in chronological order. "individual" assignment can be computationally intensive for larger datasets.

time_unit

A character string specifying the unit of time for assigning periods when period_method ="date". Acceptable values are ⁠"day", "week",⁠ or "month". "month" does not require an additional column with the months of each observation, but it can accept a separate month_col. If month_col is specified, the periods follow the calendar months strictly, and when it is not specified months are simply used as the time interval. For example if a dataset has dates starting on July 26th, under month based assignment and a specified month_col the dates July 26th and August 3rd would be in different periods, but if the month_col was not specified, they would be in the same period because the dates are less than one month apart.

date_col

Bare column in data; contains original date of event/trial. Only necessary when assigning by "Date". Must be of type Date.

month_col

Bare column in data; contains month of treatment. Only necessary when time_unit = "month", and when periods should be determined directly by the calendar months instead of month based time periods. This column can be a string/factor variable with the month names or numeric with the month number. It can easily be created from your date_col via lubridate::month(data[[date_col]]) or format(data[[date_col]], "%m").

Details

The assignment periods do not strictly have to line up with the original experiment, it is up to the researcher to test the possible options.

Month based assignment can be specified either using the months inside the month_col or date_col, if month_col is passed into the function it will be used.

Value

Updated tibble/data.table with the new period_number column. period_number is an integer representing an observation's new assignment period.


Create Necessary Columns for Multi-Arm Bandit Trial

Description

Initializes partially empty columns in data to initialize them for the simulation. These are initialized as NA except for observations with period_number = 1, whose values are copied from the provided columns, and used as the starting point for the simulation.

Usage

create_new_cols(data, col_names, blocking, delayed_feedback, vars_keep)

## S3 method for class 'data.frame'
create_new_cols(data, col_names, blocking, delayed_feedback, vars_keep)

## S3 method for class 'data.table'
create_new_cols(data, col_names, blocking, delayed_feedback, vars_keep)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

col_names

List holding the columns required from the provided data as strings and symbols.

blocking

Logical; Whether or not treatment blocking is occurring

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

vars_keep

Character vector of variables to keep

Value

A data.frame/data.table subsetted to all the user provided columns plus these 6 new columns:


Calculate Adaptive AW-AIPW Estimates

Description

Uses provided Individual AIPW scores created by compute_iaipw() and computes the final Adaptively Weighted AIPW estimate and variance using the formulas from Hadad et. al (2021). Uses the constant allocation rate adaptive weight.

Usage

estimate_aw_aipw(
  data,
  assignment_probs,
  conditions,
  iaipw,
  cluster_col,
  clustering,
  periods,
  num_clusters = NULL
)

Arguments

data

tibble or data.table holding necessary information to complete the simulation. Also used to store simulation outputs.

assignment_probs

A tibble/data.table containing the probabilities of being assigned each treatment at a given period.

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

iaipw

Individual AW-AIPW scores computed by compute_iaipw().

cluster_col

String; name of column with cluster indices.

clustering

Logical; Whether or not treatment clustering is occurring

periods

Total periods in the simulation.

Details

The formulas for the calculations in this function can be found in Hadad et al. (2021) at equation 5 (estimate), equation 11 (variance), equation 15 (allocation rate).

The estimator assumes pure sequential assignment, but we adapt the estimator to our batched assignment procedure. In the computations, of the individual estimates, regression estimates were only computed for each period, instead of for each observation, and similarly the adaptive weights used will only be computed per period, and then simply assigned to all the observations in that period, thus resulting in only a few unique weights. This keeps effective sample size large, ensuring the asymptotic properties are realized in large samples with only a few assignment periods, while also properly accounting for the assignment procedure.

If clustering is specified, within each period individual AIPW estimates are aggregated by cluster, and then the sample size becomes the sum of the number of clusters in each period, the variance formula uses the cluster deviations instead (an effective CR0 style estimator), which is adjusted via the Stata CR1 estimator (\frac{G}{G-1} * \frac{N-1}{N-k}) where k is the number of treatments, and G is the number of clusters.

The AW-AIPW estimator is unbiased, consistent, and asymptotically normal when a sufficently large non-zero probability of assignment is guaranteed for each treatment condition in each period. The provided standard errors of the so can be used for valid inference with a normal distribution. Treatment effects can also be estimated as the difference in AW-AIPW estimates with the variance of the difference as the sum of the variances of the two arms. In the clustered case, we suggest a t distribution to be more conservative, given the sample size is now the cluster, we provide G-1 as degrees of freedom, as standard by regression packages for clustered inference (CR0, and CR1).

Value

A tibble/data.table containing 5 columns:

References

Hadad, Vitor, David A. Hirshberg, Ruohan Zhan, Stefan Wager, and Susan Athey. 2021. "Confidence Intervals for Policy Evaluation in Adaptive Experiments." Proceedings of the National Academy of Sciences of the United States of America 118 (15): e2014602118. doi:10.1073/pnas.2014602118.

See Also

Other estimation: compute_iaipw(), estimate_lm(), inference_helpers, lin_contrast


OLS Estimates for Probability of Success

Description

Computes OLS estimates for true true probabilities of success using estimatr::lm_robust(). Supports IPW weighted and unweighted regression. If clustering is specified CR2 standard errors are reported. Otherwise HC2 standard errors are used. Appropriate degrees of freedom are supplied along with the regression's F-statistic

Usage

estimate_lm(
  data,
  cluster_col,
  clustering,
  conditions,
  num_clusters = NULL,
  ipw
)

Arguments

data

tibble or data.table holding necessary information to complete the simulation. Also used to store simulation outputs.

cluster_col

String; name of column with cluster indices.

clustering

Logical; Whether or not treatment clustering is occurring

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

ipw

Logical. If TRUE IPW-weighted LPM; if FALSE, fits the unweighted OLS LPM.

Details

If CR2 standard errors fail to be calculated, CR1S are computed, equivalent to vce(cluster, clustervar) in Stata, and se_type = "stata" in {estimatr}

These estimates follow the procedure in Offer-Westort et al. (2021). Degrees of freedom are not provided for the f-statistic, because the traditional F-distribution is invalid under the adaptive procedure. However, this f-statistic can be used for the randomization and bootstrap inference joint-tests provided.

The provided standard errors can be used to construct approximate confidence intervals using a t-distribution with the provided degrees of freedom. However the degrees of freedom provided are n - num_conditions, which is likely to be an overestimate given the potential for the number of observations assigned to each group to vary widely with the adaptive trial. The HC2 or CR2 corrections help but do not capture the temporal dependence created by adaptive assignment. Thus formal pairwise tests cannot be conducted using these estimates, because the estimator does not follow a precise t-distribution.

As mentioned in Offer-Westort et al. (2021), if there is a true best arm, and control augmentation is used, t-test can provide proper coverage in large samples because the algorithm converges to a two-arm comparison.

Block fixed effects are not used for estimation due to the prevalence of numerical instability in the estimates. Assignment probabilities to treatment are the same within each block, so the IPW estimator is still unbiased without the presence of the fixed effects.

Value

A list containing 3 elements:

References

Offer‐Westort, Molly, Alexander Coppock, and Donald P. Green. 2021. "Adaptive Experimental Design: Prospects and Applications in Political Science." American Journal of Political Science 65 (4): 826–44. doi:10.1111/ajps.12597.

See Also

Other estimation: compute_iaipw(), estimate_aw_aipw(), inference_helpers, lin_contrast


Fit a Robust Linear Probability Model and Compute Contrasts

Description

Internal helper that wraps linear model estimation for both IPW and OLS estimators, returning arm-level mean estimates, precomputed contrasts, and optionally the fitted model object.

Usage

estimate_lm_bundle(
  ipw,
  estimator,
  sim_results,
  col_names,
  clustering,
  conditions,
  num_clusters,
  dt,
  contrasts_list
)

Arguments

ipw

Logical. If TRUE IPW-weighted LPM; if FALSE, fits the unweighted OLS LPM.

estimator

Character string labeling the estimator, either "IPW" or "OLS"

col_names

List holding the columns required from the provided data as strings and symbols.

clustering

Logical; Whether or not treatment clustering is occurring

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

dt

Logical. If TRUE returns a data.table::data.table(); otherwise returns a tibble::tibble(). Default FALSE.

contrasts_list

List of 1 x k row vector contrasts to compute.

Value

A named list with three elements: means, contrasts, model. contrasts are the computed contrasts, the other 2 elements are the output of estimate_lm()


Extract Success Probabilities Per-Unit

Description

Looks up the success probability for each unit given their treatment assignment and, optionally, their block and/or cluster membership. Handles all supported p structures.

Usage

extract_success_prob(p, conditions, other_idx = NULL)

Arguments

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

conditions

A character or factor vector of treatment assignments of length n.

other_idx

Character vector of block or cluster assignments to be used as the additional index for extracting from p.

Value

A numeric vector of length n containing the per-unit success probabilities to be used for outcome observation.


Helpers for Joint F Test

Description

Takes the single_mab object provided and returns the proper null distribution for the randomization or the bootstrap joint test.

Usage

joint_random_null(mab, r)

joint_boot_null(mab, r)

joint_base_args(mab, sim_type)

joint_null_inner(args)

Arguments

mab

A single_rct_mab or single_param_mab object.

r

A positive integer; number of simulations used to build the null distribution. Default is 100.

sim_type

String; Type of simulation to conduct, either "resim", "param", or "test", for a resimulated rct, simulation from population parameters, or simulation for the randomization joint test.

args

Arguments list to run_mab_single()

Value

a numeric vector of simulated F-statistics

A named list of arguments to be used for run_mab_single()

The F-statistic from the IPW regression of the MAB Trial

Functions


Formula Parser

Description

Parses the input formula for mab_from_rct()

Usage

formula_parse(formula)

gather_args(x)

.block(...)

.cluster(c)

Arguments

formula

formula passed from mab_from_rct()

x

String representing an R expression, like "block(x1)".

...

Blocking variables from formula

c

cluster variable from formula

Value

List of columns specified from formula.

A list containing the function call, and the arguments so "block(x1)" gets turned into a list with elements ⁠block, "x1"⁠.

Vector of blocking variables

cluster variable

Functions


Generate Assignment Dates

Description

Generates a length(n) vector of assignment dates based on provided information.

Usage

generate_assignment_dates(n, assignment_dates)

Arguments

n

A positive integer. Total number of units to simulate.

assignment_dates

An optional Date vector of dates representing when units are assigned. If shorter than n it is recycled and sorted. If NULL (default) no assignment dates are recorded.

Value

vector of assignment dates

See Also

Other param: generate_groups(), generate_outcomes(), generate_period_idx(), prep_sim_data(), setup_mab_sim(), split_args()


Generate Block and Cluster Memberships

Description

Takes a named probability vector for blocks and clusters and uses randomizr::complete_ra() to randomly assign each of n units to a block and cluster according to those probabilities.

Usage

generate_groups(n, blocks = NULL, clusters = NULL)

Arguments

n

A positive integer. Total number of units to simulate.

blocks

A named numeric vector of block membership probabilities (must sum to 1), where names(blocks) are the block labels. Units are assigned to blocks via randomizr::complete_ra(). Pass NULL (default) for no blocking.

clusters

Cluster membership probabilities. Can be:

Numeric vector

A named vector where names(clusters) are the cluster labels e. g. ⁠cC1 = 0.4, C2 = 0.6)⁠. Used when there is not blocking.

Named list of vectors

A named list where names(clusters) are block labels, and each element is a named vector of per-block cluster proportions, e.g. list(B1 = c(C1 = 0.4, C2=0.6), B2 = c(C3 = 0.2, C4 = 0.8)) Clusters are accessed as clusters[[block]][cluster]. Inside each block, cluster proportions must sum to 1, and the same cluster cannot appear in multiple blocks.

Units are assigned to clusters via randomizr::complete_ra(). Pass NULL (default) for no clustering.

Value

A list containing the factor vectors of group assignments for blocks, clusters, both or NULL depending on what was specified. Levels for each vector come from the labels for each block and cluster.

See Also

Other param: generate_assignment_dates(), generate_outcomes(), generate_period_idx(), prep_sim_data(), setup_mab_sim(), split_args()


Generate Outcomes Per-Unit

Description

Uses provided success probabilities to draw a Bernoulli outcome for each unit. If time_model is provided, it is also used to compute dates of success

Usage

generate_outcomes(
  current_data,
  current_period,
  data,
  p,
  idx,
  simulate_dates,
  time_model = NULL,
  time_model_args = NULL
)

Arguments

current_data

A data.frame or data.table with only observations from the current sampling period.

current_period

Indicator of the current period of the simulation.

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

idx

Index of rows imputed in the period, used to update the original data.

simulate_dates

Logical; whether or not new success dates should be generated using time_model. Does not guarantee these new dates are used for assignment, delayed_feedback controls that behavior.

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

time_model_args

Arguments passed to time_model function.

Value

Updated data object containing all the outcomes generated in the period, such as the treatment assignments, treatment outcomes, and new success dates

See Also

Other param: generate_assignment_dates(), generate_groups(), generate_period_idx(), prep_sim_data(), setup_mab_sim(), split_args()


Generate Start and End Indexes

Description

Generates the start and end indexes for each period based on provided information

Usage

generate_period_idx(n, t, period_sizes = NULL)

Arguments

n

A positive integer. Total number of units to simulate.

t

Total number of assignment periods. Positive integer. Default is t = n for pure sequential (one unit per period) assignment. The sizes of each period will be equal as n %/% t, except for the last period which will be n %/% t + n %% t, when period_sizes = NULL.

period_sizes

Numeric vector of length(t), with the specific number of units to be assigned in each period. Used when it is required to assign different numbers of units to treatment across the periods of the trial.

Details

When not provided period sizes are calculated as n %/% t for all periods, with the last as n %/% t + n %% t.

Value

list of numeric vectors featuring start and end indexes for each period of the simulation

See Also

Other param: generate_assignment_dates(), generate_groups(), generate_outcomes(), prep_sim_data(), setup_mab_sim(), split_args()


Get Group Proportions

Description

Accepts input data, and a group column, and returns the proportion of the data that belongs to each group

Usage

group_prop(data, group)

## S3 method for class 'data.frame'
group_prop(data, group)

## S3 method for class 'data.table'
group_prop(data, group)

Arguments

data

Input Data

group

Column to group by

Value

A named numeric vector with the names corresponding to the group, and the value to its proportion among the provided data.


Imputing New Outcomes of Multi-Arm-Bandit Trial

Description

Imputes outcomes for the current treatment assignment period. Uses randomizr::block_ra() to impute the outcomes for observations who were assigned new treatments. The probabilities used to guide the imputation of the outcomes are pre-computed using the existing data from the original randomized experiment.

Usage

impute_outcomes(
  data,
  imputation_info,
  success_col,
  success_date_col = NULL,
  delayed_feedback,
  idx
)

compute_impute(imputation_info, success_col)

## S3 method for class 'data.frame'
impute_outcomes(
  data,
  imputation_info,
  success_col,
  success_date_col,
  delayed_feedback,
  idx
)

## S3 method for class 'data.table'
impute_outcomes(
  data,
  imputation_info,
  success_col,
  success_date_col,
  delayed_feedback,
  idx
)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

imputation_info

List containing all necessary information for imputation, generated each period by prep_imputation()

success_col

String; name of column holding outcomes from original experiment.

success_date_col

String; name of column holding outcome observation date from original experiment.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

idx

Index of rows imputed in the period, used to update the original data.

Details

When delayed_feedback = TRUE, dates of success are imputed according to the average by each period and treatment block (treatment arm + any blocking). These imputations are required because these observations do not currently have dates of success, as no success was observed during the original experiment. Therefore if they go through the next iteration of the simulation without being imputed, the new successes will still be treated as failures because of the date masking mechanism.

Observations that were successful in the original experiment, got assigned a new treatment, and then imputed as success again, will have their original date kept. This assumes that the treatment has no individual treatment effect on the date of success, which may or may not be valid depending on the context of the experiment.

Value

Updated data object with all the updates from the current period of the simulation

Numeric vector of imputed outcomes

Functions

See Also

Other imputation: precompute_imputation(), prep_imputation()


Helper Functions for Inference

Description

Internal helpers for estimation in run_mab().

Accepts a data.frame like object, and a character of vector of conditions. It checks whether or not all provided conditions are present in the data, if not their values are initialized to NA

Combines the AW-AIPW, IPW, and Sample estimates into a single object to be returned.

Performs adjustment of CR0 SE to CR1 SE, using Stata's formula

Usage

strip_condition_prefix(x)

fill_missing_conditions(x, conditions, estimator)

## S3 method for class 'data.frame'
fill_missing_conditions(x, conditions, estimator)

## S3 method for class 'data.table'
fill_missing_conditions(x, conditions, estimator)

combine_estimates(...)

cr1(x, g, n, k)

Arguments

x

vector of variances.

conditions

Character vector of treatment condition labels.

...

tibbles or data.tables to bind together.

g

integer number of clusters

n

integer, dataset size

k

integer number of treatments

Value

x with the prefix removed.

An updated estimates object with missing conditions initialized.

A list of 2 elements:

An adjusted vector of variances

Functions

See Also

Other estimation: compute_iaipw(), estimate_aw_aipw(), estimate_lm(), lin_contrast


Joint Hypothesis Test for Multi-Arm Bandit Trials

Description

Conducts a joint hypothesis test of no treatment effects across all arms, i.e. that all arms have the same true probability of success, either using a bootstrap procedure or the randomization inference procedure adapted from Offer-Westort et al. (2021). See details for a description of both methods

Usage

joint_test(mab, method = c("bootstrap", "randomization"), r = 100)

Arguments

mab

A single_rct_mab or single_param_mab object.

method

A character string; either "bootstrap" or "randomization".

r

A positive integer; number of simulations used to build the null distribution. Default is 100.

Value

A named list object containing

NOTE

This procedure is experimental and has no Type I error guarantee. Offer-Westort et. al (2021) also note the test suffers from low power, but it is provided for experimentation nonetheless.

method = "randomization" operates under the sharp null that each unit would express the same outcome no matter the treatment they were assigned. To achieve this the trial is re-simulated but new outcomes are not generated or imputed, however the adaptive algorithm still changes the assignments. This results in a null distribution that captures how the adaptive algorithm will assign even when the outcomes are not related to treatments at all.

method = "bootstrap" operates under the null hypothesis that there is no difference between treatment arms within each block/cluster. If there are no blocks or clusters, a p-matrix is built from the pooled sample mean of the original trial. With block and or cluster pooled sample means are estimated within each block or cluster. The block and or cluster assignment proportions are taken from the original dataset.

References

Offer-Westort, Molly, Alexander Coppock, and Donald P. Green. "Adaptive Experimental Design: Prospects and Applications in Political Science." American Journal of Political Science 65, no. 4 (2021): 826–44. doi:10.1111/ajps.12597.

Examples


data(tanf)
set.seed(5)
adaptive <- mab_from_rct(success ~ condition, data = tanf, algorithm = "thompson",
period_method = "batch", period_length = 500)

# Low `r` for examples, use replications in practice
joint_test(adaptive, "randomization", r = 2)
joint_test(adaptive, "bootstrap", r = 2)



Computing Linear Contrasts

Description

A set of helper functions for computing linear contrasts of AW-AIPW, IPW, and OLS estimates

Usage

build_contrast_matrices(conditions, contrasts = NULL, bandits)

make_contrasts(conditions, ref_idx, type)

compute_contrast(
  C = NULL,
  coefs = NULL,
  vcov = NULL,
  df = NULL,
  model = NULL,
  data = NULL,
  dt,
  conditions = NULL,
  estimator
)

Arguments

conditions

Vector of treatment conditions

contrasts

Character string specifying which pairwise contrasts to precompute after each replication. One of "control" (each arm vs. control arm), "best" (each arm vs. the MAB-selected best arm), "both", or "all" (all choose(k, 2) pairwise comparisons, expensive for large k). All contrasts are tested under the two-sided null of no difference. Defaults to NULL, arbitrary contrasts can be computed after if keep_models == TRUE

bandits

data.frame or data.table of bandit statistics

ref_idx

Index of the reference arm

type

Type of contrasts, "best" or "control"

C

List of 1 x k contrast vectors

coefs

named vector of coefficients

vcov

A ⁠k x k⁠ variance-covariance matrix

df

Numeric scalar giving the degrees of freedom for the t-test. AW-AIPW only uses t-test in clustered case.

model

An lm_robust model object

data

tibble or data.table holding necessary information to complete the simulation. Also used to store simulation outputs.

dt

Whether to compile results into a data.table or data.frame

estimator

Character string labeling the estimator, either "IPW" or "OLS"

Value

A list of 1 row contrast vectors, encoding a single pairwise comparison.

A matrix row vectors containing each contrast to test.

data.table or data.frame of linear contrasts with columns for each treatment arm, the estimated contrast, standard error, and degrees of freedom.

Functions

See Also

Other estimation: compute_iaipw(), estimate_aw_aipw(), estimate_lm(), inference_helpers


Simulate a Multi-Arm-Bandit Trial from an Existing Randomized Controlled Trial, With Bernoulli Distributed Outcomes.

Description

Simulates a response-adaptive, Multi-Arm-Bandit (MAB) trial using experimental data from an original randomized controlled trial (RCT), and adaptive inference strategies described in Hadad et al. (2021) and Offer-Westort et al. (2021) to robustly estimate treatment effects. See the details to learn more.

Usage

mab_from_rct(
  formula,
  data,
  algorithm = c("thompson", "ucb1", "static"),
  random_assign_prop = 0,
  control_augment = 0,
  control_condition = NULL,
  period_method = c("batch", "date", "individual"),
  time_unit = NULL,
  period_length,
  prior_periods = NULL,
  discount_rate = 1,
  date_col = NULL,
  month_col = NULL,
  delayed_feedback = FALSE,
  assignment_date_col = NULL,
  success_date_col = NULL,
  whole_experiment = FALSE,
  ndraws = 5000,
  r = 1,
  verbose = FALSE,
  check_args = TRUE,
  keep_data = FALSE,
  keep_models = FALSE,
  contrasts = NULL,
  ...
)

Arguments

formula

A formula object specifying outcome variable, treatment indicator, treatment blocking and treatment clustering for optional blocked and/or clustered randomized designs. The treatment variable should always be the first variable following ~ (Additional covariates to be added in later updates). Clustering and blocking variables should be included in specific block() or cluster() groups in the formula, e.g. outcome ~ treatment + block(x1, x2, x3) + cluster(x4).

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

control_condition

Value of the control condition. Only necessary when control_augment is greater than 0. Internally this value is coerced to a string, so it should be passed as a string, or a type that can easily be converted to a string.

period_method

A character string; one of "date", "batch", or "individual", to define the assignment into treatment waves. When using "batch" or "individual", ensure your dataset is pre-arranged in the proper order observations should be considered so that groups are assigned correctly. For "date", observations will be considered in chronological order. "individual" assignment can be computationally intensive for larger datasets.

time_unit

A character string specifying the unit of time for assigning periods when period_method ="date". Acceptable values are ⁠"day", "week",⁠ or "month". "month" does not require an additional column with the months of each observation, but it can accept a separate month_col. If month_col is specified, the periods follow the calendar months strictly, and when it is not specified months are simply used as the time interval. For example if a dataset has dates starting on July 26th, under month based assignment and a specified month_col the dates July 26th and August 3rd would be in different periods, but if the month_col was not specified, they would be in the same period because the dates are less than one month apart.

period_length

A positive integer; represents the length of each treatment period. If period_method is "date", this length refers the number of units specified in time_unit. (i.e., if "day", 10 would be 10 days). If period_method = "batch", this refers to the number of units in each batch.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

date_col

Bare column in data; contains original date of event/trial. Only necessary when assigning by "Date". Must be of type Date.

month_col

Bare column in data; contains month of treatment. Only necessary when time_unit = "month", and when periods should be determined directly by the calendar months instead of month based time periods. This column can be a string/factor variable with the month names or numeric with the month number. It can easily be created from your date_col via lubridate::month(data[[date_col]]) or format(data[[date_col]], "%m").

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

assignment_date_col

Bare column in data; contains original dates treatments were assigned to observations. Only necessary when delayed_feedback = TRUE. Used to simulate imperfect observation of outcomes in the simulation. Must be of type Date, not a character string.

success_date_col

Bare column in data; contains original dates each success occurred. Only necessary when delayed_feedback = TRUE. Must be of type Date, not a character string.

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

r

Positive integer; number of replications (under different random seed). Replications of the MAB procedure on a fixed dataset provides important diagnostic information on the stochasticity/variance of the re-simulation method. Replications can be conducted in parallel, by setting an appropriate future::plan(). See details below.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

check_args

Logical; whether or not to validate passed arguments. Default is TRUE and recommended not to be changed.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

contrasts

Character string specifying which pairwise contrasts to precompute after each replication. One of "control" (each arm vs. control arm), "best" (each arm vs. the MAB-selected best arm), "both", or "all" (all choose(k, 2) pairwise comparisons, expensive for large k). All contrasts are tested under the two-sided null of no difference. Defaults to NULL, arbitrary contrasts can be computed after if keep_models == TRUE

...

Additional named arguments passed to furrr::furrr_options()

Details

Clustering

Clusters should be contained inside each assignment wave (a warning is thrown if this is not the case), so it is possible to have 2 observations in the same cluster assigned to different treatments if they were assigned in different waves. This is assumed because without it the adaptive probabilities will not be impacting assignments. For example if someone in cluster 1 is assigned in period 1, then all other members are forced to have the same treatment, even if they are assigned in period 5, 10, 20 etc.

Implementation

At each period, either the Thompson sampling probabilities or UCB1 values are calculated based on the outcomes from the number of prior_periods specified weighted by discount_rate. New treatments are then assigned randomly using the Thompson sampling probabilities via the randomizr package, or as the treatment with the highest UCB1 values, while implementing the specific treatment blocking and control augmentation specified.

After assigning treatments, observations with new treatments have their outcomes imputed using success rates from the original randomized trial. These rates are estimated as grouped means within each treatment arm. If blocking is specified, rates are estimated within each combination of treatment arm and block.

If delayed_feedback = TRUE, new dates of success will be imputed using the means of those dates in the period, grouped by treatment block if necessary. Observations for which their treatment changed, but their outcome was success in the original and simulation, do not have their date changed. When the next period starts, the success dates are checked against the maximum/latest assignment_date for the period, and if any success occurs after that, it is treated as a failure for the purpose of the bandit decision algorithms.

Inference

At the end of the simulation the results are aggregated together to calculate the Adaptively Weighted Augmented Inverse Probability Estimator (Hadad et al. 2021) using the mean and variance formulas provided, under the constant allocation rate adaptive schema. These estimators are unbiased and asymptotically normal under the adaptive conditions and their differences are also unbiased asymptotically normal estimators for treatment effects. See Hadad et al. (2021). Asymptotic validity hinges on sub-optimal arms continually being assigned, if arms have low to 0 probability of being assigned, the central limit theorem proved no longer applies. Thus it is recommended to use random_assign_prop and control_augment to ensure all arms have non-zero probabilities of assignment over the whole trial.

Under clustering the unit of observation becomes the cluster, the sample size becomes the number of clusters. Individual estimates are aggregated in each period by cluster before being used to compute the final AIPW estimate and variance (CR0 style). The variance is adjusted by the Stata CR1 adjustment, (\frac{G}{G-1} * \frac{N-1}{N-k}) where k is the number of treatments, and G is the number of clusters. Degrees of freedom of G-1 are also provided, for use of the more conservative t-distribution, though inference is still only valid asymptotically.

Inverse Probability Weighted (IPW) estimates are also provided using estimatr::lm_robust(). Offer-Westort et al. (2021). In clustered cases CR2 standard errors are used, and CR1 (Stata) used if CR2 computation fails. HC2 standard errors are used in non-clustered cases. In high sample sizes for the arms chosen, standard t-tests of the estimates and their contrasts can be asymptotically valid. F-statistics are provided for joint tests provided in joint_test().

AIPW and IPW are unbiased, with AIPW having lower variances generally, while standard unweighted OLS estimates will be biased with spuriously low variance, but are provided for comparisons.

Performance Concerns

This procedure has the potential to be computationally expensive and time-consuming. Performance depends on the relative size of each period, number of periods, the overall size of the dataset, and number of replications. This function has separate support for data.frames and data.tables. If a data.frame is passed, the function uses a combination of dplyr, tidyr and base R to shape data, and run the simulation. However, if a data.table is passed the function exclusively uses the data.table code for all the same operations.

In general, smaller batches run faster under base R, while larger ones could benefit from the performance and memory efficiencies provided by data.table. However, we've observed larger datasets can cause numerical instability with some calculations in the Thompson sampling procedure. Internal safeguards exist to prevent this, but the best way to preempt any issues is to set prior_periods to a low number.

r > 1

Multiple simulations allows researchers to gauge the variance of the simulation procedure itself, by repeating it several times under different random states, using the same fixed data.

Parallel Processing

The function provides support for parallel processing via the future and furrr packages. When conducting a large number of simulations, parallelization can improve performance if sufficient system resources are available. Parallel processing must be explicitly set by the user, through future::plan(). Windows users should set the plan to "multisession", while Linux and MacOS users can use "multicore" or "multisession". Users running in a High Performance Computing environment (HPC), are encouraged to use future.batchtools, for their respective HPC scheduler. Note that parallel processing is not guaranteed to work on all systems, and may require additional setup or debugging effort from the user. For any issues, users are encouraged to consult the documentation of the above packages.

Value

Depends on r value if r = 1, an S3 single_rct_mab class object, and if r > 1, an S3 muti_rct_mab, with the following:

References

Agrawal, Shipra, and Navin Goyal. 2012. "Analysis of Thompson Sampling for the Multi-Armed Bandit Problem." Proceedings of the 25th Annual Conference on Learning Theory, June 16, 39.1-39.26. https://proceedings.mlr.press/v23/agrawal12.html.

Asyuraa, F. C., S. Abdullah, and T. E. Sutanto. 2021. "Empirical Evaluation on Discounted Thompson Sampling for Multi-Armed Bandit Problem with Piecewise-Stationary Bernoulli Arms." Journal of Physics: Conference Series 1722 (1): 012096. doi:10.1088/1742-6596/1722/1/012096

Auer, Peter, Nicolò Cesa-Bianchi, and Paul Fischer. 2002. "Finite-Time Analysis of the Multiarmed Bandit Problem." Machine Learning 47 (2): 235–56. doi:10.1023/A:1013689704352.

Bengtsson, Henrik. 2025. "Future: Unified Parallel and Distributed Processing in R for Everyone." https://cran.r-project.org/package=future.

Bengtsson, Henrik. 2025. "Future.Batchtools: A Future API for Parallel and Distributed Processing Using ‘Batchtools.’" https://cran.r-project.org/package=future.batchtools.

Garivier, Aurélien, and Eric Moulines. 2008. "On Upper-Confidence Bound Policies for Non-Stationary Bandit Problems." arXiv:0805.3415. Preprint, arXiv, May 22. doi:10.48550/arXiv.0805.3415

Hadad, Vitor, David A. Hirshberg, Ruohan Zhan, Stefan Wager, and Susan Athey. 2021. "Confidence Intervals for Policy Evaluation in Adaptive Experiments." Proceedings of the National Academy of Sciences of the United States of America 118 (15): e2014602118. doi:10.1073/pnas.2014602118.

Kuleshov, Volodymyr, and Doina Precup. 2014. "Algorithms for Multi-Armed Bandit Problems." arXiv. doi:10.48550/arXiv.1402.6028.

Loecher, Thomas Lotze and Markus. 2022. "Bandit: Functions for Simple a/B Split Test and Multi-Armed Bandit Analysis." https://cran.r-project.org/package=bandit.

Offer‐Westort, Molly, Alexander Coppock, and Donald P. Green. 2021. "Adaptive Experimental Design: Prospects and Applications in Political Science." American Journal of Political Science 65 (4): 826–44. doi:10.1111/ajps.12597.

Slivkins, Aleksandrs. 2024. "Introduction to Multi-Armed Bandits." arXiv. doi:10.48550/arXiv.1904.07272.

Vaughan, Davis, Matt Dancho, and RStudio. 2022. "Furrr: Apply Mapping Functions in Parallel Using Futures." https://cran.r-project.org/package=furrr.

See Also

furrr, future, joint_test(), simulate_mab()

Examples

data(tanf)
set.seed(454)

mab_from_rct(success ~ condition, data = tanf, algorithm = "thompson",
period_method = "batch", period_length = 500, delayed_feedback = TRUE,
assignment_date_col = appt_date, success_date_col = date_of_recert)

mab_from_rct(success ~ condition, data = tanf, algorithm = "ucb1",
period_method = "date", time_unit = "day", date_col = appt_date,
 period_length = 60, r = 2, discount_rate = 0.8)

mab_from_rct(success ~ condition + block(service_center), data = tanf, algorithm = "thompson",
period_method = "batch", period_length = 500, control_condition = "no_letter",
control_augment = 0.2, prior_periods = 1)


Runs Multi-Arm Bandit Trial

Description

Performs a full Multi-Arm Bandit (MAB) trial using Thompson Sampling or UCB1. The function provides a loop around each step of the process for each treatment wave, performing adaptive treatment assignment, and outcome imputation or generation as needed.

Usage

mab_loop(
  data,
  sim_type,
  p,
  algorithm,
  control_augment,
  random_assign_prop,
  prior_periods,
  discount_rate,
  whole_experiment = NULL,
  simulate_dates,
  delayed_feedback,
  clustering,
  blocking,
  conditions,
  col_names,
  imputation_information = NULL,
  ndraws,
  verbose,
  period_idxs,
  periods,
  num_conditions,
  time_model = NULL,
  time_model_args = NULL
)

compute_lookback(prior_periods = NULL, current_period)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

simulate_dates

Logical; whether or not new success dates should be generated using time_model. Does not guarantee these new dates are used for assignment, delayed_feedback controls that behavior.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

clustering

Logical; Whether or not treatment clustering is occurring

blocking

Logical; Whether or not treatment blocking is occurring

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

col_names

List holding the columns required from the provided data as strings and symbols.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

period_idxs

List containing vectors which map their entries to the starting row and ending row of each period.

periods

Number of simulation periods.

num_conditions

Number of conditions, equivalent to length(conditions).

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

time_model_args

Arguments passed to time_model function.

current_period

The current period of the simulation. Defined by loop structure inside mab_loop().

Details

The first period is used to initialize the trial, so the loop starts at period number 2.

Value

A named list containing:

Numeric value referring to the period index to look back from.

Functions

See Also

Other simulation: collect_mab_results(), run_mab()


Miscellaneous Helper Functions

Description

Converts data.frame column into a vector using another column as the names for the vector

Shorthand Function for checking verbose and then printing if TRUE

Usage

as_named_vec(df, val, name)

verbose_log(log, message)

Arguments

df

data.frame used.

val

Column name of values

name

Column name of names

log

Logical; Whether or not to print the message, this will always be the verbose argument passed from higher functions.

message

The message to be printed to screen, as a string.

Value

vector with values val and names name

Text output of message to the console when log = TRUE. If log = FALSE, returns nothing.


Precomputing Key Values for Outcome Imputation

Description

Pre-computes key values required for the outcome imputation step of the MAB procedure. Calculates the probabilities of success for each treatment block (treatment arm + any blocking specified), using the grouped means of the original experimental data. When delayed_feedback = TRUE, the average date of success is calculated for each treatment block at every period.

Usage

precompute_imputation(data, whole_experiment, delayed_feedback, col_names)

## S3 method for class 'data.frame'
precompute_imputation(data, whole_experiment, delayed_feedback, col_names)

## S3 method for class 'data.table'
precompute_imputation(data, whole_experiment, delayed_feedback, col_names)

summary_to_matrix(df)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

col_names

List holding the columns required from the provided data as strings and symbols.

df

A data.frame or data.table with columns treatment_block, success_rate, and failure_rate.

Details

precompute_imputation() is an optimization, meant to reduce the cost of calculating these variables within the simulation loop. When whole_experiment = TRUE, ⁠success' is a single matrix, and used through the simulation. When ⁠whole_experiment = FALSE⁠, ⁠success⁠is a list of matrices, each containing the cumulative probabilities of all periods up to the index⁠i'.

If delayed_feedback = FALSE, dates is not calculated, and is NULL. No covariates are used in the calculation, these are all simply grouped means.

Value

A named list containing:

A numeric matrix with row names equal to treatment_block and columns failure_rate and success_rate.

Functions

See Also

Other imputation: impute_outcomes(), prep_imputation()


Outcome Imputation Preparation

Description

Executes all preparations necessary to impute outcomes for each iteration of the simulation loop. Adds an additional column to the current data, subsets necessary information from the precompute_imputation() output, and ensures compatibility with randomizr::block_ra().

Usage

prep_imputation(
  current_data,
  block_cols,
  imputation_information,
  whole_experiment,
  blocking,
  delayed_feedback,
  current_period
)

check_impute(impute_success, current_data, impute_idx)

Arguments

current_data

A data.frame or data.table with only observations from the current sampling period.

block_cols

Names of the blocking columns

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

blocking

Logical; Whether or not treatment blocking is occurring

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

current_period

The current period number.

impute_success

The success element of the imputation_information list created by precompute_imputation() for the given period.

Details

The goal of this function is to set up the imputation procedure and prevent errors from occurring. randomizr::block_ra() does not see the names of the probabilities passed per block, so the imputation information must be subsetted to contain only the treatment blocks which exist in a given period.

When blocks are required but do not exist in the information provided it is added to the matrix, with an estimated conditional probability of success as the average across other blocks. When blocks are present but not required, they are removed. Then the rows are sorted by their names.

impute_block is the observation's new treatment block, combining any blocking variables with their new treatment assigned via the Multi-Arm-Bandit procedure.

Value

A named list containing:

Proper impute_success matrix as required by randomizr::block_ra().

See Also

Other imputation: impute_outcomes(), precompute_imputation()


Pre-Simulation Setup to Simulate a MAB Trial From an RCT

Description

Common function for all the actions that need to take place before running the Multi-Arm-Bandit re-simulation. Intakes the data and column names to check for valid arguments, format and create new columns as needed, and pre-compute key values to avoid doing so within the simulation loop.

Usage

prep_rct_data(
  data,
  random_assign_prop,
  control_augment,
  control_condition,
  period_method,
  time_unit,
  period_length,
  prior_periods,
  discount_rate,
  col_names,
  delayed_feedback,
  whole_experiment,
  verbose,
  ndraws,
  check_args,
  r,
  keep_data,
  keep_models,
  blocking,
  clustering
)

create_conditions(control_condition, data, condition_col, control_augment)

Arguments

data

A data.frame, data.table, or any object which inherits from data.frame, containing input data from the trial. This should be the results of a traditional Randomized Controlled Trial (RCT).

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

control_condition

Value of the control condition. Only necessary when control_augment is greater than 0. Internally this value is coerced to a string, so it should be passed as a string, or a type that can easily be converted to a string.

period_method

A character string; one of "date", "batch", or "individual", to define the assignment into treatment waves. When using "batch" or "individual", ensure your dataset is pre-arranged in the proper order observations should be considered so that groups are assigned correctly. For "date", observations will be considered in chronological order. "individual" assignment can be computationally intensive for larger datasets.

time_unit

A character string specifying the unit of time for assigning periods when period_method ="date". Acceptable values are ⁠"day", "week",⁠ or "month". "month" does not require an additional column with the months of each observation, but it can accept a separate month_col. If month_col is specified, the periods follow the calendar months strictly, and when it is not specified months are simply used as the time interval. For example if a dataset has dates starting on July 26th, under month based assignment and a specified month_col the dates July 26th and August 3rd would be in different periods, but if the month_col was not specified, they would be in the same period because the dates are less than one month apart.

period_length

A positive integer; represents the length of each treatment period. If period_method is "date", this length refers the number of units specified in time_unit. (i.e., if "day", 10 would be 10 days). If period_method = "batch", this refers to the number of units in each batch.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

col_names

List holding the columns required from the provided data as strings and symbols.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

check_args

Logical; whether or not to validate passed arguments. Default is TRUE and recommended not to be changed.

r

Positive integer; number of replications (under different random seed). Replications of the MAB procedure on a fixed dataset provides important diagnostic information on the stochasticity/variance of the re-simulation method. Replications can be conducted in parallel, by setting an appropriate future::plan(). See details below.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

blocking

Logical; Whether or not treatment blocking is occurring

clustering

Logical; Whether or not treatment clustering is occurring

Details

If a data.table is passed it is copied to avoid modifying the original dataset in the users environment.

Value

Named list containing:

Character vector of unique treatment conditions. Throws error if an invalid specification is used.

Functions


Prepares Data for Simulated MAB

Description

Initializes the data a simulated MAB trial. Generates block and cluster assignments, allocates all required columns, and assigns treatments and outcomes for the first period using equal assignment probabilities.

Usage

prep_sim_data(
  n,
  p,
  blocks = NULL,
  clusters = NULL,
  blocking,
  clustering,
  conditions,
  equal_probs,
  period_idxs,
  simulate_dates,
  assignment_dates = NULL,
  time_model = NULL,
  time_model_args = NULL,
  dt
)

Arguments

n

A positive integer. Total number of units to simulate.

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

blocks

A named numeric vector of block membership probabilities (must sum to 1), where names(blocks) are the block labels. Units are assigned to blocks via randomizr::complete_ra(). Pass NULL (default) for no blocking.

clusters

Cluster membership probabilities. Can be:

Numeric vector

A named vector where names(clusters) are the cluster labels e. g. ⁠cC1 = 0.4, C2 = 0.6)⁠. Used when there is not blocking.

Named list of vectors

A named list where names(clusters) are block labels, and each element is a named vector of per-block cluster proportions, e.g. list(B1 = c(C1 = 0.4, C2=0.6), B2 = c(C3 = 0.2, C4 = 0.8)) Clusters are accessed as clusters[[block]][cluster]. Inside each block, cluster proportions must sum to 1, and the same cluster cannot appear in multiple blocks.

Units are assigned to clusters via randomizr::complete_ra(). Pass NULL (default) for no clustering.

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

equal_probs

Vector of equal assignment probabilities.

period_idxs

List containing vectors which map their entries to the starting row and ending row of each period.

simulate_dates

Logical; whether or not new success dates should be generated using time_model. Does not guarantee these new dates are used for assignment, delayed_feedback controls that behavior.

assignment_dates

An optional Date vector of dates representing when units are assigned. If shorter than n it is recycled and sorted. If NULL (default) no assignment dates are recorded.

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

time_model_args

Arguments passed to time_model function.

dt

Logical. If TRUE returns a data.table::data.table(); otherwise returns a tibble::tibble(). Default FALSE.

Value

Initialized data.table or tibble with the first period simulation conducted, and all required columns for run_mab()

See Also

Other param: generate_assignment_dates(), generate_groups(), generate_outcomes(), generate_period_idx(), setup_mab_sim(), split_args()


Simulates a MAB Trial From Prepared Inputs and Performs Inference

Description

Internal helper. Centralizes necessary functions to conduct a a MAB trial with adaptive inference. It assumes all inputs have been preprocessed already.

Usage

run_mab(
  data,
  sim_type,
  estimators = c("aipw", "ipw", "ols"),
  p = NULL,
  algorithm,
  control_augment,
  random_assign_prop,
  prior_periods,
  discount_rate,
  simulate_dates = NULL,
  delayed_feedback,
  whole_experiment = NULL,
  conditions,
  blocking,
  clustering,
  col_names,
  imputation_information = NULL,
  verbose,
  ndraws,
  period_idxs,
  keep_data,
  keep_models,
  contrasts,
  time_model = NULL,
  time_model_args = NULL
)

Arguments

data

tibble or data.table holding necessary information to complete the simulation. Also used to store simulation outputs.

sim_type

String; Type of simulation to conduct, either "resim", "param", or "test", for a resimulated rct, simulation from population parameters, or simulation for the randomization joint test.

estimators

Character vector; Which estimators to compute, can include "aipw", "ipw", "ols", and any combination of them in a vector.

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

simulate_dates

Logical; whether or not new success dates should be generated using time_model. Does not guarantee these new dates are used for assignment, delayed_feedback controls that behavior.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

blocking

Logical; Whether or not treatment blocking is occurring

clustering

Logical; Whether or not treatment clustering is occurring

col_names

List holding the columns required from the provided data as strings and symbols.

imputation_information

Object created by precompute_imputation() containing the conditional means and success dates for each treatment block to impute from.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

period_idxs

List containing vectors which map their entries to the starting row and ending row of each period.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

contrasts

Character string specifying which pairwise contrasts to precompute after each replication. One of "control" (each arm vs. control arm), "best" (each arm vs. the MAB-selected best arm), "both", or "all" (all choose(k, 2) pairwise comparisons, expensive for large k). All contrasts are tested under the two-sided null of no difference. Defaults to NULL, arbitrary contrasts can be computed after if keep_models == TRUE

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

time_model_args

Arguments passed to time_model function.

Value

A named list containing:

See Also

Other simulation: collect_mab_results(), mab_loop()


Run a single MAB simulation iteration

Description

Runs a single iteration of a MAB simulation. Intended to be called either directly for r = 1 or as the mapped function inside furrr::future_map()for r > 1⁠. Handles all ⁠sim_type'.

Usage

run_mab_single(
  sim_type,
  algorithm,
  estimators = c("aipw", "ipw", "ols"),
  control_augment = 0,
  random_assign_prop = 0,
  prior_periods = NULL,
  delayed_feedback = FALSE,
  discount_rate = 1,
  conditions,
  blocking,
  clustering,
  col_names,
  ndraws = 5000,
  keep_data = FALSE,
  keep_models = FALSE,
  contrasts = NULL,
  verbose = FALSE,
  r = 1,
  imputation_information = NULL,
  whole_experiment = NULL,
  time_model = NULL,
  time_model_args = NULL,
  p = NULL,
  n = NULL,
  dt = NULL,
  blocks = NULL,
  clusters = NULL,
  equal_probs = NULL,
  assignment_dates = NULL,
  simulate_dates = NULL,
  period_idxs,
  data = NULL
)

Arguments

sim_type

String; Type of simulation to conduct, either "resim", "param", or "test", for a resimulated rct, simulation from population parameters, or simulation for the randomization joint test.

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

estimators

Character vector; Which estimators to compute, can include "aipw", "ipw", "ols", and any combination of them in a vector.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

conditions

Character vector of treatment condition labels. If a control group is specified the names attribute should be present with the control group labeled "control".

blocking

Logical; Whether or not treatment blocking is occurring

clustering

Logical; Whether or not treatment clustering is occurring

col_names

List holding the columns required from the provided data as strings and symbols.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

contrasts

Character string specifying which pairwise contrasts to precompute after each replication. One of "control" (each arm vs. control arm), "best" (each arm vs. the MAB-selected best arm), "both", or "all" (all choose(k, 2) pairwise comparisons, expensive for large k). All contrasts are tested under the two-sided null of no difference. Defaults to NULL, arbitrary contrasts can be computed after if keep_models == TRUE

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

r

Positive integer; number of replications (under different random seed). Replications of the MAB procedure on a fixed dataset provides important diagnostic information on the stochasticity/variance of the re-simulation method. Replications can be conducted in parallel, by setting an appropriate future::plan(). See details below.

imputation_information

Object created by precompute_imputation() containing the conditional means and success dates for each treatment block to impute from.

whole_experiment

Logical; if TRUE, uses all past experimental data for imputing outcomes. If FALSE, uses only data available up to the current period. In large datasets or with a high number of periods, setting this to FALSE can be more computationally intensive, though not a significant contributor to total run time. Default is FALSE.

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

time_model_args

Arguments passed to time_model function.

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

n

A positive integer. Total number of units to simulate.

dt

Logical. If TRUE returns a data.table::data.table(); otherwise returns a tibble::tibble(). Default FALSE.

blocks

A named numeric vector of block membership probabilities (must sum to 1), where names(blocks) are the block labels. Units are assigned to blocks via randomizr::complete_ra(). Pass NULL (default) for no blocking.

clusters

Cluster membership probabilities. Can be:

Numeric vector

A named vector where names(clusters) are the cluster labels e. g. ⁠cC1 = 0.4, C2 = 0.6)⁠. Used when there is not blocking.

Named list of vectors

A named list where names(clusters) are block labels, and each element is a named vector of per-block cluster proportions, e.g. list(B1 = c(C1 = 0.4, C2=0.6), B2 = c(C3 = 0.2, C4 = 0.8)) Clusters are accessed as clusters[[block]][cluster]. Inside each block, cluster proportions must sum to 1, and the same cluster cannot appear in multiple blocks.

Units are assigned to clusters via randomizr::complete_ra(). Pass NULL (default) for no clustering.

equal_probs

Vector of equal assignment probabilities.

assignment_dates

An optional Date vector of dates representing when units are assigned. If shorter than n it is recycled and sorted. If NULL (default) no assignment dates are recorded.

simulate_dates

Logical; whether or not new success dates should be generated using time_model. Does not guarantee these new dates are used for assignment, delayed_feedback controls that behavior.

period_idxs

List containing vectors which map their entries to the starting row and ending row of each period.

data

tibble or data.table holding necessary information to complete the simulation. Also used to store simulation outputs.

Value

A named list containing:


Set Up MAB Simulation

Description

Performs all one-time set-up required for simulate_mab() as opposed to prep_sim_data() which needs to be re-run each period.

Usage

setup_mab_sim(
  n,
  t,
  p,
  blocks,
  clusters,
  assignment_dates,
  time_model,
  period_sizes
)

Arguments

n

A positive integer. Total number of units to simulate.

t

Total number of assignment periods. Positive integer. Default is t = n for pure sequential (one unit per period) assignment. The sizes of each period will be equal as n %/% t, except for the last period which will be n %/% t + n %% t, when period_sizes = NULL.

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

blocks

A named numeric vector of block membership probabilities (must sum to 1), where names(blocks) are the block labels. Units are assigned to blocks via randomizr::complete_ra(). Pass NULL (default) for no blocking.

clusters

Cluster membership probabilities. Can be:

Numeric vector

A named vector where names(clusters) are the cluster labels e. g. ⁠cC1 = 0.4, C2 = 0.6)⁠. Used when there is not blocking.

Named list of vectors

A named list where names(clusters) are block labels, and each element is a named vector of per-block cluster proportions, e.g. list(B1 = c(C1 = 0.4, C2=0.6), B2 = c(C3 = 0.2, C4 = 0.8)) Clusters are accessed as clusters[[block]][cluster]. Inside each block, cluster proportions must sum to 1, and the same cluster cannot appear in multiple blocks.

Units are assigned to clusters via randomizr::complete_ra(). Pass NULL (default) for no clustering.

assignment_dates

An optional Date vector of dates representing when units are assigned. If shorter than n it is recycled and sorted. If NULL (default) no assignment dates are recorded.

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

period_sizes

Numeric vector of length(t), with the specific number of units to be assigned in each period. Used when it is required to assign different numbers of units to treatment across the periods of the trial.

Value

A named list containing:

See Also

Other param: generate_assignment_dates(), generate_groups(), generate_outcomes(), generate_period_idx(), prep_sim_data(), split_args()


Simulate an Adaptive Trial With Bernoulli Distributed Outcomes

Description

Simulates a response-adaptive randomized experiment with Bernoulli distributed outcomes. At each period, observed outcomes are used to update assignment probabilities according to the specified algorithm. algorithm = "static" is the non-adaptive uniform baseline, where probabilities of being assigned to one treatment is the same as any other.

Usage

simulate_mab(
  n,
  t = n,
  p,
  algorithm = c("thompson", "ucb1"),
  blocks = NULL,
  clusters = NULL,
  control_augment = 0,
  random_assign_prop = 0,
  delayed_feedback = FALSE,
  assignment_dates = NULL,
  time_model = NULL,
  period_sizes = NULL,
  prior_periods = NULL,
  discount_rate = 1,
  dt = FALSE,
  ndraws = 5000,
  r = 1,
  keep_data = FALSE,
  keep_models = FALSE,
  contrasts = NULL,
  check_args = TRUE,
  verbose = FALSE,
  ...
)

Arguments

n

A positive integer. Total number of units to simulate.

t

Total number of assignment periods. Positive integer. Default is t = n for pure sequential (one unit per period) assignment. The sizes of each period will be equal as n %/% t, except for the last period which will be n %/% t + n %% t, when period_sizes = NULL.

p

The true probabilities of success for each treatment arm. Specified as a matrix, where rownames(p) are the treatment arm names. If there is a control condition, specify its rowname as "Control". colnames(p) are the cluster or block labels, e.g. matrix(c(0.5, 0.3, 0.5, 0.6), nrow = 2, ncol = 2, dimnames(list(c("Control", "T1"), c("B1", "B2")))). Probabilities are accessed as p[treatment, block]. With blocks and clusters utilize the clusters for the columns because clusters are fully nested in blocks. For no clusters or blocks simply use a matrix with 1 column.

algorithm

A character string specifying the MAB algorithm to use. Options are "thompson", "ucb1", or "static", ignoring case. Algorithm defines the adaptive assignment process. For more details on these specific algorithms see Thompson 1933; Auer et al. 2002; Agrawal and Goyal 2012; Kuleshov and Precup 2014 and Slivkins 2024.

blocks

A named numeric vector of block membership probabilities (must sum to 1), where names(blocks) are the block labels. Units are assigned to blocks via randomizr::complete_ra(). Pass NULL (default) for no blocking.

clusters

Cluster membership probabilities. Can be:

Numeric vector

A named vector where names(clusters) are the cluster labels e. g. ⁠cC1 = 0.4, C2 = 0.6)⁠. Used when there is not blocking.

Named list of vectors

A named list where names(clusters) are block labels, and each element is a named vector of per-block cluster proportions, e.g. list(B1 = c(C1 = 0.4, C2=0.6), B2 = c(C3 = 0.2, C4 = 0.8)) Clusters are accessed as clusters[[block]][cluster]. Inside each block, cluster proportions must sum to 1, and the same cluster cannot appear in multiple blocks.

Units are assigned to clusters via randomizr::complete_ra(). Pass NULL (default) for no clustering.

control_augment

Minimum proportion of each treatment assignment wave guaranteed to receive the treatment labeled as "Control". Ranges from 0 to 1, and the default is 0. Adjustment is always made after the adjustment from random_assign_prop.

random_assign_prop

Proportion of each treatment wave assigned via static, equal probabilities of assignment. Adaptive probabilities are updated by p * (1 - random_assign_prop) + random_assign_prop * 1/k, where k is the number of treatment arms.

delayed_feedback

Logical; if FALSE, assumes instantaneous feedback for outcomes, as soon as a treatment is assigned, the outcome is realized and known. If TRUE, delayed feedback is assumed, so as soon as treatment is assigned, a potential outcome is realized, but it is not known to the simulation, until a certain date. When re-computing the adaptive assignment probabilities, outcomes that have not been observed on the date of assignment are treated as failures.

assignment_dates

An optional Date vector of dates representing when units are assigned. If shorter than n it is recycled and sorted. If NULL (default) no assignment dates are recorded.

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

period_sizes

Numeric vector of length(t), with the specific number of units to be assigned in each period. Used when it is required to assign different numbers of units to treatment across the periods of the trial.

prior_periods

A positive integer; number of previous periods to use in the treatment assignment model. Default is NULL, where all prior periods are considered. See below for details.

discount_rate

Rate for discounting observations from earlier periods when updating assignment probabilities. A value between 0 and 1, where outcomes from k periods ago are weighted by discount_rate^k. Default is 1 for no discounting.

dt

Logical. If TRUE returns a data.table::data.table(); otherwise returns a tibble::tibble(). Default FALSE.

ndraws

Number of draws used to approximate Thompson sampling probabilities. Used only when direct calculation fails or overflows. Default is 5000 but can be raised or lowered depending on performance and accuracy concerns.

r

Positive integer; number of replications (under different random seed). Replications of the MAB procedure on a fixed dataset provides important diagnostic information on the stochasticity/variance of the re-simulation method. Replications can be conducted in parallel, by setting an appropriate future::plan(). See details below.

keep_data

Logical; Whether or not to keep the final data from each trial. Recommended FALSE. When r = 1 the final data is always kept and reported.

keep_models

Logical; Whether or not to keep the final IPW and OLS models from each trial. Recommended FALSE. When r = 1 models are always kept and reported. Required to be TRUE to compute arbitrary pairwise contrasts. Only utilized under clustering.

contrasts

Character string specifying which pairwise contrasts to precompute after each replication. One of "control" (each arm vs. control arm), "best" (each arm vs. the MAB-selected best arm), "both", or "all" (all choose(k, 2) pairwise comparisons, expensive for large k). All contrasts are tested under the two-sided null of no difference. Defaults to NULL, arbitrary contrasts can be computed after if keep_models == TRUE

check_args

Logical; whether or not to validate passed arguments. Default is TRUE and recommended not to be changed.

verbose

Logical; whether or not to print intermediate messages. Default is FALSE.

...

Additional named arguments forwarded to time_model and furrr::furrr_options().

Details

Blocking and Clustering

When blocking and/or clustering are specified, these assignments will be randomly pregenerated before the start of the adaptive sequential assignment. These arguments allow simulating a trial when there may be heterogeneous outcomes across a treatment block or treatment cluster, so different assignment probabilities can be provided for the same treatment, depending on the block and/or cluster of a unit.

Clusters should be contained inside each assignment wave (a warning is thrown if this is not the case), so it is possible to have 2 observations in the same cluster assigned to different treatments if they were assigned in different waves. This is assumed because without it the adaptive probabilities will not be impacting assignments. For example if someone in cluster 1 is assigned in period 1, then all other members are forced to have the same treatment, even if they are assigned in period 5, 10, 20 etc.

Implementation

At each period, either the Thompson sampling probabilities or UCB1 values are calculated based on the outcomes from the number of prior_periods specified weighted by discount_rate. New treatments are then assigned randomly using the Thompson sampling probabilities via the randomizr package, or as the treatment with the highest UCB1 values, while implementing the specific treatment blocking and control augmentation specified.

After assigning treatments, observations will have their outcomes generated via a Bernoulli draw associated to the probability in the p matrix corresponding to their treatment and block/cluster. If delayed_feedback = TRUE, dates of success will be generated via the provided time_model() function. When the next period starts, the success dates are checked against the maximum/latest assignment_date for the period, and if any success occurs after that, it is treated as a failure for the purpose of the bandit decision algorithms.

Inference

At the end of the simulation the results are aggregated together to calculate the Adaptively Weighted Augmented Inverse Probability Estimator (Hadad et al. 2021) using the mean and variance formulas provided, under the constant allocation rate adaptive schema. These estimators are unbiased and asymptotically normal under the adaptive conditions and their differences are also unbiased asymptotically normal estimators for treatment effects. Hadad et al. (2021). Asymptotic validity, hinges on sub-optimal arms continually being assigned, if arms have low to 0 probability of being assigned, the central limit theorem proved no longer applies. Thus it is recommended to use random_assign_prop and control_augment to ensure all arms non-zero probabilities of assignment over the whole trial.

Under clustering the unit of observation becomes the cluster, the sample size the number of clusters. Individual estimates are aggregated in each period by cluster before being used to compute the final AW-AIPW estimate and variance (CR0 style). The variance is adjusted by the Stata CR1 adjustment, (\frac{G}{G-1} * \frac{N-1}{N-k}) where k is the number of treatments, and G is the number of clusters. Degrees of freedom of G-1 are also provided, for use of the more conservative t-distribution, though inference is still only valid asymptotically.

Inverse Probability Weighted (IPW) estimates are also provided using estimatr::lm_robust(). Offer-Westort et al. (2021). In clustered cases CR2 standard errors are used, and CR1 (Stata) used if CR2 computation fails. HC2 standard errors are used in non-clustered cases. In high sample sizes for the arms chosen, standard t-tests of the estimates and their contrasts can be asymptotically valid. F-statistics are provided for joint tests provided in joint_test().

AW-AIPW and IPW are unbiased, with AW-AIPW having lower variances generally, while standard unweighted OLS estimates will be biased with spuriously low variance but are provided for comparisons.

r > 1

Multiple simulations allow researchers to gauge the variance of the procedure and produce bootstrap estimates of variance of the procedure under the passed parameters. For each simulation new data is drawn according to the passed population parameters. This differs from mab_from_rct() where resimulations occurs on the same fixed dataset.

Further details about the adaptive procedure can be found in mab_from_rct()

Performance Concerns

This procedure has the potential to be computationally expensive and time-consuming. Performance depends on the relative size of each period, t, n, and r. This function has separate support for data.frames and data.tables, selected by the dt argument. This flag defines two separate tracks where either combination of dplyr, tidyr and base R to shape data, and run the simulation or only data.table code operations are used.

In general, smaller batches run faster under base R, while larger ones could benefit from the performance and memory efficiencies provided by data.table. However, we've observed larger sizes can cause numerical instability with some calculations in the Thompson sampling procedure. Internal safeguards exist to prevent this, but the best way to preempt any issues is to set prior_periods to a low number.

Parallel Processing

The function provides support for parallel processing via the future and furrr packages. When conducting a large number of simulations, parallelization can improve performance if sufficient system resources are available. Parallel processing must be explicitly set by the user, through future::plan(). Windows users should set the plan to "multisession", while Linux and MacOS users can use "multicore" or "multisession". Users running in a High Performance Computing environment (HPC), are encouraged to use future.batchtools, for their respective HPC scheduler. Note that parallel processing is not guaranteed to work on all systems, and may require additional setup or debugging effort from the user. For any issues, users are encouraged to consult the documentation of the above packages.

Value

Depends on r value if r = 1, an S3 single_param_mab class object, and if r > 1, an S3 muti_param_mab, with the following:

References

Agrawal, Shipra, and Navin Goyal. 2012. "Analysis of Thompson Sampling for the Multi-Armed Bandit Problem." Proceedings of the 25th Annual Conference on Learning Theory, June 16, 39.1-39.26. https://proceedings.mlr.press/v23/agrawal12.html.

Asyuraa, F. C., S. Abdullah, and T. E. Sutanto. 2021. "Empirical Evaluation on Discounted Thompson Sampling for Multi-Armed Bandit Problem with Piecewise-Stationary Bernoulli Arms." Journal of Physics: Conference Series 1722 (1): 012096. doi:10.1088/1742-6596/1722/1/012096

Auer, Peter, Nicolò Cesa-Bianchi, and Paul Fischer. 2002. "Finite-Time Analysis of the Multiarmed Bandit Problem." Machine Learning 47 (2): 235–56. doi:10.1023/A:1013689704352.

Bengtsson, Henrik. 2025. "Future: Unified Parallel and Distributed Processing in R for Everyone." https://cran.r-project.org/package=future.

Bengtsson, Henrik. 2025. "Future.Batchtools: A Future API for Parallel and Distributed Processing Using ‘Batchtools.’" https://cran.r-project.org/package=future.batchtools.

Garivier, Aurélien, and Eric Moulines. 2008. "On Upper-Confidence Bound Policies for Non-Stationary Bandit Problems." arXiv:0805.3415. Preprint, arXiv, May 22. doi:10.48550/arXiv.0805.3415

Hadad, Vitor, David A. Hirshberg, Ruohan Zhan, Stefan Wager, and Susan Athey. 2021. "Confidence Intervals for Policy Evaluation in Adaptive Experiments." Proceedings of the National Academy of Sciences of the United States of America 118 (15): e2014602118. doi:10.1073/pnas.2014602118.

Kuleshov, Volodymyr, and Doina Precup. 2014. "Algorithms for Multi-Armed Bandit Problems." arXiv. doi:10.48550/arXiv.1402.6028.

Loecher, Thomas Lotze and Markus. 2022. "Bandit: Functions for Simple a/B Split Test and Multi-Armed Bandit Analysis." https://cran.r-project.org/package=bandit.

Offer‐Westort, Molly, Alexander Coppock, and Donald P. Green. 2021. "Adaptive Experimental Design: Prospects and Applications in Political Science." American Journal of Political Science 65 (4): 826–44. doi:10.1111/ajps.12597.

Slivkins, Aleksandrs. 2024. "Introduction to Multi-Armed Bandits." arXiv. doi:10.48550/arXiv.1904.07272.

Vaughan, Davis, Matt Dancho, and RStudio. 2022. "Furrr: Apply Mapping Functions in Parallel Using Futures." https://cran.r-project.org/package=furrr.

See Also

mab_from_rct()

Examples

n <- 100
t <- 10

p <- matrix(c(0.2, 0.5, 0.45, 0.5), ncol =1, dimnames = list(paste0("T", 1:4)))

set.seed(543)
simulate_mab(n, t, p = p, random_assign_prop = 0.1,
algorithm =  "ucb1", discount_rate  = 0.5)

simulate_mab(n, t, p = p, random_assign_prop = 0.1,
period_sizes = c(37, rep(7, 9)), algorithm = "thompson")


simulate_mab(n, t, p = matrix(c(0.1, 0.5, 0.3, 0.2, 0.3, 0.3),
dimnames = list(c("T1", "T2"), c("B1", "B2", "B3")),
ncol = 3, nrow = 2), blocks = c("B1" =0.3, "B2" = 0.5, "B3" = 0.2),
algorithm = "thompson")


Split Function Arguments

Description

Uses methods::formalArgs() to match arguments provided to ... of simulate_mab() to furrr::furrr_options() and the user specified time_model

Usage

split_args(time_model = NULL, ...)

Arguments

time_model

An optional function with signature:

⁠function(n, conditions, successes, current_period, blocks = NULL, clusters = NULL, ...)⁠

It returns a vector of lubridate::period objects which will then be added to assignment_dates to produce success_date. Used to simulate delayed feedback mechanism during the trial, so outcomes are imperfectly observed. Only used whenassignment_dates is also supplied. Dates can be generated even when delayed_feedback == FALSE, but they will not be used. Default NULL. Other optional arguments Cannot share names with arguments in furrr::furrr_options().

...

Additional named arguments forwarded to time_model and furrr::furrr_options().

Value

A named list with 2 elements, furr_args and time_model_args each a list of the respective arguments to furrr::furrr_options() and the user specified time_model

See Also

Other param: generate_assignment_dates(), generate_groups(), generate_outcomes(), generate_period_idx(), prep_sim_data(), setup_mab_sim()


Public TANF Recipient Data From Washington D.C

Description

A modified version of the data set used in https://thelabprojects.dc.gov/benefits-reminder-letter with one additional column added for analysis.

Usage

data(tanf)

Format

An object of class tbl_df (inherits from tbl, data.frame) with 3517 rows and 21 columns.

Details

Variables are as follows:

ic_case_id

Unique, anonymized case identifier.

service_center

DC Department of Human Services Center assigned each case.

condition

The assigned letter condition: "No Letter", "Open Appointment", or "Specific Appointment".

recert_month

Recertification Month.

letter_sent_date

Date the second (treatment) letter was sent.

recert_id

Administrative recertification identifier.

return_to_sender

Indicates whether letter was returned as undeliverable

pdc_status

PDC Status

renewal_date

Date by which renewal must be completed.

notice_date.x

Date the first notice was sent (initial legal communication)

days_betwn_notice_and_recert_due

Number of days between the first notice and the recertification due date.

cert_period_start

Start date of the recertification period.

cert_period_end

End date of recertification period.

recert_status

Status of recertification process (Pending, Denied, etc.)

denial_reason

Reason for denial if recertification was not approved.

recert_month_year

Combined recertification month and year.

notice_date.y

Alternate record of first notice date.

recert_status_dcas

Official recertification status from DCAS

date_of_recert

Date the recertification was successfully submitted (if applicable).

success

Binary variable indicating successful recertification based on recert_status (newly added column).

Source

https://github.com/thelabdc/DHS-TANFRecertification-Public/blob/main/data/df_replication_anonymized.csv