4

I am very new in using the power of R to create graphical output.

I use the forest()-function in the metafor-package to create Forest plots of my meta-analyses. I generate several plots using a loop and then save them via png().

for (i in 1:ncol(df)-2)){
  dat <- escalc(measure="COR", ri=ri, ni=ni, data=df) # Calcultes Effect Size
  res_re <- rma.uni(yi, vi, data=dat, method="DL", slab=paste(author)) # Output of meta-analysis

  png(filename=path, width=8.27, height=11.69, units ="in", res = 210)
  forest(res_re, showweight = T, addfit= T, cex = .9) 
  text(-1.6, 18, "Author(s) (Year)", pos=4)     
  text( 1.6, 18, "Correlation [95% CI]", pos=2)
  dev.off()
}

This works great if the size of the plot is equal. However, each iteration of the loop integrates a different number of studies in the forest plot. Thus, the text-elements are not on the right place and the forest-plot with many studies looks a bit strange. I have two questions:

  1. How can I align the "Author(s) (Year)" and "Correlation [95%CI]" automatically to the changing size of the forest-plot such that the headings are above the upper line of the forest-table?
  2. How can I scale the size of the forest plot such that the width and the size of the text-elements is the same for all plots and for each additional study just a new line will be added (changing height)?

Each forest-plot should look like this:

enter image description here

jeffrey
  • 2,026
  • 5
  • 28
  • 42

2 Answers2

3

Here is what you will have to do to get this to work:

  1. I would fix xlim across plots, so that there is a fixed place to place the "Author(s) (Year)" and "Correlation [95%CI]" headings. After you have generated a forest plot, take a look at par()$usr[1:2]. Use these values as a starting point to adjust xlim so that it is appropriate for all your plots. Then use those two values for the two calls to text().

  2. There are k rows in each plot. The headings should go two rows above that. So, use text(<first xlim value>, res_re$k+2, "Author(s) (Year)", pos=4) and text(<second xlim value>, res_re$k+2, "Correlation [95% CI]", pos=2)

  3. Set cex in text() to the same value you specified in your call to forest().

  4. The last part is tricky. You have fixed cex, so the size of the text-elements should be the same across plots. But if there are more studies, then the k rows get crammed into less space, so they become less separated. If I understand you correctly, you want to keep the spacing between rows equal across plots by adjusting the actual height of the plot. Essentially, this will require making height in the call to png() a function of k. For each extra study, an additional amount needs to be added to height so that the row spacing stays constant, so something along the lines of height=<some factor> + res_re$k * <some factor>. But the increase in height as a function of k may also be non-linear. Getting this right would take a lot of try and error. There may be a clever way of determining this programmatically (digging into ?par and maybe ?strheight).

So make it easier for others to chime in, the last part of your question comes down to this: How do I have to adjust the height value of a plotting device, so that the absolute spacing between the rows in plot(1:10) and plot(1:20) stays equal? This is an interesting question in itself, so I am going to post this as a separate question.

Wolfgang
  • 2,810
  • 2
  • 15
  • 29
2

ad 4.: In Wolfgangs question (Constant Absolute Spacing of Row in R Plots) you will find how to make plot-height depending on the amount of rows in it.

For forest() it would work a little different, since this function internally modifies the par("mar")-values.

However, if you set margins to zero, you only need to include the attribute yaxs="i" in your forest()-function, so that the y-axis will be segmented for the range of the data and nothing else. The device than needs to be configured to have the height (length(ma$yi)+4.5)*fact*res with fact as inches/line (see below) and res as pixels/inch (resolution).

The 4.5 depends if you have left addfit=T and intercept=T in your meta-analysis model (in that case forest() internally sets ylim <- c(-1.5, k + 3)). Otherwise you'd have to use 2.5 (than it would be ylim <- c(0.5, k + 3)).

If you feel like using margins you would do the following (I edited the following part, after I recognized some mistake):

res <- 'your desired resolution' # pixels per inch
fact <- par("mai")[1]/par("mar")[1] # calculate inches per line
### this following part is copied from inside the forest()-function.
# forest() modifies the margin internally in the same way.
par.mar <- par("mar")
par.mar.adj <- par.mar - c(0, 3, 1, 1)
par.mar.adj[par.mar.adj < 0] <- 0
###
ylim <- c(-1.5, length(ma$yi)+3) # see above
ylim.abs <- abs(ylim[1])+abs(ylim[2])-length(ma$yi) # calculate absolute distance of ylim-argument
pixel.bottom <- (par.mar.adj[1])*fact*res # calculate pixels to add to bottom and top based on the margin that is internally used by forest().
pixel.top <- (par.mar.adj[3])*fact*res
png(filename='path', 
    width='something meaningful', 
    height=((length(ma$yi)+ylim.abs)*fact*res) + pixel.bottom + pixel.top, 
    res=res)
par(mar=par.mar) # make sure that inside the new device the margins you want to define are actually used.
forest(res_re, showweight = T, addfit= T, cex = .9, yaxs="i")
...
dev.off()
Community
  • 1
  • 1