6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

R Markdownのコードチャンク中でMarkdownを生成するときの注意点

Last updated at Posted at 2019-03-14

R Markdownコードチャンク内でMarkdownを生成できたら良い、と思う場面はたまにあります。(cat()で出力すればいいんでしょ簡単簡単)と思ってたら割と色々なところでハマってしまったので注意点をメモっておきます。

そもそも出力させる

まず、出力がそのままMarkdownに反映されないといけないので、コードチャンクのオプションではresults='asis'を指定するようにします。

あとはcat()print()で記述してけばOKです。

.Rmd
cat("### lv 3 header")

image.png

見出しを出力するときの注意

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行だけ出力するような場合はあまり問題にならないのですが、ループが伴うとひっかかりやすくなります。

例えば次のようなコードを実行してみましょう。

input
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だと(デフォルトでは)先程の仕様によって正しく処理されません。

image.png

正しく処理するためには見出しの前に少なくとも1つの空行が入るように注意深く出力を組み立てる必要があります。具体的には、ループの末尾に改行を(少なくとも)2つ入れればOKです。

for(i in seq_len(3)){
  cat("### lv 3 header\n")
  cat("some text")
  cat("\n\n") # 改行を少なくとも2つ入れる
}

image.png

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つにするという方法でも回避できますが、意図した挙動かどうかちょっと分からないのでおすすめできません)。

改行はtrimされる
text <- c("foo", "bar")
cat(glue::glue("{text[1]}\n"))
cat(glue::glue("{text[2]}"))
output
foobar
catの中で使えばOK
cat(glue::glue("{text[1]}"), "\n")
cat(glue::glue("{text[2]}"))
output
foo
bar
.trim=FALSEを指定でもOK
cat(glue::glue("{text[1]}\n", .trim = FALSE))
cat(glue::glue("{text[2]}"))
output
foo
bar

ggplot2を使う場合の注意

flexdashboardパッケージ(flexdashboard: Easy interactive dashboards for R)とループによるMarkdown生成を組み合わせると、こんな感じでTabsetsやStoryboardを利用して、条件を少しずつ変えたプロットを比較しやすい感じで簡単に出力できるので便利です。

output.gif

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にはなりますが、プロットが出力されません。

image.png

ループ中で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")
}
```

これで先程示した出力が得られます。

参考

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?