Custom Print Methods

Yihui Xie

2021-04-20

knitr v1.6以前では、Rコード chunk でオブジェクトを表示することは、基本的にRコンソールをエミュレートすることになります。 例えば、データフレームは次のように表示されます1。:

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

上のデータフレームのテキスト表現は、多くのRユーザーにとって非常に馴染みのあるものかもしれませんが、レポート作成の目的では満足のいくものではないかもしれません。 多くの場合、代わりにテーブル表現を見たいと思います。 これが、chunk オプションの render と S3 の汎用関数 knit_print() が解決しようとしている問題です。

表示のカスタマイズ

コード chunk 内の各R式を評価すると、オブジェクトが返ってきます。例えば,1 + 12 を返します。 このオブジェクトは chunk オプションの render に渡されます。 renderxoptions 、または x... という2つの引数を持つ関数です。 render オプションのデフォルト値は knitr のS3関数である knit_print です。

library(knitr)
knit_print  # an S3 generic function
## function (x, ...) 
## {
##     if (need_screenshot(x, ...)) {
##         html_screenshot(x)
##     }
##     else {
##         UseMethod("knit_print")
##     }
## }
## <bytecode: 0x000000001464bd80>
## <environment: namespace:knitr>
methods(knit_print)
##  [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
getS3method('knit_print', 'default')  # the default method
## function (x, ..., inline = FALSE) 
## {
##     if (inline) 
##         x
##     else normal_print(x)
## }
## <bytecode: 0x000000001641e638>
## <environment: namespace:knitr>
normal_print
## 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 + 1
## [1] 2
head(letters)
## [1] "a" "b" "c" "d" "e" "f"
list(a = 1, b = 9:4)
## $a
## [1] 1
## 
## $b
## [1] 9 8 7 6 5 4
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
cat('This is cool.')
## 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」と表示するダミーの関数を作ります。:

ここでは、chunk オプションの render = dummy_print を使用します。:

なお、render 関数は可視オブジェクトにのみ適用されます。 invisible() でラップされたものなど、返されるオブジェクトが不可視である場合もあります。

## [1] 2

メタデータ

print 関数には、オブジェクトに関する「メタデータ」を knitr に渡すという副次効果があり、knitr はオブジェクトを表示する際にこの情報を収集します。 メタデータを収集する動機は、表示されるオブジェクトの外部依存性を 保存することにあります。 通常、オブジェクトを表示するのはテキスト表現を得るためだけですが、もっと複雑なケースもあります。 例えば、ggvis のグラフは、ggvis.jsのような外部の JavaScript や CSS の依存関係を必要とします。 グラフ自体は基本的に JavaScript のコードの断片であり、必要なライブラリが(HTML のヘッダーに)読み込まれていないと動作しません。 そのため、オブジェクト自体を表示する以外に、オブジェクトの依存関係を収集する必要があります。

依存関係を指定する1つの方法は、asis_output()meta 引数を用いることです。 以下に擬似的な例を示します。:

そして、knitrggvis オブジェクトをプリントするときに、meta 情報が収集され、保存されます。 knit が終わったら、knit_meta() ですべての依存関係のリストを得ることができます。 このリストには重複したエントリが含まれている可能性が高いので、それらをクリーンアップしたり、独自の方法でメタデータリストを処理したりするのは、パッケージの作成者に任されています(たとえば、依存関係を HTML ヘッダに書き込むなど)。 以下では、knit_meta() がどのように動作するかを確認するために、もう少し簡単で汚い例を示します。

次に、foo オブジェクトに対する print メソッドを定義します。:

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 オブジェクト:

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つのヒントがあります。

  1. 通常、importFrom(knitr, knit_print) (または roxygen: #' @importFrom knitr knit_print) を介して、 パッケージの名前空間で knit_print をインポートする必要があります (例として、printr package を参照してください)。 もし knitr をインポートしたくない場合は、.onLoad() のフックで registerS3method() を呼び出すことができます(例として rstudio/htmltools#108を参照してください)。

  2. 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 への「ハード」な依存を回避することができます。


  1. 注意: Rはオブジェクトが visible であるとき、明示的な print() の呼び出しなしにオブジェクトを表示します; ?invisible を参照してください

  2. 2つのハッシュ ## は chunk オプションのcomment によるもので、comment = ''でオフにすることができます。