Formattable data frames are data frames to be rendered as HTML table with formatter functions applied, which resembles conditional formatting in Microsoft Excel.

## Simple examples

Suppose we have the following data frame:

    scores <- data.frame(id = 1:5,
      prev_score = c(10, 8, 6, 8, 8),
      cur_score = c(8, 9, 7, 8, 9),
      change = c(-2, 1, 1, 0, 1))

In the console, it is printed as plain texts:

    scores

    ##   id prev_score cur_score change
    ## 1  1         10         8     -2
    ## 2  2          8         9      1
    ## 3  3          6         7      1
    ## 4  4          8         8      0
    ## 5  5          8         9      1

Using `knitr::kable()` or `formattable()`, the data frame can be rendered as HTML table which looks more friendly.

    library(formattable)
    formattable(scores)

id | prev_score | cur_score | change
--- | --- | --- | ---
1 | 10 | 8 | -2
2 | 8 | 9 | 1
3 | 6 | 7 | 1
4 | 8 | 8 | 0
5 | 8 | 9 | 1

In fact, `formattable()` calls `knitr::kable()` internally to translate data frame to HTML code. In addition, `formattable()` supports formatter functions to customize the transformation between values in the data frame to HTML code to generate.

    plain_formatter <- formatter("span")
    plain_formatter(c(1, 2, 3))

    ## [1] "<span>1</span>" "<span>2</span>" "<span>3</span>"

    width_formatter <- formatter("span",
      style = x ~ style(width = suffix(x, "px")))
    width_formatter(c(10, 11, 12))

    ## [1] "<span style=\"width: 10px\">10</span>"
    ## [2] "<span style=\"width: 11px\">11</span>"
    ## [3] "<span style=\"width: 12px\">12</span>"

The values of `change` can be positive, negative or zero. We can make positives green, negatives red, and zeros black by creating a formatter function that performs conditional transformation from value to HTML code.

    sign_formatter <- formatter("span",
      style = x ~ style(color = ifelse(x > 0, "green",
        ifelse(x < 0, "red", "black"))))
    sign_formatter(c(-1, 0, 1))

    ## [1] "<span style=\"color: red\">-1</span>"
    ## [2] "<span style=\"color: black\">0</span>"
    ## [3] "<span style=\"color: green\">1</span>"

Note that we don't have to write HTML but use helper functions like `style()` and `ifelse()` to make it easier to specify conditions. Then we call `formattable()` on the data frame with a list of formatter functions so as to apply conditional formatting.

    formattable(scores, list(change = sign_formatter))

id | prev_score | cur_score | change
--- | --- | --- | ---
1 | 10 | 8 | -2
2 | 8 | 9 | 1
3 | 6 | 7 | 1
4 | 8 | 8 | 0
5 | 8 | 9 | 1

We can also create another formatter function that makes above-average values bold while leaving others unchanged.

    above_avg_bold <- formatter("span",
      style = x ~ style("font-weight" = ifelse(x > mean(x), "bold", NA)))
    formattable(scores, list(
      prev_score = above_avg_bold,
      cur_score = above_avg_bold,
      change = sign_formatter))

id | prev_score | cur_score | change
--- | --- | --- | ---
1 | 10 | 8 | -2
2 | 8 | 9 | 1
3 | 6 | 7 | 1
4 | 8 | 8 | 0
5 | 8 | 9 | 1

## Cross formatting

Sometimes, we need to format one column based on the values of another column. This can be easily done with one-sided formula in `formatter()`. When using `formatter("span", style = ~ expr)`, `expr` is evaluated in the data frame so that all columns are available for use.

    formattable(scores, list(
      cur_score = formatter("span",
        style = ~ style(color = ifelse(change >= 0, "green", "red")))))

id | prev_score | cur_score | change
--- | --- | --- | ---
1 | 10 | 8 | -2
2 | 8 | 9 | 1
3 | 6 | 7 | 1
4 | 8 | 8 | 0
5 | 8 | 9 | 1

## Hiding columns

To hide columns, use `FALSE` formatter.

    formattable(scores, list(prev_score = FALSE))

id | cur_score | change
--- | --- | ---
1 | 8 | -2
2 | 9 | 1
3 | 7 | 1
4 | 8 | 0
5 | 9 | 1

## Using built-in formatters

To making formatting easier, formattable package provides a group of built-in formatter functions. Suppose we have the following data on a number of products. Some columns are already formattable vectors.

    products <- data.frame(id = 1:5,
      price = c(10, 15, 12, 8, 9),
      rating = c(5, 4, 4, 3, 4),
      market_share = percent(c(0.1, 0.12, 0.05, 0.03, 0.14)),
      revenue = accounting(c(55000, 36400, 12000, -25000, 98100)),
      profit = accounting(c(25300, 11500, -8200, -46000, 65000)))
    products

    ##   id price rating market_share     revenue      profit
    ## 1  1    10      5       10.00%   55,000.00   25,300.00
    ## 2  2    15      4       12.00%   36,400.00   11,500.00
    ## 3  3    12      4        5.00%   12,000.00  (8,200.00)
    ## 4  4     8      3        3.00% (25,000.00) (46,000.00)
    ## 5  5     9      4       14.00%   98,100.00   65,000.00

Without any formatter functions applied, the formattable data frame is directly rendered as an HTML table.

    formattable(products)

id | price | rating | market_share | revenue | profit
--- | --- | --- | --- | --- | ---
1 | 10 | 5 | 10.00% | 55,000.00 | 25,300.00
2 | 15 | 4 | 12.00% | 36,400.00 | 11,500.00
3 | 12 | 4 | 5.00% | 12,000.00 | (8,200.00)
4 | 8 | 3 | 3.00% | (25,000.00) | (46,000.00)
5 | 9 | 4 | 14.00% | 98,100.00 | 65,000.00

We can supply a list of formatter functions to make it look more colorful. For example, we apply `sign_formatter` to `profit` column so that values of different signs are displayed in different colors.

    formattable(products, list(profit = sign_formatter))

id | price | rating | market_share | revenue | profit
--- | --- | --- | --- | --- | ---
1 | 10 | 5 | 10.00% | 55,000.00 | 25,300.00
2 | 15 | 4 | 12.00% | 36,400.00 | 11,500.00
3 | 12 | 4 | 5.00% | 12,000.00 | (8,200.00)
4 | 8 | 3 | 3.00% | (25,000.00) | (46,000.00)
5 | 9 | 4 | 14.00% | 98,100.00 | 65,000.00

Using built-in functions like `color_tile()` and `color_bar()` makes it easier to compare the magnitute of values of specified columns.

    formattable(products, list(
      price = color_tile("transparent", "lightpink"),
      rating = color_bar("lightgreen"),
      market_share = color_bar("lightblue"),
      revenue = sign_formatter,
      profit = sign_formatter))

id | price | rating | market_share | revenue | profit
--- | --- | --- | --- | --- | ---
1 | 10 | 5 | 10.00% | 55,000.00 | 25,300.00
2 | 15 | 4 | 12.00% | 36,400.00 | 11,500.00
3 | 12 | 4 | 5.00% | 12,000.00 | (8,200.00)
4 | 8 | 3 | 3.00% | (25,000.00) | (46,000.00)
5 | 9 | 4 | 14.00% | 98,100.00 | 65,000.00

## Area formatting

Sometimes, it is useful to apply a formatter function to an area so that all cells in the area share one benchmark. Area formatting is supported through the syntax of `area(row, col) ~ formatter` in the formatter list.

The following example renders the three columns altogether so that they share the same benchmark, not independently.

    set.seed(123)
    df <- data.frame(id = 1:10,
      a = rnorm(10), b = rnorm(10), c = rnorm(10))
    formattable(df, list(area(col = a:c) ~ color_tile("transparent", "pink")))

id | a | b | c
--- | --- | --- | ---
1 | -0.56047565 | 1.2240818 | -1.0678237
2 | -0.23017749 | 0.3598138 | -0.2179749
3 | 1.55870831 | 0.4007715 | -1.0260044
4 | 0.07050839 | 0.1106827 | -0.7288912
5 | 0.12928774 | -0.5558411 | -0.6250393
6 | 1.71506499 | 1.7869131 | -1.6866933
7 | 0.46091621 | 0.4978505 | 0.8377870
8 | -1.26506123 | -1.9666172 | 0.1533731
9 | -0.68685285 | 0.7013559 | -1.1381369
10 | -0.44566197 | -0.4727914 | 1.2538149

If a one-sided formula is supplied, the function will be applied to all cells.

    formattable(df[, -1], list(~ percent))

a | b | c
--- | --- | ---
-56.05% | 122.41% | -106.78%
-23.02% | 35.98% | -21.80%
155.87% | 40.08% | -102.60%
7.05% | 11.07% | -72.89%
12.93% | -55.58% | -62.50%
171.51% | 178.69% | -168.67%
46.09% | 49.79% | 83.78%
-126.51% | -196.66% | 15.34%
-68.69% | 70.14% | -113.81%
-44.57% | -47.28% | 125.38%

# Dynamically generating formatters

Since `formattable()` accepts a list of formatter functions, the list can be dynamically generated. For example, the following code applies row-wise formatting, that is, each row is colored independently.

    df <- cbind(data.frame(id = 1:10),
      do.call(cbind, lapply(1:8, function(x) rnorm(10))))
    formattable(df, lapply(1:nrow(df), function(row) {
      area(row, col = -1) ~ color_tile("lightpink", "lightblue")
    }))

id | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
--- | --- | --- | --- | --- | --- | --- | --- | ---
1 | 0.42646422 | -0.69470698 | 0.25331851 | 0.37963948 | -0.4910312 | 0.005764186 | 0.9935039 | -0.71040656
2 | -0.29507148 | -0.20791728 | -0.02854676 | -0.50232345 | -2.3091689 | 0.385280401 | 0.5483970 | 0.25688371
3 | 0.89512566 | -1.26539635 | -0.04287046 | -0.33320738 | 1.0057385 | -0.370660032 | 0.2387317 | -0.24669188
4 | 0.87813349 | 2.16895597 | 1.36860228 | -1.01857538 | -0.7092008 | 0.644376549 | -0.6279061 | -0.34754260
5 | 0.82158108 | 1.20796200 | -0.22577099 | -1.07179123 | -0.6880086 | -0.220486562 | 1.3606524 | -0.95161857
6 | 0.68864025 | -1.12310858 | 1.51647060 | 0.30352864 | 1.0255714 | 0.331781964 | -0.6002596 | -0.04502772
7 | 0.55391765 | -0.40288484 | -1.54875280 | 0.44820978 | -0.2847730 | 1.096839013 | 2.1873330 | -0.78490447
8 | -0.06191171 | -0.46665535 | 0.58461375 | 0.05300423 | -1.2207177 | 0.435181491 | 1.5326106 | -1.66794194
9 | -0.30596266 | 0.77996512 | 0.12385424 | 0.92226747 | 0.1813035 | -0.325931586 | -0.2357004 | -0.38022652
10 | -0.38047100 | -0.08336907 | 0.21594157 | 2.05008469 | -0.1388914 | 1.148807618 | -1.0264209 | 0.91899661

# Converting to `DT::datatables`

`as.datatable()` is designed to convert a formattable data frame to `DT::datatables`.

    as.datatable(formattable(products))

    ## Loading required namespace: DT

plot of chunk unnamed-chunk-19

Some formatters can be preserved well after the conversion.

    as.datatable(formattable(products, list(
      price = color_tile("transparent", "lightpink"),
      revenue = sign_formatter,
      profit = sign_formatter)))

plot of chunk unnamed-chunk-20