How to get console output and plot side by side in a R Notebook?

Solution 1:

It's not perfect. For example, I didn't add in handlers for screen size. However, I can. You would have to let me know what you're looking for, though.

This uses Javascript in R Markdown. The engine for this is built-in. However, I added the code for you to check it (should you so choose): names(knitr::knit_engines$get()).

Also, in the setup chunk, I added options(width = 75). This will affect all chunk outputs. You can change this to make it a chunk-specific option. However, the default is 80, so you probably won't notice a difference. I did this because for two of the three groups, Species wrapped to the next row. However, for versicolor, it was different. This is part of the enforcement for uniformity.

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

# confirm engine for 'js' (it was #37 for me)
names(knitr::knit_engines$get())

# set the output width for chunks' render
# this is to keep the summaries even (versicolor was doing its own thing)
options(width = 75)
library(tidyverse)
```

The styles are not in a chunk. This goes between the setup and the next chunk.

<style>
.setupCols {
  display:flex;
  flex-direction:row;
  width: 100%;
}
.setupCols p{
  display:flex;
  flex-direction: column;
  width: 45%;
}

.setupCols pre {
  display:flex;
  flex-direction: column;
  width: 55%
}
.setupCols pre code {
  font-size: 85%;
}
</style>

Next is some code before your mapply call and code after.

<div class="setupCols">

```{r graphOne}
mapply(FUN = function(.x) {
  plot(.x)
  summary(.x)
}, split(iris, iris$Species), SIMPLIFY = FALSE)
```

</div>

At any point in time after this chunk, you will add a styling chunk. If you want it to apply more than once, move this chunk to the end. If you only want it to apply to the chunk (I named) graphOne, then make it the next chunk.

```{r styler,results='asis',engine='js'}

// search for class and tags
elem = document.querySelector('div.setupCols > pre > code');
// remove hashtags
elem.innerHTML = elem.innerHTML.replace(/#{2}/g, '');
// add newlines between summaries
elem.innerHTML = elem.innerHTML.replace(/\s{9}\n/g, '<br /><br />')

```

If you run this chunk inline, you will not get any output. However, you will see this output when you knit.

I also added some text here, but this is what it looks like:

enter image description here

If you wanted to see what the Javascript is doing, you could add eval = F and knit. This is what it will look like:

enter image description here

Let me know if I missed something or if you have any questions.

Solution 2:

Efficient, but not exact

For the example setup, I would recommend splitting up the operations to easily fit them side-by-side using pandoc syntax for multiple columns. In this way, we can just call the specifics we want.

---
title: "R Notebook"
output:
  html_document:
---

```{r, echo = F}
il <- split(iris, iris$Species)
```

:::: {.columns}

::: {.column width="60%"}
```{r, echo = F, results = F}
lapply(il, plot)
```
:::

::: {.column width="40%"}
```{r, echo = F}
lapply(il, summary)
```
:::

::::

This gives an output like:

enter image description here

Inefficient, but exact

However, I understand you might need exactly your results as stated. I don't know how to split up the console output and plots in a single call. What we can do is manipulate the RMarkdown output so it appears they are coming from a single call. Please note this is inefficient and I recommend breaking up the function to produce the plots and summary output into separate functions to use like the first example.

---
title: "R Notebook"
output:
  html_document:
---

```{r, results = F, fig.show = "hide"}
mapply(FUN = function(.x) {
  plot(.x)
  summary(.x)
}, split(iris, iris$Species), SIMPLIFY = FALSE)
```

:::: {.columns}

::: {.column width="60%"}
```{r, echo = F, results = F}
mapply(FUN = function(.x) {
  plot(.x)
  summary(.x)
}, split(iris, iris$Species), SIMPLIFY = FALSE)
```
:::

::: {.column width="40%"}
```{r, echo = F, fig.show = "hide"}
mapply(FUN = function(.x) {
  plot(.x)
  summary(.x)
}, split(iris, iris$Species), SIMPLIFY = FALSE)
```
:::

::::

The results then look very close to your desired output: enter image description here

We do it by just running the same function (imagining it's the one you mentioned), and in the first case hiding the plot and text outputs, but keeping the code, then just keeping the plots in the left column and only the text outputs in the right column.

Hope this has helped.

Solution 3:

@Kat's answer seems very efficient and it feels like it should be the way to go if you have enough HTML and Javascript knowledge. However this is not my case so for now I stick with build-in knitr features.

I wanted to propose it as an edit to @caldwellst's answer but it told me "edit queue is full". So here is an idea from what he proposed and R markdown cookbook - section 16.4.

The principle is to call inside the mapply(FUN = ) another Rmarkdown script from another .Rmd file or a vector with the elements needed to make such a file. By doing so, R notebook is lost, "classic" .Rmd is created and is is not possible to see the output of the chunk under it. In fact, if you try to run the chunk, it sends the following error: Error in setwd(opts_knit$get("output.dir")) : character argument expected. However, it produces a HTML file if you click on knit.

Here is the .Rmd file:

---
title: "Example"
output:
  html_document
---

```{r, echo=FALSE, results='asis'}
out <- mapply(function(x) {
  knitr::knit_child(text = c(
    '',
    ':::: {.columns}',
    '',
    '::: {.column width="40%"}',
    '```{r}',
    'plot(x)',
    '```',
    ':::',
    '',
    '::: {.column width="60%"}',
    '```{r}',
    'summary(x)',
    '```',
    ':::',
    '',
    '::::'
  ), envir = environment(), quiet = TRUE)
}, split(iris, iris$Species), SIMPLIFY = TRUE)

cat(unlist(out), sep = '\n')
```

And the output: enter image description here

However, this seems very "hacky" to me and writting the c() vector for the text argument is prone to errors.