4 動的なコンテンツの作成

4.1 プログラムチャンク

プログラムチャンクは, R Markdown 最大の特徴であり, R のソースコードや, その実行結果を Markdown に挿入できる. さらには R 以外の言語の動作も可能である. 順番が前後してしまったが, 定理などのカスタムブロックは本来はプログラムを入力するためのチャンクブロックであり, それを静的なテキストコンテンツの挿入に流用しているだけである.

以降は R で多くのユーザが頻繁に使うパッケージと, いくつかの技術文書作成に役に立つパッケージをインポートしている前提の説明とする. なお, rmarkdown, bookdown はチャンク内で特に読み込む必要がない.

pkgs <- installed.packages()
for (p in c("tidyverse", "ggthemes", "equatiomatic", "tufte",
  "kableExtra")) {
  if (!p %in% pkgs)
    install.packages(p)
}
if (!"rmarkdown" %in% pkgs) remotes::install_github("rstudio/rmarkdown")
if (!"bookdown" %in% pkgs) remotes::install_github("rstudio/bookdown")
require(tidyverse)
 要求されたパッケージ tidyverse をロード中です 
─ Attaching packages ──────────────────── tidyverse 1.3.1 ─
✓ ggplot2 3.3.3     ✓ purrr   0.3.4
✓ tibble  3.1.2     ✓ dplyr   1.0.6
✓ tidyr   1.1.3     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.1
─ Conflicts ───────────────────── tidyverse_conflicts() ─
x dplyr::filter()     masks stats::filter()
x dplyr::group_rows() masks kableExtra::group_rows()
x dplyr::lag()        masks stats::lag()
require(ggthemes)
 要求されたパッケージ ggthemes をロード中です 
require(equatiomatic)
 要求されたパッケージ equatiomatic をロード中です 
require(kableExtra)

このように, ログを掲載することもできる. これは再現性を重視する際に重宝するが, 一方で単に画像などの出力だけを掲載したい場合もあるだろう. あるいは, プログラムを解説するためにプログラムは掲載するが実行しない, ということも必要になるかもしれない. プログラムと結果の表示/非表示はどちらも簡単に切り替え可能である. そのためには, チャンクオプションを指定する.

  • echo: プログラムを掲載するかどうか
  • message: プログラム実行結果の標準出力を掲載するかどうか
  • warning: プログラム実行結果の警告を掲載するかどうか
  • error: プログラム実行結果のエラーを掲載するかどうか
  • eval: 文書作成時にプログラムを実行するかどうか
  • include: 文書作成時にプログラムを実行し, かつ掲載しないかどうか
  • results: 出力をいつもの R の出力風にするか (markup), 隠すか ("hide"), 出力を区切らずまとめるか ("hold"), テキストをそのまま出力するか ("asis"). 最後は R Markdown のソースコードを動的に生成したい場合などに使う.

チャンクごとに個別に設定することも, デフォルト値を一括設定することもできる. 前者の場合, チャンクオプションは {} 内部にカンマ , で区切って書く. r は R で実行するという意味である. チャンクの一般的な記法は以下のようになる.

```{r [<label>], [<options>]}
data(cars)
summary(cars)
```

r の直後の <label> はラベルと呼ばれ, チャンクのIDとしての機能を持つ (省略された場合は自動で適当な名前がつけられる). ラベルは主に後述の図表の相互参照に使われる. ラベルは英数字とハイフンを使って重複しない範囲で自由に命名できる.

一括設定の場合, 以下のようなプログラムでデフォルト値を上書きできる.

knitr::opts_chunk$set(echo = F, message = T, warnings = F, error = F)

なおこのチャンクは eval=F を設定することで, 実行されることなくプログラムのみ掲載している. ただし, プログラムのみを掲載するなら, 以下のように Markdown の機能でも可能である. こちらの記法は {} がなくなっていることに注意する.

```sh
echo Hello, Bookdown
```

{} ブロック内の値にはさらに R プログラムで与えることができる. この使い方は後の章で解説する.

これらのオプションがあるおかげでプログラムとその結果の再現を説明したい場合はソースコードも表示させたり, 回帰分析やシミュレーションの結果だけを掲載したい時は結果のみ表示したりできる. これが R Markdown のチャンクの強みである. 例えば Jupyter notebook/lab などは従来, コードセルと出力セルを自由に隠すことができなかった.

チャンクに使用できる言語は R だけではない. つまり Python なども使用できる(詳細は 12 章を参照). 以下で対応しているエンジンの一覧を表示できる.

names(knitr::knit_engines$get())
 [1] "awk"         "bash"        "coffee"      "gawk"        "groovy"     
 [6] "haskell"     "lein"        "mysql"       "node"        "octave"     
[11] "perl"        "psql"        "Rscript"     "ruby"        "sas"        
[16] "scala"       "sed"         "sh"          "stata"       "zsh"        
[21] "highlight"   "Rcpp"        "tikz"        "dot"         "c"          
[26] "cc"          "fortran"     "fortran95"   "asy"         "cat"        
[31] "asis"        "stan"        "block"       "block2"      "js"         
[36] "css"         "sql"         "go"          "python"      "julia"      
[41] "sass"        "scss"        "R"           "bslib"       "theorem"    
[46] "lemma"       "corollary"   "proposition" "conjecture"  "definition" 
[51] "example"     "exercise"    "hypothesis"  "proof"       "remark"     
[56] "solution"   

また, 新たにプログラムを追加することもできる. 詳細は RDG Ch. 2.7 Other language engines を参考に.

TODO: 他の言語のプログラムを実行する際の注意点

4.2 プログラムで数式を生成する

プログラムチャンクは, 単にプログラムの計算結果を埋め込むだけでなく, 静的なコンテンツを臨機応変に変更して出力させたり, あるいは手作業でやるには煩雑な加工処理を挟んでから表示させるのに役に立つ.

R のプログラムと組み合わせることで回帰分析の結果の数値をコピペすることなく数式で表示することができる. そのためには equatiomatic パッケージの extract_eq() を使う.

まずは, 回帰係数を記号で表現するタイプ. LaTeX 数式をそのまま出力するため, チャンクオプションに results="asis" を付ける必要があることに注意する.

data(mtcars)
fit <- lm(mpg ~ ., data = mtcars)
extract_eq(fit, wrap = T, ital_vars = T, align_env = "aligned")

\[ \begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned} \]

さらに use_coef = T で係数を推定結果の数値に置き換えた.

extract_eq(fit, wrap = T, ital_vars = T, use_coef = T, align_env = "aligned")

\[ \begin{aligned} \widehat{mpg} &= 12.3 - 0.11(cyl) + 0.01(disp) - 0.02(hp)\ + \\ &\quad 0.79(drat) - 3.72(wt) + 0.82(qsec) + 0.32(vs)\ + \\ &\quad 2.52(am) + 0.66(gear) - 0.2(carb) \end{aligned} \]

equatiomatic パッケージは現時点では lm glm に対応しており, lmer への対応も進めているようだ.

TODO: この書き方だと PDF で付番できない

4.3 プログラムを使った図の挿入

既に Markdown 記法による図表の挿入方法を紹介したが, プログラムチャンクを介して画像を読み込み表示させることもできる. まずは, R のプログラムで既存の画像ファイルを表示させる方法.

knitr::include_graphics(file.path(img_dir, "Johannes_Gutenberg.jpg"))
Johannes Gutenberg

図 4.1: Johannes Gutenberg

もちろんのこと既存の画像だけでなく, データを読み込んでヒストグラムや散布図などを描いた結果を画像として掲載することもできる.

技術文書や学術論文では, 画像の上か下に「図1: XXXXX」のようなキャプションを付けることが多い. 紙の書籍では絵本のように本文と図の順序を厳密に守るより, 余白を作らないよう図の掲載位置を調整する必要があるからだ.

プログラムチャンクにはこのキャプションを入力するオプション fig.cap があるため, plot() 側でタイトルを付けないほうが良い. 例えば ggplot2 パッケージの関数を使い以下のようなチャンクを書く10.

```{r plot-sample, echo=T, fig.cap="`ggplot2` によるグラフ"}
data("diamonds")
diamonds <- diamonds[sample(1:NROW(diamonds), size =), ]
ggplot(diamonds, aes(x=carat, y=price, color=clarity)) +
  geom_point() +
  labs( x = "カラット数", y = "価格") + scale_color_pander(name = "クラリティ") +
  theme_classic(base_family = "Noto Sans CJK JP") + theme(legend.position = "bottom")
```

実際の表示は図 4.2 のようになる.

`ggplot2` によるグラフ

図 4.2: ggplot2 によるグラフ

ggplot2 以外のパッケージや言語, たとえば tikz や asymptote, DOT言語も使用できる. これらは 8 章で紹介する.

4.4 (WIP): デフォルトフォントの設定

Windows や Mac では, デフォルトのフォントが日本語グリフを持たないのでグラフが文字化けする. 現時点では最低限 rmdja::set_graphics_font() という関数を呼び出す処理を手動で書き加えなければならない. 本文のフォントと異なり, 現時点 (ver. 0.4.2) では手動設定が必要になる. OSごとのフォント名を調べて指定するのが大変なら, 私が作成した fontregisterer パッケージを使うのも1つの手である. その解説は『おまえはもうRのグラフの日本語表示に悩まない (各OS対応)』に書いた通りである. get_standard_font() で使用中のOSで標準インストールされているセリフ (明朝), サンセリフ (ゴシック) のフォントファミリ名を1つづつ取得するので, その値のどちらかを rmdja::set_graphics_font() に与えれば, ggplot2 および標準グラフィックスのデフォルトのフォントが日本語対応フォントになる.

しかしこの関数は ggplot2 のデフォルトのテーマを更新するだけなので ggthemes パッケージなどが用意するテーマプリセットを使用したい場合はその都度設定が必要である.

require(fontregisterer)
theme_set(ggthemes::theme_pander(base_family = get_standard_font()$serif))

ggplot(DATA, aes(...)) + geom_point() + ... + theme_economist(base_family = get_standard_font()$sans)

4.5 TODO: 図のレイアウト設定

PDF ならばフロート設定のため, 図が離れた位置に配置されることがある. そのため, 「図 4.2」 のような相互参照を使うと良いだろう. フロートを使うかどうかは, 後のセクションで解説する TODO

Rのグラフィックデバイスを使っている限り, 通常のRのコンソールと同じコードをチャンク内に書くだけで表示できる.

R のグラフィックデバイスではないとは, RGL や plotly など外部ライブラリに頼ったグラフ作成ツールのことである. 判断できない人は, RStudio 上で実行して, “Plots” ペーンに表示されたら R のグラフィックデバイス, “Viewer” ペーンに表示されたらそうでない, で覚えていただきたい. 後者を表示する方法は 11 章で後述する. R をこれまで使ったことがなく, それすらも何を言っているのか分からない, という場合は ggplot2 を使ってもらう.

最後の fig.cap="" がキャプションである. ただし, どうも日本語キャプションを書いたあとに他のチャンクオプションを指定するとエラーになるようだ. よって fig.cap= はオプションの末尾に書くべきである. また, fig.cap="" に数式や一部の特殊なテキストを直接入力することができない. この問題は相互参照について解説するセクション 5.1 で詳細を述べる.

fig.cap 以外のオプションはおそらく頻繁には変えないため, 冒頭でまとめて設定したほうが楽だろう.

knitr::opts_chunk$set(fig.align = "center", fig.width = 6.5,
  fig.height = 4.5, out.width = "100%", out.height = "100%")

なお, これらは rmdja でのデフォルト値であるため, 実際にこの値をあえて記述する必要はない.

ここで, fig.width と out.width の違いも述べておく. out.width/out.height は表示する画像サイズの違いで, fig.width/fig.height はプログラムが出力した画像の保存サイズである. よって ggplot2 などを使わず画像ファイルを貼り付けるだけの場合は fig.* は意味をなさない.

4.6 R プログラムを使った表の装飾

Markdown 記法を使った表記は既に紹介した. しかしこれは表の数値を全て手動で書かなければならない. R はテーブル状のデータ処理に長けているため, このような煩雑さを省くことができないか, とあなたは思っていないだろうか. もちろん R Markdown では R での作業中に使用しているデータをいちいち手書きなどせずとも表示できるし, テーブルのデザインもある程度自由に設定できる.

R Markdown のデフォルトでは R のコンソールと同様にテキストとして出力されるが, rmdja では異なるデザインで表示されている. これは knitr, kableExtra パッケージなどで事後処理をかけることで見やすいデザインの表に変換しているからである. R Markdown の基本ルールとして, チャンク内で最後に呼び出したオブジェクトが表示される. 例えば mtcars というRが用意する練習用データフレームを, チャンク内で上から10行までを呼び出してみると, 以下のように表示される.

data(mtcars)
mtcars[1:10, ]
                   mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Duster 360        14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Merc 240D         24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
Merc 230          22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
Merc 280          19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4

これはRのコンソール出力と同じで, プレーンテキストでの出力である. 表として出力する最も簡単な方法は, フォーマット関数に df_print を指定することである. たとえば df_print: kable を指定すると, 表 4.1 のようになる.

output: ...:
  df_print: kable
mtcars[1:10, ]
表 4.1: df_print: kable の場合
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4

このオプションは R Markdown の処理中にデータフレームの呼び出しを検出し, df_print のオプションに対応したスタイルを変換する関数を適用している. 他のオプションとして, tibble, paged などがあるが現時点の rmdja では大差がないので詳細な説明を省略する (図 4.2).

表 4.2: df_print のオプション一覧
オプション 効果
default print(), コンソール出力と同じ
tibble tibble 対応版 print()
paged rmarkdown::paged_table() による表示, これもオプション引数を指定しなければ大差なし
kable knitr::kable() による表スタイル

よって, これらの関数をチャンク内で呼び出すことで, 手動で表のスタイルを指定することも可能である. 表のスタイルにこだわりたい, 相互参照やキャプションを付けたい, といった場合はこれらのうち knitr::kable() 関数を手動で使うのが1つの手である. 実は, 先ほどの df_print の例も, 実際にはこの関数を呼び出して出力している. この場合, 表のキャプションは kable() 内で指定できる (現時点では, 図とは異なりチャンクオプションではキャプションを指定できない). デフォルトでは caption = の文字列はそのまま出力されるため, 太字強調など Markdown 記法も変換されずそのまま表示されてしまう. これには対処方法がいくつかある.

  1. rmdja パッケージの提供する knitr::kable() または kableExtra::kbl() 関数のラッパを使用する (表 4.3)
  2. escape = F および format = "pandoc" を指定する
  3. (非推奨) HTML と PDF でそれぞれの構文で表を描く処理を自分で書く
rmdja::kable(mtcars[1:10, ], caption = "`booktabs = T` は PDF にのみ影響する",
  booktabs = T)
表 4.3: booktabs = T は PDF にのみ影響する
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4
  1. の方法が現在最も簡単である. ただし, LaTeX の構文が評価されなくなるため同時に使うことはできない. 例えば太字強調と数式を両方表示したい場合は, knitr::is_latex_output() PDF の場合は完全に LaTeX で, HTML の場合は Markdown で書く, という場合分けを自分で書いて knitr::kable() に与えなければならない(表 4.4). また, キャプションではなく表内の markdown 構文も評価されない. 表内の markdown 構文を PDF でも反映するには, (2) の方法が必要である.

TODO: この仕様は使いづらいのでそのうちなんとかしたい.

  1. についても, kable() 単体であれば問題ないが, 後に紹介する kableExtra パッケージを併用すると書式設定がうまく反映されなくなることがある. (3) は表 4.4 の記述をキャプションだけでなく, HTML ならば Markdown または HTML タグで, PDF ならば LaTeX で表全体を書き分ける, という方法である. 1つの表を描くのに多大な労力がかかるため推奨しない.
cap <- if (knitr::is_latex_output()) "数式 $a$ と \\textbf{太字}" else "数式 $a$ と **太字**"
kable(head(mtcars), caption = cap, booktabs = T)
表 4.4: 数式 \(a\) と 太字
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

さらに, デフォルトでは kable() が PDF に出力する表のデザインはあまりよろしくないが, kable() 関数は過剰な罫線のない表の出力も簡単である. LaTeX を使ったことのある人は知っているかもしれないが, これは booktabs.sty を使った表のスタイルになっている11.

また, kable() を使う利点として, 表の絡む名に好きな名前を与えられるというものがある. データフレームの列 (変数) 名は, 括弧などプログラミングで特別な意味を持つ文字を使うことができない. そこで, kable() の col.names 引数に表のカラム名を改めて与えることで, こういった文字も出力できる.

kable() による表のスタイルは kableExtra パッケージを使うことで様々にカスタマイズできる. 例えば HTML 版ではデフォルトで奇数偶数行の背景色が異なるが, PDF ではそうなっていない. また, 図表の位置は常にフロートであり, 余白ができにくいように表示位置が前後する (これは技術文書や学術論文では普通のことだが). さらに, 表が本文の領域からはみ出しており見栄えが悪い. これらの設定をHTML版に近づけたい場合は kableExtra::kable_styling() を使って簡単にデザインを変えることができる (表 4.5). 以下のように, full_width は表の幅を本文幅にそろえるオプションである. や十分に幅の小さい表に対しては逆に間延びして見づらいためデフォルトでは無効となっているが, このようにして表幅を調整するのに使える. さらに latex_options は PDF にのみ有効なオプションである. "striped" が奇数偶数の色分け12, "hold_position" が表示位置を「なるべく」固定するオプションである (それでも表示位置が大きくずれて気に入らない場合 "HOLD_position" を代わりに使うとよい). ただし HTML と違い PDF では改ページがあるためこのオプションを多様すると, 以下のように本文に無駄な余白が増えることに注意する.

rmdja::kable(mtcars[1:10, ], booktabs = T, caption = "奇数行を強調し, PDF では `booktabs` を利用") %>%
  kable_styling(full_width = if (knitr::is_latex_output()) T else NULL,
    latex_options = c("striped", "hold_position"))
表 4.5: 奇数行を強調し, PDF では booktabs を利用
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4
Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4

このように, R Markdown ではまず表示したい表と同じ構造のデータフレームを作ることで, 簡単にスタイルの調整された表を掲載できる.

他にもいくつか表のスタイルをカスタマイズするためのパッケージが存在する. より発展的な表のスタイル指定方法については 9 章で話す.


  1. なお, Rユーザーならば標準グラフィック関数である plot() 関数をご存知だろうが, 本稿では基本的により便利な ggplot2 パッケージを使用してグラフを作成している.↩︎

  2. もし何らかの理由でこのスタイルにならない, あるいはあえてしたくない, と言う場合は kable() 関数で booktabs = T を指定せよ.↩︎

  3. ただし, full_width = T を指定した時, striped, あるいは他の色の指定の命令が反映されないことがある. これは 2019年時点での tabu.sty の不具合であるため, Issues #1 で配布されている開発者によるパッチを適用しなければならない.また, それ以外にも表の幅を調整する方法がある. 詳細は 9 章を参考に.↩︎