knitr v1.6以前では、Rコード chunk でオブジェクトを表示することは、基本的にRコンソールをエミュレートすることになります。 例えば、データフレームは次のように表示されます1。:
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
上のデータフレームのテキスト表現は、多くのRユーザーにとって非常に馴染みのあるものかもしれませんが、レポート作成の目的では満足のいくものではないかもしれません。 多くの場合、代わりにテーブル表現を見たいと思います。 これが、chunk オプションの render
と S3 の汎用関数 knit_print()
が解決しようとしている問題です。
コード chunk 内の各R式を評価すると、オブジェクトが返ってきます。例えば,1 + 1
は 2
を返します。 このオブジェクトは chunk オプションの render
に渡されます。 render
は x
と options
、または x
と ...
という2つの引数を持つ関数です。 render
オプションのデフォルト値は knitr のS3関数である knit_print
です。
## function (x, ...)
## {
## if (need_screenshot(x, ...)) {
## html_screenshot(x)
## }
## else {
## UseMethod("knit_print")
## }
## }
## <bytecode: 0x000000001464bd80>
## <environment: namespace:knitr>
## [1] knit_print.data.frame* knit_print.default*
## [3] knit_print.grouped_df* knit_print.html*
## [5] knit_print.knit_asis* knit_print.knitr_kable*
## [7] knit_print.rowwise_df* knit_print.shiny.tag*
## [9] knit_print.shiny.tag.list* knit_print.tbl_sql*
## see '?methods' for accessing help and source code
## function (x, ..., inline = FALSE)
## {
## if (inline)
## x
## else normal_print(x)
## }
## <bytecode: 0x000000001641e638>
## <environment: namespace:knitr>
## function (x, ...)
## if (isS4(x)) methods::show(x) else print(x)
## <bytecode: 0x00000000148a0110>
## <environment: namespace:evaluate>
見ての通り、knit_print()
には default
メソッドがあり、基本的にはオブジェクトが S4 オブジェクトであるかどうかに応じて print()
または show()
となります。 つまり、Rオブジェクトを表示するときには,特別なことは何もしないということです。:
knit_print(1:10)
## [1] 1 2 3 4 5 6 7 8 9 10
knit_print(head(mtcars))
## 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
S3 のジェネリック関数は、カスタムメソッドを定義できるという意味で、拡張性があります。 メソッド knit_print.foo()
は、クラス foo
を持つオブジェクトに適用されます。 ここでは、データフレームをテーブルとして出力する例を示します。:
library(knitr)
# define a method for objects of the class data.frame
knit_print.data.frame = function(x, ...) {
res = paste(c('', '', kable(x)), collapse = '\n')
asis_output(res)
}
# register the method
registerS3method("knit_print", "data.frame", knit_print.data.frame)
R 3.5.0 以降では S3 のディスパッチ機構が変更されています。 このため、knitr ドキュメントのコード chunk でメソッドを定義する場合は、R >= 3.5.0 では registerS3method()
の呼び出しが必要になります。 Rパッケージを開発されている方は、後述の[パッケージ作者の方へ]の項をご覧ください。
print メソッドは、文字ベクトル、あるいは文字ベクトルに変換できるオブジェクトを返すことが期待されます。 上の例では、kable()
関数は文字ベクトルを返しますが、これを asis_output()
関数に渡すことで、後にknitrが、この結果は特別な処理が必要ない(そのまま書けばよい)ことを認識します。 そうでなければ、chunk オプションの results
(= 'asis'
/ 'markup'
/ 'hide'
)によって、通常の文字ベクトルをどのように書くべきかが決まります。 関数 asis_output()
は、results = 'asis'
と同じ効果がありますが、この chunk オプションを明示的に提供する手間が省けます。 次に、表示の動作がどのように変化するかを確認します。 以下の chunk では、数値、文字ベクトル、リスト、データフレームを表示し、cat()
を使って文字値を書き込んでいます。:
## [1] 2
## [1] "a" "b" "c" "d" "e" "f"
## $a
## [1] 1
##
## $b
## [1] 9 8 7 6 5 4
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 |
## This is cool.
データフレーム以外のすべてのオブジェクトが “正常”2 に出力されていることがわかります。 データフレームは実際のテーブルとして表示されました。 表を作成するのに、kable()
を使う必要はありません。 xtable など、他にもいろいろなオプションがあります。 print メソッドが文字列を返すことを確認してください。
printr パッケージは knitr に付属しており、行列やデータフレームなどの一般的なオブジェクトの表示メソッドを含んでいます。 ユーザーはこのパッケージをロードするだけで、魅力的な表示結果を得ることができます。 表示メソッドを定義する際に考慮すべき主な要因は(printrでも考慮されている)出力形式です。 例えば、出力が LaTeX の場合と Markdown の場合では、表の構文が全く異なる場合があります。
S3 のメソッドには、...
の引数を付けることを強くお勧めします。そうすれば、knit_print()
に渡されたが、あなたのメソッドで定義されていない引数を、あなたのメソッドが安全に無視することができます。 現時点では、knit_print()
メソッドは2つのオプションの引数を持つことができます。
アプリケーションに応じて、これらの引数をオプションで使用することができます。 以下に例を示します。:
knit_print.classA = function(x, ...) {
# ignore options and inline
}
knit_print.classB = function(x, options, ...) {
# use the chunk option out.height
asis_output(paste0(
'<iframe src="https://yihui.org" height="', options$out.height, '"></iframe>',
))
}
knit_print.classC = function(x, inline = FALSE, ...) {
# different output according to inline=TRUE/FALSE
if (inline) {
'inline output for classC'
} else {
'chunk output for classC'
}
}
knit_print.classD = function(x, options, inline = FALSE, ...) {
# use both options and inline
}
自分で作成したの(あるいは他の)knit_print()
メソッドをインラインで使用する場合(それがサポートされている場合)、オブジェクト上で knit_print()
を呼び出してはならず、単にそれを返すようにしなければならないことに注意してください。 例えば、あなたのインラインコードは、 foo
であり、。 ではないです。 後者のインラインコードでは、in-chunk(インラインではない)のメソッドの結果が得られます。 なぜなら、上で設定したように、
knit_print()
メソッドのデフォルトは inline = FALSE
だからです。 このデフォルトは、knit_print()
が呼び出されたときのコンテキスト(インラインかインチャンクか)に応じて上書きされますが、render
オプション(後述)を介して knitr(あなたではない)から knit_print()
が呼び出されたときだけは、このデフォルトが上書きされます。 もちろん,手動でインラインオプション `r knit_print(c("foo"), inline = TRUE)`
とすることもできますが、これは非常に多くのタイプを必要とします。
低レベルな実装の詳細が気にならない場合は、このセクションをスキップしても構いません。
render
オプション前述したように、チャンクオプションの render
は、デフォルトでは knit_print()
という関数になっています。他の render 関数を使用することも可能です。 例えば、どんなオブジェクトを受け取っても常に「I do not know what to print」と表示するダミーの関数を作ります。:
dummy_print = function(x, ...) {
cat("I do not know what to print!")
# this function implicitly returns an invisible NULL
}
ここでは、chunk オプションの render = dummy_print
を使用します。:
1 + 1
## I do not know what to print!
head(letters)
## I do not know what to print!
list(a = 1, b = 9:4)
## I do not know what to print!
head(mtcars)
## I do not know what to print!
cat('This is cool.')
## This is cool.
なお、render
関数は可視オブジェクトにのみ適用されます。 invisible()
でラップされたものなど、返されるオブジェクトが不可視である場合もあります。
## [1] 2
print 関数には、オブジェクトに関する「メタデータ」を knitr に渡すという副次効果があり、knitr はオブジェクトを表示する際にこの情報を収集します。 メタデータを収集する動機は、表示されるオブジェクトの外部依存性を 保存することにあります。 通常、オブジェクトを表示するのはテキスト表現を得るためだけですが、もっと複雑なケースもあります。 例えば、ggvis のグラフは、ggvis.js
のような外部の JavaScript や CSS の依存関係を必要とします。 グラフ自体は基本的に JavaScript のコードの断片であり、必要なライブラリが(HTML のヘッダーに)読み込まれていないと動作しません。 そのため、オブジェクト自体を表示する以外に、オブジェクトの依存関係を収集する必要があります。
依存関係を指定する1つの方法は、asis_output()
の meta
引数を用いることです。 以下に擬似的な例を示します。:
# pseudo code
knit_print.ggvis = function(x, ...) {
res = ggvis::print_this_object(x)
knitr::asis_output(res, meta = list(
ggvis = list(
version = '0.1.0',
js = system.file('www', 'js', 'ggvis.js', package = 'ggvis'),
css = system.file('www', 'www', 'ggvis.css', package = 'ggvis')
)
))
}
そして、knitr が ggvis オブジェクトをプリントするときに、meta
情報が収集され、保存されます。 knit が終わったら、knit_meta()
ですべての依存関係のリストを得ることができます。 このリストには重複したエントリが含まれている可能性が高いので、それらをクリーンアップしたり、独自の方法でメタデータリストを処理したりするのは、パッケージの作成者に任されています(たとえば、依存関係を HTML ヘッダに書き込むなど)。 以下では、knit_meta()
がどのように動作するかを確認するために、もう少し簡単で汚い例を示します。
次に、foo
オブジェクトに対する print メソッドを定義します。:
library(knitr)
knit_print.foo = function(x, ...) {
res = paste('> **This is a `foo` object**:', x)
asis_output(res, meta = list(
js = system.file('www', 'shared', 'shiny.js', package = 'shiny'),
css = system.file('www', 'shared', 'shiny.css', package = 'shiny')
))
}
foo
オブジェクトを表示するとどうなるかを見てみましょう。:
This is a
foo
object: hello
メタデータを確認:
## List of 2
## $ js : chr ""
## $ css: chr ""
## - attr(*, "knit_meta_id")= chr [1:2] "unnamed-chunk-9" "unnamed-chunk-9"
別の foo
オブジェクト
This is a
foo
object: world
同様に、bar
オブジェクト:
knit_print.bar = function(x, ...) {
asis_output(x, meta = list(head = '<script>console.log("bar!")</script>'))
}
new_bar = function(x) structure(x, class = 'bar')
new_bar('> **hello** world!')
hello world!
hello world!
メタデータの最終バージョン、きれいにします:
## List of 6
## $ js : chr ""
## $ css : chr ""
## $ js : chr ""
## $ css : chr ""
## $ head: chr "<script>console.log(\"bar!\")</script>"
## $ head: chr "<script>console.log(\"bar!\")</script>"
## - attr(*, "knit_meta_id")= chr [1:6] "unnamed-chunk-9" "unnamed-chunk-9" "unnamed-chunk-11" "unnamed-chunk-11" ...
## list()
自分のパッケージにカスタムプリント方式を導入する場合、2つのヒントがあります。
通常、importFrom(knitr, knit_print)
(または roxygen: #' @importFrom knitr knit_print
) を介して、 パッケージの名前空間で knit_print
をインポートする必要があります (例として、printr package を参照してください)。 もし knitr をインポートしたくない場合は、.onLoad()
のフックで registerS3method()
を呼び出すことができます(例として rstudio/htmltools#108を参照してください)。
asis_output()
は、単純にクラス knit_asis
のオブジェクトをマークする関数で、 この関数をパッケージにインポートする必要もありません。
## function (x, meta = NULL, cacheable = NA)
## {
## structure(x, class = "knit_asis", knit_meta = meta, knit_cacheable = cacheable)
## }
## <bytecode: 0x0000000015cc9968>
## <environment: namespace:knitr>
なお、DESCRIPTION の Suggests
フィールドに knitr を記述し、 knitr::asis_output()
を使用することで、knitr への「ハード」な依存を回避することができます。