Rmarkdown: Explicitly specify the figure size of a plot within a chunk
Asked Answered
W

4

7

I'm using RMarkdown to functionally create a document using results = 'asis' with a purrr::map. There are multiple plots that come out of the chunk on each purrr iteration. Most of them are the same size, and can be set using the chunk options for figure size. However one or two need to have a different size. It is not possible to separate the code into different chunks due to the way the loop/map is set up.

The closest I've found is http://michaeljw.com/blog/post/subchunkify/, however when I use this on the plot that needs different sizing, it causes the first iteration's plots that were output using the print() function to be recycled in the subchunkify's plots location.

Is there a different, less hacky way to do this? Or is there something obvious in the subchunkify code that would be fixable?

Here is subchunkify():

subchunkify <- function(g, fig_height=7, fig_width=5) {
  g_deparsed <- paste0(deparse(
    function() {g}
  ), collapse = '')

  sub_chunk <- paste0("
  `","``{r sub_chunk_", floor(runif(1) * 10000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", echo=FALSE}",
  "\n(", 
    g_deparsed
    , ")()",
  "\n`","``
  ")

  cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
}
Warfeld answered 5/5, 2020 at 18:51 Comment(0)
W
1

So I haven't found an alternative to subchunkify(), however I did solve the issue with it reusing the same plots on each loop iteration (though I haven't dug into why it was yet).

I added an id argument to subchunkify() and included it in the file name, and then within my loop/map I created an id value that would be a combination of variables within each iteration that would be unique for each one.

subchunkify <- function(g, fig_height=7, fig_width=5, id = NULL) {
  g_deparsed <- paste0(deparse(
    function() {g}
  ), collapse = '')

  sub_chunk <- paste0("
  `","``{r sub_chunk_", id, "_", floor(runif(1) * 10000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", echo=FALSE}",
  "\n(", 
    g_deparsed
    , ")()",
  "\n`","``
  ")

  cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
}

So I'm not sure why the runif in subchunkify was failing to result in distinct file names on each iteration. My suspicion is that it has something to do with how knitr caching works. I noticed that if a subsequent iteration of my loop ended up going through the same conditional chain to produce graph A, then graph A would be reused everywhere that the condition chain matched. However if an iteration went off on a different conditional branch to produce graph B, it would correctly generate a new graph. (However then graph B would be reused in all places with the same conditional branch ending).

This still doesn't explain why me introducing a unique file name with id works, but using runif doesn't since in both cases the file name should be unique, so this is only a guess.

So I guess if anyone else is having problems, I have a solution here but not an explanation. Very unsatisfying but does the trick!

Warfeld answered 6/5, 2020 at 15:37 Comment(0)
C
4

You can create a list of all of the specs for the plots then use purrr::pwalk:

```{r, echo = FALSE, results = 'asis'}
library(ggplot2)
library(purrr)
plots <- map(1:3, ~ggplot(mtcars, aes(wt, mpg)) + geom_point())
specs <- list(plots, fig_height = 1.5, fig_width = list(2, 3, 4))
pwalk(specs, subchunkify)
```

enter image description here

Castroprauxel answered 5/5, 2020 at 19:28 Comment(1)
That's not the issue. The issue is that despite the runif(), the files seem to end up with the same file name on each iteration of the loop.Warfeld
P
2

This might be too late but I want to share my way hacking through the reuse-plot-issue using subchunkify().

The main idea of subchunkify() is having the plot embedded inside pseudo-subchunk. Each pseudo-subchunk need a unique name to be correctly referenced when knitting the final document. Subchunkify() utilized random-number generator - runif() to obtain unique pname, which works most of the time except dealing with loops or complicated markdown blocks.

Based on my observation the cause of reuse-plot-issue is locked random number seed. I suspected that knitting process mistakenly locks seed - like set.seed() in complicated markdown structures, which led to identical random number lists obtained from runif(), finally having same plots referenced at multiple locations.

Adding a suffix id could fix this problem as it preserve unique subchunk names. Another hacky way is to unlock random number seed every time subchunkify() is needed.

subchunkify <- function(g, fig_height=7, fig_width=5, id = NULL) {
  rm(.Random.seed, envir = globalenv()) # to remove locked seed
  g_deparsed <- paste0(deparse(function() {g}), collapse = '')
  sub_chunk <- paste0("
  `","``{r sub_chunk_", id, "_", floor(runif(1) * 100000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", echo=FALSE}",
    "\n(", 
  g_deparsed
  , ")()",
  "\n`","``
  ")

  cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
}

rm(.Random.seed, envir = globalenv()) is the only line of code added. For me this quick fix works like a charm.

By the way I would also encourage to have more digits in random number. runif() by chance can output same number with enough iterations. With floor(runif(1) * 10000) used for 50 embedded plots in a single markdown document, it actually have ~10% chance to have at least one collison. Do floor(runif(1) * 1000000) instead of floor(runif(1) * 10000) to decrease chance of accidentally having random number collision.

Philippians answered 13/4, 2022 at 3:55 Comment(0)
W
1

So I haven't found an alternative to subchunkify(), however I did solve the issue with it reusing the same plots on each loop iteration (though I haven't dug into why it was yet).

I added an id argument to subchunkify() and included it in the file name, and then within my loop/map I created an id value that would be a combination of variables within each iteration that would be unique for each one.

subchunkify <- function(g, fig_height=7, fig_width=5, id = NULL) {
  g_deparsed <- paste0(deparse(
    function() {g}
  ), collapse = '')

  sub_chunk <- paste0("
  `","``{r sub_chunk_", id, "_", floor(runif(1) * 10000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", echo=FALSE}",
  "\n(", 
    g_deparsed
    , ")()",
  "\n`","``
  ")

  cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
}

So I'm not sure why the runif in subchunkify was failing to result in distinct file names on each iteration. My suspicion is that it has something to do with how knitr caching works. I noticed that if a subsequent iteration of my loop ended up going through the same conditional chain to produce graph A, then graph A would be reused everywhere that the condition chain matched. However if an iteration went off on a different conditional branch to produce graph B, it would correctly generate a new graph. (However then graph B would be reused in all places with the same conditional branch ending).

This still doesn't explain why me introducing a unique file name with id works, but using runif doesn't since in both cases the file name should be unique, so this is only a guess.

So I guess if anyone else is having problems, I have a solution here but not an explanation. Very unsatisfying but does the trick!

Warfeld answered 6/5, 2020 at 15:37 Comment(0)
D
0

The answer of jzadra is fine but the ID is random. In the same spirit, I fixed this issue using the digest library, computing a hash from the object to be subchunkified.

subchunkify = function(g, fig_height, fig_width, chunk_name = NULL) {
  g_deparsed = paste0(deparse(
    function() {
      g
    }
  ), collapse = "")

  if (is.null(chunk_name)) { # Generate a unique ID for g
    chunk_name = digest::digest(g)
  }

  sub_chunk = paste0(
    "<center>\n\n\`\`\`{r ", chunk_name,
    ", ", "fig.height=",
    fig_height,
    ", fig.width=",
    fig_width,
    ", echo=FALSE}",
    "\n(",
    g_deparsed,
    ")()",
    "\n\`\`\`\n\n</center>"
  )

  cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))
}

The resulting ID is then deterministic. This would still raise an error if subchunkify is called twice from the same object but I guess this is reasonable.

Desinence answered 7/8, 2023 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.