thomasfedb
thomasfedb

Reputation: 5983

Loop within RMarkdown code block

I have a document that's something like this:

```{r, include = F}
require(data.table)

dt <- data.table(
  A = c(1, 4, 1, 4, 2, 4, 8),
  B = c(2, 2, 3, 4, 3, 1, 5)
)
```


```{r, echo = F}
col <- "A"

knitr::asis_output(paste0("### Column ", col, "\n"))

knitr::asis_output("Counts\n")

dt[, .(n = .N), by = col]

knitr::asis_output("Statistics\n")

summary(dt[, get(col)])
```


```{r, echo = F}
col <- "B"

knitr::asis_output(paste0("### Column ", col, "\n"))

knitr::asis_output("Counts\n")

dt[, .(n = .N), by = col]

knitr::asis_output("Statistics\n")

summary(dt[, get(col)])
```

Whilst this works fine, obviously, this is very repetitive. I tried to use a loop rather than the copy-paste, like so:

```{r, echo = F}
for (col in c("A", "B")) {
  knitr::asis_output(paste0("### Column ", col, "\n"))

  knitr::asis_output("Counts\n")

  dt[, .(n = .N), by = col]

  knitr::asis_output("Statistics\n")

  summary(dt[, get(col)])
}
```

This however prints no output at all. How can I loop in an RMarkdown document and produce both markdown and R output?

Upvotes: 1

Views: 534

Answers (1)

steveb
steveb

Reputation: 5532

You need a print statement for the summary results. Your code would change to the following where only the print statement was added to the summary output:

```{r, echo = F}
for (col in c("A", "B")) {
  print(knitr::asis_output(paste0("### Column ", col)))

  print(knitr::asis_output("Counts"))

  print(dt[, .(n = .N), by = col])

  print(knitr::asis_output("Statistics"))

  print(summary(dt[, get(col)]))
}
```

This produces the following results:

## [1] "### Column A"
## attr(,"class")
## [1] "knit_asis"
## attr(,"knit_cacheable")
## [1] NA
## [1] "Counts"
## attr(,"class")
## [1] "knit_asis"
## attr(,"knit_cacheable")
## [1] NA
##    A n
## 1: 1 2
## 2: 4 3
## 3: 2 1
## 4: 8 1
## [1] "Statistics"
## attr(,"class")
## [1] "knit_asis"
## attr(,"knit_cacheable")
## [1] NA
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.500   4.000   3.429   4.000   8.000 
## [1] "### Column B"
## attr(,"class")
## [1] "knit_asis"
## attr(,"knit_cacheable")
## [1] NA
## [1] "Counts"
## attr(,"class")
## [1] "knit_asis"
## attr(,"knit_cacheable")
## [1] NA
##    B n
## 1: 2 2
## 2: 3 2
## 3: 4 1
## 4: 1 1
## 5: 5 1
## [1] "Statistics"
## attr(,"class")
## [1] "knit_asis"
## attr(,"knit_cacheable")
## [1] NA
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   3.000   2.857   3.500   5.000

You may want to reformat and remove attributes when printing.

Edit

At the suggestion from @user2554330 here is an example using knitr::knit_print:

```{r, echo = F}
for (col in c("A", "B")) {
  knitr::knit_print(paste0("### Column ", col))
  knitr::knit_print("Counts")
  knitr::knit_print(dt[, .(n = .N), by = col])
  knitr::knit_print("Statistics")
  knitr::knit_print(summary(dt[, get(col)]))
}
```

which gives the following output

## [1] "### Column A"
## [1] "Counts"
##    A n
## 1: 1 2
## 2: 4 3
## 3: 2 1
## 4: 8 1
## [1] "Statistics"
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.500   4.000   3.429   4.000   8.000 
## [1] "### Column B"
## [1] "Counts"
##    B n
## 1: 2 2
## 2: 3 2
## 3: 4 1
## 4: 1 1
## 5: 5 1
## [1] "Statistics"
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   3.000   2.857   3.500   5.000

Edit 2

The following alternate solution will more closely print what the SO is asking for. It removes the "## " prefix globally and adds it back in for the data.frame (i.e. only where needed). It also uses cat in place of knitr::knit_print to avoid printing the array indexes (i.e. [1]).

```{r, echo = F, comment=NA}
for (col in c("A", "B")) {
  cat(paste0("Column ", col), "\n")
  cat("Counts\n")
  ## Add comment prefix
  tdf <- as.data.frame(dt[, .(n = .N), by = col]) ## Convert to data.frame for printing
  rownames(tdf) <- paste("## ", 1:nrow(tdf))      ## Add comments for printing.
  knitr::knit_print(tdf)
  cat("Statistics\n")
  ## Add comment prefix
  knitr::knit_print(summary(dt[, get(col)]))
}
```

The output below is more of what the SO is asking for (based on comments to this post). It prefixes the data.frame output with "## " but the summary output does not have the "## " prefix; this can be added if needed.

Column A 
Counts
      A n
##  1 1 2
##  2 4 3
##  3 2 1
##  4 8 1
Statistics
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   1.500   4.000   3.429   4.000   8.000 
Column B 
Counts
      B n
##  1 2 2
##  2 3 2
##  3 4 1
##  4 1 1
##  5 5 1
Statistics
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   3.000   2.857   3.500   5.000 

Upvotes: 1

Related Questions