Design, replicates and sample size · Choose a method
How to randomise and blind an animal experiment: a practical method with code
Generate the allocation with a computer, within blocks such as cages or litters, before the experiment; hide group identity behind codes until the analysis is fixed. Picking animals by hand biases results. Python and R code to make the allocation.
On this page
Quick answer. Fix the number per group first. Then randomise with a computer, not by hand: list the animals (or cages, if treatment is given per cage), and let a script assign groups before any outcome data are seen, ideally within blocks such as cages, litters or body-weight bands so that each block contains every group. Save the script and its seed before the experiment starts. Blind by giving each unit (each animal, or each cage when the cage is treated) a code that hides its group from whoever treats, measures and first analyses the data; keep the key with someone else until the analysis plan is fixed. Report both, as ARRIVE 2.0 items 4a, 4b and 5 ask. In the simulation below, assigning the first-caught mice to treatment gave 12.9% false positives with no real effect, against 4.4% with random allocation.
Terms used below. Randomisation: assigning animals to groups by a chance process. Experimental unit: the thing that receives a treatment independently — the animal, or the cage if the whole cage gets the treatment. Block: a set of similar units (a cage, a litter, a body-weight band, a day) within which every group is represented. Allocation concealment: keeping the next assignment hidden from the person enrolling animals, so it is left to chance. Blinding: keeping group identity hidden from the people treating, measuring and analysing. Seed: the number that makes a random sequence reproducible. False positive: a “significant” result when there is no real effect. ARRIVE 2.0: the reporting guidelines for animal research. Key: the file linking codes to groups. Unblinding: opening the key. Analysis plan: the outcome, test and model written down before unblinding. Permutation: a random reordering. Vehicle: the control treatment without the active substance. Welch’s t-test: compares two means without assuming equal spread. r: a correlation coefficient. Confounder: something other than the treatment, such as cage position or the order of procedures, that can change the outcome. Factor: a variable treated as categories (cage 1, cage 2 …) rather than as a number.
What ARRIVE asks for
ARRIVE 2.0 item 4a asks authors to “state whether randomisation was used to allocate experimental units to control and treatment groups. If done, provide the method used to generate the randomisation sequence”, item 4b asks for the strategy used to minimise confounders such as the order of treatments and measurements or cage location, and item 5 asks them to “describe who was aware of the group allocation at the different stages of the experiment (during the allocation, the conduct of the experiment, the outcome assessment, and the data analysis)” (Percie du Sert et al. 2020). The explanation document adds that the lack of randomisation and blinding “can contribute to as much as 30%–45% inflation of effect sizes” (ARRIVE explanation and elaboration). Landis and colleagues define the two safeguards: for allocation concealment, “the investigator should be unaware of the group to which the next animal taken from a cage will be allocated”; for blinded assessment, “investigators assessing, measuring or quantifying experimental outcomes should be blinded to the intervention” (Landis et al. 2012).
| Step | Do | Why |
|---|---|---|
| 0. Fix the sample size | n per group from a power calculation (how many biological replicates) | The allocation is made for a fixed set of animals |
| 1. Define the experimental unit | Animal, or cage if treatment is in the food, water or air of the whole cage | “If the treatment is given in the diet and all animals in the same cage therefore have the same diet, the cage of animals (not the individual animals within the cage) is the experimental unit” (Festing & Altman 2002) |
| 2. Choose blocks | Cages, litters, body-weight bands (measured before allocation, as part of the design), or batches or days, each containing every group | Removes known variation from the comparison and balances groups |
| 3. Generate the allocation | A script with a recorded seed, run before the experiment | Reproducible and free of choices by hand |
| 4. Conceal and code | One person holds the key; others see only animal codes | Prevents selection and biased handling |
| 5. Keep blinding through measurement and analysis | Analysis plan fixed before unblinding | Measurement and analysis choices cannot drift toward a result |
| 6. Report | Method, blocks, software and seed (the seed published with the results, after unblinding); how confounders such as cage position and order of procedures were handled; and who knew what at each stage | ARRIVE items 4a, 4b and 5 |
Why choosing by hand biases results
When a researcher takes mice from a cage one by one and puts the first ones into the treatment group, the first ones caught are not a random sample: they may be slower, heavier or calmer. If the outcome relates to those traits, the groups differ before treatment. In 10,000 simulated experiments with no treatment effect, 20 independent mice (no cage structure) and an outcome correlated with body weight (r = 0.5), allocating the first 10 caught to treatment (catch order = 0.58 × weight + noise, a rank correlation between catch order and weight of about 0.5) gave a “significant” Welch’s t-test in 12.9% of experiments; random allocation gave 4.4%, a simulation estimate close to the 5% false-positive rate the test is designed to have (simulation.py, the simulation output). The size of the bias depends on how strongly the trait drives the outcome, which is unknown in advance. Random allocation removes the systematic part; chance imbalance remains in small studies, which is why blocking helps.
Code: a blocked allocation with blinding codes
The example has 24 mice in 6 cages of 4, individually dosed, and 4 groups (n = 6 per group): each cage gets one mouse of each group, so cage differences are balanced across groups. Replace the example animal list with your own ear tags and cages, and the placeholder seed with a freshly drawn number; a published seed would let anyone reconstruct your allocation. The scripts, with their output, are stored as allocation.py, allocation.R and the allocation check output. The script writes two files: a coded sheet for the people doing the work, and a key that the key-holder moves to storage that the others cannot open. Python and R use different random-number generators, and results can change between software versions, so the saved key file, not a rerun, is the record of the allocation. With one animal per group in each cage, the last animal’s group is predictable once the others are known; if that matters, have someone not involved in treatment run the allocation, or use blocks with more animals than groups. A cage with fewer or more animals than groups stops the script; put small cages together into one block that holds one of each group, or block on body-weight bands instead. If no second person is available, ask a colleague from another project to run the script and keep the key, or seal the printed key in a dated envelope that you open only after the analysis plan is fixed.
import numpy as np, pandas as pd
SEED = 123456789 # replace: draw a fresh number once, e.g. python -c "import secrets; print(secrets.randbelow(10**9))"
rng = np.random.default_rng(SEED)
groups = ["vehicle", "low", "mid", "high"]
animals = pd.DataFrame({ # or: animals = pd.read_csv("my_animals.csv"), columns tag, cage
"tag": [f"T{i:03d}" for i in range(1, 25)],
"cage": np.repeat(range(1, 7), 4)})
parts = []
for cage, a in animals.groupby("cage"): # every group once per cage
assert len(a) == len(groups), f"cage {cage}: needs {len(groups)} animals"
parts.append(a.assign(group=rng.permutation(groups)))
key = pd.concat(parts)
key["code"] = rng.permutation([f"A{i:02d}" for i in range(1, len(key) + 1)]) # codes reveal nothing
key.to_csv("allocation_key.csv", index=False) # move to the key-holder's own storage at once
key[["cage", "tag", "code"]].to_csv("coded_sheet.csv", index=False) # for treatment and measurement
print(pd.crosstab(key.cage, key.group)) # key-holder's check: one of each group per cage
seed <- 123456789 # replace: draw a fresh number once, e.g. sample.int(1e9, 1) with no seed set
set.seed(seed)
groups <- c("vehicle", "low", "mid", "high")
animals <- data.frame(tag = sprintf("T%03d", 1:24), cage = rep(1:6, each = 4)) # or: read.csv("my_animals.csv"), columns tag, cage
key <- do.call(rbind, lapply(split(animals, animals$cage), function(a) {
stopifnot(nrow(a) == length(groups)) # every group once per cage
a$group <- sample(groups); a }))
key$code <- sample(sprintf("A%02d", seq_len(nrow(key)))) # codes reveal nothing
write.csv(key, "allocation_key.csv", row.names = FALSE) # move to the key-holder's own storage at once
write.csv(key[, c("cage", "tag", "code")], "coded_sheet.csv", row.names = FALSE)
table(key$cage, key$group) # key-holder's check: one of each group per cage
For other designs, block by litter or by body-weight band instead of cage, or use the NC3Rs Experimental Design Assistant (EDA), which “provides support for randomisation and blinding” (EDA). If the treatment is given to a whole cage, randomise cages, not mice: make each cage one row of the list and assign groups to cages without the within-cage loop (for 12 cages and 4 groups, rng.permutation(np.repeat(groups, 3)) in Python or sample(rep(groups, 3)) in R); then analyse cage means (what counts as n).
Practical points
- Randomise before you see the animals’ data. Run the script when the animals arrive, not after baseline measurements, unless you block on those measurements by design (body-weight bands are measured first, then allocation is randomised within each band).
- Lost animals. Keep the original allocation; record losses by group and reason, and analyse every animal with a measured outcome in its allocated group. Losses related to treatment (humane endpoints) are themselves results; report them, because no analysis of the survivors can correct for them (unequal group sizes). Re-randomising the remaining animals breaks the design.
- Already allocated by hand. Do not re-randomise animals in a running study; record how they were allocated, say so in the Methods, and blind outcome assessment and analysis from now on.
- Order of procedures. Treat and measure in an order that mixes groups (for example by code), so time of day or operator fatigue does not coincide with group.
- When blinding is impossible (a visible phenotype, a surgical scar), blind the outcome assessment and the analysis at least, and say which stages were not blinded.
- Analyse the blocks. If you blocked by cage or litter, include the block as an extra term:
lm(outcome ~ group + factor(cage), data = d)in R, orsmf.ols("outcome ~ C(group) + C(cage)", d).fit()in Python afterimport statsmodels.formula.api as smf, wheredholds one row per animal; report the F-test for group from that model (anova()in R,statsmodels.stats.anova.anova_lm()in Python). Leave out a group × block interaction: with one animal per group in each block, that term would use up every remaining degree of freedom. With only two groups, a paired t-test that pairs the two animals in each block gives the same p-value as the group F-test. - Fix the plan before unblinding. Write the outcome, test and block term to a dated file (or a preregistration) before the key-holder releases the key, and keep the seed out of the Methods and any registration until then; the analyst then runs that plan and records any later change as exploratory.
Check what an AI assistant ran
- An allocation made by sorting or alternating (every other mouse, first half to treatment). Use a random permutation within blocks.
- No seed recorded. Set and save it so the allocation can be shown to have been made in advance.
- Mice randomised when the cage is the unit (treatment in diet or water). Randomise cages.
- Blocks ignored in the analysis. Include them.
Report it
Mice (n = [6] per group) were allocated to groups by a computer-generated randomisation within cages (each cage of four contained one mouse per group), using [Python 3.13 / R 4.3.3] with seed [number] (disclosed after unblinding); the allocation file is archived. Allocation was concealed: the sequence was generated and held by [role], who was not involved in treatment or measurement, and treatments were prepared in coded syringes. Investigators administering treatments, assessing outcomes and performing the initial analysis were blinded to group; the analysis plan was recorded on [date], before the key was released. Treatments and measurements were carried out in coded order, and cages were [describe the cage arrangement]. [State any stage that could not be blinded.]
Replace the bracketed values with your own.
References
- Percie du Sert N, Hurst V, Ahluwalia A, et al. (2020). The ARRIVE guidelines 2.0: updated guidelines for reporting animal research. PLOS Biology 18:e3000410. doi:10.1371/journal.pbio.3000410
- Percie du Sert N, Ahluwalia A, Alam S, et al. (2020). Reporting animal research: explanation and elaboration for the ARRIVE guidelines 2.0. PLOS Biology 18:e3000411. doi:10.1371/journal.pbio.3000411
- Landis SC, Amara SG, Asadullah K, et al. (2012). A call for transparent reporting to optimize the predictive value of preclinical research. Nature 490:187–191. doi:10.1038/nature11556
- Festing MFW, Altman DG (2002). Guidelines for the design and statistical analysis of experiments using laboratory animals. ILAR Journal 43:244–258. doi:10.1093/ilar.43.4.244
- NC3Rs. Experimental Design Assistant (EDA). eda.nc3rs.org.uk (checked 11 November 2026)
How this page was made
- Written and published by Licklider. Recommendations cite the ARRIVE 2.0 guidelines, peer-reviewed methods papers and the NC3Rs Experimental Design Assistant listed under References; cited passages were checked against the source text.
- The allocation code was run in Python 3.13 (NumPy 2.5.3, pandas 3.0.5) and R 4.3.3; the two languages use different random-number generators, so their allocations differ. The simulation was run in Python.
- Separate review passes checked the statistics, the citations and readability before publication.