evals is aimed at collecting as much information as possible while evaluating R code. It can evaluate a character vector of R expressions, and it returns a list of information captured while running them:
src holds the R expression,result contains the raw R object as-is,output represents how the R object is printed to the standard output,type is the class of the returned R object,msg is a list of messages captured while evaluating the R expression. Among other messages, warnings/errors will appear here.stdout contains what, if anything, was written to the standard output.Besides capturing evaluation information,evals is able to automatically identify whether an R expression is returning anything to a graphical device, and can save the resulting image in a variety of file formats.
Another interestingevals feature is caching the results of evaluated expressions. Read thecaching section for more details.
evals has a large number of options, which allow users to customize the call exactly as needed. Here we will focus on the most useful features, but the full list of options, with explanations, can be viewed by calling?evalsOptions. Alsoevals support permanent options that will persist for all calls toevals, this can be achieved by callingevalsOptions.
Let’s start with a basic example by evaluating1:10 and collecting all information about it:
evals('1:10')#> [[1]]#> $src#> [1] "1:10"#>#> $result#> [1] 1 2 3 4 5 6 7 8 9 10#>#> $output#> [1] " [1] 1 2 3 4 5 6 7 8 9 10"#>#> $type#> [1] "integer"#>#> $msg#> $msg$messages#> NULL#>#> $msg$warnings#> NULL#>#> $msg$errors#> NULL#>#>#> $stdout#> NULL#>#> attr(,"class")#> [1] "evals"Not all the information might be useful, soevals makes it is possible to capture only some of the information, by specifying theoutput parameter:
evals('1:10',output =c('result','output'))#> [[1]]#> $result#> [1] 1 2 3 4 5 6 7 8 9 10#>#> $output#> [1] " [1] 1 2 3 4 5 6 7 8 9 10"#>#> attr(,"class")#> [1] "evals"One of the neat features ofevals that it catches errors/warnings without interrupting the evaluation and saves them.
evals('x')[[1]]$msg#> $messages#> NULL#>#> $warnings#> NULL#>#> $errors#> [1] "object 'x' not found"evals('as.numeric("1.1a")')[[1]]$msg#> $messages#> NULL#>#> $warnings#> [1] "NAs introduced by coercion"#>#> $errors#> NULLAs mentioned before,evals captures the output to graphical devices and saves it:
evals('plot(mtcars)')[[1]]$result#> [1] "my_plots/test.jpeg"#> attr(,"class")#> [1] "image"You can specify the output directory using thegraph.dir parameter, and the output type using thegraph.output parameter. Currently, it could be any ofgrDevices:png,bmp,jpeg,jpg,tiff,svg, orpdf.
evals('plot(mtcars)',graph.dir ='my_plots',graph.output ='jpg')[[1]]$result#> [1] "my_plots/test.jpeg"#> attr(,"class")#> [1] "image"Moreover,evals provides facilities to:
recordPlot to distinct files withrecodplot extensionlattice orggplot2) while generating the plot to distinct files withRDS extensionevals provides very powerful facilities to unify the styling of images produced by different packages, likeggplot2 andlattice.
Let’s prepare the data for plotting:
## generating datasetset.seed(1)df<- mtcars[,c('hp','wt')]df$factor<-sample(c('Foo','Bar','Foo bar'),size =nrow(df),replace =TRUE)df$factor2<-sample(c('Foo','Bar','Foo bar'),size =nrow(df),replace =TRUE)df$time<-1:nrow(df)Now let’s plot the histograms:
evalsOptions('graph.unify',TRUE)evals('histogram(df$hp, main = "Histogram with lattice")')[[1]]$result#> [1] "my_plots/test.jpeg"#> attr(,"class")#> [1] "image"evals('ggplot(df) + geom_histogram(aes(x = hp), binwidth = 50) + ggtitle("Histogram with ggplot2")')[[1]]$result#> [1] "my_plots/test.jpeg"#> attr(,"class")#> [1] "image"evalsOptions('graph.unify',FALSE)Options for unification can be set withpanderOptions. For example:
panderOptions('graph.fontfamily',"Comic Sans MS")panderOptions('graph.fontsize',18)panderOptions('graph.fontcolor','blue')panderOptions('graph.grid.color','blue')panderOptions('graph.axis.angle',3)panderOptions('graph.boxes', T)panderOptions('graph.legend.position','top')panderOptions('graph.colors',rainbow(5))panderOptions('graph.grid',FALSE)panderOptions('graph.symbol',22)More information and examples on style unification can be obtained byPandoc.brewing the tutorial availablehere.
To make execution and debugging easier to understand,evals provides logging with thelog parameter. Logging inevals relies on thelogger package, which provides a logging API similar tolog4j. Basic example:
x<-evals('1:10',log ='foo')logger’s thresholds range from most verbose to least verbose:TRACE,DEBUG,INFO,WARN,ERROR,FATAL. The threshold defaults toINFO, which will hide some unessential information. To permanently set the threshold for logger uselog_threshold:
evalsOptions('log','evals')log_threshold(TRACE,namespace ='evals')x<-evals('1:10',cache.time =0)logger also provides a very useful ability to write logs to files instead of printing them to the prompt:
t<-tempfile()log_appender(appender_file(t),namespace ='evals')x<-evals('1:10',log ='evals')readLines(t)#> [1] "INFO [2021-06-11 13:36:56] Command run: 1:10"#> [2] "TRACE [2021-06-11 13:36:56] Returning cached R object."# revert back to consolelog_appender(appender_stdout,namespace ='evals')evals is uses a custom caching algorithm to cache the results of evaluated R expressions.
evals is split into single expressions and parsed.evals extracts symbols in a separate list ingetCallParts. This list describes the unique structure and the content of the passed R expressionspander’s local environments. This is useful if you are using large data frames; otherwise, the caching algorithm would have to compute the hash for the same data frame each time it’s touched! This way the hash is recomputed only if the R object with the given name is changed.panderOptions andevalsOptions, which all together is unique and there is no real risk of collision.evals can find the cached results in the appropriate environment (ifcache.mode set to environment) or in a file named to the computed hash (ifcache.mode set todisk), then it is returned on the spot. The objects modified/created by the cached code are also updated.cache is active and if the evaluationproc.time() >cache.time parameter). Cached results are saved incached.results inpander’s namespace.evals also remembers if R expressions change the evaluation environment (for example assignments) and saves such changes incached.environemnts inpander’s namespace.We will setcache.time to 0, to cache all expressions regardless of time they took to evaluate. We will also use the logging facilites described above to simplify the understanding of how caching works.
evalsOptions('cache.time',0)evalsOptions('log','evals')log_threshold(TRACE,'evals')Let’s start with small example.
system.time(evals('1:1e5'))#> INFO [2021-06-11 13:36:56] Command run: 1:1e+05#> TRACE [2021-06-11 13:36:57] Cached result#> DEBUG [2021-06-11 13:36:57] Returned object: class = integer, length = 100000, dim = , size = 400048 bytes#> user system elapsed#> 0.514 0.013 0.528system.time(evals('1:1e5'))#> INFO [2021-06-11 13:36:57] Command run: 1:1e+05#> TRACE [2021-06-11 13:36:57] Returning cached R object.#> user system elapsed#> 0.002 0.000 0.002Results cached byevals can be stored in anenvironment in currentR session or permanently on disk by setting thecache.mode parameter appropriately.
res<-evals('1:1e5',cache.mode ='disk',cache.dir ='cachedir')#> INFO [2021-06-11 13:36:57] Command run: 1:1e+05#> TRACE [2021-06-11 13:36:57] Cached result#> DEBUG [2021-06-11 13:36:57] Returned object: class = integer, length = 100000, dim = , size = 400048 byteslist.files('cachedir')#> [1] "cd31af3803220ee24ffc4e81b2749d08f977bd6c"Since the hash for caching is computed based on thestructure andcontent of the R commands, instead of the variable names or R expressions,evals is able to achieve great results:
x<- mtcars$hpy<-1e3system.time(evals('sapply(rep(x, y), mean)'))#> INFO [2021-06-11 13:36:57] Command run: sapply(rep(x, y), mean)#> TRACE [2021-06-11 13:36:58] Cached result#> DEBUG [2021-06-11 13:36:58] Returned object: class = numeric, length = 32000, dim = , size = 256048 bytes#> user system elapsed#> 0.167 0.000 0.167Let us create some custom functions and variables, which are not identical to the above call:
f<- sapplyg<- reph<- meanX<- mtcars$hp*1Y<-1000system.time(evals('f(g(X, Y), h)'))#> INFO [2021-06-11 13:36:58] Command run: f(g(X, Y), h)#> TRACE [2021-06-11 13:36:58] Returning cached R object.#> user system elapsed#> 0.002 0.000 0.003Another important feature ofevals is that it notes changes in the evaluation environment. For example:
x<-1res<-evals('x <- 1:10;')#> INFO [2021-06-11 13:36:58] Command run: x <- 1:10#> TRACE [2021-06-11 13:36:58] Cached resultx <- 1:10 will be cached; if the same assignment occurs again we won’t need to evaluate it. But what about the change ofx when we get the result from the cache?evals takes care of that.
So in the following example we can see thatx <- 1:10 is not evaluated, but retrieved from cache with the change tox in the environment.
evals('x <- 1:10; x[3]')[[2]]$result#> INFO [2021-06-11 13:36:58] Command run: x <- 1:10#> TRACE [2021-06-11 13:36:58] Returning cached R object.#> INFO [2021-06-11 13:36:58] Command run: x[3]#> TRACE [2021-06-11 13:36:58] Cached result#> DEBUG [2021-06-11 13:36:58] Returned object: class = integer, length = 1, dim = , size = 56 bytes#> [1] 3Alsoevals is able to cache output to graphical devices produced during evaluation:
system.time(evals('plot(mtcars)'))#> INFO [2021-06-11 13:36:58] Command run: plot(mtcars)#> TRACE [2021-06-11 13:36:58] Image file written: my_plots/test.jpeg#> TRACE [2021-06-11 13:36:58] Cached result#> user system elapsed#> 0.114 0.000 0.114system.time(evals('plot(mtcars)'))#> INFO [2021-06-11 13:36:58] Command run: plot(mtcars)#> TRACE [2021-06-11 13:36:58] Image found in cache: my_plots/test.jpeg#> user system elapsed#> 0.002 0.000 0.003