22

I need to produce a report that is composed of several sections, all sections look similar, with only some differences in data. The number of sections is also dependent on the data. What I ultimately want to have is something like this:

```{r}
  section_names = c("A","B","C")
  section_data = c(13,14,16)
```

# some looping mechanism here with variable i

This is section `r section_names[i]`

This section's data is `r section_data[i]`

#more things go here for the section

#end of loop should go here

The result should be a single html/document with all the sections one after the other.

Can you point me to a way for producing such an Rmd file with the loop?

Ideally I would have hoped to see something like in PHP:

<$php for(i=0;i<10;i++) { ?>
   ## some html template + code chunks here
<$php } ?>
CL.
  • 14,577
  • 5
  • 46
  • 73
amit
  • 3,332
  • 6
  • 24
  • 32
  • What do you mean with 'section' in this context? Is it just a block of text to be inserted? Or one chunk for each name? – Heroka Apr 17 '16 at 11:47
  • Possible duplicate of [Using loops with knitr to produce multiple pdf reports... need a little help to get me over the hump](http://stackoverflow.com/questions/15396755/using-loops-with-knitr-to-produce-multiple-pdf-reports-need-a-little-help-to) – Ben Apr 17 '16 at 11:58
  • @Ben Not quite. in the post you refer to the question was about producing multiple pdf documents. Here I am interested in basing the structure of the same report on the data, that is, to produce as many sections there need to be (based on the data). similar, but not duplicate. – amit Apr 18 '16 at 06:03
  • @Heroka Ablock of text, with possible code chunks that need to be "knitted" several times one after the other, each time with slightly different data. I Edited my question to clarify – amit Apr 18 '16 at 06:05

5 Answers5

29

This question is similar to that one, although it is LateX/RNW based. Besides, this answer demonstrates how to generate a rmarkdown document dynamically. However, neither of the questions is a exact duplicate of this one.

Basically, there are two mental steps to take:

  • Figure out the markdown markup needed per section. This could be something along the lines of

    ## This is section <section_name>
    Section data is `<section_data>`.
    Additional section text is: <section_text>.
    
  • Write R code that generates this markup, replacing the placeholders with the appropriate values.

For step 2, using sprintf is a natural candidate to combine static and dynamic text. Don't forget to use the chunk options results = "asis" to prevent knitr from adding formatting to your output and use cat (instead of print) to prevent R from adding additional stuff like quotes and element numbers.

I changed the input data structure a little bit for the sake of clarity (using a data.frame instead of independent vectors section_names and section_data).

```{r echo = FALSE, results = "asis"}
input <- data.frame(
  name = LETTERS[1:4],
  data = runif(n = 4),
  text = replicate(4, paste(sample(x = LETTERS, size = 100, replace = TRUE), collapse = "")),
  stringsAsFactors = FALSE)

template <- "## This is section %s
Section data is `%0.2f`.
Additional section text is: %s.

" # dont't forget the newline

for (i in seq(nrow(input))) {
  current <- input[i, ]
  cat(sprintf(template, current$name, current$data, current$text))
}
```

Output:

This is section A

Section data is 0.83. Additional section text is: PUFTZQFCYJFNENMAAUDPTWIKLBSVKWMJWODFHSPRJRROTVDGNEROBVQPLLMVNPOUUHGVGRPMKAOAOMVYXKMGMUHNYWZGPRAWPYLU.

This is section B

Section data is 0.49. Additional section text is: PFTYCGFSGSMAYSSCZXWLNLDOQEBJYEVSJIYDJPEPSWQBNWJVRUKBTYIUSTOICFKJFEJCWCAYBCQSRTXUDEQLLXCZNPUKNLJIQJXE.

This is section C

Section data is 0.58. Additional section text is: FCJDDDMNLBUSJMCZVSBPYWCKSFJEARBXXFPAGBTKCWKHPEDGYWYTNGLVGQGJAFZRUMNSDCHKTTMGRFNSUZKFLOUGNWHUBNLVMGDB.

This is section D

Section data is 0.52. Additional section text is: YQIXHABFVQUAAYZNWTZXJDISSLTZJJAZOLJMJSXEENFTUOFOTYKDNNUMFDXLJSWZEVDLCLSYCTSMEXFLBVQYRTBEVZLCTEBPUGTT.

Community
  • 1
  • 1
CL.
  • 14,577
  • 5
  • 46
  • 73
  • 1
    This works, of course, but it is really programming the solution, rather than "templating" it. The beauty of Rmarkdown (my own opinion) is its ability to create a simple document template without programming, using programming only to compute data needed for the document. probably a matter of style. Eventually, I've solved the problem in another way. see the answer below. – amit Apr 25 '16 at 14:31
  • @amit I tend to disagree. In your question, the whole content of the document is data. So quite naturally, there is a lot "working with data"/programming and little plain text. But the "templating" part is there, too: see my variable `template` ... Anyways, I'll see your solution soon. – CL. Apr 25 '16 at 14:34
  • 2
    @amit Ok, read your solution. Technically, it is very similar. My `template` is a child document in your case. My `sprintf` is your `knit_child`. – CL. Apr 25 '16 at 14:36
  • I agree about the similarity, except that I am very sensitive to "seperation of concerns" issues, so I don't like my templates to be programmed, but rather to be real documents. again - a matter of taste. Also, while not evident from my simple example, the real use require each section to be rather big (more than 3 sentences, more like 3-4 pages of text, graphs & tables) - so the extra RMD file makes more sense, I think. – amit Apr 25 '16 at 14:41
  • @amit With a more complex template, I completely agree. Just with a simple template, the additional complexity of the child document (another RMD -> MD -> include step) is not worth the separation of concerns advantage, IMHO. – CL. Apr 25 '16 at 14:45
  • It is important to use `cat()` instead of `print()` also sometimes a couple of line breaks `\n\n` are needed before titles so that they appear correctly. – Paul Rougieux Jan 27 '20 at 16:49
  • Doesn't seem to generate a table of contents when used with `toc: true` – dca Dec 24 '20 at 02:18
4

Just sharing the approach I've used eventually.

I wrote a markdown file for the section. prepared the data for each section in the master document, and looped over all the sections I needed, each time calling to knit_child() with the section Rmd.

amit
  • 3,332
  • 6
  • 24
  • 32
  • 5
    It would be great if this answer was reproducible, just as one would expect from any other answer. If you didn't already know how to do this, you wouldn't know what to make of this answer. – Hendy Sep 12 '22 at 17:22
0

I know this is late, but I used this in my code to make numbered sections and it works a treat.

for (k in 1:length(listcsv)){ #Begin Loop at pdf file one and continue until all have been completed
subsection <- paste("5", k, sep = ".")}

this uses the loop number (k) to create the subsection number and then paste it against the section number. This happens to be in section 5, but you could use the same principle to make sections and subsections ad infinitum.

0

A late answer building an example on @amit 's answer about the use of knit_child(). According to knitr documentation it is possible to do something like what was asked by:

  1. Setting output of the chunk to as-is.
  2. Set possible variables accessed in the child document before calling knitr_child(), as once rendered, the environment for the child is the same as the parent. This is the only way I found to kind of "pass parameters" to the child, but maybe there is a more elegant way to do this.
  3. Storing your knitr_child() outputs in a list.
  4. Use cat to print them all right after the other.

I have done this using Quarto but I think it should work just the same, as knitr was built to work on Rmarkdown.

A small reproducible example:

If your child document child.qmd looked like this:

---
tag: "Child document"
title: "Child document"
---

## `r child_section_title`

This section changes title according to what `child_section_title` value
is in the main markdown document.

You could render multiple differently titled chunks on your main .qmd file doing something like:

```{r, results='asis'}
knit_child_with_params <- function(p) {
  child_section_title <- p
  knitr::knit_child(
    "child.qmd",
    envir = environment(),
    quiet = TRUE)
}

# This needs to be declared outside the function
child_section_title <- ""
title_list <- list("Section title 1",
                   "Another title",
                   "Some other thing")

res <- lapply(title_list, knit_child_with_params)

cat(unlist(res), sep = '\n')

Think about keeping your child document chunks unnamed if you are going to do this, otherwise repeated names will make the rendering crash.

cnluzon
  • 1,054
  • 1
  • 11
  • 22
0

I came upon this question, looking to do the same. However I found the answers didn't work.

I generated this which works for my script, taking some of the comments above. It dynamically generates the \subsection headers in LaTeX and neatly produces the subchapters in the final pdf document.

I used the following as chunk header:

{r Automated, echo=FALSE, background=NA, comment=NA, results = 'asis'}


for (i in YourData) {

  # add a subsection title for the LaTeX report
  subsection_title <- paste("\\subsection{ The name of your chapter ", i ,"}")
  cat(subsection_title, "\n")

  # Rest of your code to include in chapter...

}