R Markdownで楽々レポートづくり

第4回レポつく自由自在 ~R Markdownにまつわるエトセトラ~

はじめに

早いもので本連載ももう4回目です。マラソンに例えると、折り返し地点を目前に、先頭を走るロサ・モタの後ろ姿をおぼろげながらも視界に捉えてきたというところでしょうか。

前回はコードチャンクごとの処理を効率化するということで、チャンクオプションを中心に解説しました。今回はR Markdownにまつわるエトセトラとして、レポートづくりをさらに加速するためのテクニックを紹介します。ただし全てのエトセトラをフォローできるわけではありませんので、疑問質問などはコメント欄またはツイッター@kohskeまでお願いします。

インラインコード

前回までに説明するつもりですっかり忘れていたのですが、文章中に簡単な計算結果を埋め込みたいときなどは、コードチャンクはちょっと大げさです。R Markdownでは、Rのコードをコードチャンク以外にインラインコードとしても記述することができます。書式は次のとおりです。

テキスト・・・`r Rのコード` テキスト・・・

バッククォートに続けて、r半角スペース、その後にRのコードを記述して、バッククォートを閉じます。Rのコード部分は実行結果に置き換えられます。

irisの1列目と2列目の相関は`r cor(iris[[1]], iris[[2]])`です。

この部分は、レポートでは

irisの1列目と2列目の相関は-0.1175698です。

というように出力されます。もちろんコード部分では別の(ただしインラインコードよりも前にある)コードチャンクで作成したオブジェクトを参照することもできます。

YAMLフロントマターによるメタ情報の設定

R Markdownの先頭にはYAMLフロントマターと呼ばれるブロックを記述します(必須ではありませんが、記述したほうが良いでしょう⁠⁠。ここではタイトル、作成者、作成日などのレポートのメタ情報の設定や、R Markdownからレポートを作成する際の変換に関する設定を行います。

基本的な書式は次の通りです。インデントされた行は子要素として処理されますが、YAMLではインデントにタブが使えません。必ず半角スペースによりインデントする必要があります。

---
title: "R Markdownで楽々レポートづくり<br/>第4回 レポつく自由自在〜R Markdownにまつわるエトセトラ〜"
author: '@kohske'
date: "`r format(Sys.time(), '%Y/%m/%d')`"
output:
  html_document:
    self_contained: true
    toc: true
---

タイトルなどのメタ情報が、実際のレポートの中でどのように使われるかは、レポートのフォーマットにより異なります。通常は、titleauthordateくらいを指定しておけばよいでしょう。また、YAMLフロントマターの内容もRにより処理されるので、上記のdateの例のようにRのコードを記述しておくこともできます。

レポート作成の設定は、output:の子要素として出力フォーマットを指定し、さらにその子要素として出力フォーマット用のオプションを指定します。指定できるフォーマットにはhtml_documentpdf_documentword_documentbeamer_presentationioslides_presentationslidy_presentationなどがあります。指定できるオプションは、Rコンソール上でlibrary(rmarkdown)とした後に、?html_documentなどとしてヘルプファイルを開けば参照できます。

上記の例では、フォーマットをHTMLとして、self_containedを有効にして、画像等を外部ファイルではなくHTML内部に埋め込むようにしています。またtocを有効にして目次を追加しています。HTML以外のフォーマットのレポートを作成するコツや、利用できるオプションやについては、次回以降で紹介します。

日本語HTMLレポートでハマる罠として、目次を有効にした場合、ヘッダ要素###の内容が日本語文字のみだと目次をクリックしても対応する要素にジャンプできないということがあります。ちょっと込み入った話をすると(興味ない人は無視してください⁠⁠、pandocにはascii_identifiersというパースオプションがあり、これが有効にされていると識別子に非ascii文字を使えなくなります。pandocではヘッダ要素の文字列からHTMLのアンカー用の識別子を自動生成しますが、ここが非asciiだと識別子が生成されないというわけです。R Markdownではこのパースオプションがデフォルトで有効になっているので、この問題を回避するには、フロントマターで

output:
  html_document:
    toc: true
    pandoc_args: [
      "--from", "markdown+autolink_bare_uris+tex_math_single_backslash-implicit_figures"
      ]

とします。

パッケージオプションとフック

R Markdownで.Rmdファイルからレポートを作る際、.Rmdファイル内のRコードを実行して結果を出力する処理自体は{knitr}パッケージにより行われています。{knitr}のパッケージオプションを設定することで、変換処理をカスタマイズできます。ここでは役に立ちそうなパッケージオプションをいくつか紹介します。

パッケージオプションの設定方法

パッケージオプションはセットアップチャンクで設定するのが良いでしょう。opts_knit$set(オプション名 = 値)という書式で設定します。

```{r setup, include=FALSE}
# セットアップチャンク
library(knitr)
opts_knit$set(progress = FALSE) # 変換処理の際のプログレスバーを非表示に
```

という感じです。

aliases

aliasesオプションを設定すると、チャンクオプションのオプション名を簡単に記述できるようになります。例えば

```{r setup, include=FALSE}
# セットアップチャンク
library(knitr)
opts_knit$set(aliases = c(h="fig.height", w="fig.width"))
```

テキスト・・・

```{r iris-plot, h=12, w=12}
plot(iris)
```

とすれば、iris-plotチャンクではfig.height=12, fig.width=12と指定されたことになります。地味機能ですが、記述ミスを減らすのに役立ちます。

チャンクオプションの評価とeval.after

実はRコードチャンクでは、チャンク内のコードだけでなく、チャンクオプションも同様に評価されます。通常はチャンク内のコードが評価される前にチャンクオプションが評価されますが、パッケージオプションeval.afterで指定されたチャンクオプションは、チャンク内のコードが評価された後に評価されるようになります。

意味がよくわからないと思いますので、具体例を考えてみると良いでしょう。例えば、グラフのキャプションに計算内容を入れたいような場合があります。

```{r setup, include=FALSE}
# セットアップチャンク
library(knitr)
opts_knit$set(eval.after = c("fig.cap"))
```

テキスト・・・

```{r cor-plot, fig.cap = paste0('図1: R = ', corr)}
x = rnorm(100)
y = rnorm(100)
corr = cor(x, y)
plot(x, y)
```

このようにすることで、まずチャンク内のコードでcorrが計算されて、その値がfig.capに反映されます。 サンプル出力はこちらです。

global.parによるグラフィックスパラメータのグローバル化

解析処理のレポートには大量のグラフが含まれていることも多いでしょう。Rではマージンや背景色といったグラフのパラメータはpar()関数で設定します。R Markdownの場合、デフォルトではコードチャンクごとにグラフィックスパラメータが初期化されてしまいます。これでは、全てのコードチャンクでpar()による設定を記述する必要があり、効率的ではありません。パッケージオプションglobal.parTRUEとすることで、チャンク間でグラフィックスパラメータの設定を引き継ぐことができるようになります。

```{r setup, include=FALSE}
# セットアップチャンク
library(knitr)
opts_knit$set(global.par = TRUE)
```

```{r}
par(bg  = "pink")
plot(1:10)
```

```{r}
# 上のチャンクのpar()の設定が再利用される
plot(10:1)
```

これで、2つ目のグラフも背景色がピンクで描画されます(悪趣味ですが⁠⁠。サンプル出力はこちらです。

フック

R Markdown(というか裏で走る{knitr}パッケージ)にはフックという強力な機能があります。フックを設定すると、チャンクの動作や出力を思い通りにカスタマイズすることができます。チャンクフックを用いると、チャンクコードの評価の前後に任意の処理を実行できます。アウトプットフックを用いると、チャンクコードの処理結果に対して任意の変換処理を実行できます。

ここでは、各チャンクコードの行数を自動的に出力するようなチャンクフックを紹介します。レポートにコードを載せるのは冗長ですが、せめてコードの行数くらい表示しておけば、あなたがどれだけ頑張って解析コードを書いたのか、レポートを読んだ人にわかってもらえるかもしれません。

```{r setup, include=FALSE}
# セットアップチャンク
library(knitr)
prow = function(before, options, envir) {
  if (!before) {
    paste0(c("<p>コードは", length(options$code), "行です</p>"))
  }
}   
knit_hooks$set(prow = prow) 
    
```

```{r, prow=TRUE, echo=FALSE}
# ?histでのヒストグラム作成例
r <- hist(sqrt(islands), breaks = c(4*0:5, 10*3:5, 70, 100, 140),
          col = "blue1")
text(r$mids, r$density, r$counts, adj = c(.5, -.5), col = "blue3")
sapply(r[2:3], sum)
sum(r$density * diff(r$breaks)) # == 1
lines(r, lty = 3, border = "purple") # -> lines.histogram(*)
```

サンプル出力はこちらです。

まずセットアップチャンクで、フック関数prowを定義し、knit_hooks$setでフック名prowとしてフックに登録しています。次に、コードチャンクではチャンクオプションprow=TRUEとして、このチャンクでprowフックを使うことを指示します。そうすると、このチャンクの評価時にフック関数prowが呼び出されます。フック関数ではoptions引数を使ってチャンクの情報にアクセスすることができます。

この連載ではフックについての解説はいわゆるbeyond scopeですのでこれ以上の説明はやめておきますが、フックを使えばレポートづくりのオートメーション化の自由度は飛躍的に上がります。ただし、オートメーション化のほうにばかり時間を使って、肝心のレポートづくりが進まないのでは意味ないので気をつけましょう。

なお、フックをさらに使いこなしたいという場合は、ドキュメント生成本で詳しく解説されていますので参考にしてください。

キャッシュにまつわるエトセトラ

前回の記事で紹介したように、時間がかかる処理を行うコードチャンクではキャッシュを有効にすると効率よくレポートづくりを進められます。チャンクオプションcache=TRUEを指定したコードチャンクでは、コードに変更がなければコードは再評価されずに以前のキャッシュの出力が再利用されます。コードに変更があればコードが再評価されて、新しい出力が利用されます。

通常はこれで充分ですが、実際の解析ジョブではコードの変更だけでなく、作業環境の変更や入力データの変更などに応じてコードの再評価が必要な場面が出てきます。ここでは、キャッシュされたチャンクを再評価するための方法をいくつか紹介します。

キャッシュのリフレッシュ

最も確実に全てのコードを再評価する方法は、キャッシュオブジェクトが保存されているディレクトリの削除です。キャッシュ保存パスはチャンクオプションcache.pathで指定できます。R Markdownではデフォルトは.Rmdファイル名_cacheとなっています。このディレクトリを削除すればキャッシュされたものがなくなってしまうので、全てのコードが再評価されます。

cache.extraの利用

チャンクオプションが変更された場合もキャッシュは無効化されます。この仕組みを利用して、セットアップチャンクなどでチャンクオプションcache.extraを一括指定することで、全チャンクを強制的に再評価することができます。例えば次のようにcache.extraにRのバージョン文字列を指定しておけば、Rのバージョンが変更になった時にキャッシュが無効になり再評価されます。

```{r setup, include=FALSE}
# セットアップチャンク
library(knitr)
opts_chunk$set(cache.extra = R.version.string)
```

Rのバージョン文字列の代わりに、日付Sys.Date()や、あるファイルのmd5ハッシュ値tools::md5sum('foo.ext')などにしておけば、日付が変わったりファイルに変更があった時にコードが再評価されるようになります。

入力ファイルへの依存

大きなファイルの処理はコストがかかるので、できるだけキャッシュを活用したいことでしょう。ですが、外部からログを取得する場合など、ファイルに変更があった時だけコードを再評価してほしいという事情もあります。もちろん、そのようなときに手作業でキャッシュフォルダを削除したり、cache.extraを変更することも可能ですが、そもそもこの連載の目的は手作業の排除です。自動的にログを取得しているのに、それに応じて手作業でキャッシュ無効化の操作するとか、ありえませんよね?

このような場合、チャンクヘッダで適当なチャンクオプションにファイルのハッシュ値などを指定することで、ファイルに変更があった場合だけコードを再評価させることができます。

```{r cache=TRUE, cache.e = tools::md5sum("foo.csv")}
# foo.csvに依存する処理
# x = read.csv("foo.csv")
# ...
```

これで、foo.csvのmd5sumハッシュ値に変更があった時(≒ファイルに変更があった時)のみ、コードが再評価されます。どやっ!!

チャンクの依存関係

コードチャンクAで作成したオブジェクトをコードチャンクBで使って処理する場合、コードチャンクB自体に変更がなくてもコードチャンクAに変更があれば、コードチャンクBも再評価する必要があります。チャンク間の依存関係を明示することで、この動作を実現できます。チャンクオプションdependsonに依存するチャンクのチャンクラベル(以下の例ではc1を渡すことで、c1チャンクに変更があった時にc2チャンクも再評価されます。

```{r c1, cache=TRUE}
a = 1
print(a)
```

```{r c2, cache=TRUE, dependson = "c1"}
# c1に変更があればこのチャンクも評価される
b = a + 2
print(b)
```

また、チャンクオプションautodepTRUEにすると、自動的に依存するチャンクを検出してくれます。ただし、100%信頼できるわけではないので、可能な限りdependsonで明示するのが良いでしょう。

まとめ

今回の内容はやや高度だったかもしれません。このエトセトラを身に付ければR Markdown名人と言っても過言ではありません。きっと同僚から「いよっ、名人!」と賞賛されることでしょう。

また、今回も入稿したオリジナルの原稿である、自動変換処理などを行う前のR Markdownファイルやサンプルなどをまとめて公開しておきます。

突然ですが、著者は学生時代10年ほど京都に住んでいました。毎年この季節になると鴨川の納涼床も活気付き、祇園祭へ向けて京都の街中がソワソワとしてくるのを感じたものです。京都を離れてはや10年近く……最近では郷愁にも近いものを感じます。

今回紹介したレポートづくりのエトセトラを活用して仕事をさっさと終わらせて、そうだ京都、行こう!

次回は

レポートといえば主役はグラフや表です。次回はR Markdownでグラフや表を効果的に扱う方法を紹介します。

おすすめ記事

記事・ニュース一覧