I have written a function that shows histograms of variables in a dataset in a grid using loop. I'm adding labels to the bars, but the label for the highest bar is always cut-off in half by the plot. I've looked for a way to extend the plot margins, but couldn't find one anywhere. (par(oma) or par(mar)) didn't work for me. I thought of a workaround which would first generate a historgram in a function, get the ylims from that and return those limits, which can then be used by the original loop to define extended ylim in the hist() function.
But the function that returns the ylim() also plots the histogram. I want to know if there is a way to not show a plot. I tried dev.off() but that also turned off the plots in the loop.
Function that returns ylim
histylim = function(data, nclass=NULL) {
a = hist(unlist(data), nclass=nclass)
corners = par("usr")
# dev.off()
return(c(corners[3], corners[4]))
}
The above function also returns a histogram, aside from the specified return() object.
When adding def.off(), and calling the function within another loop that is generating a grid of histograms, the dev.off() from histylim also turns off the grid.
Here's the function that I have written to return a gird of histograms
multihist = function(data, numcols, nclass=NULL, labels=NULL, perc=F, axis=F, quants=NULL,
plot_dim=NULL) {
vars = names(which(sapply(data, is.numeric)))
numrows = if (length(vars)%%numcols == 0) ((length(vars)%%numcols)+1) else ((length(vars)%/%numcols)+1)
if (is.null(plot_dim)) {options(repr.plot.width=17, repr.plot.height=5*numrows)}
else {options(repr.plot.width=plot_dim[1], repr.plot.height=plot_dim[2])}
histylim = function(data, nclass=NULL) {
a = hist(unlist(data), nclass=nclass)
corners = par("usr")
# dev.off()
return(c(corners[3], corners[4]))
}
par(mfrow=c(numrows, numcols))
par(mar=c(5.1, 4.1, 4.1+2, 2.1))
par(oma=c(0,0+2,0,0))
for (i in seq(length(vars))) {
var = vars[i]
par(mai=c(0, 0, 0, 0))
corners = par('usr')
a = hist(unlist(data[, var]), nclass=nclass, col=c(i+1),
ylim=histylim(unlist(data[, var]), nclass=nclass),
main=paste(var), xlab=vars[i], ylab='freq', cex.main=2, cex.lab=1.7, cex.axis=1.5)
median.var = median(unlist(data[,var]))
mtext(paste('median : ', median.var), 3, adj = 0.5, line = 0.5, cex=1.2)
abline(v=median.var, lty=2, lwd=2)
if (!is.null(quants)) {for (q in quantile(unlist(df[,var]), quants)) {
abline(v=q,lty=2,lwd=1,col='grey')}}
if (axis==T) {axis(side=1, at=a$breaks, label=a$breaks)}
if (!is.null(labels)) {
if (perc==T) {
labs = paste0(round(a$counts/sum(a$counts)*100),'%')
labs[which(a$counts == 0)] = ''
text(a$mids, a$counts, labels=labs, cex=labels, pos=3, col=1, font.axis=1)}
else {text(a$mids, a$counts, labels=a$counts, cex=labels, pos=3, col=1, font.axis=1)}
}
}
}
I want the highest histogram label to not be cut in half.
simply adding plot = FALSE
won't work here because you want to get some parameters that are produced while plotting. In this case, you can follow somthing similar to this question.
histylim <- function(...){
ff <- tempfile()
png(filename=ff)
res <- hist(...)
corners <- par("usr")
dev.off()
unlink(ff)
return(c(corners[3], corners[4]))
}
histylim(mtcars$mpg)
#> [1] -0.48 12.48
histylim(mtcars$disp)
#> [1] -0.28 7.28
Add the argument plot=FALSE
to the histogram function and if/when needed, plot the object by calling it within plot()
, as in:
mtcars
p1 <- hist(mtcars$mpg, plot = FALSE)
plot(p1)