rplotlatticer-base-graphics

Save plot from mirt inside a function


I have a curious issue saving a plot generated inside a function when using the mirt package for R. Here's a reprex:

#save image from base plot
save_as_file = function(code, filename, width = 1000, height = 750) {
  png(filename, width = width, height = height)
  eval(substitute(code))
  dev.off()
  try(dev.off(), silent = T)
}

#test it
plot(1:3)
save_as_file(plot(1:3), "test.png")
#> null device 
#>           1
file.remove("test.png")
#> [1] TRUE

which makes this plot:

enter image description here

However, when I try to use this with mirt, nothing is produced:

#mirt test
library(mirt)
#> Loading required package: stats4
#> Loading required package: lattice
set.seed(1)
mirt_data = mirt::simdata(
  a = c(1:4),
  d = c(0,0,1,1),
  N = 1000,
  itemtype = "2PL"
)
mirt_fit = mirt(
  data = mirt_data,
  model = 1,
  itemtype = "2PL",
  verbose = F
)
plot(mirt_fit, type = "trace") #works
save_as_file(plot(mirt_fit, type = "trace"), "mirt.png") #nothing happens
#> null device 
#>           1

Producing this plot, but no file is saved. Why?

enter image description here


Solution

  • As you already found out, you mysteriously have to use print. The reason is that plot(mirt_fit,...) dispatches to mirt:::plot_mixture(mirt_fit, ...) which uses lattice.

    lattice functions behave differently from standard R graphics functions in that they do not immediately create a plot when executed. Instead, like conventional functions, they create an object of class "trellis", and the plot is created using the print method print.trellis (ggplot has probably cribbed that). Therefore, it may be necessary to explicitly print (dispatches to lattice:::print.trellis in this case) the object to display the plot.

    There is a slight problem with your proposed solution; if there's an error during plotting, it won't close the png device and will clutter your device list over time.

    graphics.off()
    dev.list()
    # NULL
    save_as_file(plot(stop(), type="trace"), "mirt.png")
    # Error ...
    save_as_file(plot(stop(), type="trace"), "mirt.png")
    # Error ...
    dev.list()
    # png png 
    #   2   3 
    graphics.off()
    

    So it's better to use dev.off() in on.exit(). Also we can use case handling, to avoid base R plots to be printed.

    save_as_file <- function(code, filename, width=1000, height=750) {
      on.exit(dev.off())
      png(filename, width=width, height=height)
      p <- eval(substitute(code))
      if (!is.null(p)) print(p)
    }
    
    save_as_file(mirt:::plot_mixture(stop(), type="trace"), "mirt.png")
    dev.list()  ## no device open
    # NULL
    save_as_file(plot(mirt_fit, type="trace"), "mirt.png")
    save_as_file(plot(1:3), "mirt.png")