
randotools
randotools.Rmd
Randomisation is one of the key aspects of clinical trials, ensuring
that treatment groups are balanced and that the results are not biased.
The randotools
package provides functions to create
randomisation lists in R, with a focus on flexibility and ease of
use.
Creating randomization lists
Randomisation lists are easily created with the
randolist
function. Specify the number of participants to
randomise (per strata, if there are any), any strata, the arms to
randomise between, and the block sizes.
randolist(n = 20, arms = c("Trt1", "Trt2"))
#> seq_in_strata block_in_strata blocksize seq_in_block arm
#> 1 1 1 4 1 Trt1
#> 2 2 1 4 2 Trt1
#> 3 3 1 4 3 Trt2
#> 4 4 1 4 4 Trt2
#> 5 5 2 2 1 Trt1
#> 6 6 2 2 2 Trt2
#> 7 7 3 6 1 Trt2
#> 8 8 3 6 2 Trt1
#> 9 9 3 6 3 Trt2
#> 10 10 3 6 4 Trt1
#> 11 11 3 6 5 Trt1
#> 12 12 3 6 6 Trt2
#> 13 13 4 4 1 Trt2
#> 14 14 4 4 2 Trt1
#> 15 15 4 4 3 Trt2
#> 16 16 4 4 4 Trt1
#> 17 17 5 4 1 Trt1
#> 18 18 5 4 2 Trt1
#> 19 19 5 4 3 Trt2
#> 20 20 5 4 4 Trt2
In the above call,
-
n
specifies the number of participants to randomise per stratum. In this case, we are randomising 20 participants in a single stratum. -
arms
specifies the names of the arms to randomise between.
Any number of arms can be specified, so randolist
can be
used for trials with two, three, even 10 or more arms, so platform
trials can be accommodated by randolist
, although
implementing them within the database is not trivial.
Block randomisation
By default, randolist
uses block randomisation - rather
than using random selection along the whole list in the hope that the
arms are balanced, it creates blocks of randomisation, whereby each
block is balanced, and block sizes are chosen at random. This not only
helps with balancing, but also makes it harder to guess the next
allocation. Block sizes are controlled via the blocksizes
argument, where the values should be the potential number of each arm to
include in any given block. E.g. c(1,2)
would produce
blocks with either one of each arm, or two of each arm, for a total
block size or 2 or 4.
Additionally, blocksizes
are selected approximately
proportional to Pascal’s Triangle, so that medium sizes blocks are more
likely to be selected than small or large blocks. This is done to ensure
that the randomisation list is not too predictable, and helps with
balance by reducing the chance of finishing mid-way through a large
block.
To disable block randomisation, set the block size to n
divided by the number of arms (in this case n
/2, so
10):
randolist(n = 20,
arms = c("Trt1", "Trt2"),
blocksizes = 10)
#> seq_in_strata block_in_strata blocksize seq_in_block arm
#> 1 1 1 20 1 Trt1
#> 2 2 1 20 2 Trt1
#> 3 3 1 20 3 Trt2
#> 4 4 1 20 4 Trt1
#> 5 5 1 20 5 Trt1
#> 6 6 1 20 6 Trt2
#> 7 7 1 20 7 Trt1
#> 8 8 1 20 8 Trt2
#> 9 9 1 20 9 Trt1
#> 10 10 1 20 10 Trt2
#> 11 11 1 20 11 Trt1
#> 12 12 1 20 12 Trt1
#> 13 13 1 20 13 Trt2
#> 14 14 1 20 14 Trt1
#> 15 15 1 20 15 Trt2
#> 16 16 1 20 16 Trt1
#> 17 17 1 20 17 Trt2
#> 18 18 1 20 18 Trt2
#> 19 19 1 20 19 Trt2
#> 20 20 1 20 20 Trt2
The blockrand
function can also be used for
non-stratified randomisation lists.
Stratified randomisation lists
It is very common to need a stratified randomisation where the
randomisation is balanced within strata. This is done by specifying the
strata
argument, which should be a list of the stratifying
variables. The function will then create a randomisation list for each
stratum, and combine them into a single list. The list for each strata
contains n
participants.
rs <- randolist(n = 10,
strata = list(sex = c("Male", "Female"),
age = c("Teen", "Adult")))
table(rs$sex)
#>
#> Male Female
#> 20 24
table(rs$sex, rs$arm)
#>
#> A B
#> Male 10 10
#> Female 12 12
By using factors to specify the strata, the labels and levels are available for use when exporting the randomisation list, which is useful for importing into electronic data capture systems such as REDCap, which requires a specific format.
Unbalanced randomisation lists
It is not uncommon to have unbalanced randomisation lists. E.g. 2
control participants per experimental participant. This is easily done
by changing the arms
argument:
Adaptive trials sometimes modify the randomisation balance part way through a trial, which can be accomplished via this method.
Summarizing randomisation lists
It can be helpful to summarize the randomisation list to check that
the requirements, such as the balance, coding, etc, are met. The
randolist
package includes a summary
precisely
for this purpose:
randolist(n = 20, arms = c("Trt1", "Trt2")) |> summary()
#> ---- Randomisation list report ----
#> -- Overall
#> Total number of randomisations: 22
#> Randomisation groups: Trt1 : Trt2
#> Randomisation ratio: 1:1
#> Randomisations to each arm:
#> Trt1 Trt2
#> 11 11
#> Block sizes:
#> 2 4 6
#> 1 2 2
The summary for stratified randomisation lists also includes information at the strata level.
Exporting randomisation lists
Once a randomisation list is created, it needs to be transferred into
a system that will ultimately perform the randomisation. We primarily
use two systems for this: REDCap and SecuTrial, but you might use
others, which may require other modifications. The
randolist
package includes a function to convert the
randomisation list into a format that should be, with minimal effort, be
importable into these systems.
# create a very small randomisation list for demonstration purposes
rs2 <- randolist(n = 2, blocksizes = 1,
arms = c("Aspirin", "Placebo"),
strata = list(sex = c("Male", "Female"),
age = c("Teen", "Adult")))
The randolist_to_db
function is used to convert the
randomisation list into a format that can be imported into the system.
The function takes the randomisation list as input, and converts it to a
data frame with the columns appropriate for the target database
(target_db
). In the case of REDCap, it is necessary to
provide a data frame which maps the arms provided in
randolist
to the database variables.
randolist_to_db(rs2, target_db = "REDCap",
rando_enc = data.frame(arm = c("Aspirin", "Placebo"),
rand_result = c(1, 2)),
strata_enc = list(sex = data.frame(sex = c("Male", "Female"), code = 1:2),
age = data.frame(age = c("Teen", "Adult"), code = 1:2)))
#> rand_result sex age
#> 1 1 1 1
#> 2 2 1 1
#> 3 2 2 1
#> 4 1 2 1
#> 5 1 1 2
#> 6 2 1 2
#> 7 2 2 2
#> 8 1 2 2
SecuTrial uses a more standardised format, so
rando_encoding
is not required.
randolist_to_db(rs2, target_db = "secuTrial",
strata_enc = list(sex = data.frame(sex = c("Male", "Female"), code = 1:2),
age = data.frame(age = c("Teen", "Adult"), code = 1:2)))
#> Number Group sex age
#> 1 1 Aspirin value = 1 value = 1
#> 2 2 Placebo value = 1 value = 1
#> 3 3 Placebo value = 2 value = 1
#> 4 4 Aspirin value = 2 value = 1
#> 5 5 Aspirin value = 1 value = 2
#> 6 6 Placebo value = 1 value = 2
#> 7 7 Placebo value = 2 value = 2
#> 8 8 Aspirin value = 2 value = 2
The dataframe returned can then be exported to CSV or xlsx and imported into the relevant database.