Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test functions for base graphics and lattice graphics #64

Open
kplevoet opened this issue Mar 25, 2021 · 0 comments
Open

test functions for base graphics and lattice graphics #64

kplevoet opened this issue Mar 25, 2021 · 0 comments
Labels
enhancement New feature or request

Comments

@kplevoet
Copy link
Contributor

The function testGGPlot() tests whether a (student) generated ggplot2 plot is equal to an expected one, but many graphics in R are made with either the package lattice or the (core) package graphics. That means that some sort of support for these graphics needs to be provided, especially since visualization has become so important nowadays.

Support for lattice graphics

A test function for a lattice plot could be perhaps be a straightforward generalization of testGGPlot() because lattice graphics make use of the grid system, just as ggplot2 does. That means that lattice makes a graphical object, or grob, that contains all the features/settings of the plot in the same way as ggplot2 does. Now, I have personally not yet had the time to find out all the details of the testGGPlot() function, but if it compares the generated grob with the expected grob, then the same thing could be done for lattice graphics: create a test function which compares the generated grob with the expected one. See Chapter 6 en Chapter 7 in Murrell, Paul (2019) R Graphics. Third Edition for more information on graphical objects in the grid system.

Support for base graphics

For "base" graphics made with the graphics package, things are not so easy since no graphical object is made containing the features/settings of the graph. However, there may be two solutions, the first of which can entirely be done in core R and the second of which involves rendering the base graphic to a grid graphic. Which solution is ultimately chosen depends on whether or not we want a uniform way of testing and comparing plots.

Possible solution 1: Comparing base graphics with recordPlot()

If a base graphic is open/active in R, then the function recordPlot() can be used to create an object containing the display list. Technically, this is not the same as a graphical object because the display list contains the calls necessary to render the graph. However, for the purpose of comparing graphs, display lists will do because the display list of a generated graph can simply be compared to the display list of an expected graph using functions like all.equal() or identical().

For instance, if we make a simple scatter plot of the first ten integers and their squares, then its display list (called display_list) can be made as follows:

xs <- 1:10
ys <- xs^2
plot(x = xs, y = ys)
display_list <- recordPlot()

The display list itself is not so informative (partial output):

display_list
List of 2
 $ :Dotted pair list of 8
  ..$ :Dotted pair list of 2
  .. ..$ :function (.NAME, ..., PACKAGE)  
  .. ..$ :Dotted pair list of 1
  .. .. ..$ :List of 4
  .. .. .. ..$ name         : chr "C_plot_new"
 
...

However, the display lists of two separate plots can be compared with each other using e.g. all.equal():

xs <- 1:10
ys2 <- xs^2
plot(x = xs, y = ys2)
test_same <- recordplot()

all.equal(display_list, test_same)
[1] TRUE

For a plot with different features, e.g. containing the cubes of the first ten integers:

xs <- 1:10
ys <- xs^3
plot(x = xs, y = ys)
test_different <- recordPlot()

all.equal(display_list, test_different)
[1] "Component 1: Component 3: Component 2: Component 3: Mean relative difference: 9"                 
[2] "Component 1: Component 4: Component 2: Component 2: Component 2: Mean relative difference: 6.875"

# Or simply:
isTRUE(all.equal(display_list, test_different))
[1] FALSE

The only issue with recordPlot() is that it can only be used with an active plot. This is of course never a problem in an interactive session but it can be a problem in an "offline" session. If the R Judge has a way of keeping track of active plots, then this can be implemented.

Possible solution 2: converting base graphics to grid graphics using the gridGraphics package

Base graphics can also be converted to grid graphics using the gridGraphics package. Since a grid graphic alway has an associated grob, this means that the grobs corresponding to two base graphics can be compared with each other. The gridGraphics package has the following functions for this purpose:

  • grid.echo(): render the current plot or display list as a grid graphic, thereby creating a corresponding grob
  • echoGrob() create a grob out of the current plot or display list, to be stored in an object
  • plotdiff(): compare two base graphics, using grid.echo() under the hood
  • ...

The exact details on the arguments of these functions can be found on the help pages of the functions. The gridGraphics package is aso briefly described in Chapter 12 of Murrell, Paul (2019) R Graphics. Third Edition. A simple illustration of creating a grob based on the active plot is:

library(gridGraphics)
xs <- 1:10
ys <- xs^2
plot(x = xs, y = ys)
grob_square <- echoGrob()

An illustration of creating a grob based on a display list (i.e. the output of recordPlot()):

grob_cube <- echoGrob(x = test_different)

Creating a grob of a plot command can also be done, but this requires the plot command to be wrapped in a function without arguments:

grob_square <- echoGrob(x = function() plot(x = xs, y = ys) )

etc.

The advantage of working with the gridGraphics package is that all plots are converted to grobs, possibly allowing for a uniform way of dealing with base graphics, lattice graphics and ggplot2 graphics. The availability of the plotdiff() function could also mean that much of the work has already been done. The solution could then be to modify the testGGPlot() function so it can also handle the grobs of these other graphics.

However, that depends on which features of the grob the testGGPlot() function actually looks at: is it the whole grob or are they particular features in it which are important for the ggplot2 package? If that it settled, then it can be decided which solution is most efficient for the R Judge in order to be able to test for all important graphics in R.

@chvp chvp added the enhancement New feature or request label Mar 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants