7

I am using the knitr package for R to produce a LaTeX document combining text with embedded R plots and output.

It is common to write something like this:

We plot y vs x in a scatter plot and add the least squares line:
<<scatterplot>>=
plot(x, y)
fit <- lm(y~x)
abline(fit)
@

which works fine. (For those not familiar with knitr or Sweave, this echos the code and output in a LaTeX verbatim environment and also adds the completed plot as a figure in the LaTeX document.)

But now I would like to write more detailed line-by-line commentary like:

First we plot y vs x with a scatterplot:
<<scatterplot>>=
plot(x, y)
@
Then we regress y on x and add the least squares line to the plot:
<<addline>>=
fit <- lm(y~x)
abline(fit)
@

The problem is that there are now two knitr code chunks for the same plot. The second code chunk addline fails because the plot frame created in the first code chunk scatterplot is not visible to the code in the second code chunk. The plotting window doesn't seem to be persistent from one code chunk to the next.

Is there any way that I can tell knit() to keep the plot window created by plot() active for the second code chunk?

If that is not possible, how else might I achieve LaTeX-style commentary on code lines that add to existing plots?

One Day Later

I can now see that essentially the same question has been asked before, see: How to build a layered plot step by step using grid in knitr? from 2013 and Splitting a plot call over multiple chunks from 2016. Another question from 2013 is also very similar: How to add elements to a plot using a knitr chunk without original markdown output?

Gordon Smyth
  • 663
  • 5
  • 14
  • What do you want the final document to look like? Are you showing the code in the document (i.e., `echo=TRUE`)? Is it just that you want to show and comment on the code and then render the final plot once? Or do you want to first show the plot without the abline and then a second time with the abline? – eipi10 Dec 10 '17 at 08:21
  • @eipi10 I am showing the code (echo=TRUE and eval=TRUE are set to be the defaults). My intention is to include only one complete plot in the document, rather than to repeat it with and without the least square line. The plot will be in a floating environment. I am in fact writing a code workflow article for a journal (F1000Research). – Gordon Smyth Dec 10 '17 at 10:28

2 Answers2

5

You can set knitr::opts_knit$set(global.device = TRUE), which means all code chunks share the same global graphical device. A full example:

\documentclass{article}

\begin{document}
<<setup, include=FALSE>>=
knitr::opts_knit$set(global.device = TRUE)
@

First we plot y vs x with a scatterplot:
<<scatterplot>>=
x = rnorm(10); y = rnorm(10)
plot(x, y)
@
Then we regression y and x and add the least square line to the plot:
<<addline>>=
fit <- lm(y~x)
abline(fit)
@

\end{document}
Yihui Xie
  • 28,913
  • 23
  • 193
  • 419
  • I just realized I had answered similar questions before: https://stackoverflow.com/q/17502050/559676 https://stackoverflow.com/q/37189068/559676 – Yihui Xie Dec 10 '17 at 21:01
  • Thanks, I've accepted your answer. But I cannot find the `global.device` option mentioned either on your knitr web page or in the R package documentation. Have I missed it or is it undocumented? Can I reset `global.device` several times during a document or does it have to be set once for the whole document? – Gordon Smyth Dec 10 '17 at 22:30
  • It is undocumented, and for advanced users only. You can reset it several times. When you need to reset the device, you just close the global device via `dev.off()` in a code chunk. Then in the next code chunk, a new global device will be opened automatically. – Yihui Xie Dec 11 '17 at 03:33
3

You can show code without evaluating it by adding the chunk option eval=FALSE. If you only want to show the final version of the plot with the regression line added, then use eval=FALSE for the first plot(x,y).

Then we add two chunks for the regression line: One is the complete code needed to render the plot, but we don't want to display this code, because we don't want to repeat the plot(x,y) call. So we add a second chunk that we echo, to display the code, but don't evaluate.

---
output: pdf_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

```{r data}
set.seed(10)
x=rnorm(10)
y=rnorm(10)
```

First we plot y vs x with a scatterplot:

```{r scatterplot, eval=FALSE}
plot(x, y)
```

Then we regress y on x and add the least squares line to the plot:

```{r addline, eval=FALSE}
fit <- lm(y~x)
abline(fit)
```

```{r echo=FALSE}
plot(x,y)
fit <- lm(y~x)
abline(fit)
```

Here's what the output document looks like:

enter image description here

eipi10
  • 91,525
  • 24
  • 209
  • 285
  • Thanks. This produces the document as I want, but it is a work-around rather than the solution I was after. I would rather avoid having to repeat the same code twice (once for show and once for evaluation). – Gordon Smyth Dec 10 '17 at 22:14
  • Yes, @YihuiXie's answer is certainly the way to go. – eipi10 Dec 15 '17 at 20:16