yihui / knitr

A general-purpose tool for dynamic report generation in R
https://yihui.org/knitr/
2.36k stars 873 forks source link

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

Open iago-pssjd opened 6 months ago

iago-pssjd commented 6 months ago

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
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

<!-- -->

![](https://i.imgur.com/UGcbxXD.png)<!-- -->

<!-- -->

|                     |  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 |

<!-- -->

![](https://i.imgur.com/qxQ3ews.png)<!-- -->
![](https://i.imgur.com/qEMGsW4.png)<!-- -->

<!-- -->

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

<sup>Created on 2023-12-20 with [reprex v2.0.2](https://reprex.tidyverse.org)</sup>

---

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.
cderv commented 6 months ago

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:

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.

iago-pssjd commented 6 months ago

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

cderv commented 6 months ago

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

iago-pssjd commented 6 months ago

@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!

cderv commented 6 months ago

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
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

<details>
<summary>test.Rmd</summary>

````markdown
---
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
res <- lapply(1:2, function(j) {
  knit_child("_child.Rmd", envir = environment(), quiet = TRUE)
})
cat(unlist(res), sep = '\n')
kable(mtcars)
plot(pressure)

</details>

<details>
<summary>_child.Rmd</summary>

````markdown

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")
plot(Speed, Distance, panel.first = lines(stats::lowess(Speed, Distance), lty = "dashed"), pch = 0, cex = 1.2, col = "blue")

</details>

## 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

<details>
<summary>test.Rmd</summary>

````markdown
---
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
res <- lapply(1:2, function(j) {
  knit_child("_child.Rmd", envir = environment(), quiet = TRUE)
})
cat(unlist(res), sep = '\n')
kable(mtcars)
plot(pressure)

</details>

<details>
<summary>Details</summary>

````markdown

j = {{j}}

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


</details>

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 
iago-pssjd commented 6 months ago

@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?)

cderv commented 6 months ago

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 ?

iago-pssjd commented 6 months ago

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.

cderv commented 6 months ago

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

Oh that is why. Ok thanks for clarification !

cderv commented 6 months ago

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](https://github.com/yihui/knitr/assets/6791940/f5007d3f-3536-4a91-8eef-ba2b34fd09e1)

Which do no happen without 

````markdown
---
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](https://github.com/yihui/knitr/assets/6791940/2442f912-4913-43a1-8f48-be1c51ad038e)

So probably something to look into 🤔 
yihui commented 6 months ago

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