ill-identified diary

所属組織の見解などとは一切関係なく小難しい話しかしません

[R] R Markdown の YAML ヘッダでハマったおまえのための記事

この記事は最終更新日から3年以上が経過しています



概要

よくきたな. おれは ill-identified だ. 俺は毎日 R にすごい数のエラーを吐かせているが, その全てをおまえに見せるつもりはない.

R MarkdownYAML ヘッダ (フロントマター, あるいはメタデータブロック) については既に色々紹介している人もいるが, 「インデントの位置がよくわからない」「例を真似してもうまくいかなかった」といった経験をした人もいるだろう. R Markdown の仕組みを分かっていないとやや複雑なのでハマる. そういう時にどこを確認したらいいかをおまえに見せてやる.

2020/11/9 追記: ymlthis パッケージには YAML フロントマターの入力フォームウィンドウを表示する機能がある. これを使えば以降で紹介するよくあるミスの発生を減らせるかもしれない.


初めに

R Markdown のヘッダについて, 比較的豊富な例を掲載している資料をまず紹介しておく.

まずは公式

それ以外のユーザが書いたもののうち, html 系.

一方で pdf の情報はだいぶ少なくなる. 基本的な用例がいくつかある程度2.

ググれば他にもいろいろなページが見つかる3が, 上記の記事を超えて詳細に解説しているものは見つからなかったのであえてここでは取り上げない. 今回話すのは個別の用例ではなく, YAML ヘッダを扱う際の普遍的なトラブルシューティングである.

たいていの場合はここで紹介されている項目を設定すればうまくいくし, 先人の書いたテンプレに変更を加えなければ問題ない. しかし, すこし応用を効かせてデザインを変えようとこれらを参考にしてもうまく行かなかったことはないだろうか?

たとえば目次を付けたい時,

----
title: "コナン・ザ・グレート"
output:
  pdf_document:
    toc: true
----

とかいてあるかとおもえば別の記事では

----
title: "コナン・ザ・グレート"
output:  pdf_document
toc: true
----

と書いてあったりするかもしれない.

さらにはヘッダに

----
title: "コナン・ザ・グレート"
output:
  pdf_document:
    dev: "cairo_pdf"
----

と書かれているのがあるかと思えば, ヘッダ直後のチャンクで

```{r setup, include=F}
knitr::opts_chunk$set(dev = "cairo_pdf")
```

と同名のオプションを指定する人もいる. なんだ全然別の箇所に書いても動くのか, インデントはあってもなくてもいいのか, と思ってゆだんするやつは過酷なメキシコでは10秒と生きていけないだろう.

メキシコでは「インデントが必要なのか必要でないのか, どっちだ?」とか「このオプションはどこに書けばいいんだ?」とゆう一瞬のしゅじゅんが命取りになる. これからおれがおまえに教えるのは R Markdown というメキシコで生き抜くための知恵である.


R Markdown の仕組み

ヒントは

  1. Atusy『R MarkdownユーザーのためのPandoc’s Markdown (有料)』
  2. Kazutan 『R Markdownの内部とテンプレート開発

などに書かれている. これら自体にはYAMLフロントマターについてそこまで詳細に書かれていないが, ここで言及されている R Markdown (および knitr) や pandoc の仕組みが今回の話に関係する.

 (2) に書かれているように, R Markdown の大まかな流れは rmd ファイルをまず knit で R Markdown 特有のプログラムチャンクを Markdown で扱える静的なものに置き換えるなどの処理を行い, それから pandoc というプログラムを経由して PDF や HTML など各種の媒体のレイアウトに調整している. 一応 kazutan 作のイメージ図を再掲しておく.

R Markdown の処理フローのイメージ, kazutan作
R Markdown の処理フローのイメージ, kazutan作

しかし, 実際には R Markdown でできる様々なこと, 画像の出力, 画像のリサイズ, コードのシンタックスハイライト, を反映するためには, 単純な順繰りの工程ではなくその間に中間処理をしたり値を渡したりとフローが入り組んでいる. それらの各処理がそれぞれ YAML ヘッダのあちこちを参照しているのがややこしさの原因の1つである.

端的に要点だけを言うとYAML ヘッダに依存しているのは,

  1. pandoc のテンプレートファイル
  2. output: に指定したフォーマット

である4.

原則その1: output: 以下の項目はフォーマット関数へ代入される

フォーマット関数というのは output: で指定する関数である. あまり使ったことのない人は意識しないだろうが, html_documentpdf_documentrmarkdown パッケージの関数である. bookdown や私の作った rmdja を使った事のある人は, bookdown::gitbook のように書くので気づいた人もいるかもしれない. フォーマットによっても有効なオプションは変わってくる. たとえば PDF に html 向けのJavaScript を埋め込んだり CSS でデザインを設定したりはできない.

より詳しく書くと,

output:
  pdf_document:
    ...
    ...

のように, output: のあとにインデントを下げて書かれたフォーマット関数の, さらに下のインデントに書かれた項目は, フォーマット関数に与えられる. よって, output: 以下に書いてもいい項目は, 使っているフォーマット関数のヘルプを見れば分かる.

トップレベルでは動作しない例: シンタックスハイライト

Rのコードが暗転するのでデフォルトから見た目が大きく変わるよう highlight: zenburn を指定して, 結果を比較してみると良いだろう.

----
title: "コナン・ザ・グレート"
output:
  html_document:
    highlight: zenburn
----
----
title: "コナン・ザ・グレート"
output: html_document
highlight: zenburn
----

これは highlight: の適用が pandoc ではなくフォーマット関数の時点で処理されることを意味する.

原則その2: トップレベルの項目は pandoc へ渡される

原則2: トップレベルの項目は pandoc のオプションとして渡される

トップレベルにどのような項目を書いてよいかは, (1) pandoc 自体がサポートしているオプションと, (2) テンプレートで使用されている変数を確認することで分かる. 前者は 公式リファレンスガイド で確認でき5, 後者は pandoc -D で出力されるテンプレートファイルで確認する. たとえば LaTeX なら pandoc -D latex, html なら pandoc -D html のようにターミナルで入力すればテンプレートファイルが出力される6. または, フォーマット関数にも主要な使用可能オプションが書かれていることもある (網羅しているとは限らない).

R Markdown で使われる pandoc は RStudio 側がインストールしたスタンドアローンな物を使っている (バージョン制約が厳しいため). この実行ファイルの場所は Sys.getenv("RSTUDIO_PANDOC") で知ることができる.

特に LaTeX の場合はページ余白, 行間, といったレイアウトの微調整はテンプレートをいじらないとできないことも多い. 自作のテンプレートを使用する場合は, 例えば pdf_documenttemplateを使う.

トップレベルに書くオプションの例: タイトル

手抜き感があるが, これまでの原則から推理すると output: html_document:pdf_document: 以下に title: を書いても反映されないとわかる.

output:
  html_document:
    title: "デスペラード"

原則その3: その1とその2は信じるな

しかし, 原則その1, その2はいくらでも例外がある. もしおまえが原則その1, その2だけを見て「なんだ大げさに煽っておきながら実際簡単じゃないか, 見て損した」などと知ったかぶりをしてドリトスをつまみながら悦に入っているならば, おまえはたちまちサボテンの影に隠れたダニー・トレホのナイフのえじきになることだろう.

例えば html_documentpdf_document には pandoc_args という引数が存在する. 名前の通り, pandoc に渡すべき項目をリストで与えることができる. ただし '--toc' のようなコマンドライン引数の形式で書くことになる. つまり

---
title: "コナン・ザ・グレート"
output:
  pdf_document:
    pandoc_args:
      - '--toc'
---

もやはり目次を表示することができる.

より厄介なのは, 冒頭に挙げた例のように, toc: はトップレベルに書くのも output: 以下に書くのもどちらも正解だということだ. なぜならフォーマット関数は pandoc用の引数 toc (=トップレベルに書かれるもの) を自分に与えられた toc (=output: pdf_document: 以下に書いたもの) の値で上書きするため.

残念ながら厳密な確認にはフォーマット関数の実装1つ1つの中身を見る必要があるが, トップレベルで指定できるものと同じ引数が用意されているフォーマット関数の多くはフォーマット関数に与えた値が優先されると見ていいだろう.

pandoc_args と同様に, knitr::opts_chunk$set() で設定すべきチャンクオプションのデフォルト値もフォーマット関数で設定できる場合がある. 冒頭で挙げた dev がその1つである. これも内部で上書きしている. R Markdown の変換処理の内部では knitrpandoc の処理が複雑に絡み合っているのでこのようなことができてしまうのだ.

さらに言うなら, pandoc は意味のない引数を与えても基本的にエラーを返さないし, フォーマット関数には ... を引数に取るものが多いため引数名をタイプミスしてもエラーが出ないことが多い. こういった要因が重なり合うと原因の特定に苦労することだろう.

どういう書き方が良いかは私にも分からない. pandoc_argsのようなものを除いてフォーマット関数で指定できるところは全てoutput:以下に書くというルールにすることが多いが, 例えば文献引用関係の設定で引用フォーマットを決めるcitation_packageoutput以下だが参考文献リストbibliographyやフォーマットbiblio-styleはトップレベルであることが多いので同じカテゴリの設定を別々の場所に書くことになり, 見づらい. そもそもYAMLヘッダを使うのは pandoc 由来のやり方なので, いっそ独自フォーマットを作ってしまったほうが良いのかもしれない. 既に書いたように R Markdown のフォーマットは関数であり, ドキュメント生成処理も関数 rmarkdown::render() として定義されている. そのためユーザー側で独自のフォーマットを読み込むスクリプトを作ることもできる (たとえば 『rmarkdownパッケージで楽々ドキュメント生成』を参考にして)

その他細かい話

以下では, やや特殊な状況なのでさして重要ではないが, 今の所解説しているページが見つからなかった事柄をいくつか書いておく.

ハイフンの有無の違い

以下のようにYAMLヘッダにハイフンが使われることがある.

output: pdf_document
header-includes:
  - \usepackage{bxcoloremoji}
  - hogehoge
  - hogehoge

ハイフンはリストであり, 記入した順番に従って与えられる. 順番に従って与えられるということは, 名前でマッチされないことを意味する. よってキーワードつきの項目にハイフンを付けてしまうとオプションが正しく認識されないことがある. 例えば以下のように書いたとする.

output:
  html_document:
    - toc_depth: 3
    - toc: false

html_document() のヘルプを見ると, 引数は toc, toc_depth の順で定義されている. よってこの書き方では toc = 3, toc_depth = F と評価されてしまい, エラーが発生する.

一方で, LaTeX プリアンブルのようにキーワードを持たない項目はハイフンで書くことになる.

LaTeX プリアンブル/ HTML ヘッダの挿入位置

PDF を使う場合は LaTeX を使って生成するため, LaTeX のコマンドを使ってレイアウトの調整をする必要がある. HTML でも body 内や本文冒頭に何か挿入したい場合がある. ときにはテンプレートをいじる必要もあるが, 簡単に済ませたい場合は YAMLヘッダ内でも指定できる. やり方には数通りある.

トップレベルの includes: 以下で使用できる3種類のオプションにファイルを指定すれば, 文書の特定の位置にその内容を挿入する. 挿入位置はそれぞれ以下の通り.

オプション LaTeX HTML
in_header プリアンブルの後半, natbib など書誌引用関係の設定の直前 head の後半, script の途中 (なんでこんな中途半端な位置なんだ?)
before_body document 内, タイトルページの直後 body 開始直後, div main-container 内冒頭
after_body document 環境の末尾, 参考文献リストよりも後 body の後半, main-container の末尾, bootstrap 等の記述の直前

また, LaTeX のみ トップレベルの header-includes: 以下には挿入したい内容を直接書き込める. 場所は in_header の直後.

header-includes:
  - \usepackage{...}

特に LaTeX は読み込み順で競合が発生したりするので注意が必要になる. もしうまく動作しないならテンプレートの出力で正確な挿入位置を確認したほうが良いだろう.

YAML ヘッダの true/false

R と違い, T/F では認識しない. 全部小文字 true/false か全部大文字, TRUE/FALSE, 先頭大文字 True/False, あるいは yes/no のいずれかである. さらに言えば yes/no も全部大文字や先頭大文字にした場合, 現在のRStudio (1.3.1056) ではハイライトされないもののyes/noとして認識されるというタルサ・ドゥームのわながある.


  1. チートシートにも書いてあるがちょっと端折っているので分かりにくい.↩︎

  2. 普段 LaTeX を使っている私としても, そのうち情報を整理したりプレゼンテーション以外の日本語用フォーマットを用意したりしておきたい↩︎

  3. 『再現可能性のすゝめ』などのR Markdownに言及してそうな書籍にはもしかするともっと詳しい話が載っているかもしれないが, 私は持ってないので確認しようがない. (さすがにこの確認のためだけに買う気はおきないが, ご厚意はいつでも歓迎する)↩︎

  4. kazutan 氏の資料を見れば分かるように, 実際にはもっと細かく分かれている. ここではユーザーが表面上触れることになるものだけ限定して説明している. もっと細かい話を知りたければ彼の資料や開発の中心人物である Yihui (謝益輝) 氏のドキュメント, 例えば “Output Formats” のセクションを見れば良い.↩︎

  5. pandoc のドキュメントにも YAML メタデータブロックの記述がある. しかし RMarkdown のガイドを読めば分かるように, 指定しても反応しないものもあるので注意. pandoc のドキュメントは, 日本語: https://pandoc-doc-ja.readthedocs.io/ja/latest/users-guide.html 英語: https://pandoc.org/MANUAL.html#metadata-blocks↩︎

  6. https://github.com/jgm/pandoc-templates でも見ることができるが, もしかすると細部で変更があるかもしれない↩︎