R Markdownコードチャンク内でMarkdownを生成できたら良い、と思う場面はたまにあります。(cat()
で出力すればいいんでしょ簡単簡単)と思ってたら割と色々なところでハマってしまったので注意点をメモっておきます。
そもそも出力させる
まず、出力がそのままMarkdownに反映されないといけないので、コードチャンクのオプションではresults='asis'
を指定するようにします。
あとはcat()
やprint()
で記述してけばOKです。
cat("### lv 3 header")
見出しを出力するときの注意
Pandoc - Pandoc User’s Guideには次のようなことが書いてあります。
Standard Markdown syntax does not require a blank line before a header. Pandoc does require this (except, of course, at the beginning of the document).
要するに見出しの前には空行が必要、ということです。
この仕様、Markdownを1行だけ出力するような場合はあまり問題にならないのですが、ループが伴うとひっかかりやすくなります。
例えば次のようなコードを実行してみましょう。
for(i in seq_len(3)){
cat("### lv 3 header\n")
cat("some text")
cat("\n")
}
このコードは一見問題なさそうですが、出力は次のような形となっています。
### lv 3 header
some text
### lv 3 header
some text
### lv 3 header
some text
この形は例えばQiitaならOKですが、Pandocだと(デフォルトでは)先程の仕様によって正しく処理されません。
正しく処理するためには見出しの前に少なくとも1つの空行が入るように注意深く出力を組み立てる必要があります。具体的には、ループの末尾に改行を(少なくとも)2つ入れればOKです。
for(i in seq_len(3)){
cat("### lv 3 header\n")
cat("some text")
cat("\n\n") # 改行を少なくとも2つ入れる
}
glue::glue()
を使うときの注意
glue
パッケージのglue()
関数はプレースホルダが利用可能で、変数を活用してテキストを組み立てるのに便利な関数です(tidyverse/glue: Glue strings to data in R. Small, fast, dependency free interpreted string literals.)。
ですが、この関数はデフォルトで結果がtrim()
で処理されます。ヘルプには次のような記述があり、仕様であることが分かります。
Leading whitespace and blank lines from the first and last lines are automatically trimmed.
なので、改行を入れたつもりで入ってない、というミスが起こりがちです。glue()
の外で改行を入れるか、.trim = FALSE
を指定することで回避できます(実は改行を2つにするという方法でも回避できますが、意図した挙動かどうかちょっと分からないのでおすすめできません)。
text <- c("foo", "bar")
cat(glue::glue("{text[1]}\n"))
cat(glue::glue("{text[2]}"))
foobar
cat(glue::glue("{text[1]}"), "\n")
cat(glue::glue("{text[2]}"))
foo
bar
cat(glue::glue("{text[1]}\n", .trim = FALSE))
cat(glue::glue("{text[2]}"))
foo
bar
ggplot2
を使う場合の注意
flexdashboard
パッケージ(flexdashboard: Easy interactive dashboards for R)とループによるMarkdown生成を組み合わせると、こんな感じでTabsetsやStoryboardを利用して、条件を少しずつ変えたプロットを比較しやすい感じで簡単に出力できるので便利です。
Rの標準のプロットであるgraphics::plot()
を使う場合は特に注意点は無いのですが、ggplot2
だとループ時に割と問題が発生します。
flexdashboard
のTabsetを使う例として、次のような.Rmdをknitしてみるとします。
---
output:
flexdashboard::flex_dashboard
---
```{r setup, include=FALSE}
library(flexdashboard)
library(dplyr)
library(ggplot2)
```
## ggplot2 {.tabset}
```{r results='asis', echo=FALSE}
target <- iris$Species %>% as.character %>% unique
for(i in seq_along(target)){
cat(glue::glue("### {i}. {target[i]}"), "\n")
iris %>% filter(Species == target[i]) %>%
ggplot(aes(Sepal.Width, Sepal.Length)) +
geom_point()
cat("\n\n")
}
```
しかしこれはダメです。Tabsetにはなりますが、プロットが出力されません。
ループ中でggplotを出力するには、一旦変数に格納してからprint()
してやります。つまり、次のようにします。
## ggplot2 {.tabset}
```{r results='asis', echo=FALSE}
target <- iris$Species %>% as.character %>% unique
for(i in seq_along(target)){
print(glue::glue("### {i}. {target[i]}"))
iris %>% filter(Species == target[i]) %>%
ggplot(aes(Sepal.Width, Sepal.Length)) +
geom_point() -> p # 一旦変数に保存する必要がある
print(p)
cat("\n\n")
}
```
これで先程示した出力が得られます。