knitr icon indicating copy to clipboard operation
knitr copied to clipboard

Plot in asis output before a `knit_child` gets placed after

Open iagogv3 opened this issue 2 years ago • 11 comments

Hi @yihui!

In the next minimal reproducible example, for j=1, the scatterplot between Speed and Distance is placed before the mtcars kable, as expected, but for j=2 it is placed just before the plot in knit_child. This issue happens only when including the knit_child block. Further, I include 2 different plots for j=1 and j=2, since if it is the same plot it is printed a unique time just before the plot in knit_child (would be this another bug?).

I included the cat('\n\n<!-- -->\n\n') thinking if they could solve the issue, as you suggests in https://bookdown.org/yihui/rmarkdown-cookbook/kable.html#generate-multiple-tables-from-a-for-loop.

Since that does not work, how may I avoid this issue?

Also, may this issue be related to https://github.com/yihui/knitr/issues/2166 ?

Thanks!

Below the minrep and at the end the promises you ask for to fill an issue.

```{r setup, echo=TRUE, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, results = 'asis', 
                      fig.height = 6, fig.width = 10,
                      eval = TRUE, message = FALSE, warning = FALSE)

library(knitr)

Speed <- cars$speed
Distance <- cars$dist

```



```{r pressure, echo=TRUE}
for(i in 1:1){
  for(j in 1:2){
    cat("j = ", j, "\n")
    cat('\n\n<!-- -->\n\n')
    if(j==1) plot(Speed, Distance, panel.first = grid(8, 8), pch = 0, cex = 1.2, col = "blue")
    if(j==2) plot(Speed, Distance, panel.first = lines(stats::lowess(Speed, Distance), lty = "dashed"), pch = 0, cex = 1.2, col = "blue")
    cat('\n\n<!-- -->\n\n')
    kable(mtcars) |> print()
    cat('\n\n<!-- -->\n\n')
  }
  knitr::knit_child(text = c(
    '```{r}',
    'plot(pressure)',
    '```'
  ), envir = environment(), quiet = TRUE) |> cat(sep="\n")
  cat('\n\n<!-- -->\n\n')
}
```

j = 1

mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1

j = 2

mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1

R version 4.3.2 (2023-10-31) Platform: x86_64-pc-linux-gnu (64-bit) Running under: Debian GNU/Linux 12 (bookworm)

Locale: LC_CTYPE=en_GB.UTF-8 LC_NUMERIC=C
LC_TIME=en_GB.UTF-8 LC_COLLATE=en_GB.UTF-8
LC_MONETARY=en_GB.UTF-8 LC_MESSAGES=en_GB.UTF-8
LC_PAPER=en_GB.UTF-8 LC_NAME=C
LC_ADDRESS=C LC_TELEPHONE=C
LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C

Package version: evaluate_0.23 graphics_4.3.2 grDevices_4.3.2 highr_0.10
knitr_1.45 methods_4.3.2 stats_4.3.2 tools_4.3.2
utils_4.3.2 xfun_0.41 yaml_2.3.8

Created on 2023-12-20 with reprex v2.0.2


By filing an issue to this repo, I promise that

  • [x] I have fully read the issue guide at https://yihui.org/issue/.
  • [x] I have provided the necessary information about my issue.
    • If I'm asking a question, I have already asked it on Stack Overflow or RStudio Community, waited for at least 24 hours, and included a link to my question there.
    • If I'm filing a bug report, I have included a minimal, self-contained, and reproducible example, and have also included xfun::session_info('knitr'). I have upgraded all my packages to their latest versions (e.g., R, RStudio, and R packages), and also tried the development version: remotes::install_github('yihui/knitr').
    • If I have posted the same issue elsewhere, I have also mentioned it in this issue.
  • [x] I have learned the Github Markdown syntax, and formatted my issue correctly.

I understand that my issue may be closed if I don't fulfill my promises.

iagogv3 avatar Dec 20 '23 14:12 iagogv3

Not sure from which data Distance or Speed comes from to reproduce, but several comments.

Instead of for loop with cat(), you should consider going all knit_child way, and generate the all md content you want to iterate on as a child using knit_expand() or directly text content in knit_child with inline R code

See the others recipes in the cookbook about that:

  • https://bookdown.org/yihui/rmarkdown-cookbook/knit-expand.html
  • https://bookdown.org/yihui/rmarkdown-cookbook/child-document.html

This will allow you to add your plots inside a chunk, with right conditional. That way each plot will be differently handle and correctly by knitr.

You can't just cat() plot, so having it process by knit_child() could be rather important.

I let you try a version. I can show you if you share with the data.

cderv avatar Dec 20 '23 15:12 cderv

@cderv Sorry because the minrep was not complete. Now I updated including data source.

iagogv3 avatar Dec 20 '23 16:12 iagogv3

Can you format correctly please ? https://yihui.org/issue/#please-format-your-issue-correctly

Also you can try with what I shared. I believe it will make your intention works

cderv avatar Dec 20 '23 16:12 cderv

@cderv Yes. I updated the format again. Sorry for the inconvenience.

Going to matter, I do not understand how should I do. https://bookdown.org/yihui/rmarkdown-cookbook/child-document.html with knit_child is what I am already using. How should be the exemple adapted to your proposal?

Thanks!

iagogv3 avatar Dec 20 '23 23:12 iagogv3

Several examples

directly creating text to knit as a child

This is close to what you are doing, but instead, you are just generating the child content as a whole as a vector. Not using cat() and letting knit_child() to all the printing when knitting

---
title: test
output: 
  html_document:
    keep_md: true
---

```{r setup, echo=TRUE, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, results = 'asis', 
                      fig.height = 6, fig.width = 10,
                      eval = TRUE, message = FALSE, warning = FALSE)
library(knitr)
Speed <- cars$speed
Distance <- cars$dist
```

```{r echo = FALSE, results='asis'}
content <- c()
for (i in 1:1) {
  for (j in 1:2) {
    content <- c(
      content,
      sprintf("j = %d", j),
      "\n\n",
      "```{r, echo = FALSE}",
      if (j == 1) 'plot(Speed, Distance, panel.first = grid(8, 8), pch = 0, cex = 1.2, col = "blue")',
      if (j == 2) 'plot(Speed, Distance, panel.first = lines(stats::lowess(Speed, Distance), lty = "dashed"), pch = 0, cex = 1.2, col = "blue")',
      "```",
      "\n\n",
      "```{r, echo = FALSE}",
      "kable(mtcars)",
      "```",
      "\n\n"
    )
  }
  content <- c(
    content,
    c(
    '```{r}',
    'plot(pressure)',
    "```",
    "\n\n"
    )
  )
}
knitr::knit_child(text = content, envir = environment(), quiet = TRUE) |> cat(sep = "\n")
```

Using an external child document

You write the content to use in knit_child in another doc, using inline R code and code chunk as you would in a generic doc, but to access variable from your main document

test.Rmd
---
title: test
output: 
  html_document:
    keep_md: true
---

```{r setup, echo=TRUE, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, results = 'asis', 
                      fig.height = 6, fig.width = 10,
                      eval = TRUE, message = FALSE, warning = FALSE)
library(knitr)
Speed <- cars$speed
Distance <- cars$dist
```

```{r echo = FALSE, results='asis'}
res <- lapply(1:2, function(j) {
  knit_child("_child.Rmd", envir = environment(), quiet = TRUE)
})
cat(unlist(res), sep = '\n')
```

```{r}
kable(mtcars)
```

```{r}
plot(pressure)
```
_child.Rmd

j = `r j`

```{r, eval = j == 1, echo = j == 1}
plot(Speed, Distance, panel.first = grid(8, 8), pch = 0, cex = 1.2, col = "blue")
```

```{r, eval = j == 2, echo = j == 2}
plot(Speed, Distance, panel.first = lines(stats::lowess(Speed, Distance), lty = "dashed"), pch = 0, cex = 1.2, col = "blue")
```

Same but using knit_expand() or another template function to fill a child file

You write the content to knit_child in an external file, using templating expansion to fill the value you need to be variable

test.Rmd
---
title: test
output: 
  html_document:
    keep_md: true
---

```{r setup, echo=TRUE, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, results = 'asis', 
                      fig.height = 6, fig.width = 10,
                      eval = TRUE, message = FALSE, warning = FALSE)
library(knitr)
Speed <- cars$speed
Distance <- cars$dist
```

```{r echo = FALSE, results='asis'}
res <- lapply(1:2, function(j) {
  knit_child("_child.Rmd", envir = environment(), quiet = TRUE)
})
cat(unlist(res), sep = '\n')
```

```{r}
kable(mtcars)
```

```{r}
plot(pressure)
```
Details

j = {{j}}

```{r, eval = {{ j == 1 }}, echo = {{ j == 1 }} }
plot(Speed, Distance, panel.first = grid(8, 8), pch = 0, cex = 1.2, col = "blue")
```

```{r, eval = {{ j == 2 }}, echo = {{ j == 2 }} }
plot(Speed, Distance, panel.first = lines(stats::lowess(Speed, Distance), lty = "dashed"), pch = 0, cex = 1.2, col = "blue")
```

Other templating tools like brew, whisker , even glue could be used with same purpose.

Hope it helps see what you can do to avoid cat() and print() mixing text and R object output

cderv avatar Dec 21 '23 08:12 cderv

@cderv Wow! That is impressive.

Finally I could get what I wanted using an external child document and splitting code in distinct chunks there.

Thank you for your help.

I keep the issue open as I believe the behaviour reported is yet a bug. Feel free to close it if that behaviour is expected (and already documented?)

iagogv3 avatar Dec 21 '23 09:12 iagogv3

It is tricky as issue because you are missing

cat() of raw markdow, and print() of plots and kable(), without any results = "asis" set is quite specific, and does not seem correct usage. So I don't know if this is a bug or not.

It would require to minimized the example I believe.

@yihui do you think there is something to look into ?

cderv avatar Dec 21 '23 10:12 cderv

Actually results = 'asis' is specified by default through knitr::opts_chunk$set, and I only use cat for knit_child and the separator cat('\n\n<!-- -->\n\n'), which I would not use if they were unneeded.

iagogv3 avatar Dec 21 '23 11:12 iagogv3

Actually results = 'asis' is specified by default through knitr::opts_chunk$set,

Oh that is why. Ok thanks for clarification !

cderv avatar Dec 21 '23 11:12 cderv

A smaller reprex with knit_child()

---
title: test
output: 
  html_document:
    keep_md: true
---

```{r pressure, results='asis'}
plot(cars$speed, cars$dist, panel.first = grid(8, 8), pch = 0, cex = 1.2, col = "blue")

knitr::kable(head(cars))

knitr::knit_child(text = c(
    '```{r}',
    'plot(pressure)',
    '```'
), envir = environment(), quiet = TRUE) |> cat(sep="\n")
```

image

Which do no happen without

---
title: test
output: 
  html_document:
    keep_md: true
---

```{r pressure, results='asis'}
plot(cars$speed, cars$dist, panel.first = grid(8, 8), pch = 0, cex = 1.2, col = "blue")

knitr::kable(head(cars))

plot(pressure)
```

image

So probably something to look into 🤔

cderv avatar Dec 21 '23 11:12 cderv

This does seem to be a complicated bug. I haven't figured out the reason after some investigation. Sorry.

yihui avatar Dec 22 '23 03:12 yihui