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.
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 6 1 Trt1
#> 2 2 1 6 2 Trt2
#> 3 3 1 6 3 Trt1
#> 4 4 1 6 4 Trt1
#> 5 5 1 6 5 Trt2
#> 6 6 1 6 6 Trt2
#> 7 7 2 4 1 Trt2
#> 8 8 2 4 2 Trt1
#> 9 9 2 4 3 Trt2
#> 10 10 2 4 4 Trt1
#> 11 11 3 4 1 Trt1
#> 12 12 3 4 2 Trt2
#> 13 13 3 4 3 Trt2
#> 14 14 3 4 4 Trt1
#> 15 15 4 4 1 Trt1
#> 16 16 4 4 2 Trt1
#> 17 17 4 4 3 Trt2
#> 18 18 4 4 4 Trt2
#> 19 19 5 4 1 Trt2
#> 20 20 5 4 2 Trt1
#> 21 21 5 4 3 Trt1
#> 22 22 5 4 4 Trt2In 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.
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 Trt2
#> 2 2 1 20 2 Trt1
#> 3 3 1 20 3 Trt2
#> 4 4 1 20 4 Trt1
#> 5 5 1 20 5 Trt2
#> 6 6 1 20 6 Trt2
#> 7 7 1 20 7 Trt1
#> 8 8 1 20 8 Trt2
#> 9 9 1 20 9 Trt2
#> 10 10 1 20 10 Trt1
#> 11 11 1 20 11 Trt2
#> 12 12 1 20 12 Trt1
#> 13 13 1 20 13 Trt1
#> 14 14 1 20 14 Trt2
#> 15 15 1 20 15 Trt1
#> 16 16 1 20 16 Trt1
#> 17 17 1 20 17 Trt1
#> 18 18 1 20 18 Trt1
#> 19 19 1 20 19 Trt2
#> 20 20 1 20 20 Trt2The blockrand function can also be used for
non-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 12By 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.
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.
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:
#> 4 6
#> 4 1The summary for stratified randomisation lists also includes information at the strata level.
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 1 2 1
#> 4 2 2 1
#> 5 2 1 2
#> 6 1 1 2
#> 7 1 2 2
#> 8 2 2 2SecuTrial 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 Aspirin value = 2 value = 1
#> 4 4 Placebo value = 2 value = 1
#> 5 5 Placebo value = 1 value = 2
#> 6 6 Aspirin value = 1 value = 2
#> 7 7 Aspirin value = 2 value = 2
#> 8 8 Placebo value = 2 value = 2The dataframe returned can then be exported to CSV or xlsx and imported into the relevant database.