在DT中对货币格式化的数字进行四舍五入

7

我正在尝试以货币格式获取数字,然后将它们四舍五入,但是我从 DT(v 0.1)中得到了意外的行为。

我希望像 808084.227872401 这样的值变成 £808,084.2

以下是代码:

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")

根据文档,这应该可以工作:
datatable(m) %>% formatCurrency("A", "£", digits = 1)

但我遇到了以下错误:

在 formatCurrency(., "A", "£", digits = 1) 中出现错误:未使用的参数 (digits = 1)

然后我尝试了另一条命令:

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

但它只是格式化了货币,而没有将其四舍五入。

enter image description here

有什么想法吗?

PS. 我知道 this 的答案,但我不想显示字符串,因为我想在 datatable 中显示并排序数字。


1
为什么不先在该列上使用常规的“round”函数,然后再将其输入到“datatable”中呢? - Gopala
我不想改变底层数据,只想改变它的显示方式。此外,我想知道在使用DT时发生了什么。 - epo3
2个回答

7
我的结论是你不能使用DT将两个格式化器添加到同一列中,尽管当然我可能错了。
请注意,即使在DT的文档中没有明确说明,每列表格只能添加一个格式化器。还要注意,在您提供的链接示例或输入?formatCurrency时,当它们包含两个管道符号%>%时,它们总是影响两个不同的列。
在您的示例中,当您执行以下操作时:
datatable(m) %>% formatRound("A", digits=1) %>% formatCurrency("A", currency="£")

结果将保留一位小数且不带货币符号,如下所示:
datatable(m) %>% formatCurrency("A", currency="£") %>% formatRound("A", digits=1)

结果是货币加上没有舍入的数字。
我对R如何与js集成的了解非常有限,但是看着cran软件包中的R源代码,似乎每个管道中的格式命令都会追加一个格式化程序,但由于某种原因只有一个格式化程序起作用:
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, ...)
  ))
}

每个格式化程序最终都会调用formatColumns,并使用不同的template,而i会解析列的id。正如我所说,我不知道这是因为追加操作覆盖了格式化程序,还是与执行有关。
< p > < em >编辑:抱歉我不小心按下了发布按钮并被打断了。实际上,我实现了一个接受更多参数的格式化程序。解决方案有点复杂,但它可以工作。这是一个接受货币和数字的格式化程序:
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
  )
}

你需要将所有这些函数添加到你的会话中:

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, ...)
  ))
}

然后您可以使用新的格式化程序运行,以获得所需的结果:

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

(但是这不会在每三个数字后添加,,如果您真的需要,我可以将其添加到格式化程序中...)

评论后的EDIT2:

这将是同时使用货币和数字位数以及','标记的格式化函数:

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)
}

为了使用它,只需输入

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

抱歉 @epo3,我不小心按下了“发布”按钮,已经编辑了帖子并提供了一种比较复杂的解决方案。 - lrnzcig
好答案。如果您可以在每3个数字后添加一个逗号,那就太好了。这将使函数更完整。我想您可以建议改进“DT”。将其添加到包中会非常有用。 - epo3
1
非常欢迎。我已经更新了答案,包括每三位数字一个逗号的格式化程序。也许我会向 DT 提交一个 PR。谢谢。 - lrnzcig
@lrnzcig 很好的回答!对于如何将规模的缩写显示为“£13.4 k”、“-£98.5 mil”、“£53.5 bil”或“-£42.2 mil”或仅为“-£7.43”,同时仍允许在DT中进行数字排序,您有什么想法吗? - h.l.m
真是个好答案。 - yeahman269

3
在进行了一些研究之后,我发现可以通过formatCurrency()函数来实现这个目标。
将代码更改为以下内容将解决问题:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接