Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rounding currency formatted numbers in DT

Tags:

r

formatting

dt

I'm trying to get the numbers in a currency format and then round them but I get unexpected behaviour from DT (v 0.1).

I want the values looking like 808084.227872401 into £808,084.2

Here's the code:

library(DT)

m <- structure(list(A = c(808084.227872401, 1968554.9592654, 751271.053745238, 
-248530.769710688, 1022891.09543523, -407303.626363765), B = c(143073.342325492, 
-1440469.87343229, -590080.736184761, -608299.78907882, 1167155.65688074, 
803870.898483576), C = c(-447086.9382469, 606572.488852836, 89371.3745637198, 
-1496047.6143101, -410103.544644035, 1106358.3287006), D = c(0.754009573487565, 
0.364774209912866, 0.525769896339625, 0.44853704655543, 0.909551323624328, 
0.439131782157347), E = c(98.8604132297185, 98.9055931760521, 
99.3795062166865, 98.5895350315005, 101.194549174315, 102.325111315431
)), .Names = c("A", "B", "C", "D", "E"), row.names = c(NA, -6L
), class = "data.frame")

According to documentation this should work:

datatable(m) %>% formatCurrency("A", "£", digits = 1)

But I get the following error:

Error in formatCurrency(., "A", "£", digits = 1) : unused argument (digits = 1)

Then I tried another command:

datatable(m) %>% formatCurrency("A", "£") %>% formatRound("A", 1)

But it only formatted the currency without rounding it.

enter image description here

Any ideas?

PS. I'm aware of this answer, but I don't want to display strings because I want to sort the numbers when displayed with datatable.

like image 478
epo3 Avatar asked May 13 '16 12:05

epo3


2 Answers

My conclusion is that you cannot add 2 formatters to the same column with DT, although of course I could be wrong.

Note that, even this is not explicitly stated in the documentation of DT, only one formatter can be added per column table. Note also that on the examples in the link you provide, or when you type ?formatCurrency, when they include two pipes %>%, they always affect to two different columns.

In your example, when you do

datatable(m) %>% formatRound("A", digits=1) %>% formatCurrency("A", currency="£")

the result is round for 1 digit with no currency, and if you do

datatable(m) %>% formatCurrency("A", currency="£") %>% formatRound("A", digits=1)

the result is the currency added with no rounding.

My knowledge on how R integrates with js is very limited, but looking at the R sources for the package in cran, it looks like every format command in the pipe appends a formatter, but for some reason only one formatter comes into play:

formatCurrency = function(table, columns, currency = '$', interval = 3, mark = ',') {
  formatColumns(table, columns, tplCurrency, currency, interval, mark)
}

formatRound = function(table, columns, digits = 2) {
  formatColumns(table, columns, tplRound, digits)
}

formatColumns = function(table, columns, template, ...) {
  ...
  x$options$rowCallback = appendFormatter(
    x$options$rowCallback, columns, colnames, rownames, template, ...
  )
  ...
}

appendFormatter = function(js, name, names, rownames = TRUE, template, ...) {
  ...
  JS(append(
    js, after = 1,
    template(i, ...)
  ))
}

where every formatter ends up calling formatColumns with a different template, and i resolves an id for the column. As I said, I have no idea if this is because the append operations overwrites the formatter, or if it has to do with the execution.


EDIT: Sorry I accidentally pressed the post button and got interruped. I actually implemented a formatter that takes more arguments. The solution is a bit convoluted but it works. This is a formatter that accepts both currency and digits:

tplRound2 = function(cols, currency, digits) {
  sprintf(
    "var d = parseFloat(data[%d]); $(this.api().cell(row, %s).node()).html(isNaN(d) ? '' : '%s' + d.toFixed(%d).toString());",
    cols, cols, currency, digits
  )
}

You need to add all these functions to your session:

formatRound2 = function(table, columns, currency, digits = 2) {
  formatColumns2(table, columns, tplRound2, currency, digits)
}

formatColumns2 = function(table, columns, template, ...) {
  if (inherits(columns, 'formula')) columns = all.vars(columns)
  x = table$x
  colnames = base::attr(x, 'colnames', exact = TRUE)
  rownames = base::attr(x, 'rownames', exact = TRUE)
  x$options$rowCallback = appendFormatter2(
    x$options$rowCallback, columns, colnames, rownames, template, ...
  )
  table$x = x
  table
}

name2int = function(name, names) {
  if (is.numeric(name)) {
    return(if (all(name > 0)) name else seq_along(names)[name])
  }
  names = setNames(seq_along(names), names)
  unname(names[name])
}

appendFormatter2 = function(js, name, names, rownames = TRUE, template, ...) {
  js = if (length(js) == 0) c('function(row, data) {', '}') else {
    unlist(strsplit(as.character(js), '\n'))
  }
  i = name2int(name, names)
  if (is.character(name) || (is.numeric(name) && !rownames)) i = i - 1
  if (any(is.na(i))) stop(
    'You specified the columns: ', paste(name, collapse = ', '), ', ',
    'but the column names of the data are ', paste(names, collapse = ', ')
  )
  JS(append(
    js, after = 1,
    template(i, ...)
  ))
}

And then you can run with the new formatter to get the desired result:

datatable(m) %>% formatRound2("A", "£", digits=1)

(However this does not add the , every 3 digits, if you really need it I could add it to the formatter...)


EDIT2 after comments:

This would be the formatter function for using both currency and number of digits, plus the ',' marks:

tplRound3 = function(cols, currency, digits, interval, mark) {
  sprintf(
    "var d = parseFloat(data[%d]); $(this.api().cell(row, %s).node()).html(isNaN(d) ? '' : '%s' + d.toFixed(%d).toString().replace(/\\B(?=(\\d{%d})+(?!\\d))/g, '%s'));",
    cols, cols, currency, digits, interval, mark
  )
}


formatRound3 = function(table, columns, currency, digits = 2, interval=3, mark=',') {
  formatColumns2(table, columns, tplRound3, currency, digits, interval, mark)
}

In order to use it just type

datatable(m) %>% formatRound3("A", "£", digits=1)
like image 143
lrnzcig Avatar answered Sep 30 '22 13:09

lrnzcig


After some research i figured that there is way to accomplish this within formatCurrency()

Changing the code to below will solve the issue:

datatable(m) %>% formatCurrency("A", '\U20AC', digits = 1)

like image 27
Sudhakar Samak Avatar answered Sep 30 '22 13:09

Sudhakar Samak