Tibbles print numbers with three significant digits by default, switching to scientific notation if the available space is too small. Underlines are used to highlight groups of three digits. This display works for many, but not for all use cases.

Per-column number formatting

The new num() constructor allows creating vectors that behave like numbers but allow customizing their display.

num(-1:3, notation = "sci")
#> <pillar_num(sci)[5]>
#> [1] -1e0  0    1e0  2e0  3e0

tibble(
  x4 = num(8:12 * 100 + 0.5, digits = 4),
  x1 = num(8:12 * 100 + 0.5, digits = -1),
  usd = num(8:12 * 100 + 0.5, digits = 2, label = "USD"),
  percent = num(8:12 / 100 + 0.0005, label = "%", scale = 100),
  eng = num(10^(-3:1), notation = "eng", fixed_exponent = -Inf),
  si = num(10^(-3:1) * 123, notation = "si")
)
#> # A tibble: 5 x 6
#>          x4       x1     usd percent      eng      si
#>   <num:.4!> <num:.1>     USD       %    <eng>    <si>
#> 1  800.5000    800.5  800.50    8.05     1e-3 123   m
#> 2  900.5000    900.5  900.50    9.05    10e-3   1.23 
#> 3 1000.5000   1000.5 1000.50   10.0    100e-3  12.3  
#> 4 1100.5000   1100.5 1100.50   11.0   1000e-3 123    
#> 5 1200.5000   1200.5 1200.50   12.0  10000e-3   1.23k

Computing on num

Formatting numbers is useful for presentation of results. If defined early on in the analysis, the formatting options survive most operations. It is worth defining output options that suit your data once early on in the process, to benefit from the formatting throughout the analysis. We are working on seamlessly applying this formatting to the final presentation (plots, tables, …).

Arithmetics

num(1) + 2
#> <pillar_num[1]>
#> [1] 3
1 + num(2)
#> <pillar_num[1]>
#> [1] 3
1L + num(2)
#> <pillar_num[1]>
#> [1] 3
num(3.23456, sigfig = 4) - num(2)
#> <pillar_num:4[1]>
#> [1] 1.235
num(4, sigfig = 2) * num(3, digits = 2)
#> <pillar_num:2[1]>
#> [1] 12
num(3, digits = 2) * num(4, sigfig = 2)
#> <pillar_num:.2![1]>
#> [1] 12.00
-num(2)
#> <pillar_num[1]>
#> [1] -2

Mathematics

min(num(1:3, label = "$"))
#> <pillar_num{$}[1]>
#> [1] 1
mean(num(1:3, notation = "eng"))
#> <pillar_num(eng)[1]>
#> [1] 2e0
sin(num(1:3, label = "%", scale = 100))
#> <pillar_num{%}*100[3]>
#> [1] 84.1 90.9 14.1

Recovery

The var() function is one of the examples where the formatting is lost:

x <- num(c(1, 2, 4), notation = "eng")
var(x)
#> [1] 2.333333

One way to recover is to apply num() to the result:

num(var(x), notation = "eng")
#> <pillar_num(eng)[1]>
#> [1] 2.33e0

For automatic recovery, we can also define our version of var(), or even override the base implementation:

var_ <- function(x, ...) {
  out <- var(x, ...)
  vctrs::vec_restore(out, vctrs::vec_proxy(x))
}
var_(x)
#> <pillar_num(eng)[1]>
#> [1] 2.33e0

This pattern can be applied to all functions that lose the formatting. The make_restore() function defined below is a function factory that consumes a function and returns a derived function:

make_restore <- function(fun) {
  force(fun)
  function(x, ...) {
    out <- fun(x, ...)
    vctrs::vec_restore(out, vctrs::vec_proxy(x))
  }
}

var_ <- make_restore(var)
sd_ <- make_restore(sd)

var_(x)
#> <pillar_num(eng)[1]>
#> [1] 2.33e0
sd_(x)
#> <pillar_num(eng)[1]>
#> [1] 1.53e0
library(units)
#> udunits database from /usr/share/xml/udunits/udunits2.xml

set_units.pillar_num <- function(x, ...) {
  unclassed <- x
  class(unclassed) <- NULL
  set_units(unclassed, ...)
}

m <- set_units(1:3, m)
km <- set_units(1:3, km)

tibble(
  sci_int = set_num_opts(m + km, notation = "sci"),
  digits_int = set_num_opts(km + m, digits = 4),
  sci_ext = set_units(num(1:3 + 0.001, notation = "sci"), km)
)
#> # A tibble: 3 x 3
#>   sci_int digits_int sci_ext
#>       [m]       [km]    [km]
#> 1  1.00e3     1.0010  1.00e0
#> 2  2.00e3     2.0020  2.00e0
#> 3  3.00e3     3.0030  3.00e0

tibble(
  sci_int = set_num_opts(m, notation = "sci") + km,
  digits_int = set_num_opts(km, digits = 4) + m,
  sci_ext = set_units(num(1:3, notation = "sci"), m) + km
)
#> # A tibble: 3 x 3
#>   sci_int digits_int sci_ext
#>       [m]       [km]     [m]
#> 1  1.00e3     1.0010  1.00e3
#> 2  2.00e3     2.0020  2.00e3
#> 3  3.00e3     3.0030  3.00e3

formattable

library(formattable)

pillar_shaft.formattable <- function(x, ...) {
  pillar::new_pillar_shaft_simple(format(x), align = "right")
}

pillar_shaft.formattable_currency <- function(x, ...) {
  formattable <- attr(x, "formattable")

  pillar_shaft(num(unclass(x), digits = formattable$digits))
}

pillar_shaft.formattable_percent <- function(x, ...) {
  formattable <- attr(x, "formattable")

  pillar_shaft(num(unclass(x), digits = formattable$digits, label = "%", scale = 100))
}

pillar_shaft.formattable_scientific <- function(x, ...) {
  pillar_shaft(num(unclass(x), notation = "sci"))
}

type_sum.formattable <- function(x) {
  formattable <- attr(x, "formattable")

  if (inherits(x, "formattable_currency")) {
    I(sub("^formattable_", "", class(x)[[1]]))
  } else if (inherits(x, "formattable_percent")) {
    I("%")
  } else {
    abbreviate(sub("^formattable_", "", class(x)[[1]]), 4)
  }
}

num_currency(1:3 * 100 + 0.1)
#> [1] $100.10 $200.10 $300.10
num_percent(1:3 * 0.1 + 0.001)
#> [1] 10.10% 20.10% 30.10%
num_scientific(1:3 * 0.1 + 0.001)
#> [1] 1.0100e-01 2.0100e-01 3.0100e-01

tibble(
  currency = num_currency(1:3 * 100 + 0.1),
  percent = num_percent(1:3 * 0.1 + 0.001),
  scientific = num_scientific(1:3 * 0.1 + 0.001)
)
#> # A tibble: 3 x 3
#>   currency percent scientific
#>          $       %     <scnt>
#> 1     100.    10.1    1.01e-1
#> 2     200.    20.1    2.01e-1
#> 3     300.    30.1    3.01e-1

scales

library(scales)
#> 
#> Attaching package: 'scales'
#> The following objects are masked from 'package:formattable':
#> 
#>     comma, percent, scientific

x <- num(1:10 / 100, label = "%", scale = 100)

scales::squish(x)
#> <pillar_num{%}*100[10]>
#>  [1]  1  2  3  4  5  6  7  8  9 10

x < 0
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
x < 0L
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

scales::cscale(x, scales::rescale_pal())
#> Error in UseMethod("rescale"): no applicable method for 'rescale' applied to an object of class "c('pillar_num', 'pillar_vctr', 'vctrs_vctr', 'double')"

ggplot2

library(ggplot2)

scale_type.pillar_num <- function(x, ...) {
  "continuous"
}

data.frame(x = x, y = 1:10) %>%
  ggplot(aes(x = x, y = y)) %>%
  + geom_point()

Rule-based decoration

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following object is masked from 'package:pillar':
#> 
#>     dim_desc
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

data_units <-
  palmerpenguins::penguins %>%
  mutate(across(ends_with("_mm"), set_units, "mm")) %>%
  mutate(across(ends_with("_g"), set_units, "g"))

data_units %>%
  mutate(bill_area = bill_length_mm * bill_depth_mm, .after = island)
#> # A tibble: 344 x 9
#>    species island    bill_area bill_length_mm bill_depth_mm flipper_length_mm
#>    <fct>   <fct>        [mm^2]           [mm]          [mm]              [mm]
#>  1 Adelie  Torgersen      731.           39.1          18.7               181
#>  2 Adelie  Torgersen      687.           39.5          17.4               186
#>  3 Adelie  Torgersen      725.           40.3          18                 195
#>  4 Adelie  Torgersen       NA            NA            NA                  NA
#>  5 Adelie  Torgersen      708.           36.7          19.3               193
#>  6 Adelie  Torgersen      810.           39.3          20.6               190
#>  7 Adelie  Torgersen      692.           38.9          17.8               181
#>  8 Adelie  Torgersen      768.           39.2          19.6               195
#>  9 Adelie  Torgersen      617.           34.1          18.1               193
#> 10 Adelie  Torgersen      848.           42            20.2               190
#> # … with 334 more rows, and 3 more variables: body_mass_g [g], sex <fct>,
#> #   year <int>
data_decor <-
  data_units %>%
  decorate(year, digits = 0) %>%
  decorate(where(is.numeric), digits = 3)
data_decor %>%
  mutate(bill_area = bill_length_mm * bill_depth_mm, .after = island)
#> # A tibble: 344 x 9
#>    species island    bill_area bill_length_mm bill_depth_mm flipper_length_mm
#>    <fct>   <fct>        [mm^2]           [mm]          [mm]              [mm]
#>  1 Adelie  Torgersen   731.170         39.100        18.700           181.000
#>  2 Adelie  Torgersen   687.300         39.500        17.400           186.000
#>  3 Adelie  Torgersen   725.400         40.300        18.000           195.000
#>  4 Adelie  Torgersen    NA             NA            NA                NA    
#>  5 Adelie  Torgersen   708.310         36.700        19.300           193.000
#>  6 Adelie  Torgersen   809.580         39.300        20.600           190.000
#>  7 Adelie  Torgersen   692.420         38.900        17.800           181.000
#>  8 Adelie  Torgersen   768.320         39.200        19.600           195.000
#>  9 Adelie  Torgersen   617.210         34.100        18.100           193.000
#> 10 Adelie  Torgersen   848.400         42.000        20.200           190.000
#> # … with 334 more rows, and 3 more variables: body_mass_g [g], sex <fct>,
#> #   year <int>

`````