Welcome
This web-based tutorial complements the workshop:
Introduction to Systematic Review & Meta-Analysis of Animal
Studies
We will use the R programming language to work through a
meta-analysis of animal data but you do not need to have used R
previously to complete the exercises.
For the tutorial, we will use example data from the publication:
McCann SK, Cramond F, Macleod MR et al. Systematic Review and Meta-Analysis of the Efficacy of Interleukin-1 Receptor Antagonist in Animal Models of Stroke: an Update. Translational Stroke Research 7, 395–406 (2016).
Background information on the data set:
Interleukin-1 receptor antagonist (IL-1RA) is an anti-inflammatory
protein used clinically to treat rheumatoid arthritis and is considered
a promising candidate therapy for stroke. In this systematic review and
meta-analysis, we sought to assess the efficacy of IL-1RA in animal
models of ischaemic stroke, the range of conditions under which efficacy
has been tested, and whether the data appear to be confounded due to
reported study quality and publication bias. The outcomes we will assess
in this tutorial are post-stroke infarct volume (brain damage),
mortality, and neurobehavioural outcomes e.g. motor deficits, in
IL-1RA-treated vs. control animals.
Overview
We will analyse the effect of IL-1RA treatment on each of our three outcomes (infarct volume, mortality, and neurobehaviour) separately.
For infarct volume and mortality, all of the R code is provided and you just need to click on the blue Run Code button on the top right of each code chunk to view and interpret the results.
For neurobehaviour, you will have the opportunity to write your own code to analyse the data set provided.
Within each section, there are multiple-choice questions to test your understanding. If you answer a question incorrectly the first time, you can click the orange Try Again button.
The tutorial comprises five main sections and at the end of the first
three sections are data dictionaries describing the variables included
in each data set:
1. Meta-analysis of continuous data (infarct volume)
In the first section of the tutorial we will cover the steps to
meta-analyse continuous data:
1.1 Calculating
effect sizes using normalised mean difference (NMD)
1.2 Performing a random effects meta-analysis
1.3 Exploring heterogeneity using univariable
meta-regression
1.4 Data dictionary: infarct
volume
2. Meta-analysis of binary data (mortality)
In the second section we will look at the binary outcome, mortality,
calculate odds ratio effect sizes and perform a random effects
meta-analysis.
2.1 Data dictionary: mortality
3. Test your skills: meta-analysis (neurobehaviour)
In this section we provide you with a data set that describes neurobehavioural outcomes (continuous data) from the studies included in the systematic review, along with prompts to insert code to analyse the data.
You can copy and edit the code provided in section 1 to perform the
analyses. If you are unsure about what code to use, click on the
Hint(s) buttons. You can also compare your results to
those in the paper,
McCann
et al. 2016.
3.1 Data dictionary: neurobehaviour
4. Small study effects (infarct volume)
In this section we will assess the infarct volume data set for the presence of small study effects using funnel plot-based methods including Egger’s regression and trim and fill.
5. Test your skills: small study effects (neurobehaviour)
Lastly, you will have the opportunity to apply your skills in assessing small study effects using a neurobehaviour data set.
For further information on systematic review and meta-analysis
of animal studies, please see the
CAMARADES wiki page.
1. Meta-analysis of continuous data
First, let’s load the data we will use for the meta-analysis of infarct volume:
infarctVol <- read.csv("IL-1RA_InfarctVolume_section1.csv",
fileEncoding = "UTF-8-BOM")
It’s good practice to take a look at the data you import to make
sure there are no errors. After you run the next section of code, the
imported data will open in a table below. To explore the table you can
use the arrowhead to the right of the column headings to move through
the columns and the blue numbers at the bottom right to move through the
rows.
You can find a description of each variable in the data dictionary table in section 1.4.
infarctVol %>%
mutate_if(is.numeric, round, digits = 2)
1.1 Calculating effect sizes
Now we will calculate an effect size for each experiment in the data
set. An
effect
size is a measure of the difference between two experimental
cohorts.
Infarct volume is a continuous variable and we will calculate a normalised mean difference (NMD) effect size using the control (vehicle-treated) and treatment (IL-1RA-treated) groups. The effect size is expressed as the proportional or percentage improvement in the treatment group compared with the control group. NMD is not widely used in clinical systematic reviews and there is no NMD effect size calculator included in the meta-analysis package we are using. Therefore, we will calculate NMD effect sizes manually.
Note that green text following a hash symbol (#) in the code chunk is not read by R but simply provides a description of the code to make it easier to follow.
First, specify the columns in the data set that contain the mean, standard deviation, and number of animals in the control groups:
# save the group means to a variable called mCi
mCi <- infarctVol$control_group_mean
# save the group standard deviations to a variable called sdCi
sdCi <- infarctVol$control_group_sd
# save the number of animals to a variable called nCi
nCi <- infarctVol$control_group_calc_n
…and the columns that contain the mean, standard deviation, and number of animals in the treatment groups:
# save the group means to a variable called mTi
mTi <- infarctVol$treatment_group_mean
# save the group standard deviations to a variable called sdTi
sdTi <- infarctVol$treatment_group_sd
# save the number of animals to a variable called nTi
nTi <- infarctVol$treatment_group_n
You have just created six new variables that you will use to calculate effect sizes:
| Variable | Description |
|---|---|
| mCi | mean in the control group |
| sdCi | standard deviation in the control group |
| nCi | number of animals in the control group |
| mTi | mean in the treatment group |
| sdTi | standard deviation in the treatment group |
| nTi | number of animals in the treatment group |
You can view variables to check that they contain the correct data. For example, running the following section of code will show you the list of values in the mCi variable (data for the mean in the control group). This should match the corresponding column in the data table you saw in the previous section (use the navigation bar on the left to go back and check). You can edit this code to view the other variables if you wish:
print(mCi, digits =4)
Now let’s calculate an NMD effect size for each experiment (saved as
the variable “ESi”) and add the effect size data as a new column in the
infarctVol data set. The function cbind takes
a table of data and binds a new column to the right side. Remember, each
variable in this equation is a list of numbers, with each entry
corresponding to a single experiment.
Note: we are using a truncated form of the NMD equation here because we know that a sham animal that has not suffered a stroke will have no infarct. This reduces the corresponding parts of the NMD equation to zero; see the CAMARADES wiki page for more information.
# calculate the NMD effect size for each experiment and save the results to the ESi variable
ESi <- (mCi - mTi) / mCi * 100
# add the effect size data as a new column
infarctVol <- cbind(infarctVol, ESi)
Next, we calculate the normalised standard deviation for each
group:
# control group
nsdCi <- sdCi / mCi * 100
# treatment group
nsdTi <- sdTi / mCi * 100
We will now calculate the normalised standard error for each experiment (saved as the variable “SEi”) and add the standard error data as a new column in the infarctVol data set:
# calculate the NMD standard error
SEi <- sqrt((nsdCi^2) / nCi + (nsdTi^2) / nTi)
# add the standard error data as a new column in the table
infarctVol <- cbind(infarctVol, SEi)
Check that your new variables have been added as columns to the right hand side of the data set:
| Variable | Description |
|---|---|
| ESi | effect size for each comparison (NMD) |
| SEi | standard error for each comparison |
infarctVol %>%
mutate_if(is.numeric, round, digits = 2)
1.2 Random effects meta-analysis
Building the meta-analysis model
Next we combine the individual effect sizes with meta-analysis using the function metagen from the {meta} package. This gives us an overall summary estimate of the effect of IL-1RA on infarct volume, compared to control. Note that to begin with, we will print both the random effects and fixed effect estimates so that we can compare the results:
infarctVolMA <- metagen(
# specify the variable that contains the effect size for each experiment
ESi,
# specify the variable that contains the standard error for each experiment
SEi,
# specify the data set
data = infarctVol,
# specify the study labels
studlab = study_id,
# specify a random effects model
comb.random = TRUE,
# specify a fixed effect model
comb.fixed = TRUE,
# specify which method is used to estimate the between-study variance, τ^2: restricted maximum-likelihood (REML) estimator
method.tau = "REML"
)
View the results of the meta-analysis:
infarctVolMA
Forest plot
A forest plot is a way to visualise the effect size and confidence interval for each individual study in a meta-analysis. We can create a forest plot using the function forest:
infarctVolFor <- forest(
# specify the meta-analysis to plot
infarctVolMA,
# sort the data according to effect size
sortvar = TE,
# do not plot the fixed effect estimate
comb.fixed = FALSE,
# plot the random effects estimate
comb.random = TRUE,
# specify the columns to print on the left side of the plot
leftcols = "studlab",
# specify the labels for the left side plot columns
leftlabs = "Study",
# specify the labels for the right side plot columns
rightlabs = c("NMD", "95% CI", "Weight"),
# specify the label for the heterogeneity statistics
hetlab = "Het: ",
# specify the x axis label
xlab = "NMD (% reduction in infarct volume)",
# specify the graph label on right side of plot
label.right = "Favours IL-1RA",
# specify the graph label on left side of plot
label.left = "Favours Control",
# specify the x axis limits
xlim = c(-150,150),
# specify the size of text (in points)
fontsize = 10,
# specify the width of the plotting region
plotwidth = "10cm",
# specify minimal number of significant digits for treatment effects
digits = 1,
# specify minimal number of significant digits for standard errors
digits.se = 1
)
1.3 Exploring heterogeneity
Next we will investigate sources of heterogeneity in our data set using meta-regression. A meta-regression model uses one or more explanatory variables to predict differences in the true effect sizes of our outcome variable. These explanatory variables can be continuous or categorical and are sometimes called ‘potential effect modifiers’ or ‘covariates’. We will investigate each variable individually, using univariable models, but multivariable meta-regression models can also be implemented, if there is an adequate number of included studies. For more information on multivariable, or multiple, meta-regression see here.
We will investigate four variables in different subsets of the infarct volume data, as performed in the publication McCann et al. 2016. For this analysis we will use the function metareg from the {meta} package.
Meta-regression: blinding
First, investigate the variable “blinded induction of ischaemia” in the complete data set. This variable addresses whether authors reported blinding of researchers during stroke induction in the animals. Blinding is an important variable when assessing risks of bias.
Categorical variables like this one can be added to a meta-regression model using what are known as “dummy variables”. In this tutorial we will allow R to handle the coding of these dummy variables automatically, but you can also code them manually. For more information see here.
# check the different levels of the variable and number of experiments in each
table(infarctVol$blinded_induction_of_ischaemia)
infarctVolBlind <- metareg(
# specify the meta-analysis
infarctVolMA,
# specify the experimental variable of interest
~ blinded_induction_of_ischaemia
)
# view the meta-regression
infarctVolBlind
You can create a bar chart of these results using the code below. Don’t worry about trying to understand the code at this stage, it’s very long!
infarctVolBlind.p <- predict.rma(infarctVolBlind, addx = TRUE)
blind <- as_tibble(infarctVolBlind.p)%>% group_by(X.blinded_induction_of_ischaemiaTRUE)
blind.s <- blind %>% summarise(
Blind = mean(X.blinded_induction_of_ischaemiaTRUE),
es = mean(pred),
se = mean(se),
ci.lb = mean(ci.lb),
ci.ub = mean(ci.ub))
blind.s <- blind.s %>%
mutate(Blind=replace(Blind,Blind==0, "No"),
Blind=replace(Blind,Blind==1, "Yes"))
N <- (infarctVol$treatment_group_n + infarctVol$control_group_calc_n)
infarctVol <- cbind(infarctVol, N)
blind.n <- infarctVol %>%
group_by(blinded_induction_of_ischaemia) %>%
summarise(
sum.N = sum(N))
ggplot(data=blind.s,
aes(x=Blind, y=es)) +
geom_rect(xmin=0.4,
xmax=2.6,
ymin = infarctVolMA$lower.random,
ymax = infarctVolMA$upper.random,
alpha=0.2) +
geom_bar(data=blind.s,
aes(x=Blind, y=es),
color = "cadetblue4",
fill = "cadetblue",
width = c(sqrt(blind.n$sum.N[2])*0.03, sqrt(blind.n$sum.N[1])*0.03),
stat = "identity") +
geom_hline(yintercept = 0) +
geom_errorbar(data=blind.s,
aes(x = Blind, ymin = ci.lb, ymax = ci.ub),
width=0.1,
colour="cadetblue4",
alpha=0.9,
size=1) +
scale_y_continuous(name="NMD (% reduction in infarct volume)",
limits=c(0, 50),
breaks = seq(0, 50, 10), expand = c(0,0)) +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
axis.line = element_line(colour = "black"),
axis.title.x = element_text(margin = margin(t = 20, b = 20), size = 16),
axis.title.y = element_text(margin = margin(r = 20), size = 16),
axis.text.x= element_text(size = 14),
axis.text.y= element_text(size = 14))+
labs(x = "Blinded induction of ischaemia", caption = "The horizontal grey bar represents the 95% CIs of the global estimate.
The width of the bars is proportional to the number of animals in each subgroup.")
Meta-regression: route of drug delivery
Next, investigate the variable “route of delivery” in the subset of data where the drug delivery mode is “Protein”.
In this data set, IL-1RA was administered using several different delivery modes (column drug_delivery_mode). We will restrict subsequent analyses to those experiments where IL-1RA was delivered in protein form, due to uncertainty around the timing and effective dose achieved in transgenic and transfection studies (the alternative drug delivery modes).
Route of delivery refers to how IL-1RA was administered e.g. intravenous, subcutaneous. Several different methods were used in these experiments, which may have an influence on effect sizes.
First we will create a new data set including only the protein studies using the function subset:
infarctVol2 <- subset(
# specify the data to subset
infarctVol,
# specify the relevant variable
!(drug_delivery_mode %in%
# specify the variable levels to exclude from analysis
c("Transgenic", "Vector", "IL-1RA Tg BM cells", "IL-1 RA Tg BM cells"))
)
# View the new data subset
infarctVol2 %>%
mutate_if(is.numeric, round, digits = 2)
Have a look at the table above and check that the only studies remaining are those where the drug delivery mode is “Protein”.
Next we will run a meta-analysis on this subset of the data:
infarctVolMA2 <- metagen(
# specify the variable that contains the effect size for each experiment
ESi,
# specify the variable that contains the standard error for each experiment
SEi,
# specify the data set
data = infarctVol2,
# specify the study labels
studlab = study_id,
# specify a random effects model
comb.random = TRUE,
# specify that the fixed effect estimate should not be printed
comb.fixed = FALSE,
# specify which method is used to estimate the between-study variance τ^2, restricted maximum-likelihood (REML) estimator
method.tau = "REML",
)
# view the results of the meta-analysis
infarctVolMA2
Now check the variable data and perform the meta-regression investigating “route of delivery”:
# check the different delivery routes and number of experiments using each
table(infarctVol2$route_of_delivery)
infarctVolRoute <- metareg(
# specify the meta-analysis
infarctVolMA2,
# specify the experimental variable of interest
~ factor(route_of_delivery)
)
# view the results of the meta-regression
infarctVolRoute
We can also graph the results of a meta-regression using a forest plot:
infarctVolRoute.p <- predict.rma(infarctVolRoute, addx = TRUE)
infarctVol2<- cbind(infarctVol2, infarctVolRoute.p$pred)
route <- as_tibble(infarctVolRoute.p)%>% group_by(pred)
route.s <- route %>% summarise(
Route = mean(pred),
es = mean(pred),
se = mean(se),
ci.lb = mean(ci.lb),
ci.ub = mean(ci.ub),
IP = mean(X.factor.route_of_delivery.IPeritoneal),
IV = mean(X.factor.route_of_delivery.IVenous),
Stereo = mean(X.factor.route_of_delivery.Stereotactic),
SubCut = mean(X.factor.route_of_delivery.SubCut),
ICV = mean(X.intrcpt)
)
route.s <- route.s %>%
mutate(Route=replace(Route,IP==1, "IP"),
Route=replace(Route,IV==1, "IV"),
Route=replace(Route,Stereo==1, "Stereo"),
Route=replace(Route,SubCut==1, "SubCut"),
Route=replace(Route,IP==0&IV==0&Stereo==0&SubCut==0&ICV==1, "ICV")
)
infarct.for.route <- forest.rma(
infarctVolRoute,
xlab = "NMD (% reduction in infarct volume)",
mlab = "NMD",
ylim = c(-2.5, 81),
xlim = c(-300, 300),
alim = c(-150, 150),
rows=c(75.5:60.5, 56.5:40.5, 36.5:13.5, 9.5:6.5, 2:-1),
header = c("Study label", "NMD [95% CI]"),
order = order(-infarctVol2$`infarctVolRoute.p$pred`, infarctVol2$ESi),
slab = infarctVol2$study_id,
addfit = FALSE,
digits = 1,
cex = 0.9,
cex.lab = 1,
efac = 0.5,
#top = 1
)
text(-300,
c(77, 58, 38, 11, 3.5),
c("Intracerebroventricular", "Intravenous", "Subcutaneous", "Intraperitoneal", "Stereotactic"),
adj = c(0,1),
font = 4,
cex = 0.9
)
addpoly(rev(route.s$es),
ci.lb = rev(route.s$ci.lb),
ci.ub = rev(route.s$ci.ub),
rows = c(59, 39, 12, 5, -2.5),
cex = 0.9,
col = "cadetblue",
border = "cadetblue4",
digits = 1,
efac = 0.8
)
Meta-regression: species
Using the same data set (infarctVol2) and meta-analysis (infarctVolMA2), now investigate whether animal species is a source of heterogeneity:
table(infarctVol2$species)
infarctVolspecies <- metareg(
# specify the meta-analysis
infarctVolMA2,
# specify the experimental variable of interest
~ factor(species)
)
# view the results of the meta-regression
infarctVolspecies
Meta-regression: dose of IL-1RA
Lastly, investigate the variable “dose” in the subset of data where the drug delivery mode is “Protein” and the location of administration is “Central”.
Now let’s check if the dose of IL-1RA is associated with effect size differences (i.e. is there a dose response) using only data where the drug was administered centrally (i.e. directly into the brain) and excluding data where the drug was administered peripherally.
First we will create a new data set including only the centrally-administered protein studies, again using the function subset:
infarctVol3 <- subset(
# specify the data to subset
infarctVol2,
# specify the relevant variable
drug_delivery_location %in%
# specify the experiments to include in the analysis
"Central"
)
# View the new data subset
infarctVol3 %>%
mutate_if(is.numeric, round, digits = 2)
Have a look at the table above and check that the only studies remaining are those where the drug delivery location is “Central”.
Next run a meta-analysis on this new data set:
infarctVolMA3 <- metagen(
# specify the variable that contains the effect size for each experiment
ESi,
# specify the variable that contains the standard error for each experiment
SEi,
# specify the data set
data = infarctVol3,
# specify the study labels
studlab = study_id,
# specify a random effects model
comb.random = TRUE,
# specify that the fixed effect estimate should not be printed
comb.fixed = FALSE,
# specify which method is used to estimate the between-study variance τ^2, restricted maximum-likelihood (REML) estimator
method.tau = "REML",
)
# view the results of the meta-analysis
infarctVolMA3
Finally, perform the meta-regression investigating “dose”:
infarctVoldose <- metareg(
# specify the meta-analysis
infarctVolMA3,
# specify the experimental variable of interest
~ "dose"
)
# view the results of the meta-regression
infarctVoldose
To graph a meta-regression of a continuous variable we can use a bubble plot:
# specify the meta-regression analysis to plot
bubble(infarctVoldose,
# specify the x axis label
xlab = "IL-1RA dose (µg)",
# specify the y axis label
ylab = "NMD (% reduction in infarct volume)"
)
1.4 Data dictionary: infarct volume
| Variable | Description | Levels |
|---|---|---|
| study_id | a study identifier for each experimental group within a study | Identifier variable - author, year of publication, experimental group |
| species | the animal species used in the experiment | Mouse, Rat |
| strain | the animal strain used in the experiment | Balb/c, C57BL/6, C57BL/6J, C57/SV129, CD-1, Sprague dawley, Unknown, Wistar |
| sex | the sex of the animals used in the experiment | Male, Unknown |
| drug | the drug administered in the experiment | IL-1RA |
| drug_delivery_mode | the mode of the drug delivered to the animals | IL-1 RA Tg BM cells, Protein, Transgenic, Vector |
| route_of_delivery | the route used to deliver the drug to the animals | ICerebVentricular, IPeritoneal, IVenous, Stereotactic, SubCut |
| drug_delivery_location | the location of the drug delivery into the animals | Central, Peripheral |
| dose | the dose of the drug administered to the animals | Numerical - continuous |
| unit | the unit of the drug dose administered to the animals | cells, mg/kg, ng/g, Tg, µg |
| time_of_administration | the time of the drug administration, relative to stroke induction, in minutes | Numerical - continuous (mins) |
| number_of_doses | the number of doses of the drug administered to the animals | Continuous, Multiple, Single |
| outcome_measure | the outcome measured in the animals | Infarct volume |
| time_of_assessment | the time the outcome was assessed, relative to stroke induction, in hours | Numerical - continuous (hours) |
| blinded_assessment_of_infarct_volume | reporting of blinded assessment of outcome | FALSE, TRUE |
| blinded_induction_of_ischaemia | reporting of blinded induction of ischaemia (stroke) | FALSE, TRUE |
| random_allocation | reporting of random allocation of animals to treatment or control groups | FALSE, TRUE |
| sample_size_calculation | reporting of a sample size calculation | FALSE, TRUE |
| use_of_comorbid_animals | use of animals with a comorbidity | FALSE, TRUE |
| animal_welfare_compliance | reporting of a statement of compliance with animal welfare regulations | FALSE, TRUE |
| statement_of_potential_coi | reporting of a statement of potential conflicts of interest | FALSE, TRUE |
| duration_of_ischaemia | the duration of ischaemic stroke in minutes | Numerical - continuous (mins) |
| type_of_ischaemia | the type of ischaemic stroke | Permanent, Temporary, Thrombotic |
| anaesthetic | the anaesthetic used during stroke induction | Chloral Hydrate, Halothane, Isoflurane, Ketamine, Tribromoethanol, Unknown |
| method_of_mcao | the method used for middle cerebral artery occlusion (mcao) to induce stroke | electrocoagulation, Intraluminal filament/suture, Ligation, thrombin |
| control_group_mean | the mean value of the outcome measure in the control group | Numerical - continuous |
| treatment_group_mean | the mean value of the outcome measure in the treatment group | Numerical - continuous |
| control_group_n | the number of animals in the control group | Numerical - discrete |
| treatment_group_n | the number of animals in the treatment group | Numerical - discrete |
| control_group_calc_n | the number of animals in the control group after adjusting for the number of treatment groups it serves | Numerical - continuous |
| treatment_groups_per_control | the number of treatment groups compared to each control group wihin an experiment | Numerical - discrete |
| control_group_sd | the standard deviation in the control group | Numerical - continuous |
| treatment_group_sd | the standard deviation in the treatment group | Numerical - continuous |
2. Meta-analysis of binary data
We will now move on to meta-analyse our second outcome of interest, mortality. Mortality is a binary outcome (animals are alive or deceased) so we need to use an effect size appropriate for this data type. There are several such effect sizes but we will use an odds ratio, which represents the odds that our outcome (death) will occur given IL-1RA treatment, compared to the odds of our outcome occurring in the absence of treatment (control).
For this exercise, we will use the function metabin for meta-analysis of binary outcomes. metabin will calculate an odds ratio effect size directly if we provide the number of animals and events (deaths) in the treatment and control groups, so there is no need to manually calculate these values as we did for NMD effect sizes in section 1.
Load and view the data
mortality <- read.csv("IL-1RA_Mortality_section2.csv",
fileEncoding = "UTF-8-BOM")
mortality %>%
mutate_if(is.numeric, round, digits = 2)
Take a look at the table above and compare the outcome data to that from the infarct volume (continuous) data in section 1.
You can find a description of each variable in the data dictionary table in section 2.1.
Meta-analysis
Now perform a meta-analysis using the function metabin:
mortalityMA <- metabin(
# specify the number of events in experimental group
event.e = number_affected_in_treatment_group,
# specify the number of observations in the experimental group
n.e = treatment_group_n,
# specify the number of events in control group
event.c = number_affected_in_control_group,
# specify the number of observations in the control group
n.c = control_group_n,
# specify that the meta-analysis should include studies with zero or all events in both groups
allstudies = TRUE,
# specify the study labels
studlab = study_id,
# specify the data set
data = mortality,
# specify which method is used to estimate the between-study variance τ^2, restricted maximum-likelihood (REML) estimator
method.tau = "REML",
# specify the summary measure, "OR" = odds ratio
sm = "OR",
# specify a random effects model
comb.random = TRUE,
)
# view the results of the meta-analysis
mortalityMA
Forest plot
Next we will create a forest plot of the mortality data using the function forest:
mortalityFor <- forest(
# specify the meta-analysis to plot
mortalityMA,
# sort the data according to effect size
sortvar = TE,
# do not plot the fixed effect estimate
comb.fixed = FALSE,
# plot the random effects estimate
comb.random = TRUE,
# specify the effect size label
smlab = "Odds ratio",
# specify the graph label on right side of plot
label.right = "Favours Control",
# specify the graph label on left side of plot
label.left = "Favours IL-1RA",
# specify the size of text (in points)
fontsize = 11,
# specify minimal number of significant digits for treatment effects
digits = 2,
# specify minimal number of significant digits for standard errors
digits.se = 2
)
2.1 Data dictionary: mortality
| Variable | Description | Levels |
|---|---|---|
| study_id | a study identifier for each experimental group within a study | Identifier variable - author, year of publication, experimental group |
| drug | the drug administered in the experiment | IL-1RA |
| number_affected_in_control_group | the number of animals affected by the outcome measure in the control group | Numerical - discrete |
| number_affected_in_treatment_group | the number of animals affected by the outcome measure in the treatment group | Numerical - discrete |
| control_group_n | the number of animals in the control group | Numerical - discrete |
| treatment_group_n | the number of animals in the treatment group | Numerical - discrete |
| outcome_measure | the outcome measured in the animals | Mortality |
| blinded_assessment_of_outcome | reporting of blinded assessment of outcome | FALSE, TRUE |
| blinded_induction_of_ischaemia | reporting of blinded induction of ischaemia (stroke) | FALSE, TRUE |
| method_of_mcao | the method used for middle cerebral artery occlusion (mcao) to induce stroke | electrocoagulation, Intraluminal filament/suture, thrombin |
| random_allocation | reporting of random allocation of animals to treatment or control groups | FALSE, TRUE |
| sample_size_calculation | reporting of a sample size calculation | FALSE, TRUE |
| sex | the sex of the animals used in the experiment | Male |
| type_of_ischaemia | the type of ischaemic stroke | Permanent, Temporary, Thrombotic |
| comorbidity | the comorbidity suffered by the animals | Aged, None |
| dose | the dose of the drug administered to the animals | Numerical - continuous |
| unit | the unit of the drug dose administered to the animals | mg/kg |
| time_of_administration | the time of the drug administration, relative to stroke induction, in minutes | Numerical - continuous (mins) |
| time_of_assessment | the time the outcome was assessed, relative to stroke induction, in hours | Numerical - continuous (hours) |
3. Test your skills: meta-analysis
Now you can explore the neurobehavioural outcomes data set by writing your own code. We have only included experiments where IL-1RA was administered in protein form and motor / sensory outcomes were assessed.
In this data set, a normalised mean difference effect size (column ESi) and standard error (column SEi) have already been calculated for each experiment.
You can edit the code we used to analyse the infarct volume data above and compare your results to those in the paper, McCann et al. 2016. If you are unsure what code to use, click on the Hint(s) buttons.
Start by loading and viewing the data set, you can find a description of each variable in the data dictionary table in section 3.1:
# load and view the data set "IL-1RA_Neurobehaviour_nested_section3.csv" using the function read.csv().
# load and view the data set "IL-1RA_Neurobehaviour_nested_section3.csv" using the function read.csv().
neurobeh <- read.csv("IL-1RA_Neurobehaviour_nested_section3.csv", fileEncoding = "UTF-8-BOM")
head(neurobeh)
# use the function metagen()
# Perform a random effects meta-analysis using the function metagen().
neurobehMA <- metagen(
# specify the variable that contains the effect size for each experiment
ESi,
# specify the variable that contains the standard error for each experiment
SEi,
# specify the data set
data = neurobeh,
# specify the study labels
studlab = study_id,
# specify a random effects model
comb.random = TRUE,
# specify not to print the fixed effect model
comb.fixed = FALSE,
# specify which method is used to estimate the between-study variance τ^2, restricted maximum-likelihood (REML) estimator
method.tau = "REML"
)
# view the meta-analysis
neurobehMA
# Create a forest plot of the data using the function forest().
# Create a forest plot of the data using the function forest().
neurobehFor <- forest(
neurobehMA, # specify the meta-analysis to plot
sortvar = TE, # sort the data according to effect size
comb.fixed = FALSE, # do not plot the fixed effect estimate
comb.random = TRUE, # plot the random effects estimate
leftlabs = c("Study", "ESi", "SEi"), # specify the labels for the left hand plot columns
xlab = "NMD (% improvement in neurobehavioural outcomes)", # specify the x axis label
smlab = "NMD", # specify the effect size label
label.right = "Favours IL-1RA", # specify the graph label on right side of plot
label.left = "Favours Control", # specify the graph label on left side of plot
xlim = c(-150,150), # specify the x axis limits
fontsize = 10, # specify the size of text (in points)
plotwidth = "10cm", # specify the width of the plotting region
digits = 1, # specify minimal number of significant digits for treatment effects
digits.se = 1 # specify minimal number of significant digits for standard errors
)
Now you can select some variables to investigate as possible sources of heterogeneity using the function metareg e.g. species, sex, dose, time of administration, route of delivery, use of comorbid animals, method of induction of ischaemia, type of ischaemia, anaesthetic used, time of assessment, single vs. multiple administrations.
Remember that the variable name you use in your meta-regression model must match the column name in the data sheet exactly. To see a list of all the column names, you can use the function colnames or check the data dictionary in section 3.1:# use the metareg() function
# check the names of all the columns in the data set
colnames(neurobeh)
# check the different levels of the variable and number of experiments in each
table(neurobeh$species)
neurobehspecies <- metareg(
# specify the meta-analysis
neurobehMA,
# specify the experimental variable of interest
~ factor(species)
)
# view the results of the meta-regression
neurobehspecies
3.1 Data dictionary: neurobehaviour
| Variable | Description | Levels |
|---|---|---|
| study_id | a study identifier for each experimental group within a study | Identifier variable - author, year of publication, experimental group |
| species | the animal species used in the experiment | Mouse, Rat |
| strain | the animal strain used in the experiment | Balb/c, C57BL/6, C57BL/6J, Unknown, Wistar |
| sex | the sex of the animals used in the experiment | Male, Unknown |
| drug | the drug administered in the experiment | IL-1RA |
| drug_delivery_mode | the mode of the drug delivered to the animals | Protein |
| drug_delivery_location | the location of the drug delivery into the animals | Peripheral |
| route_of_delivery | the route used to deliver the drug to the animals | IPeritoneal, IVenous, SubCut |
| dose | the dose of the drug administered to the animals | Numerical - continuous |
| unit | the unit of the drug dose administered to the animals | mg/kg |
| number_of_doses | the number of doses of the drug administered to the animals | Multiple, Single |
| time_of_administration | the time of the drug administration, relative to stroke induction, in minutes | Numerical - continuous (mins) |
| blinded_induction_of_ischaemia | reporting of blinded induction of ischaemia (stroke) | FALSE, TRUE |
| blinded_assessment_of_outcome | reporting of blinded assessment of outcome | FALSE, TRUE |
| random_allocation | reporting of random allocation of animals to treatment or control groups | FALSE, TRUE |
| sample_size_calculation | reporting of a sample size calculation | FALSE, TRUE |
| use_of_comorbid_animals | use of animals with a comorbidity | FALSE, TRUE |
| animal_welfare_compliance | reporting of a statement of compliance with animal welfare regulations | FALSE, TRUE |
| statement_of_potential_coi | reporting of a statement of potential conflicts of interest | FALSE, TRUE |
| method_of_mcao | the method used for middle cerebral artery occlusion (mcao) to induce stroke | electrocoagulation, Intraluminal filament/suture, Ligation |
| type_of_ischaemia | the type of ischaemic stroke | Permanent, Temporary |
| duration_of_ischaemia | the duration of ischaemic stroke in minutes | Numerical - continuous (mins) |
| anaesthetic | the anaesthetic used during stroke induction | Halothane, Isoflurane, Ketamine, Tribromoethanol, Unknown |
| outcome_measure | the outcome measured in the animals | NeurobehaviouralScore |
| outcome_measure_type | the type of behaviour the outcome assessed | Motor/sensory |
| time_of_assessment | the time the outcome was assessed, relative to stroke induction, in hours | Numerical - continuous (hours) |
| ESi | the normalised mean difference effect size | Numerical - continuous |
| SEi | the normalised mean difference standard error | Numerical - continuous |
4. Small study effects
Small study effects in meta-analysis can be assessed using a funnel plot, which is a scatter plot of individual study effect sizes against a measure of each study’s precision. Ideally, the plot should approximately resemble a symmetrical (inverted) funnel. If there is bias due to missing results, for example because smaller studies without statistically significant effects remain unpublished, this will lead to asymmetry in the appearance of the funnel plot.
In this exercise, we will use the complete infarct volume data set and meta-analysis from the first section of this tutorial.
First, draw a funnel plot using the function funnel:
infarctVolFun <- funnel(
# specify the meta-analysis to plot
infarctVolMA,
# plot the random effects estimate
comb.random = TRUE,
# specify the x axis label
xlab = "NMD (% reduction in infarct volume)",
# specify the measure plotted on the y axis, "invse" = inverse of the standard error
yaxis = "se"
)
# infarctVolFun
Funnel plot asymmetry
In addition to assessing the funnel plot visually, we can test for asymmetry statistically. Here we will use Egger’s test, which is a weighted linear regression of the treatment effect on its standard error.
Test for funnel plot asymmetry using the function metabias:infarctVolBias <- metabias(
# specify the meta-analysis to analyse
infarctVolMA,
# specify the method behind the test statistic
method.bias = "Egger",
# plot the results
plotit = TRUE
)
# view the results of the funnel plot asymmetry analysis
infarctVolBias
Trim and fill
We can also estimate and adjust for the number and outcomes of theoretically missing studies in a meta-analysis, using a method called trim and fill. We can test for and impute missing studies using the function trimfill:
infarctVolTrim <- trimfill(
# specify the meta-analysis to plot
infarctVolMA,
# specify the side of the plot where missing studies are expected
left = TRUE,
# plot the random effects estimate
comb.random = TRUE,
# specify which method is used to estimate the between-study variance τ^2, restricted maximum-likelihood (REML) estimator
method.tau = "REML"
)
# view the results of the trim-and-fill analysis
infarctVolTrim
You can draw a funnel plot of the filled data using the function funnel:
infarctVolTrimFun <- funnel(infarctVolTrim,
# plot the random effects estimate
comb.random = TRUE,
# specify the x axis label
xlab = "NMD (% reduction in infarct volume)",
# specify the measure plotted on the y axis, "invse" = inverse of the standard error
yaxis = "se"
)
5. Test your skills: small study effects
Now you can assess and visaulise small study effects in the neurobehavioural data set. Create a funnel plot, test for asymmetry, and impute missing studies using the funnel, metabias, and trimfill functions. In section 3. Test your skills: meta-analysis we used only those experiments where IL-1RA was administered in protein form and motor / sensory outcomes were assessed. To assess for small study effects, we will use the entire neurobehavioural data set.
We have provided the first part of the code to load the data and perform the meta-analysis. Start by writing code to create a funnel plot visualising these data:
# first, load the data set "IL-1RA_Neurobehaviour_prenest_section5.csv" using the function read.csv().
neurobeh2 <- read.csv("IL-1RA_Neurobehaviour_prenest_section5.csv", fileEncoding = "UTF-8-BOM")
# perform a random effects meta-analysis using the function metagen().
neurobehMA2 <- metagen(
ESi,
SEi,
data = neurobeh2,
comb.random = TRUE,
comb.fixed = FALSE,
method.tau = "REML"
)
# view the results of the meta-analysis
neurobehMA2
# use the funnel() function to generate a funnel plot and visually assess the plot for asymmetry
neurobehFun <- funnel(
# specify the meta-analysis to plot
neurobehMA2,
# plot the random effects estimate
comb.random = TRUE,
# specify the x axis label
xlab = "NMD (% improvement in neurobehavioural outcome)",
# specify the measure plotted on the y axis, "se" = inverse of the standard error
yaxis = "se"
)
Next, use Egger’s test to assess funnel plot symmetry statistically:
# use the metabias() function to test for funnel plot asymmetry
neurobehBias <- metabias(
# specify the meta-analysis to analyse
neurobehMA2,
# specify the method behind the test statistic
method.bias = "Egger",
# plot the results
plotit = TRUE
)
# view the results of the funnel plot asymmetry analysis
neurobehBias
Now use trim and fill to assess whether there are any theoretically missing studies in the data set and then create a funnel plot of the results:
# use the trimfill() function to test for "missing" studies
# use the funnel() function to plot the filled data set
neurobehTrim <- trimfill(
# specify the meta-analysis to assess
neurobehMA2,
# specify the side of the plot where missing studies are expected
left = TRUE,
# plot the random effects estimate
comb.random = TRUE,
# specify which method is used to estimate the between-study variance τ^2, restricted maximum-likelihood (REML) estimator
method.tau = "REML"
)
# view the results of the trim-and-fill analysis
neurobehTrim
neurobehTrimFun <- funnel(neurobehTrim,
# plot the random effects estimate
comb.random = TRUE,
# specify the x axis label
xlab = "NMD (% improvement in neurobehavioural outcome)",
# specify the measure plotted on the y axis, "se" = inverse of the standard error
yaxis = "se"
)
Notes and resources
We have created this tutorial to guide users through a preclinical meta-analysis.
Meta-analysis comes with many researcher degrees of freedom and it’s important that when performing your own meta-analysis, your statistical analysis plan is specified a priori in your study protocol.
In this tutorial we have implemented standard analyses, as performed in a published meta-analysis, but in the functions we have used (and the many others available), multiple details can be tailored to suit different analyses.
If you use R to analyse your data (or any other statistical software package!), you should have a thorough understanding of how the analysis is run and why, and the assumptions and default settings that are built into the functions you use in different software applications.
There are many more things to explore in R, with lots of options to customise graphs and data visualisations for presentations and publications.
For more information on any of the R packages and functions we have used, you can use the help function in R studio, which is described in detail on the Getting Help with R page, along with multiple options on where else to look for information and help.
A useful online resource that describes meta-analysis with R in detail is “Doing Meta-Analysis with R: A Hands-On Guide” .
If you have specific problems developing code in R, there is also a thriving online community with whom to engage. Many platforms offer forums to submit problems and solutions, one of the most popular is Stack Exchange.
If you have questions about your own systematic review of animal data, you can contact CAMARADES Berlin. Alternatively, you can contact the CAMARADES SyRF help desk.
This resource has been created using CC-BY-4.0 sources including: Viechtbauer W. Conducting meta-analyses in R with the metafor package. Journal of Statistical Software 36(3), 1-48 (2010).
This resource was last updated on: 20 June, 2026
About CAMARADES
The materials presented here have been developed by CAMARADES Berlin. CAMARADES (Collaborative Approach to Meta-Analysis and Review of Animal Data from Experimental Studies) provide a framework supporting researchers to perform systematic reviews of data from experimental animal studies. This support includes best practice methodological assistance and access to collaborative tools such as our online review facility, SyRF.
If you have questions about this resource, or would like to seek support for your systematic review, please email us.
CAMARADES Berlin are supported by Charité 3R. For more information about the 3Rs at Charité – Universitätsmedizin Berlin, visit the Charité 3R Toolbox.