2

My question is similar to this SO question but instead of creating a plot, I want to evaluate code chunk. I wanted to know if it's possible to programmatically generate headers and evaluate code chunks following their respective headers? My attempt at this is below.

I have a list of data frames that I want to create a header and evaluate code chunk for each list element. The example below is just to calculate the number of rows of each data frame using nrow().

```{r data}
data("iris","airquality","mtcars")
my_list <- list(iris,airquality,mtcars)
names(my_list) <- c("iris","airquality","mtcars")
```

```{r headers, results = 'asis'}
for (i in seq_along(my_list)) {
  cat('#', names(my_list)[i], '\n')
  cat('```{r}', '\n')

  cat('nrow(mylist[[i]])') #evaluate any other code here

  cat('\n')
  cat('```')
  cat('\n')
}
```

Any insight would be much appreciated!

EJJ
  • 1,474
  • 10
  • 17
  • That seems reasonable on first glance, have you `knit` to markdown and seen what the results look like? You might see just from doing that. – r2evans Sep 19 '18 at 22:08
  • @r2evans i have tried to `knit` the markdown and the headers are fine but the code chunk just says `nrow(mylist[[i]])` and it is not being evaluated – EJJ Sep 19 '18 at 22:54

2 Answers2

2

This behavior is because R cannot find your mylist object within the new environment that is getting created by all the cat calls. Also since mylist[[i]] is an expression we would have to parse and evaluate it to get the final result.

For example -

```{r headers, results = 'asis'}
for (i in seq_along(my_list)) {
  cat('#', names(my_list)[i], '\n')
  cat('```{r}', '\n')
  cat(eval(parse(text = 'i')))

  cat('\n')
  cat('```')
  cat('\n')
}
```

The above code will give a result as follows (this is the result we need)-

# iris 
```{r} 
1
```
# airquality 
```{r} 
2
```
# mtcars 
```{r} 
3
```

But to do that with mylist we would need to somehow make sure that mylist object is within the new environment before it can be evaluated. Also please note that people discourage the use of parse() since it can lead to behaviors such as this. More here - Assigning and removing objects in a loop: eval(parse(paste(

This answer will work for your example but please do consider what I have mentioned and the discussion in the SO question -

```{r data}
data("iris","airquality","mtcars")
my_list <- list(iris,airquality,mtcars)
names(my_list) <- c("iris","airquality","mtcars")
```

```{r headers, results = 'asis'}
for (i in seq_along(my_list)) {
  cat('#', names(my_list)[i], '\n')
  cat('```{r}', '\n')

  # somehow get the data or any other objects in here
  # this works for trivial examples please be carefule before using it on actual code chunks
  cat(eval(parse(text = paste(nrow(my_list[[i]])))))


  cat('\n')
  cat('```')
  cat('\n')
}
```

# iris 
```{r} 
150
```
# airquality 
```{r} 
153
```
# mtcars 
```{r} 
32
```
Suhas Hegde
  • 366
  • 1
  • 6
  • 13
  • Thanks for the explanation - I should go back and give the _Advanced R Environments_ chapter a read. As you suggested, this method would not great due to using `parse`. I've found an alternative method to generate a similar output in this [SO post](https://stackoverflow.com/questions/21729415/generate-dynamic-r-markdown-blocks). – EJJ Sep 20 '18 at 13:10
  • Would you mind posting a small sample of what you end up doing as the answer to this question? I think everyone will be interested in these kind of issues – Suhas Hegde Sep 20 '18 at 14:52
  • I’ll create a sample once I get a chance – EJJ Sep 21 '18 at 22:14
1

Here's one way that I tackle a problem similar to this. I was given the hint here on SO but cannot find the answer ...

Two documents: one parent, one child. Think of the child document as a subroutine that can be called any number of times, and its output is saved (and concatenated to the character vector out). When I'm done in that loop, I use an inline `r code` block.

The main document, the "parent" I guess:

---
output: md_document
---

```{r data}
data("iris","airquality","mtcars")
my_list <- list(iris,airquality,mtcars)
names(my_list) <- c("iris","airquality","mtcars")
```

```{r headers, echo = FALSE, include = FALSE, results = 'asis'}
out <- NULL
for (i in seq_along(my_list)) {
  out <- c(out, knitr::knit_child("somedoc-child.Rmd"))
}
```

`r paste(out, collapse="\n")`

The child document, same directory, called as somedoc-child.Rmd above, called by the parent doc. It sees the environment exactly as the parent leaves it, so my_list and i are seen perfectly.

# `r names(my_list[i])`

```{r, results="asis"}
nrow(my_list[[i]])
```

When I knit to markdown, I get:

    data("iris","airquality","mtcars")
    my_list <- list(iris,airquality,mtcars)
    names(my_list) <- c("iris","airquality","mtcars")

iris
====

    nrow(my_list[[i]])

\[1\] 150

airquality
==========

    nrow(my_list[[i]])

\[1\] 153

mtcars
======

    nrow(my_list[[i]])

\[1\] 32
r2evans
  • 141,215
  • 6
  • 77
  • 149
  • 1
    I like the idea of using `child` documents and that's definitely something I need to read into. I've found an alternative method recently to create a similar output in this [SO post](https://stackoverflow.com/questions/21729415/generate-dynamic-r-markdown-blocks) using the `knit_expand()` function. Thanks for the help – EJJ Sep 20 '18 at 13:14
  • That's another approach, and I hadn't seen that yet. It seems that the differences are minor, so the effect should be the same (especially since they both capture the output into `out` and dump them after the chunk). – r2evans Sep 20 '18 at 15:30