Tips and tricks for working with images and figures in R Markdown documents

Writing reports in R Markdown allows you to skip painful and error-prone copy-paste in favor of dynamically-generated reports written in R and markdown that are easily reproducible and updateable. R Markdown reports that are heavy on graphs and maps, though, can yield large HTML files that are not optimized for web viewing.

R Markdown offers a wide range of functions and arguments for full control of image sizes but knowing how and when to use them can be daunting particularly given the differences in how external images are handled vs R-generated figures. We assembled this blog post to help guide you through image processing decision-making as you construct your own R Markdown reports.

NOTE 1: This post is focused on the production of HTML documents and some of our conclusions and recommendations may not apply if you're using R Markdown to create a LaTeX document, PDF or Word document.

NOTE 2: Images in the final HTML documents are responsive – meaning that their dimensions may change with changes to the browser view size. In this post, we report image dimensions as they appear at full size on a computer monitor for reference.

Table of contents

Our examples: one pre-existing image and one dynamically generated plot

In this post we'll work with a pre-existing image as well as a dynamically generated plot. The plot is created using the package ggplot2. Moving forward we're going to refer to our uploaded image as image and the R-generated plot as figure. The image was downloaded to our local drive from here and can be used under the Creative Commons CC0 license.

As a starting point, we can compute the dimensions of our raw image using the readPNG function from the package png. There are two ways to grab the dimensions (height and width) of the image.

  1. Use the dim function: get the dimensions of the image
  2. Use the attr function: get the dimensions of the image as well as some other potentially useful information (color type, dpi, etc).

NOTE: you can use the same process to examine jpegs, simply swap out png for jpeg. For example library(jpeg); readJPEG(img1).

Start by loading the packages

library(knitr)    # For knitting document and include_graphics function
library(ggplot2)  # For plotting
library(png)      # For grabbing the dimensions of png files

Image 1

The raw image on disk has a width of 1000px and height of 667px (300 dpi). Size is 1.2 MB.

img1_path <- "images/sloth1.png"
img1 <- readPNG(img1_path, native = TRUE, info = TRUE)
attr(img1, "info")
## $dim
## [1] 1000  667
## 
## $bit.depth
## [1] 8
## 
## $color.type
## [1] "palette"
## 
## $gamma
## [1] 0.45455

plot of chunk initialimage

Figure 1

For our R-generated figure, Figure 1, we are using the ggplot2 package and the built in cars data set. Since the figure is being generated on the fly the dimensions and size will depend on the default settings. For this initial view we've set the width to be the same as the image above.

plot of chunk initialfigure

Default settings for including images and figures in R Markdown

Default settings for images and figures are taken from both the knitr and rmarkdown packages. If a setting exists in both packages the rmarkdown value will be used. For example both packages include a default setting for fig.retina. The knitr source code shows a default value of 1 for fig.retina. However if you leave fig.retina blank in your R chunk it will apply the default rmarkdown value of 2.

The table below shows some commonly-used settings from the rmarkdown and knitr packages and their corresponding default values. All settings shown below except for out.width and out.height will default to the rmarkdown value if left blank (rmarkdown does not have settings for out.width and out.height).

table_compare

The full documentation including default settings for each package can be found below. We’re also including a link to more documentation about the differences in certain settings as they relate to the knitr and rmarkdown packages.

Using the include_graphics function for adding images and figures

We include external images in our R markdown documents using the include_graphics function from the knitr package. Images can also be included using either raw HTML with img tags (<img src = "" />) or using markdown directly (![image](imagepath)). We are using include_graphics for two reasons. First, the function is document format agnostic – meaning it can work with LaTeX or Markdown documents. Second, although you can technically include an image in a markdown document using standard HTML image tags (<img src = "" />), using include_graphics will respect image settings listed in the R chunks like out.width and out.height.

As mentioned above, the figure is included by creating a new plot on the fly with the ggplot2 package.

How images and figures in the HTML document are affected by using defaults

You'll see below that the default for images is to display them at ½ their original size – you will see below that this is due to the fig.retina = 2 setting (making the same image ½ the size doubles the resolution). This is the default for images using the include_graphics function – original px width * 50%. The external images are unaffected by the fig.width argument (which is set to 7 inches by default). The R-generated figure however is output using the fig.width default of 7 inches.

  • Image 1 output (width = 500px and height = 333.5px, 300dpi, 1.2mb on disk): The viewable size in our HTML document is ½ the size of the original image – the default for an external image. The fig.width argument has no effect on how external images are rendered.
  • Figure 1 output (width = 672px (7 inches x 96 dpi) and height = 480px (5 inches x 96dpi), 60kb on disk): Width, height and resolution of the dynamically-generated figure are controlled by fig.width, fig.height and dpi defaults.
```{r}
# All defaults
include_graphics(img1_path)
```

plot of chunk defaultimage1

```{r}
# All defaults
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk defaultfigure1

Use fig.width and fig.height for R-generated figures only

  • Default is fig.width = 7 and fig.height = 5 (in inches, though actual width will depend on screen resolution). Remember that these settings will default to rmarkdown values, not knitr values.

How images and figures in the HTML document are affected by using fig.width and fig.height:

The fig.width and fig.height arguments only affect the dimensions of R-generated figures as you can see below. The ggplots shown are 2 and 4 inches while the image is still 500px no matter the setting of fig.width.

```{r, fig.width = 2}
# Small fig.width
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk figwidthsm_fig

```{r, fig.width = 4}
# Bigger fig.width
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk figwidthlg_fig

```{r, fig.width = 2}
# Small fig.width
include_graphics(img1_path)
```

plot of chunk figwidthsm_img

```{r, fig.width = 4}
# Bigger fig.width
include_graphics(img1_path)
```

plot of chunk figwidthlg_img

Arguments out.width and out.height apply to both existing images and R-generated figures

  • Default is out.width = NULL and out.height = NULL.

Unlike the fig.width and fig.height arguments which only affect dynamic figures, the out.width and out.height arguments can be used with any type of graphic and conveniently can accept sizes in pixels or percentages as a string with % or px as a suffix.

Keep in mind that the % refers to the percent of the HTML container. For example, if the block of text that the image is in is 1000px wide then the image will be 200px using 20%.

How images and figures in the HTML document are affected by using out.width and out.height:

  • For both R-generated figures and external images the graphics dimensions are scaled to match the width/height specified
```{r  out.width = "20%"}
include_graphics(img1_path) 
```

plot of chunk outwidth20_img

```{r  out.width = "50%"}
include_graphics(img1_path) 
```

plot of chunk outwidth50_img

```{r out.width = "20%"}
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk outwidth20_fig

```{r out.width = "50%"}
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk outwidth50_fig

Use dpi to change the resolution of images and figures

In general dpi is a measure of resolution – the higher the dpi, the sharper the image. For the most part, this is less relevant when it comes to HTML files given that the default DPI of computer displays are generally set to 72 or 96 DPI. For the web, using the rmarkdown default of 96dpi should be adequate except for retina screens where you may want to use a multiplier in the form of the fig.retina argument (see below).

When you change the dpi of an R-generated plot, larger numbers result in a larger plot unless other arguments like out.width are specified. With external images, there is no way to increase resolution so knitr compensates by making the same image smaller on the page (the same number of pixels in a smaller area).

Note that the include_graphics function has its own dpi argument. You might think that using dpi=300, for example, in the include_graphics function would have the same effect as using dpi=300 in the chunk, but this is not the case. Using dpi=300 in the include_graphics function appears to override the default chunk setting to make the image 50% width. As a result, using dpi=300 in the chunk on an image that is 1000px yields an image 1000 * 0.5/(300/96) = 160 px wide while using dpi=300 in the include_graphics function results in an image 1000/(300/96) = 320px.

How images and figures in the HTML document are affected by using dpi:

  • For external images the dpi argument will alter the width of the image on the page with higher dpi yielding smaller, “denser” images

  • In R-generated figures higher dpi will yield larger images generally. If you specify a number that maxes out the image size on the page then a larger dpi will result in no visual change, but the image will be higher resolution and thus a bigger file. In general the formula for calculating the width of the figure using the dpi argument is (width in pixels) * (dpi/96dpi).

```{r dpi = 72}
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk dpi72_fig

```{r dpi = 200}
# Higher dpi
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk dpi300_fig

```{r dpi = 72}
include_graphics(img1_path)
```

plot of chunk dpi72_img

```{r dpi = 200}
include_graphics(img1_path)
```

plot of chunk dpi300_img

The fig.retina argument is a resolution multiplier

  • Default is fig.retina = 2

A retina display is a screen developed by Apple with a significantly higher pixel density than previous models. Prior to the release of retina displays most web images were optimized at 72dpi or 96dpi. On a high pixel density device these images will be displayed as either a smaller image (though still crisp looking) or at the original dimensions (and potentially fuzzy) – this site has a nice discussion. In order to avoid these display issues and create images that look good on all screens you may want to increase the resolution of your images. This can be done with the dpi argument directly or you can make use of the fig.retina argument.

The fig.retina argument is a dpi multiplier for displaying HTML output on retina screens and changes the chunk option dpi to dpi * fig.retina. If you are worried about your images displaying properly on retina screens you can leave the default as fig.retina = 2 – this will ensure crisp display on retina screens but be aware that it will double the physical size of your images. If you don't want this to happen you should set fig.retina = 1.

How images are affected in our HTML document when using fig.retina:

  • External images: Since external images already exist and resolution cannot be increased, setting fig.retina = 2 results in an image on the page that is ½ of the original (creating a smaller but denser image).
  • R-generated figures will appear on the page as being the same size, but figures with no explicit fig.retina setting will use the default fig.retina = 2 setting. These figures will be twice as dense and thus twice the physical size as figures with fig.retina = 1 or fig.retina = NULL. In the case of our ggplot, using fig.retina = 1 or fig.retina = NULL results in an image that is 25kb while not specifying fig.retina or using the default fig.retina = 2 results in a file that is 60kb.
```{r}
# No fig.retina (using default fig.retina = 2)
include_graphics(img1_path) 
```

plot of chunk figretinanone_img

```{r, fig.retina = 1}
include_graphics(img1_path) 
```

plot of chunk figretina1_img

```{r}
# No fig.retina (using default fig.retina = 2)
# This file is 60kb
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk figretinanone_fig

```{r, fig.retina = 1}
# This file is 25kb
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk figretina1_fig

Optimize R-generated images with optipng or pngquant

The knitr package includes “hooks” you can use to run functions before or after a code chunk to tweak the output. There are two pre-created hooks available in knitr that will optimize PNG images for web viewing: 1) hook_optipng and 2) hook_pngquant. The hook functions are available within knitr but before you can use either of them, you need to install the background programs on your machine.

Note that the built-in optimizers can only be used within R markdown on R-generated figures. To optimize external images see below.

To optimize your images you need to download optipng or pngquant. On a Windows machine you can download the zip files and you'll need to add the location of the programs to your PATH. On a Mac you can use homebrew to install using:

brew install optipng
brew install pngquant

To use either hook you have two steps, first you add the hook in a chunk with knit_hooks$set and then you optimize a specific image by setting the optipng or pngquant argument within the R chunk. (You could also set optipng or pngquant to run on all R generated images by setting a global chunk option with opts_chunk$set()).

  • For optipng: the level of optimization is specified with optipng = '-oX' where X is a number 0-7 with 7 being the maximum optimization. Note that using -o7 can result in additional processing time.
  • For pngquant: there is a speed/quality tradeoff parameter with pngquant = --speed=1 being the smallest file size. There are other arguments discussed on the wesite.

In our ggplot example, you can see below that the figures without optipng and with maximum optimization look identical, but optipng reduces the file size from 60kb to 17kb, a 3x size reduction. You can use optipng with a self-contained or non-self-contained HTML document.

```{r}
# Set up the hook
library(knitr)
knit_hooks$set(optipng = hook_optipng)
knit_hooks$set(pngquant = hook_pngquant)
```
```{r}
# No optimization, size is 60kb
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk optipngNone

```{r, optipng = '-o7'}
# Maximum optimization, size is 17kb
ggplot(cars, aes(speed, dist)) + geom_point()
```

plot of chunk optipng7

If you have external files you have two options: 1) you can use optipng or pngquant outside of R markdown. Using a terminal cd into your folder of images and run the programs. For our sloth images optipng does not result in significantly smaller images but pngquant reduces the files to approximately 1/3 their original size.

optipng -o7 *.png
pngquant --speed=1 *.png

Alternatively, you can write your own hook to optimize images in a folder. An example provided by the knitr creator Yihui Xie:

optipng = function(dir = '.') {
  files = list.files(dir, '[.]png$', recursive = TRUE, full.names = TRUE)
  for (f in files) system2('optipng', shQuote(f))
}

Bonus knitr and R markdown functionality

More functionality from include_graphics

Add multiple images at a time

The path argument in include_graphics will accept a vector of names. If you have a folder of images and want to add all them to your document at the same time simply point to the folder and voila!

```{r, echo = TRUE, out.width="30%"}
myimages<-list.files("images/", pattern = ".png", full.names = TRUE)
include_graphics(myimages)
```

plot of chunk unnamed-chunk-5plot of chunk unnamed-chunk-5

Load an image from a URL

Super easy – point to an image on the web.

```{r, out.width = "50%"}
include_graphics("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/MC_Drei-Finger-Faultier.jpg/330px-MC_Drei-Finger-Faultier.jpg")
```

plot of chunk unnamed-chunk-6

Additional methods for adding images

We touched on this in an earlier section – here are two additional methods for adding images to your R Markdown document.

Adding images using markdown directly

Here we use markdown syntax to include an image.

![Upside-down sloths are so cute](images/sloth1.png)

Upside-down sloths are so cute

Adding images using HTML

Here we use raw HTML to include an image

<img src="images/sloth1.png" alt="Upside-down sloths are so cute", width = "40%">

Upside-down sloths are so cute

Style your image environment with CSS

You can use CSS to arrange your images, center them, add backgrounds etc. There are several ways to do this depending on whether your changes are quick and local or you want them to apply more broadly.

Use out.extra to apply CSS styles

The knitr package provide the out.extra argument to apply styles to a single chunk.

```{r, out.width="50%", out.extra='style="background-color: #9ecff7; padding:10px; display: inline-block;"', eval=FALSE}
plot(10:1, pch=21, bg="red")
```

plot of chunk unnamed-chunk-7

Add CSS class to R chunk to apply styles

If you want to make significant styling changes you can create your CSS and include your styles either in your R markdown document directly by including the CSS between style tags like this:

<style>
.blue-outline {
   background-color: #9ecff7;
   padding:10px;
   display: inline-block;
}
</style>

or you can include your styles in a separate docucument and reference this file in the YAML at the top. So, for example, you might create a style.css file and then at the top of your R markdown document you would include:

---
title: Tips and tricks for working with images and figures in R Markdown documents
output:
  html_document:
    css: style.css
---

Then to add a class directly to a single chunk, you can create a new hook that adds the opening HTML tags before and then closing tags after. So here we create a hook that allows us to feed our class to a class argument in the chunk.

knitr::knit_hooks$set(class = function(before, options, envir) {
  if(before){
    sprintf("<div class = '%s'>", options$class)
  }else{
    "</div>"
  }
})

Then in your chunk, you can use your new hook like this:

```{r, class = "blue-outline"}
plot(1:10, pch=21, bg="blue")
```

Summary

R Markdown provides an useful framework for including images and figures in reproducible reports. But getting the image sizes and resolutions set correctly can be a challenge. Key considerations include:

  • User-generated images and R-generated figures are handled differently. Not all of the same arguments can be applied to both types.
  • Default settings are taken from both the rmarkdown and knitr packages. If a setting exists in both it will use the rmarkdown default.
  • The dpi argument is mostly not relevant for HTML output (though see the fig.retina argument)
  • The fig.width and fig.height arguments only apply to R-generated images, not to external images
  • The easiest way to change width is probably the out.width argument which applies to both R-generated and external images.
  • To ensure proper display on retina screens you can use the default fig.retina = 2 (or leave this argument blank as it is the default) but beware this will double the physical size of your images potentially leading to slower page loading.
  • The viewable size of external images can be changed with, for example, the out.width argument, but the actually physical size of the image will not change. So if you have a 5 MB image it will be 5 MB in your report even if it is 1 inch wide.
  • If the size of the HTML document matters to you, keep an eye on your figure sizes by checking the 'figure-html' folder that is associated with your report.
  • You can use optipng to help optimize image size. You can apply optipng to R-generated images from within R markdown and apply optipng to external images from the command line.

3 responses

    • Hi Andrew –

      Thanks for your comment and good question! Yes the dimensions of a leaflet map can be controlled using both the out.width/out.height and fig.width/fig.height settings. If no width/height setting is applied to the R chunk the map will assume the default dimensions of 7in (width) by 5in (height).

      Hopefully that answers your question.
      Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *