Search code examples
rplotgraphicshistogram

R - Generate a plot without displaying it


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.


Solution

  • edit:

    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
    

    the following won't work for you

    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)