creating multiple documents by calling values from a list

Hi
I wanted to create 100 invitation letters for a group of 100 people whose name and phone number I have in an excel sheet.
I have written one generic letter and wanted to the put in the name and phone number from the list in the excel sheet and save the 100 pdf files in a folder. What is the best way to do it please? Would R-markdown do the job? say my letter looks like this

Hello XXXX,
Please show up at my place on Mon 4th July,
Best,
Tom

XXXX = names that shall be called from an external excel list

One option would be a parameterized rmarkdown report. You would need 3 files, as follows:

  1. Your Excel file with contact information. For this example, I created an Excel file called contact_info.xlsx that looks like this:

    41%20PM

  2. An rmarkdown file (that I've called letter.rmd) containing the text of your letter. In this file we've declared 2 parameters (params), name and phone (along with their default values, which have to be there to avoid an error). We can then reference these parameters in the main document with params$name and params$phone.

---
output: pdf_document
params:
  name: aaa
  phone: 555-5555
---

Hello `r params$name`,

Please show up at my place on Mon 4th July. I'll call you at `r params$phone` to confirm.

Best,  
Tom
  1. A separate script to render a PDF file for each contact (each row of contact_info.xlsx) by iteratively passing each name and phone number as parameters into letter.rmd. First we read contact_info.xlsx into R, then we run the render function for each name in the Excel file.
library(readxl)
library(tidyverse)
library(rmarkdown)

contacts = read_excel("contact_info.xlsx")

pmap(contacts,
     ~render("letter.rmd", 
             params=list(name=.x, phone=.y),
             output_file=paste0("Letter to ", .x, ".pdf"))
)

I've used pmap from the purrr package for the iteration above, but it can be done with a for loop (or other iterative approach) as well:

for (i in 1:nrow(contacts)) {
  render("letter.rmd", 
         params=list(name=contacts$name[i], phone=contacts$phone[i]),
         output_file=paste0("Letter to ", contacts$name[i], ".pdf"))
}

And here's one of the four output files. This one is Letter to Mal.pdf:

If you want to get fancy, instead of printing out these PDF files and snail-mailing them, you could email them directly from R.

5 Likes

The is also this :package: that is very helpful to send emails prepared programmatically:

If you look ate the example, you'll have the ability to create template body using placeholder like {name} to be replace with the variable you have in R. You even can exectute R code in {Sys.Date()}. From the README:

library(blastula)

email_object <-
  compose_email(
    body = "
  ## Hiya! This is an email message. Exciting Right?
  Enjoy it. And this here image:

  ![The alt text]({img_link} \"The title\")
      
  **Yeah!** I seriously hope that you enjoy this \\
  message and the good vibes it will bring to you \\
  and yours.
  
  Peace out,

  {sender}",
    footer = 
  "Brought to you by Smile AG on {current_date_time}",
    sender = "Mike")
  • {current_date_time} and {img_link} are from the workspace of the current R session
  • {sender} is provided in the function

The README shows a lot of feature.

You can also reproduce the templating feature with a package like whisker and it is sufficient if you don't need the rmarkdown rendering. You can use whisker :package: with any type of text document. Basically, you will read character string into R, these string will have placeholder and whisker syntax for its feature, you'll generate multiple text from these by using whisker rendering then you could write back the text in a file the way you want from R.
For example, assuming the text template has already been loaded in R as a vector of strings here (one by line)

tab <- tibble::tribble(
  ~"Name", ~"Phone",
  "Mal", "737-3648",
  "Zoe", "555-8641"
)

text <- c("Hello {{name}}",
"Please show up at my place on Mon 4th July",
"Best",
"Tom")

# map through your name vector
purrr::map(tab$Name, ~ whisker::whisker.render(text, list(name = .x)))
#> [[1]]
#> [1] "Hello Mal\nPlease show up at my place on Mon 4th July\nBest\nTom"
#> 
#> [[2]]
#> [1] "Hello Zoe\nPlease show up at my place on Mon 4th July\nBest\nTom"

Created on 2018-11-08 by the reprex package (v0.2.1)

You see that whisker.render generate a string with the place holder filled. Check the README and documentation to see how powerful if can be (conditional template and stuff like that).

You have also some tools like glue :package: that could be helpful for rendering multiple text with R variable.

tab <- tibble::tribble(
  ~"Name", ~"Phone",
  "Mal", "737-3648",
  "Zoe", "555-8641"
)

vec <- glue::glue_data(tab, 
"Hello {Name}

  Please show up at my place on Mon 4th July
  
  Best

Tom")

vec
#> Hello Mal
#> 
#>   Please show up at my place on Mon 4th July
#>   
#>   Best
#> 
#> Tom
#> Hello Zoe
#> 
#>   Please show up at my place on Mon 4th July
#>   
#>   Best
#> 
#> Tom

# A character vector of length 2
str(vec)
#>  'glue' chr [1:2] "Hello Mal\n\n  Please show up at my place on Mon 4th July\n  \n  Best\n\nTom" ...
4 Likes