R でレポートを作成するのに knitr が超便利

何を今更って話なんですが、knitr 便利ですね。1
knitr は、ざっくり説明すると、レポートを作成する際に文章の中に R のソースコードを埋め込んで、最終的に文章と R の出力結果をレポートとして出力するためのパッケージです。
カッコイイ言い方をすると「R での Reproducible Research を支援するパッケージ」です。

「Reproducible Research」とは、「再現可能な研究」のことらしいです。大人気の「データサイエンティスト養成読本」に書いてあったんできっと間違いないです。
たぶん、「あるディレクトリ下であるコマンドを叩くだけで毎回同じレポートが作成される状況」みたいな意味だと思います。

というわけで、knitr についてざっくりと紹介してみようと思います。

はじめての knitr!

knitr を使うと次のようなファイルから簡単にレポートを作成できちゃいます。

<!-- report.Rmd -->

# タイトル

リード文

## 分析結果

2013 年の男女別の MAU は以下のとおり

```{r, results = "asis", echo = FALSE, comment = NA, fig.width = 14}
data <- read.delim("/path/to/dau.tsv")
mau <- table(subset(data, select = c(month, gender)))
kable(mau)
library(ggplot2)
q <- ggplot(as.data.frame(mau))
q + geom_line(aes(x = as.Date(month), y = Freq, color = gender))
```

このファイルに対して knitr パッケージの knit 関数を実行します。

> knitr::knit("report.Rmd")

すると report.md が生成され、Chrome の MarkView extension で表示すると次のようになります。
20140221084004

あ、ロードしたデータの内容は次のものと同じです。

set.seed(0)
months <- seq(as.Date("2013-01-01"), as.Date("2013-12-01"), by = "1 month")
N <- 100000
data <- data.frame(
    month = sample(months, N, replace = TRUE),
    gender = sample(factor(c("M", "F")), N, replace = TRUE)
)

knitr の便利なところ

純粋に knitr だけを使ってみて便利だと思った点を挙げてみます。他のパッケージと比較してというわけではないです。

  • データやロジックが変わった時に手作業で結果を差し替える必要がない
  • 文章の内容に対応するコードを埋め込むことでコードのコメント代わりになる
  • キャッシュ機能がある

まぁ主に便利なのは 1 で、あとはおまけなんですが。

学生時代や数年前に仕事で R を使っていた頃は LaTeX や PowerPoint や Excel のファイルに結果を載せるのが苦痛でしょーがなかったです。特に PowerPoint や Excel は結果が変わる度に表を手作業で更新して、画像を差し替えるという苦行が発生するじゃないですか。
あまりに苦痛だったので、Excel に載せる表は XLConnect パッケージを使って更新していました。

今は PowerPoint や Excel を使わず Markdown 形式で資料を作成しているので、knitr を使うことでデータがちょっと変わっても一発コマンドを実行するだけで変更を反映できます。

個人的な使い方

未だに RStudio じゃなくて ESS を使ってる老害でごめんなさいという感じなんですが、自分は ESS (Emacs) を使ってレポートを作成しています。

最初に示した例をもうちょっと丁寧に書くと、自分の場合は次のように書きます。

<!-- report.Rmd -->

```{r setup, echo = FALSE}
library(pander)
library(ggplot2)
opts_chunk$set(comment = NA, tidy = FALSE)
panderOptions("table.style", "rmarkdown")
panderOptions("table.split.table", 200)
panderOptions("keep.trailing.zeros", TRUE)
```

# タイトル

リード文

## 分析結果

2013 年の男女別の MAU は以下のとおり。

```{r echo = FALSE, cache = TRUE}
# Cached at 2014-02-21 XX:XX:XX JST
data <- read.delim("/path/to/dau.tsv")
```

```{r, results = "asis", echo = FALSE}
mau <- table(subset(data, select = c(month, gender)))
tbl <- cbind(month = format(as.Date(rownames(mau)), "%Y-%m"), as.data.frame.matrix(mau))
colnames(tbl) <- c("月", "女性", "男性")
rownames(tbl) <- NULL
pander::pandoc(tbl, justify = "right", emphasize.strong.cols = 1)
```

```{r, echo = FALSE, fig.width = 12, fig.height = 7, warning = FALSE}
q <- ggplot(as.data.frame(mau))
q <- q + geom_line(aes(x = as.Date(month), y = Freq, color = gender))
q <- q + scale_x_date(breaks = "1 month", labels = scales::date_format("%Y-%m"))
q <- q + xlab("月") + ylab("MAU") + scale_color_hue(name = "性別", labels = c("女性", "男性"))
q + theme(text = element_text("Osaka"))
```

っで、このファイルを Emacs で開いた状態で M-x ess-swv-knit を実行します。
そうすると、同じディレクトリに report.md ファイルが生成されます。

それを Chrome で開きます。Chrome には予め MarkView extension を入れて chrome://extensions/ から MarkView の “Allow access to file URLs” にチェックを入れておきます。

> system("open -a 'Google Chrome' /path/to/report.md")

20140221084003

report.Rmd ファイルを編集して再度 M-x ess-swv-knit を実行して report.md ファイルの内容が変わればリロードしなくても変更が反映されます。

report.Rmd の解説

knitr では次のような形式のコードブロックを記述すると、コードブロックの内容を R で評価します。指定できる options については本家のページ@teramonagi さんのエントリーが参考になります。

```{r [block_name, ] [options]}

```

っで、最初のコードブロック(setup という名前のブロック)ではライブラリのロードやオプションの設定を行っています。echo オプションに FALSE を指定するとコード自体は出力されません。コードとその結果両方を出力したい場合は echo オプションを TRUE(デフォルト)にします。

```{r setup, echo = FALSE}
library(pander)
library(ggplot2)
opts_chunk$set(comment = NA, tidy = FALSE)
panderOptions("table.style", "rmarkdown")
panderOptions("table.split.table", 200)
panderOptions("keep.trailing.zeros", TRUE)
```

chunk options の tidy は必ず FALSE にすることをお勧めします。そうしないと、

```{r}
N <- 10
df <- data.frame(
  id   = seq(1, length = N, by = 1),
  odd  = seq(1, length = N, by = 2),
  even = seq(2, length = N, by = 2)
)
```

のように改行やインデントに気を遣って記述したコードが最終的な Markdown ファイルには次のように出力されます。

```r
N <- 10
df <- data.frame(id = seq(1, length = N, by = 1), odd = seq(1, length = N, by = 2), 
    even = seq(2, length = N, by = 2))
```

また、panderOptions 関数で設定しているのは pander パッケージのオプションです。このレポートでは表の出力に pander パッケージを使っています。knitr パッケージの kable 関数もそこそこ良いのですが、kable 関数の Description に

This is a very simple table generator. It is simply by design. It
is not intended to replace any other R packages for making tables.

とあるように、柔軟な処理を行いたい場合は他のパッケージの関数を使った方が良さそうです。2

pander パッケージを Markdown の表の出力に使う場合は次のような設定をしておくと便利そうです。

panderOptions("table.style", "rmarkdown")   # Markdown の表を出力
panderOptions("table.split.table", 200)     # 横長の表を分割する長さの閾値
panderOptions("keep.trailing.zeros", TRUE)  # 例えば "0.10" を "0.1" ではなく "0.10" で出力する

次のコードブロックではデータのロードを行っています。

```{r echo = FALSE, cache = TRUE}
# Cached at 2014-02-21 XX:XX:XX JST
data <- read.delim("/path/to/dau.tsv")
```

データの読み込みや前処理のように比較的重い処理は cache オプションを TRUE にしておくと便利です。
コードブロックの内容を表すハッシュ値が変わるとキャッシュが作り直されるようになっているので、キャッシュした時刻などをコメントとして埋め込んでおくと便利です。
キャッシュを作り直したい場合はこのコメント部分を変更します。

キャッシュの詳細についてはここを読んでください。

次のコードブロックでは表を出力しています。

```{r, results = "asis", echo = FALSE}
mau <- table(subset(data, select = c(month, gender)))
tbl <- cbind(month = format(as.Date(rownames(mau)), "%Y-%m"), as.data.frame.matrix(mau))
colnames(tbl) <- c("月", "女性", "男性")
rownames(tbl) <- NULL
pander::pandoc(tbl, justify = "right", emphasize.strong.cols = 1)
```

デフォルトだと R の出力結果は で括られますが、R のpander::pandoc 関数では Markdown を出力するので、 で括らずそのまま出力結果を埋め込んでほしいです。そこで results オプションに “asis” を指定しています。
また、pander::pandoc 関数を使うと、対象のオブジェクトに行名がある場合、出力結果の表の 1 列名は行名になり、そのヘッダは   になります3。それだと困るので、行名を取り除き、行名相当のカラムを追加しています。

最後のブロックでは ggplot2 パッケージを使ってグラフを描画しています。日本語を表示するためにフォントに Osaka を指定すると大量の warnings が出るので warning オプションに FALSE を指定して、warnings を出力しないようにしています。

```{r, echo = FALSE, fig.width = 12, fig.height = 7, warning = FALSE}
q <- ggplot(as.data.frame(mau))
q <- q + geom_line(aes(x = as.Date(month), y = Freq, color = gender))
q <- q + scale_x_date(breaks = "1 month", labels = scales::date_format("%Y-%m"))
q <- q + xlab("月") + ylab("MAU") + scale_color_hue(name = "性別", labels = c("女性", "男性"))
q + theme(text = element_text("Osaka"))
```

おまけ

knitr というか R の実装に問題があるんですが、言語設定が日本語でも knitr 経由で実行すると言語設定が反映されない場合があります。
そんな時は次のエントリーを参考にしてください。
R で一部の関数のメッセージの言語が実行条件によって変わる理由とその回避策 - あらびき日記

以上、ざっくりとした knitr の紹介でした。
このエントリーを通じて少しでも効率良くレポートを作成できる方が増えれば幸いです。

Tokyo.R #36 では @teramonagi 先生による「~knitrパッケージではじめる~『R MarkdownでReproducible Research』」という発表もあるみたいなので要チェックですね!!

  1. 「R言語上級ハンドブック」の執筆の際にみなさん使っていたので自分も使っていましたが、当時はそこまで便利とは感じませんでした 

  2. その割に、kable 関数は数値のカラムを右寄せにするなど気の利いた部分もあります 

  3. ちなみに knitr::kable 関数の場合は id になります