Setting local chunk options for a specific chunk in knitr? - r

I'm using knitr to knit RMarkdown, and there have been multiple times where I have wanted to add code chunks programmatically, but failed to find a way to do so satisfactorily. Say I want to have knitr play a sound when a file has finished knitting. My way around this problem has been like so:
beep_on_knit <- function(beep_sound=3, sleep=3) {
library(beepr)
last_label <- tail(knitr::all_labels(),n=1)[[1]]
knitr::knit_hooks$set(
.beep_on_last_chunk =
function(before, options) {
if (options$label == last_label & !before) {
beepr::beep(beep_sound)
Sys.sleep(sleep)
invisible(NULL)
}
})
# Sets the options for every chunk so the hook will be run on them
knitr::opts_chunk$set(.beep_on_last_chunk = TRUE)
}
However, having to edit the chunk properties of every single chunk (i.e., knitr::opts_chunk$set(.beep_on_last_chunk = TRUE) means that if I add this function to a document, it invalidates the cache of every previously cached chunk.
Is there a way to set the options of a specific chunk beforehand?

I don't know why you need to set knitr::opts_chunk$set(.beep_on_last_chunk = TRUE) globally for the document. Is it possible for you to set .beep_on_last_chunk = TRUE only on the last chunk as a local chunk option? If this is possible, you won't need to test if (options$label == last_label) in the hook.
Alternatively, you may consider using the document hook, which is executed after the whole document has been knitted, e.g.,
knitr::knit_hooks$set(document = function(x) {
beepr::beep(3)
x
})

Related

Is it possible to set chunk defaults only for a specific engine?

I am using SQL blocks in an RMarkdown document, and I want the echo option to default to FALSE for all of them – but only for sql blocks, not others.
I know I can set knitr::opts_chunk$set(echo = TRUE), but that would set it for all chunks.
As Yihui suggested in a comment, the proper way to do this is to use an option hook. The following sets echo=FALSE for sql chunks and echo=TRUE otherwise:
knitr::opts_hooks$set(echo = function(options) {
options$echo <- options$engine != "sql"
return(options)
})
I'll leave my original answer below … for entertainment. It's a workaround, required in a hypothetical parallel universe without option hooks.
You can query the current engine via opts_current$get("engine"). Based on this, you can use the following function (and extend it however you like) to determine the desired value for echo:
conditionalDefaut_echo <- function() {
return(opts_current$get("engine") != "sql")
}
The challenge is to evaluate this function whenever parsing a new chunk. This can be achieved with quote:
opts_chunk$set(echo = quote(conditionalDefaut_echo()))
To be honest, I am not sure how reliable this is – this kind of metaprogramming depends on the internal workings of knitr and might break in the future. (Maybe Yihui wants to comment on this …)
A full example with engines r and asis, where echo is FALSE for asis chunks and TRUE otherwise:
```{r}
library(knitr)
conditionalDefaut_echo <- function() {
return(opts_current$get("engine") != "asis")
}
opts_chunk$set(echo = quote(conditionalDefaut_echo()))
```
```{asis}
I'm invisible.
```
```{r}
print(1) # code visible
```

How can I hide code blocks in xaringan presentation?

I'm running some plot code in markdown to generate a plot in a xaringan presentation. The code works but is a little long and so takes up the whole presentation slide forcing the actual plot off the edge (see img).
How can I hide the code block generating the plot?
Also how can I compress the code block with a scroll bar?
```{r}
r_exp.fun <- function(r = 0.05, N_pop = 10, t = 150)
{
N <- vector("numeric", length = t)
N[1] <- N_pop
for (i in 2:t)
{
N[i] <- N[i-1] + (N[i-1] * r)
}
return(N)
}
args_list <- list(0.045, 0.055, 0.06)
matplot(
mapply(
r_exp.fun,
r = args_list
)
,type = "l")
abline(h = list(7052, 29150, 59000))
```
The alternative is of course to save as an image but if possible I would prefer to be able keep the code as a resource for anyone with the link.
Thanks!
As alistaire already mentioned in the comments, RMarkdown has various chunk options to customize the output.
For your problem the option echo needs to be set to FALSE.
The other options (from https://rmarkdown.rstudio.com/lesson-3.html):
include = FALSE
prevents code and results from appearing in the finished file. R Markdown still runs the code in the chunk, and the results can be used by other chunks.
echo = FALSE
prevents code, but not the results from appearing in the finished file. This is a useful way to embed figures.
message = FALSE
prevents messages that are generated by code from appearing in the finished file.
warning = FALSE
prevents warnings that are generated by code from appearing in the finished.
fig.cap = "..."
adds a caption to graphical results.

Suppress messages in markdown, but not in R console

I currently use the following header:
```{r, message=FALSE}
foo <- function(x) message(x)
for(i in 1:10) foo(i)
```
Inside this code chunk, there is a loop over simulated scenarios, with message() function that prints status of currently executed scenario.
I would like to suppress those messages from display in RStudio and final HTML output, but I still want to control the simulation progress and see the message() output in console. Is this achievable? Maybe with other arguments/functions?
You can write/append status to a file (this is a workaround solution, there should be a more direct answer).
For example:
file <- file("status.txt", open = "wt")
sink(file, type = "message")
message("all good")
In this example message won't be displayed - it'll be written to a status.txt file.
In you're using specific function and iterating over a set you can try this example:
foo <- function(x) {
message(x)
}
file <- file("status.txt", open = "wt")
sink(file, type = "message")
for(i in 1:3) {
foo(i)
}
Function foo should return (message) value, however it appends it to a status.txt file.
You can track changes in status.txt file using bash tail command with -f argument. First send R to background and then use tail -f status.txt in your console.
One approach would be to put this in the start of your file.
mymessage <- function (text) {
if(knitr::opts_knit$get('out.format') != NULL) message(text)
}
There are various ways to know if you are within knitr, recent versions have knitr::is_latex_output and similar.

Having multiple pander()s in a function

How do I create multiple outputs via pander() in a knitted document "asis" ?
When I have multiple calls of pander in a function, only the most recent one is shown in the HTML output. Here's an example:
tmp = function() {
pander('A')
pander('B')
pander('C')
}
tmp()
In the knitted document this gives: C
I could set panderOptions('knitr.auto.asis', FALSE) or I could use cat() so that the pander() output is written to the standard output. But then it's formatted as code, not as part of the document. As I need pander() to format a few tables for me, this does not help.
The tmp function will return only the last object -- that's why only C is printed. If you want to write each object to the stdout right away without the auto-asis convenience option, then you have to both disable the option like you did and use the relate knitr chunk option, eg:
```{r results='asis'}
library(pander)
panderOptions('knitr.auto.asis', FALSE)
tmp = function() {
pander('A')
pander('B')
pander('C')
}
tmp()
```
See more examples in the related "Using pander with knitr" vignette.

knitr HTML output too large

I have been using rmarkdown/knitr's knit to html capability to generate html code for some blogs. I've found it extremely helpful and convenient, but have been running into some problems lately with file size.
When I knit a script that has graphics that use shapefiles or ggmap images, the html file gets too big for the blog host to make sense of it (I've tried with both blogger and wordpress). I believe this has to do with the relatively large data.frames/files that are the shapefiles/ggmap being put into html form. Is there anything I can do to get a smaller html file that can be parsed by a blog host?
For reference, the html output from an rmarkdown script with one graphic using a ggmap layer, a layer of shapefiles and some data is 1.90MB, which is too big for blogger or wordpress to handle in html input. Thanks for any ideas.
Below are 3 different options to help you reduce the file size of HTML files with encoded images.
1. Optimize an existing HTML file
You can run this Python script on an existing HTML file. The script will:
decode the base64 encoded images
run pngquant to optimize the images
re-encode the optimized images as base64
Usage:
python optimize_html.py infile.html
It writes output to infile-optimized.html.
2. Use the built-in knitr hook for optimizing PNG images
knitr 1.15 includes a hook called hook_optipng that will run the optipng program on generated PNG files to reduce file size.
Here is a .Rmd example (taken from: knitr-examples/035-optipng.Rmd):
# 035-optipng.Rmd
This demo shows you how to optimize PNG images with `optipng`.
```{r setup}
library(knitr)
knit_hooks$set(optipng = hook_optipng)
```
Now we set the chunk option `optipng` to a non-`NULL` value,
e.g. `optipng=''`, to activate the hook. This string is passed to
`optipng`, so you can use `optipng='-o7'` to optimize more heavily.
```{r use-optipng, optipng=''}
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
```
3. Write your own knitr hook for any image optimizer
Writing your own hook is also quite easy, so I wrote a hook that calls the pngquant program. I find that pngquant runs faster, and the output files are smaller and look better.
Here is a .R example that defines and uses hook_pngquant (taken from this gist).
#' ---
#' title: "pngquant demo"
#' author: "Kamil Slowikowski"
#' date: "`r Sys.Date()`"
#' output:
#' html_document:
#' self_contained: true
#' ---
#+ setup, include=FALSE
library(knitr)
# Functions taken from knitr/R/utils.R
all_figs = function(options, ext = options$fig.ext, num = options$fig.num) {
fig_path(ext, options, number = seq_len(num))
}
in_dir = function(dir, expr) {
if (!is.null(dir)) {
owd = setwd(dir); on.exit(setwd(owd))
}
wd1 = getwd()
res = expr
wd2 = getwd()
if (wd1 != wd2) warning(
'You changed the working directory to ', wd2, ' (probably via setwd()). ',
'It will be restored to ', wd1, '. See the Note section in ?knitr::knit'
)
res
}
is_windows = function() .Platform$OS.type == 'windows'
in_base_dir = function(expr) {
d = opts_knit$get('base.dir')
if (is.character(d) && !file_test('-d', d)) dir.create(d, recursive = TRUE)
in_dir(d, expr)
}
# Here is the code you can modify to use any image optimizer.
hook_pngquant <- function(before, options, envir) {
if (before)
return()
ext = tolower(options$fig.ext)
if (ext != "png") {
warning("this hook only works with PNG")
return()
}
if (!nzchar(Sys.which("pngquant"))) {
warning("cannot find pngquant; please install and put it in PATH")
return()
}
paths = all_figs(options, ext)
in_base_dir(lapply(paths, function(x) {
message("optimizing ", x)
cmd = paste(
"pngquant",
if (is.character(options$pngquant)) options$pngquant,
shQuote(x)
)
message(cmd)
(if (is_windows())
shell
else system)(cmd)
x_opt = sub("\\.png$", "-fs8.png", x)
file.rename(x_opt, x)
}))
return()
}
# Enable this hook in this R script.
knit_hooks$set(
pngquant = hook_pngquant
)
#' Here we set the chunk option `pngquant='--speed=1 --quality=0-50'`,
#' which activates the hook.
#+ use-pngquant, pngquant='--speed=1 --quality=0-50'
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
I prefer to write my reports in R scripts (.R) instead of R markdown documents (.Rmd). See http://yihui.name/knitr/demo/stitch/ for more information on how to do that.
One thing you could do would be to not use embedded image and other resources. To achieve this, you can set the self_contained option in the YAML header for your document to false, e.g.:
---
output:
html_document:
self_contained: false
---
More info here: http://rmarkdown.rstudio.com/html_document_format.html

Resources