Knitr Tricks I Learned the Hard Way

You would have thought that I would have learned to RTFM by now, but too often I find myself learning the subtleties of an R package by tearing it apart. I swear that I read the documentation and do the requisite Googling and StackExchanging, but it always seems whatever I want to do is just a little too esoteric for the mainstream. I’m just too addicted to R’s wonderful metaprogramming abilities, and I guess making that work often involves needing to understand deeper parts of the code.

I got distracted by these types of problems most recently with the knitr package in R. knitr is used to make basically any type of document these days, and the package does an amazing job of walking the line between being user-friendly and deep and customizable. Here I’ll show you a few tricks that will hopefully get you thinking about how you can customize knitr!

Fanfare when your document is done

First off, I love the beepr package, which basically just lets you play sounds from R. As I’m writing my thesis, I have a few documents I’ve been knitting that can take a relatively long time to knit. So that I don’t sit around waiting for them to finish, I put something like

beepr::beep(3)

in a chunk at the end of the document, so that right before it finishes loading it plays a trumpet sound, alerting me that it’s done. That way, I don’t have to be working in that window to know that I can switch back.

Although adding a simple chunk to the end of each of my reports is hardly any work, it wasn’t elegant enough for me. I wanted to something programmatic. Unfortunately, knitr doesn’t have a way of generating new chunks programmatically within the document, but you can set a hook that will happen right at the end of the document1.

Voila: by calling beep_on_knit() in the your document, you’ll automatically hear a sound when your document’s done!

#' Beep on successful knit
#'
#' This function requires the `beepr` package. 
#' It will play a sound when the document finishes knitting.
#'
#' @param beep_sound Input to `beepr::beep()` for what sound to play
#' @param sleep The amount of seconds to sleep before 
#' moving on (so that the sound isn't cut off after it knits)
#'
#' @export
beep_on_knit <- function(beep_sound=3, sleep=4) {
  library(beepr)
  # In case something else modified the document hook,
  #   we don't want to get rid of it
  prev_fn <- knitr::knit_hooks$get("document")
  knitr::knit_hooks$set(document = function(x) {
    beepr::beep(beep_sound)
    Sys.sleep(sleep)
    prev_fn(x)
  })
}

Autolabeling figures in LaTeX

Almost all of the reports I’m making for my thesis are PDFs in LaTeX. My advisor is very insistent that each figure have a caption and is referenced by number in each report. Currently, the way I’ve solved this is by labeling the figures in their captions, like so:

```{r test_fig, fig.cap="This is the caption.\\label{fig:testfig}"}
<plotting code here>
```

Having to type fig.cap="...\\label{fig:...}" for every single figure has become super annoying. The docs seem to suggest that labels for figures are generated automatically (see fig.lp under ‘Plots’), but it doesn’t do that in my RStudio/RMarkdown setup. I also felt I knew enough about the package at this point to whip up my own solution.

I made a custom option hook that would add \\label{fig:X} to the caption where X is a slightly edited/trimmed version of the label. No more extra typing!

#' Autolabel figures in Latex
#'
#' I hate having to add `\\label{fig:blah}` to `fig.cap` 
#' in Latex .Rmd files.  There's probably a better way to do 
#' this, but if you call this function, it will automatically 
#' add `\\label{fig:<label>}` to the `fig.cap` option of any 
#' chunk with a `fig.cap` and a label, substituting whitespace 
#' stretches (after trimming it)
#'
#' @export
autolabel_latex_figs <- function() {
  knitr::opts_hooks$set(
    .autolabel =
      function(options) {
        if (isTrue(options$.autolabel) && !is.null(options$label)) {
          label = trimws(gsub("\\s+", "_", options$label))
          options$fig.cap = paste0(options$fig.cap,
                                   "\\label{fig:",label,"}")
          options
        } else {
          options
        }
      })
  knitr::opts_chunk$set(.autolabel = TRUE)
}

Using Plotly and HTML widgets with Jekyll the right, right way

So you may remember this previous post of mine talking about how to make interactive plots with Jekyll and GitHub Pages. While I still stand by the fact that I was righter than anything else online, I recently found out that most of that post was just reinventing the wheel.

In that post, I ended up making a stateful plotting function that collected the CSS/JavaScript files used for each interactive plot, copy them into a specified directory, and then set a hook that would search all the chunks until it found the last one and load them all at once at the end.

It turns out that knitr has already implemented the tools for this, and that the original htmlwidgets package was already doing something relatively similar (I think). knitr takes the output from each expression in a code block, calls knit_print() on each of them, and then decides what to do with the output of each of these when deciding what to actually show in the document.2

knitr’s knit_print() varies based on the class of the object it’s called on, and let’s package creators create their own version of knit_print() for their own custom objects. For example, htmlwidgets has htmlwidgets:::knit_print.htmlwidget(), which tells knitr how to print HTML widgets. knitr also lets you bundle additional information (metadata) about the object in the output of knit_print() in with the object itself. knitr then collects all this metadata from everything it prints, just like my stateful printing function did, when it collected dependency information from each plotly plot.

The thing is, htwmlwidgets already does this exact same thing with knit_meta_add()—you can access all the dependencies used in the document with knit_meta(). I realized that I could use knit_meta('html_dependency') to get the dependencies and the end-of-document hook I mentioned earlier to add them in at the end.

Compare this:

set_widget_hooks <- function(dep_dir, base_path, 
                             hrefFilter = function(x) paste0("/", x)) {
  
  # Move the dependencies into the specified folder,
  #  makes them relative to the base directory,
  #  and outputs the HTML that loads them.
  render_deps <- function() {
    l <- knitr::knit_meta(class = "html_dependency",
                          clean = FALSE)
    if (length(l) > 0)
      dir.create(dep_dir, showWarnings = FALSE, recursive = TRUE)
    l <- lapply(unique(l), function(dep) {
      dep <- htmltools::copyDependencyToDir(dep, dep_dir, FALSE)
      dep <- htmltools::makeDependencyRelative(dep, base_path, FALSE)
      dep } )
    l <- htmltools::renderDependencies(l,  hrefFilter=hrefFilter)
    htmltools::htmlPreserve(l)
  }
  
  # Adds the dependency-loading HTML at the end of the doc,
  #  without upsetting the previous doc-hook.
  prev_doc_hook <- knitr::knit_hooks$get("document")
  knitr::knit_hooks$set(document = function(x) {
    prev_doc_hook(append(x, render_deps()))
  })
  
  # Sets the default of all chunks to not force
  #  screenshots. You can change it to `TRUE`
  #  on the chunks you want it to screenshot.
  knitr::opts_chunk$set(screenshot.force=FALSE)
}

to the original version of my code here. It’s so much better, and applies to all HTML widgets.

You might notice that I set the screenshot.force global chunk option to FALSE. Currently, there’s no way around doing something like this—knitr checks for HTML widgets to replace with screenshots before it lets custom knit_print functions do their thing.3 If you really want to screenshot one widget in particular, you can always set screenshot.force=TRUE for that particular chunk.

library(ggplot2)
library(plotly)

ggplot_plot <- iris %>%
  ggplot(aes(Petal.Length, Petal.Width, color=Species)) + 
  geom_point()

plotly_plot <- ggplotly(ggplot_plot)

plotly_plot

My knitr hooks in action!



Source Code:

knit_hook_setter.R

This is the better, improved version of the HTML widget enabler.

Footnotes:

  1. Previously, I had attempted to play a sound after the last chunk of the document, by setting a global chunk option and hook that would check the label of each chunk to see if it was the same as the last chunk’s, and play the sound after that. Unfortunately, cached chunks are never checked this way, so if the last chunk was cached, the sound would never play. 

  2. As far as I can gather! This is definitely an oversimplication at best though. 

  3. You could change the global render chunk option to a function that automatically checks if an object is a widget, and then temporarily disables screenshotting before passing it to knit_print(), but that’s less straightforward and unnecessary complexity. 


Tags:

R, knitr, plotly, chunks, interactive plots, data visualization, Jekyll, GitHub Pages,

  Buy me a beer? Litecoin address: LaiZUuF4RY3PkC8VMFLu3YKvXob7ZGZ5o3