Jenny Bryan
25 July, 2014
A group of us at UBC are working through Wickham's Advanced R Programming book together. We just finished the chapter on Functions and one of the exercises was giving me an unseemly amount of trouble.
It's the third exercise in the subsection on return values and the use of on.exit()
:
- "Write a function that opens a graphics device, runs the supplied code, and closes the graphics device (always, regardless of whether or not the plotting code worked)."
If I hard-wire some graphics code, all is well.
safe_plot_0 <- function(file = 'test_0.png', ...) {
png(file = file, ...)
on.exit(dev.off())
plot(cars)
}
safe_plot_0()
Here I embed test_0.png
:
If I execute silly non-graphics code, all is well (adding a dashed orange line, to any PNGs produced by this version of the function).
safe_plot_1 <- function(code, file = 'test_1.png', ...) {
png(file = file, ...)
on.exit(dev.off())
plot(cars)
abline(coef(lm(dist ~ speed, cars)), col = "orange", lty = "longdash")
force(code)
invisible(NULL)
}
safe_plot_1(print('hello world'))
## [1] "hello world"
safe_plot_1(print(getwd()))
## [1] "/Users/jenny/resources/R/code/gists"
safe_plot_1(write("hello?", 'hello.txt'))
readLines('hello.txt')
## [1] "hello?"
Here I embed test_1.png
:
And now I can even execute graphics code and solve the exercise!
safe_plot_2 <- function(code, file = 'test_2.png', ...) {
png(file = file, ...)
on.exit(dev.off())
force(code)
invisible(NULL)
}
safe_plot_2({plot(cars)
abline(coef(lm(dist ~ speed, cars)), col = "purple", lwd = 3)
})
Here I embed test_2.png
(The code I passed requested a purple thick line, to prove the PNG came from this function call):
What was doing wrong before? Enclosing the arbitrary code I wanted executed in quotes. Apparently it must be unquoted.
I figured this out when writing safe_plot_1()
, where I passed arbitrary non-graphics code via a function argument and which caused me to re-read the bit of the chapter again where the function in_dir()
got defined. And I noticed that Hadley did not quote his code in his invocation: in_dir("~", getwd())
.
Yet another illustration of the value of creating a minimal, self-contained example. It's amazing the questions you can answer for yourself.
Last thing I'll do: check the function actually does it's job by inspecting the list of graphics devices before and after triggering an inelegant exit from some plotting code.
dev.list()
## pdf
## 2
safe_plot_2({plot(flying_cars)
abline(coef(lm(dist ~ speed, cars)), col = "purple", lwd = 3)
})
## Error: object 'flying_cars' not found
dev.list()
## pdf
## 2
Finally, I'll make a ggplot
plot with my function.
safe_plot_2({
require(ggplot2)
p <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point()
print(p)
}, file = 'test_3.png')
## Loading required package: ggplot2
## Loading required package: methods
Here I embed test_3.png
: