Printing a tibble: Control and data flow

library(pillar)

tibble を表示するとどうなるのか? この vignette は、制御フローとデータフローを記録し、設計上の選択を説明し、"tbl" クラスのデフォルトの実装を示す。 主にテーブルのサブクラスの実装者が関心を持つものである。 tibble 内のベクタクラスの書式のカスタマイズは、以下で説明する。 vignette("pillar ", package =" vctrs") . さまざまなカスタマイズオプションは、vignette("extending") で紹介されている。

必要条件

概要

(注:図が作成されない。英語版を参照されたい)全体の制御とデータの流れは下図の通りである。 箱は関数とメソッドである。 実線は関数呼び出し。 点線は、関数が引数や(オプションの場合)クエリを介して積極的に取得する情報を表す。

pillar パッケージはデバッグのために debugme を使用している。 pillar のデバッグを有効にすることは、制御フローを追跡する別の方法である。詳しくは vignette("debugme") を参照。

初期化

tibble は、クラス "tbl_df""tbl" の列のリストである。 印刷は、遅延テーブルのようなデータフレームでないテーブルのようなオブジェクトに対して機能するように設計されている。 print.tbl() メソッドは、そのオブジェクトに対して format() を呼び出し、出力を表示す。

tbl <- tibble::tibble(a = 1:3, b = tibble::tibble(c = 4:6, d = 7:9), e = 10:12)
print(tbl, width = 23)
#> # A tibble: 3 × 3
#>       a   b$c     e
#>   <int> <int> <int>
#> 1     1     4    10
#> 2     2     5    11
#> 3     3     6    12
#> # … with 1 more
#> #   variable:
#> #   b$d <int>
str(tbl)
#> tibble [3 × 3] (S3: tbl_df/tbl/data.frame)
#>  $ a: int [1:3] 1 2 3
#>  $ b: tibble [3 × 2] (S3: tbl_df/tbl/data.frame)
#>   ..$ c: int [1:3] 4 5 6
#>   ..$ d: int [1:3] 7 8 9
#>  $ e: int [1:3] 10 11 12
Source code of pillar:::print.tbl()
print.tbl <- function (x, width = NULL, ..., n = NULL, max_extra_cols = NULL, 
    max_footer_lines = NULL) 
{
    print_tbl(x, width, ..., n = n, max_extra_cols = max_extra_cols, 
        max_footer_lines = max_footer_lines)
}

format.tbl() メソッドは setup オブジェクトを作成し、そのオブジェクトを使用してヘッダー、ボディ、フッターをフォーマットする。

Source code of pillar:::format.tbl()
format.tbl <- function (x, width = NULL, ..., n = NULL, max_extra_cols = NULL, 
    max_footer_lines = NULL) 
{
    format_tbl(x, width, ..., n = n, max_extra_cols = max_extra_cols, 
        max_footer_lines = max_footer_lines)
}

"tbl" のサブクラスでこれらのメソッドを拡張したりオーバーライドすることも可能であるが、 多くの場合、以下に示すより特殊なメソッドをオーバーライドすれば十分である。

セットアップ

書式設定のための作業のほとんどは、実際には tbl_format_setup() で行われる。 希望する出力幅は、セットアップオブジェクトに焼き付けられ、呼び出し時に利用可能でなければならない。 セットアップオブジェクトは tibble のように表示されるが、ヘッダ、ボディ、フッタは明確に分離されている。

setup <- tbl_format_setup(tbl, width = 24)
setup
#> <pillar_tbl_format_setup>
#> <tbl_format_header(setup)>
#> # A tibble: 3 × 3
#> <tbl_format_body(setup)>
#>       a   b$c     e
#>   <int> <int> <int>
#> 1     1     4    10
#> 2     2     5    11
#> 3     3     6    12
#> <tbl_format_footer(setup)>
#> # … with 1 more
#> #   variable: b$d <int>

ここでは、情報の二重計算を避けるために、セットアップオブジェクトが必要である。 例えば、ヘッダに表示される寸法やフッタに表示される余分な列は、ボディが計算された後でのみ利用可能である。

ジェネリックはコンテナ上でディスパッチするので、必要であればオーバーライドすることができる。 これは、メソッドに渡す前に引数にデフォルト値を割り当てる役割を担っている。

Source code of tbl_format_setup()
tbl_format_setup <- function (x, width = NULL, ..., n = NULL, max_extra_cols = NULL, 
    max_footer_lines = NULL, focus = NULL) 
{
    "!!!!DEBUG tbl_format_setup()"
    width <- get_width_print(width)
    n <- get_n_print(n, nrow(x))
    max_extra_cols <- get_max_extra_cols(max_extra_cols)
    max_footer_lines <- get_max_footer_lines(max_footer_lines)
    out <- tbl_format_setup_dispatch(x, width, ..., n = n, max_extra_cols = max_extra_cols, 
        max_footer_lines = max_footer_lines, focus = focus)
    return(out)
    UseMethod("tbl_format_setup")
}

デフォルトの実装では、as.data.frame(head(x)) を介して入力をデータフレームに変換し、データフレームと追加情報を含む new_tbl_format_setup() で構築されたオブジェクトを返す。 より多くの情報を取り入れるためなどにこのメソッドをオーバーライドすると、デフォルトの設定オブジェクトに新しい項目を追加することができるが、既存の項目を上書きしてはいけない。

Source code of pillar:::tbl_format_setup.tbl()
tbl_format_setup.tbl <- function (x, width, ..., n, max_extra_cols, max_footer_lines, 
    focus) 
{
    "!!!!DEBUG tbl_format_setup.tbl()"
    rows <- nrow(x)
    if (is.na(rows)) {
        df <- df_head(x, n + 1)
        if (nrow(df) <= n) {
            rows <- nrow(df)
        }
        else {
            df <- vec_head(df, n)
        }
    }
    else {
        df <- df_head(x, n)
    }
    if (is.na(rows)) {
        needs_dots <- (nrow(df) >= n)
    }
    else {
        needs_dots <- (rows > n)
    }
    if (needs_dots) {
        rows_missing <- rows - n
    }
    else {
        rows_missing <- 0
    }
    tbl_sum <- tbl_sum(x)
    rownames(df) <- NULL
    colonnade <- ctl_colonnade(df, has_row_id = if (.row_names_info(x) > 
        0) 
        "*"
    else TRUE, width = width, controller = x, focus = focus)
    body <- colonnade$body
    extra_cols <- colonnade$extra_cols
    extra_cols_total <- length(extra_cols)
    if (extra_cols_total > max_extra_cols) {
        length(extra_cols) <- max_extra_cols
    }
    abbrev_cols <- colonnade$abbrev_cols
    new_tbl_format_setup(x = x, df = df, width = width, tbl_sum = tbl_sum, 
        body = body, rows_missing = rows_missing, rows_total = rows, 
        extra_cols = extra_cols, extra_cols_total = extra_cols_total, 
        max_footer_lines = max_footer_lines, abbrev_cols = abbrev_cols)
}

核となるのは、内部関数 ctl_colonnade()、身体を構成している。 その関数とカスタマイズのポイントは、以下の「Colonnade」のセクションで詳しく説明する。

コロネード

内部関数 ctl_colonnade() は、身体を構成する。 以下のような作業を行う。

  1. ctl_new_pillar_list() , ctl_new_pillar() そして最終的には pillar()pillar_shaft() を介して、最小の幅を使用して、フィットするすべてのトップレベル列のための柱オブジェクトを作成する。
  2. 段数、各段の幅を決める
  3. 各柱に幅を割り当てて、段に分散させる。
  4. 各柱を format() 関数でフォーマットし、今分かっている幅を渡する。
  5. フォーマットされた柱を水平に組み合わせる。
  6. 縦に段を組み合わせる。
  7. フォーマットされた本体と、収まりきらなかった列を返す。

以下では、第1ステップと第4ステップについて説明する。

pillar オブジェクトの作成

最初の tibble は ctl_new_pillar_list() に渡され、最終的に ctl_new_pillar() を1回または数回呼び出す。 各トップレベルの列に対して、1つの柱オブジェクトが構築される。 最小幅を考慮しても使用可能な幅を使い切った時点でループを終了させる。

柱状リスト

ctl_new_pillar_list() ジェネリックがコンテナ上でディスパッチする。

ctl_new_pillar_list(tbl, tbl$a, width = 20)
#> [[1]]
#> <pillar>
#> <int>
#>     1
#>     2
#>     3
#> 
#> attr(,"remaining_width")
#> [1] 14
#> attr(,"simple")
#> [1] TRUE
ctl_new_pillar_list(tbl, tbl$b, width = 20)
#> [[1]]
#> <pillar>
#>     c
#> <int>
#>     4
#>     5
#>     6
#> 
#> [[2]]
#> <pillar>
#>     d
#> <int>
#>     7
#>     8
#>     9
#> 
#> attr(,"extra")
#> character(0)
#> attr(,"remaining_width")
#> [1] 8
#> attr(,"simple")
#> [1] FALSE

tibble では、各列はデータフレーム、行列、あるいは配列そのものであることがあり、そのような列は複合列と呼ばれる。 このような列は、サブ pillar に分解され、pillar のリストとして返される。 通常のベクタは ctl_new_pillar() に転送され、長さ1のリストとして返される。 "tbl" のサブクラスの実装者がこのメソッドを拡張またはオーバーライドする必要があることは、ほとんどないだろう。

Source code of pillar:::ctl_new_pillar_list.tbl()
ctl_new_pillar_list.tbl <- function (controller, x, width, ..., title = NULL, first_pillar = NULL) 
{
    "!!!!DEBUG ctl_new_pillar_list.tbl(`v(width)`, `v(title)`)"
    if (is.data.frame(x)) {
        new_data_frame_pillar_list(x, controller, width, title = title, 
            first_pillar = first_pillar)
    }
    else if (is.matrix(x) && !inherits(x, c("Surv", "Surv2"))) {
        new_matrix_pillar_list(x, controller, width, title = title, 
            first_pillar = first_pillar)
    }
    else if (is.array(x) && length(dim(x)) > 2) {
        new_array_pillar_list(x, controller, width, title = title, 
            first_pillar = first_pillar)
    }
    else {
        if (is.null(first_pillar)) {
            first_pillar <- ctl_new_pillar(controller, x, width, 
                ..., title = prepare_title(title))
        }
        new_single_pillar_list(first_pillar, width)
    }
}

シンプルな柱

ctl_new_pillar() メソッドは、データフレームや配列でない列に対して呼び出され、コンテナ上にもディスパッチされる。

ctl_new_pillar(tbl, tbl$a, width = 20)
#> <pillar>
#> <int>
#>     1
#>     2
#>     3
Source code of pillar:::ctl_new_pillar.tbl()
ctl_new_pillar.tbl <- function (controller, x, width, ..., title = NULL) 
{
    "!!!!DEBUG ctl_new_pillar.tbl(`v(width)`, `v(title)`)"
    pillar(x, title, if (!is.null(width)) 
        max0(width))
}

デフォルトのメソッドは pillar() を直接呼び出し、利用可能な最大幅を渡する。

Source code of pillar()
pillar <- function (x, title = NULL, width = NULL, ...) 
{
    "!!!!DEBUG pillar(`v(class(x))`, `v(title)`, `v(width)`)"
    pillar_from_shaft(new_pillar_title(title), new_pillar_type(x), 
        pillar_shaft(x, ...), width)
}

タイトルとタイプのフォーマットは、new_pillar_title()new_pillar_type() で提供される。 ベクタクラスの pillar_shaft() を実装することにより、本体をカスタマイズすることができる。 vignette("pillar ", package =" vctrs") を参照。 title や type が利用可能な幅に収まらない場合、pillar_shaft() は決して呼び出されない。

この関数は、データを格納するのに十分な幅がない場合、NULL を返すようになった。 ctl_new_pillar() をオーバーライドまたは拡張することで、ピラーの外観を変更することが可能である。

コンポーネント

柱状オブジェクトは同じ構造を持ち、最終的には new_pillar() で構築される。

Source code of new_pillar()
new_pillar <- function (components, ..., width = NULL, class = NULL, extra = deprecated()) 
{
    "!!!!DEBUG new_pillar(`v(width)`, `v(class)`)"
    if (is_present(extra)) {
        deprecate_warn("1.7.0", "pillar::new_pillar(extra = )")
    }
    check_dots_empty()
    if (length(components) > 0 && !is_named(components)) {
        abort("All components must have names.")
    }
    structure(components, width = width, class = c(class, "pillar"))
}

pillar は構成要素のリストとして格納される。 各 pillar は1つの単純な(原子)列のみを表し、複合列は常に複数の pillar オブジェクトとして表現される。

Formatting pillars

pillar オブジェクトを構築する際、最小幅と希望する(最大)幅を持つ。 まだ建設されていない他の柱のオブジェクトの数や幅に依存するため、最終的な幅はまだわかっていない。 これは format() に渡され、空の場合は希望の幅を使用する。

Source code of pillar:::format.pillar()
format.pillar <- function (x, width = NULL, ...) 
{
    if (is.null(width)) {
        width <- get_width(x)
    }
    if (is.null(width)) {
        width <- pillar_get_width(x)
    }
    as_glue(pillar_format_parts_2(x, width)$aligned)
}