I’m excited to have the r2pptx package on CRAN! r2pptx is an easy way to use R to make Powerpoint presentations. It can be helpful if you often create slide decks that contain tables, plots, or calculations from R code.

r2pptx is a wrapper around officer. It wouldn’t be possible without officer developer David Gohel’s hard work. Thanks David!

In this post I’ll go through how to make basic and advanced presentations using r2pptx.

Installation

First, you’ll need to install r2pptx. You can do so with

install.packages("r2pptx")
# or you can install the development version from github
# remotes::install_github("mattle24/r2pptx")

We’ll also load the ggplot2, dplyr, and flextable packages for this example. You don’t need any of these to use r2pptx, but you may find them useful.

# install.packages(c("dplyr", "ggplot2", "flextable"))
library(ggplot2)
library(dplyr)
library(flextable)
library(r2pptx)

Simple example

First we need to choose a PowerPoint template. This is a normal .pptx file that has the template we want in Slide Master. By default, r2pptx uses the default template in officer. This seems like a slight variation on the default Microsoft PowerPoint template.

Let’s start by creating a presentation.

presentation <- new_presentation()
print(presentation)
## Presentation with 0 slides.
print(template_path(presentation))
## [1] "/Library/Frameworks/R.framework/Versions/4.1-arm64/Resources/library/officer/template/template.pptx"

We can see this is an empty presentation with the template path set to the default officer template. What’s in this template? We can check using some utility functions.

get_layouts(presentation)
##              layout       master
## 1       Title Slide Office Theme
## 2 Title and Content Office Theme
## 3    Section Header Office Theme
## 4       Two Content Office Theme
## 5        Comparison Office Theme
## 6        Title Only Office Theme
## 7             Blank Office Theme

This template has seven different layouts. That’s good to know, but we still don’t know which layout to use for which purpose. Let’s keep going!

layouts <- get_layouts(presentation)$layout
for (layout in layouts) {
  plot_layout(presentation, layout)
  title(main = layout)
}

Now we can see for each slide layout what options we have to use as placeholders! Knowing this, we can move forward and start constructing our presentation!

In r2pptx syntax, presentations consist of slides which consist of elements. elements represent anything we want to add to the deck. This could be text, plots, images, tables, etc. Anything compatible with the officer package works here!

We start out making a title slide with a title and subtitle. We give elements for the title and subtitle as arguments to the new_slide() function. And we give “Title Slide” as the layout name, per the information from above.

title_slide <- new_slide(
  layout = "Title Slide",
  elements = list(
    new_element(key = "Title 1", value = "The title"),
    new_element(key = "Subtitle 2", value = "The subtitle")  
  )
)
print(title_slide)
## Slide with layout `Title Slide` and 2 elements:
## -  character
## -  character

elements have two arguments. The key tells R where to place the element. Currently, this only supports a placeholder label. Placeholders are the objects in a PowerPoint slide layout (as seen above). Placeholder labels are the name of placeholders. Future development may allow other location specifications, such as giving x and y positions.

Now we add the title slide to our presentation.

presentation <- presentation + title_slide
print(presentation)
## Presentation with 1 slides.

Easy!

Let’s add another slide with a ggplot. This time, we’ll add the elements to the slide rather than initializing the slide with elements.

# create the slide object
plot_slide <- new_slide(layout = "Title and Content")
print(plot_slide)
## Slide with layout `Title and Content` and 0 elements:
## -
# create an element for the title element
title_element <- new_element(key = "Title 1", value = "A plot")

# create a ggplot
cars_plot <- ggplot(mtcars, aes(x = mpg, y = cyl)) + 
  geom_point()
# create an element for the plot
plot_element <- new_element(key = "Content Placeholder 2", value = cars_plot)

plot_slide <- plot_slide +
  title_element +
  plot_element
print(plot_slide)
## Slide with layout `Title and Content` and 2 elements:
## -  character
## -  gg
presentation <- presentation +
  plot_slide
print(presentation)
## Presentation with 2 slides.

And then we can write the presentation to PowerPoint.

ppt_path <- tempfile(fileext = ".pptx")
write_pptx(presentation, ppt_path)
if (interactive()) system(paste("open", ppt_path))

And you have a slide deck! Congrats! The deck should look like:

Title slide Slide with plot

Advanced example

In a more advanced example, we may want to produce many similar slides. For example, we’ll use a dataset which has metrics on US Senators. In this example, we’d like to make a presentation that has one slide per state. Each state’s slide will have the same layout, but data specific to the given state.

G. Elliott Morris has an R package called politicaldata which we will use to get DW Nominate scores for US Senators. DW Nominate is an academic project that calculates a score for each senator ranging from -1 to 1. A score of -1 indicates the senator is very far to the left, and a score of 1 indicates the senator is very far to the right. A score of 0 indicates the senator is in the middle of the left / right spectrum. There is more nuance, but we will ignore that in this example.

Let’s install the politicaldata package, which contains the data we’ll use.

remotes::install_github("elliottmorris/politicaldata")

And now we load the data into our R session.

senate_dw_df <- politicaldata::get_senate_nominate()
# first congress met in 1789, new congress every two years after that
senate_dw_df$year <- ((senate_dw_df$congress - 1) * 2) + 1789

We get a list of states to iterate over.

states <- sort(unique(senate_dw_df$state_abbrev))

# helpers to go from state abbreviation to state name
state_bridge <- data.frame(
  state_abbrev = state.abb,
  state_name = state.name
)
state_abbrev_to_name <- function(x) {
  state_bridge %>% 
    dplyr::inner_join(data.frame(state_abbrev = x), by = "state_abbrev") %>% 
    dplyr::pull(state_name)
}

I’m making some helper functions to deal with color scales – this is not important to understand.

.transform_0_1 <- function(x, low_pt, high_pt) {
   (x - low_pt) / (high_pt - low_pt)
}

# takes in a DW nomiate value and returns a color. values between 0 and 1 will
# be on a white -> red color scale. values between -1 and 0 will be on a blue ->
# white color scale
color_scale_fn <- function(x) {
  pos_vals <- .transform_0_1(x, 0, 1)
  neg_vals <- .transform_0_1(x, -1, 0)
  pos_colors <- scales::seq_gradient_pal(low = "white", high = "red")(pos_vals)  
  neg_colors <- scales::seq_gradient_pal(low = "blue", high = "white")(neg_vals)
  res <- pos_colors
  res[x < 0] <- neg_colors[x < 0]
  res
}

Now we can loop through each state to make 50 slides. For each state, we’ll make one slide. The slide will have a plot showing how the state’s Senators’ DW Nominate scores have changed over time and a table showing the scores for recent Senators.

# make a slidelist - a new class of object that is a helpful way to hold slides
# that aren't part of a presentation yet
slide_list <- new_slidelist()

for (state in states) {
  state_senate_dw_df <- senate_dw_df %>% 
    dplyr::filter(state_abbrev == !!state)
  
  # make the table
  dw_recent_table <- state_senate_dw_df %>% 
    dplyr::filter(state_abbrev == !!state) %>% 
    dplyr::arrange(-year) %>% 
    dplyr::slice(1:10) %>% 
    dplyr::select(year, bioname, nominate_dim1) %>% 
    flextable() %>% 
    set_formatter(year = as.character) %>% 
    bg(j = 3, bg = color_scale_fn) %>% 
    set_header_labels(
      year = "Year",
      bioname = "Name",
      nominate_dim1 = "DW Nominate (1)"
    ) %>% 
    # placeholder 2 width is 4.416667
    width(j = 2, width = 2.5) %>% 
    width(j = 3, width = 1) %>% 
    width(j = 1, width = 4.416667 - 1 - 2.5) 
  
  # make the time series plot
  dw_time_series_plot <-
    ggplot(state_senate_dw_df, aes(x = year, y = nominate_dim1, fill = nominate_dim1)) +
    geom_hline(yintercept = 0, linetype = "dashed") +
    geom_smooth(method = "loess", formula = y ~ x, color = "gray45") +
    geom_point(pch = 21) +
    scale_fill_gradient2(low = "blue", high = "red", guide = "none") +
    theme_minimal() +
    labs(x = "Year", y = "DW Nominate (Dim1)")

  # make the slide
  state_slide <- new_slide(
    layout = "Two Content",
    elements = list(
      new_element("Title 1", state_abbrev_to_name(state)),
      new_element("Content Placeholder 2", dw_time_series_plot),
      new_element("Content Placeholder 3", dw_recent_table)
    )
  )
  
  # add the slide to the slidelist
  slide_list <- slide_list + state_slide
}

# and now make a presentation and add the slidelist (containing all the slides)
# to the presentation
presentation <- new_presentation() + slide_list

Last, we write the presentation as a PowerPoint file and open it!

ppt_path <- tempfile(fileext = ".pptx")
write_pptx(presentation, ppt_path)
if (interactive()) system(paste("open", ppt_path))

The first few slides should look like:

Slide with a plot and table for Alaska Slide with a plot and table for Alabama

Slide with a plot and table for Arkansas

I hope this has shown the power and flexability of r2pptx! You can find more documentation at the package’s website, linked here.