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
- Default settings for including images and figures in R Markdown
- Use
fig.width
andfig.height
for R-generated figures only - Arguments
out.width
andout.height
apply to both existing images and R-generated figures - Use
dpi
to change the resolution of images and figures - The
fig.retina
argument is a resolution multiplier - Optimize R-generated images with
optipng
orpngquant
- Bonus
knitr
and R markdown functionality - Summary
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.
- Use the
dim
function: get the dimensions of the image - 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
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.
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
).
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
anddpi
defaults.
```{r} # All defaults include_graphics(img1_path) ```
```{r} # All defaults ggplot(cars, aes(speed, dist)) + geom_point() ```
Use fig.width
and fig.height
for R-generated figures only
- Default is
fig.width = 7
andfig.height = 5
(in inches, though actual width will depend on screen resolution). Remember that these settings will default tormarkdown
values, notknitr
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() ```
```{r, fig.width = 4} # Bigger fig.width ggplot(cars, aes(speed, dist)) + geom_point() ```
```{r, fig.width = 2} # Small fig.width include_graphics(img1_path) ```
```{r, fig.width = 4} # Bigger fig.width include_graphics(img1_path) ```
Arguments out.width
and out.height
apply to both existing images and R-generated figures
- Default is
out.width = NULL
andout.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) ```
```{r out.width = "50%"} include_graphics(img1_path) ```
```{r out.width = "20%"} ggplot(cars, aes(speed, dist)) + geom_point() ```
```{r out.width = "50%"} ggplot(cars, aes(speed, dist)) + geom_point() ```
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() ```
```{r dpi = 200} # Higher dpi ggplot(cars, aes(speed, dist)) + geom_point() ```
```{r dpi = 72} include_graphics(img1_path) ```
```{r dpi = 200} include_graphics(img1_path) ```
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 defaultfig.retina = 2
setting. These figures will be twice as dense and thus twice the physical size as figures withfig.retina = 1
orfig.retina = NULL
. In the case of our ggplot, usingfig.retina = 1
orfig.retina = NULL
results in an image that is 25kb while not specifyingfig.retina
or using the defaultfig.retina = 2
results in a file that is 60kb.
```{r} # No fig.retina (using default fig.retina = 2) include_graphics(img1_path) ```
```{r, fig.retina = 1} include_graphics(img1_path) ```
```{r} # No fig.retina (using default fig.retina = 2) # This file is 60kb ggplot(cars, aes(speed, dist)) + geom_point() ```
```{r, fig.retina = 1} # This file is 25kb ggplot(cars, aes(speed, dist)) + geom_point() ```
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'
whereX
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() ```
```{r, optipng = '-o7'} # Maximum optimization, size is 17kb ggplot(cars, aes(speed, dist)) + geom_point() ```
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) ```
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") ```
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)
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%">
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") ```
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
andknitr
packages. If a setting exists in both it will use thermarkdown
default. - The
dpi
argument is mostly not relevant for HTML output (though see thefig.retina
argument) - The
fig.width
andfig.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.
Great coverage. have you tried with leaflet. At first blush cannot manipulate dimensions
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!
GOLD!!!!
Many thanks for the post. Clarified all about images on Shiny / flexdashboard to me.
Great post! As a quite advanced Shiny / Rmarkdown user I found it quite refreshing.
And class argument in the chunk function is a life saver for me! Many thanks!
I found the “Use out.extra to apply CSS styles” method for adding a line around an image really helpful. I tried the method but it didn’t work for me. I didn’t get an error but no line was added either. I didn’t spend much time looking into it though. Thanks for a great resource!
I thought maybe this was because there had been updates to R markdown/knitr, but I just tried again and it works for me. Are you using eval = TRUE (I see that I have eval = FALSE).
Thanks for you clear presentation of all of this.
I’m also not able to get the line to appear around my image. The rest of your suggestions have worked great. I’m outputing to PDF but also trying html and it didn’t work there either.
“`{r tutor, out.width = “40%”, out.align = “right”, fig.extra=’style=”background-color: #9ecff7; padding:10px; display: inline-block;”‘, eval=TRUE}
include_graphics(“AC-Ciscka Tutoring at Dr Oscar-Loya.JPG”)
“`
Thank you very much for the article, it’ great help at flexdashboard with images.
Tamás
When I run knitr to make a word document, it also outputs all the graphs in another folder. It labels them 1 to 138. Is there a way to have knitr label them as their site ID? I can get the site ID to show up in the title for the graph, but I’m not sure how I can get that imported to the file name for the graph.
Thanks!
Are your R chunks named? I think that will do what you want (but not positive). Let me know.
Thanks Hollie! Hope to see you again at the rstudio conference. 🙂
Thanks Will! Yes, looking forward to it 🙂
you are truly a just right webmaster. The site loading speed is amazing.
It kind of feels that you are doing any unique trick.
Furthermore, The contents are masterwork. you’ve performed a fantastic task on this topic!