# Controlling display of numbers

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.

    library(pillar)
    library(tibble)

## 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 × 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.05   100e-3  12.3
    #> 4 1100.5000   1100.5 1100.50   11.05  1000e-3 123
    #> 5 1200.5000   1200.5 1200.50   12.05 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.14710 90.92974 14.11200

### 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.333333e0

For automatic recovery, we can also define our version of `var()`, or even overwrite the base implementation. Note that this pattern is still experimental and may be subject to change:

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

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(vctrs::vec_proxy(x), ...)
        vctrs::vec_restore(out, x)
      }
    }

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

    var_(x)
    #> <pillar_num(eng)[1]>
    #> [1] 2.333333e0
    sd_(x)
    #> <pillar_num(eng)[1]>
    #> [1] 1.527525e0

    library(units)
    #> udunits database from /Library/Frameworks/R.framework/Versions/4.1-arm64/Resources/library/units/share/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 × 3
    #>   sci_int digits_int sci_ext
    #>       [m]       [km]    [km]
    #> 1 1.001e3     1.0010 1.001e0
    #> 2 2.002e3     2.0020 2.001e0
    #> 3 3.003e3     3.0030 3.001e0

    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 × 3
    #>   sci_int digits_int sci_ext
    #>       [m]       [km]     [m]
    #> 1 1.001e3     1.0010 1.001e3
    #> 2 2.002e3     2.0020 2.002e3
    #> 3 3.003e3     3.0030 3.003e3

### 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", exact = TRUE)

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

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

      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", exact = TRUE)

      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)
    #> Error in num_currency(1:3 * 100 + 0.1): could not find function "num_currency"
    num_percent(1:3 * 0.1 + 0.001)
    #> Error in num_percent(1:3 * 0.1 + 0.001): could not find function "num_percent"
    num_scientific(1:3 * 0.1 + 0.001)
    #> Error in num_scientific(1:3 * 0.1 + 0.001): could not find function "num_scientific"

    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)
    )
    #> Error in num_currency(1:3 * 100 + 0.1): could not find function "num_currency"

### 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 × 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.17           39.1          18.7               181
    #>  2 Adelie  Torgersen    687.3            39.5          17.4               186
    #>  3 Adelie  Torgersen    725.4            40.3          18                 195
    #>  4 Adelie  Torgersen     NA              NA            NA                  NA
    #>  5 Adelie  Torgersen    708.31           36.7          19.3               193
    #>  6 Adelie  Torgersen    809.58           39.3          20.6               190
    #>  7 Adelie  Torgersen    692.42           38.9          17.8               181
    #>  8 Adelie  Torgersen    768.32           39.2          19.6               195
    #>  9 Adelie  Torgersen    617.21           34.1          18.1               193
    #> 10 Adelie  Torgersen    848.4            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 × 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>

`````