format()
メソッドを用意することで、tibble にベクトルがどのように印刷されるかを基本的に制御することができます。 もっとコントロールしたいのであれば、印刷の仕組みを理解する必要があります。 tibble 内のカラムの表示は、2つのS3ジェネリックで制御されます。
vctrs::vec_ptype_abbr()
は、列のヘッダーに何を入れるかを決定します。pillar::pillar_shaft()
は、列の body 、つまりシャフトに入るものを決定します。技術的には pillar は、(ornamentで飾られた)shaft と、上に capital、下に base で構成されています。 複数の柱は、複数の階層に積み重ねることができるコロネードを形成します。 これが、私たちのAPIの名前の背景にある動機です。
この短い vignette では、latlon
ベクトルを使ったカラムスタイリングの基本を紹介します。 この vignette では、コードがパッケージに入っていることを想定しており、ドキュメント作成に必要な roxygen2 のコマンドや、NAMESPACE
ファイルを確認することができます。 この vignette では、pillar と vctrs を attach します。
library(vctrs)
library(pillar)
これをパッケージで行う必要はありません。 代わりに、DESCRIPTION
の Imports:
セクションに、パッケージを import する必要があります。 以下のヘルパーがこれを行ってくれます。
::use_package("vctrs")
usethis::use_package("pillar") usethis
基本的なアイデアを説明するために、地理的な座標をレコードにエンコードする "latlon"
クラスを作成します。 このコードは earth というパッケージに入っていることにしましょう。 簡単にするために,値は度と分だけで表示されます。 vctrs_rcrd()
を使うことで,このクラスをデータフレームと完全に互換性のあるものにするためのインフラがすでに無料で手に入ります。 レコードデータタイプの詳細については、vignette("s3-vector", package = "vctrs")
を参照してください。
#' @export
<- function(lat, lon) {
latlon new_rcrd(list(lat = lat, lon = lon), class = "earth_latlon")
}
#' @export
<- function(x, ..., formatter = deg_min) {
format.earth_latlon <- which(!is.na(x))
x_valid
<- field(x, "lat")[x_valid]
lat <- field(x, "lon")[x_valid]
lon
<- rep(NA_character_, vec_size(x))
ret <- paste0(formatter(lat, "lat"), " ", formatter(lon, "lon"))
ret[x_valid] # It's important to keep NA in the vector!
ret
}
<- function(x, direction) {
deg_min <- if (direction == "lat") c("N", "S") else c("E", "W")
pm
<- sign(x)
sign <- abs(x)
x <- trunc(x)
deg <- x - deg
x <- round(x * 60)
min
# Ensure the columns are always the same width so they line up nicely
<- sprintf("%d°%.2d'%s", deg, min, ifelse(sign >= 0, pm[[1]], pm[[2]]))
ret format(ret, justify = "right")
}
latlon(c(32.71, 2.95), c(-117.17, 1.67))
#> <earth_latlon[2]>
#> [1] 32°43'N 117°10'W 2°57'N 1°40'E
このクラスの列は,vctrs のインフラを利用してクラスを作成し,format()
メソッドを提供しているので,すぐに tibble で使用することができます。:
library(tibble)
#>
#> 次のパッケージを付け加えます: 'tibble'
#> 以下のオブジェクトは 'package:vctrs' からマスクされています:
#>
#> data_frame
<- latlon(
loc c(28.3411783, 32.7102978, 30.2622356, 37.7859102, 28.5, NA),
c(-81.5480348, -117.1704058, -97.7403327, -122.4131357, -81.4, NA)
)
<- tibble(venue = "rstudio::conf", year = 2017:2022, loc = loc)
data
data#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <erth_ltl>
#> 1 rstudio::conf 2017 28°20'N 81°33'W
#> 2 rstudio::conf 2018 32°43'N 117°10'W
#> 3 rstudio::conf 2019 30°16'N 97°44'W
#> 4 rstudio::conf 2020 37°47'N 122°25'W
#> 5 rstudio::conf 2021 28°30'N 81°24'W
#> 6 rstudio::conf 2022 NA
この出力はOKですが、次のように改善することができます。
<erth_ltl>
よりももっと説明的なタイプの略語を使う。
値の最も重要な部分を強調するために色のダッシュを使用する。
水平方向のスペースが限られている場合に、より狭い表示を提供する。
次のセクションでは、レンダリングを強化する方法を紹介します。
<erth_ltl>
の代わりに <latlon>
を使いたいと思います。 これを実現するには、vec_ptype_abbr()
メソッドを実装して、列のヘッダに使える文字列を返すようにします。 自分のクラスでは、6文字以内のわかりやすい略語を使うようにしましょう。
#' @export
<- function(x) {
vec_ptype_abbr.earth_latlon "latlon"
}
data#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <latlon>
#> 1 rstudio::conf 2017 28°20'N 81°33'W
#> 2 rstudio::conf 2018 32°43'N 117°10'W
#> 3 rstudio::conf 2019 30°16'N 97°44'W
#> 4 rstudio::conf 2020 37°47'N 122°25'W
#> 5 rstudio::conf 2021 28°30'N 81°24'W
#> 6 rstudio::conf 2022 NA
レンダリングにはデフォルトで format()
メソッドが使われます。 カスタムフォーマットのためには、pillar_shaft()
メソッドを実装する必要があります。 この関数は常に new_pillar_shaft_simple()
などで作成されたピラーシャフトオブジェクトを返す必要があります。 new_pillar_shaft_simple()
ではANSIエスケープコードによる色付けが可能で、pillar には style_subtle()
のようなスタイルが組み込まれています。 ここでは、データをよりわかりやすくするために、度と分のセパレータに微妙なスタイルを使うことができます。
まず、style_subtle()
を利用する度数フォーマッタを定義します。
<- function(x, direction) {
deg_min_color <- if (direction == "lat") c("N", "S") else c("E", "W")
pm
<- sign(x)
sign <- abs(x)
x <- trunc(x)
deg <- x - deg
x <- round(x * 60)
rad <- sprintf(
ret "%d%s%.2d%s%s",
deg,::style_subtle("°"),
pillar
rad,::style_subtle("'"),
pillarifelse(sign >= 0, 1, 2)]
pm[
)format(ret, justify = "right")
}
そして、それを format()
メソッドに渡します。
#' @importFrom pillar pillar_shaft
#' @export
<- function(x, ...) {
pillar_shaft.earth_latlon <- format(x, formatter = deg_min_color)
out ::new_pillar_shaft_simple(out, align = "right")
pillar }
現在、ANSI エスケープは vignette ではレンダリングされないので、この結果は何も変わっていませんが、自分でコードを走らせてみると、表示が改善されているのがわかります。
data#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <latlon>
#> 1 rstudio::conf 2017 28°20'N 81°33'W
#> 2 rstudio::conf 2018 32°43'N 117°10'W
#> 3 rstudio::conf 2019 30°16'N 97°44'W
#> 4 rstudio::conf 2020 37°47'N 122°25'W
#> 5 rstudio::conf 2021 28°30'N 81°24'W
#> 6 rstudio::conf 2022 NA
pillar の機能に加えて、cli パッケージは、テキストにスタイルを付けるための様々なツールを提供します。
tibble は、すべてを表示するのに十分な水平方向のスペースがない場合、自動的に列をコンパクトにすることができます。
print(data, width = 30)
#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <latlon>
#> 1 rstu… 2017 28°20'N 81°33'W
#> 2 rstu… 2018 32°43'N 117°10'W
#> 3 rstu… 2019 30°16'N 97°44'W
#> 4 rstu… 2020 37°47'N 122°25'W
#> 5 rstu… 2021 28°30'N 81°24'W
#> 6 rstu… 2022 NA
現在、シャフトを構成する際に最小の幅を指定していないため、latlon クラスがコンパクトになることはありません。 これを修正して、データを再表示しましょう。
#' @importFrom pillar pillar_shaft
#' @export
<- function(x, ...) {
pillar_shaft.earth_latlon <- format(x)
out ::new_pillar_shaft_simple(out, align = "right", min_width = 10)
pillar
}
print(data, width = 30)
#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <latlon>
#> 1 rstudio::c… 2017 28°20'N …
#> 2 rstudio::c… 2018 32°43'N …
#> 3 rstudio::c… 2019 30°16'N …
#> 4 rstudio::c… 2020 37°47'N …
#> 5 rstudio::c… 2021 28°30'N …
#> 6 rstudio::c… 2022 NA
文字データの場合は切り捨てが有効ですが、緯度・経度データの場合は、完全な度数を表示して分を削除する方が良いでしょう。 まずはこれを行う関数を書いてみましょう。
<- function(x, direction) {
deg <- if (direction == "lat") c("N", "S") else c("E", "W")
pm
<- sign(x)
sign <- abs(x)
x <- round(x)
deg
<- sprintf("%d°%s", deg, pm[ifelse(sign >= 0, 1, 2)])
ret format(ret, justify = "right")
}
その後、より洗練された pillar_shaft()
メソッドの実装の一部として使用します。
#' @importFrom pillar pillar_shaft
#' @export
<- function(x, ...) {
pillar_shaft.earth_latlon <- format(x, formatter = deg)
deg <- format(x)
deg_min
::new_pillar_shaft(
pillarlist(deg = deg, deg_min = deg_min),
width = pillar::get_max_extent(deg_min),
min_width = pillar::get_max_extent(deg),
class = "pillar_shaft_latlon"
) }
ここで、pillar_shaft()
メソッドは、new_pillar_shaft()
で作成したクラス "pillar_shaft_latlon"
のオブジェクトを返します。 このオブジェクトには、値をレンダリングするために必要な情報と、幅の最小値と最大値が含まれています。
簡単にするために,どちらのフォーマットもプリレンダリングされ,そこから最小幅と最大幅が計算されます。 (get_max_extent()
は、文字ベクトルの値が占めるディスプレイの最大幅を計算するヘルパーです)。
あとは、新しい "pillar_shaft_latlon"
クラスに format()
メソッドを実装するだけです。 このメソッドは、width
の引数で呼び出され、どのフォーマットを選択するかを決定します。 選んだフォーマットは new_ornament()
関数に渡されます。
#' @export
<- function(x, width, ...) {
format.pillar_shaft_latlon if (get_max_extent(x$deg_min) <= width) {
<- x$deg_min
ornament else {
} <- x$deg
ornament
}
::new_ornament(ornament, align = "right")
pillar
}
data#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <latlon>
#> 1 rstudio::conf 2017 28°20'N 81°33'W
#> 2 rstudio::conf 2018 32°43'N 117°10'W
#> 3 rstudio::conf 2019 30°16'N 97°44'W
#> 4 rstudio::conf 2020 37°47'N 122°25'W
#> 5 rstudio::conf 2021 28°30'N 81°24'W
#> 6 rstudio::conf 2022 NA
print(data, width = 30)
#> # A tibble: 6 × 3
#> venue year loc
#> <chr> <int> <latlon>
#> 1 rstudio::c… 2017 28°N 82°W
#> 2 rstudio::c… 2018 33°N 117°W
#> 3 rstudio::c… 2019 30°N 98°W
#> 4 rstudio::c… 2020 38°N 122°W
#> 5 rstudio::c… 2021 28°N 81°W
#> 6 rstudio::c… 2022 NA
コードの出力をテストしたい場合、テキストファイルに記録された既知の状態と比較することができます。 testthat::expect_snapshot()
関数は、出力を生成する関数を簡単にテストする方法を提供します。 この関数は、UnicodeやANSIエスケープ、出力幅などの詳細を考慮します。 さらに、CRAN上でテストが失敗することもありません。これは重要なことです。 なぜなら、あなたの出力はあなたがコントロールできない詳細に依存しているかもしれないからです。 これらはいずれ修正されるべきですが、あなたのパッケージがCRANから削除されるようなことがあってはなりません。
スナップショットテストを作成するには、テストファイルの1つにこの testthat 期待値を使用します。:
expect_snapshot(pillar_shaft(data$loc))
詳しくはhttps://testthat.r-lib.org/articles/snapshotting.htmlをご覧ください。