Aligning text with a variable size graphic in R
I am very new to using the power of R to generate graphical output.
I use the forest () function in the metaphor package to create forest plots for my meta-analyzes. I am generating multiple plots using a loop and then saving 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 plot size is equal. However, each iteration of the loop combines a different amount of research in the forest. Thus, the text elements are not in the right place, and the forest area with many explorations looks a little strange. I have two questions:
- How can I auto-align "Author (s) (Year)" and "Correlation [95% CI]" automatically to resize the forest parcel so that the headers are above the top line of the forest table?
- How can I scale the size of the forest patch so that the width and size of the text elements are the same for all plots, and a new line (height change) will be added for each additional exploration?
Each forest area should look like this:
source to share
Here's what you need to do to get this to work:
-
I would fix
xlim
on the graphs to have room to place the "Author (s) (Year)" and "Correlation [95% CI]" headers. After you've created the forest lot, take a look atpar()$usr[1:2]
. Use these values as a starting point for customizationxlim
to fit all of your stories. Then use these two values for two callstext()
. -
There are
k
lines in every graph . Headings should be two rows higher. So usetext(<first xlim value>, res_re$k+2, "Author(s) (Year)", pos=4)
andtext(<second xlim value>, res_re$k+2, "Correlation [95% CI]", pos=2)
-
Set
cex
intext()
to the same value you specified when callingforest()
. -
The last part is tricky. You fixed
cex
, so the size of the text elements should be the same in different areas. But if there is more research, then the linesk
are packed into less space, so they become less split. If I understand you correctly, you want the row spacing to be equal between the graphs by adjusting the actual height of the graph. Essentially, this will require doing itheight
in apng()
function callk
. For each additional exploration, an additional amount has to be added inheight
to keep the line spacing constant, so something along the linesheight=<some factor> + res_re$k * <some factor>
. But the increase in height as a functionk
can also be non-linear. Getting this right will take a lot of effort and error. There might be a clever way to figure it out programmatically (digging in?par
and out, maybe?strheight
).
So to make it easier for others to call, the last part of your question boils down to this: How do I adjust the height
build device value so that the absolute line spacing in plot(1:10)
and plot(1:20)
stays the same? This is an interesting question in itself, so I'm going to post this as a separate question.
source to share
ad 4: In Wolfgang's question ( Constant absolute line spacing in R-plots ) you will find how to make the height of the plot depending on the number of lines in it.
For forest()
it will be slightly different, since this function internally changes values par("mar")
.
However, if you are setting the fields to zero, you need to include the attribute yaxs="i"
in the forest()
-function so that the y-axis is segmented for the data range and nothing else. The device must be set to height (length(ma$yi)+4.5)*fact*res
with fact
both inches / line (see below) and res
pixels / inches (resolution).
4.5
depends on whether meta-analysis remained in the model addfit=T
and intercept=T
(in this case, forest()
internally sets ylim <- c(-1.5, k + 3)
). Otherwise, you will have to use 2.5
(than ylim <- c(0.5, k + 3)
).
If you feel like using fields, you would do the following (I edited the next part after I found out some kind of error):
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()
source to share