使用
datatable
中的shiny
,我遇到了以下有关数字列格式和值排序的问题。
我希望根据给定的格式显示数值,例如
X.000
3 位数字令人困惑使用某种格式来操作数值,表中的顺序被破坏,因为值是作为字符串而不是数值进行比较的。我发现数据表的给定语言选项将告诉它如何根据给定的规范处理列,但这似乎不起作用。
此外,
DT
提供了formatRound
方法,该方法在数字格式化和排序有效的意义上起作用,但是例如始终显示固定数量的数字,这对于整数来说是令人困惑的。但整数也应该格式化,因为它们可能很大,并且像“,”这样的标记将有助于掌握数字。
以下应用程序说明了问题
library(shiny)
markerDecimal <- ","
markerThousand <- "."
df <- data.frame(
"numCol" = c(1900,2.111,0.9),
"someStrings" = c("A","BB","CCC")
)
ui <- fluidPage(
h4("Original: No format, numeric sorting works, but numbers are not displayed like I want them to be displayed"),
DT::dataTableOutput(outputId = "dtNoFormat"),
h4("Format using base::prettyNum destroying 'numeric behaviour' because of conversion to string"),
DT::dataTableOutput(outputId = "dtFormat1"),
h4("Format using DT::formatRound keeping ordering but showing too much decimals that are not required"),
DT::dataTableOutput(outputId = "dtFormat2")
)
server <- function(input, output, session) {
.formatNumber <- function(x) {
base::prettyNum(x = x,
big.mark = markerThousand, big.interval = 3,
decimal.mark = markerDecimal,
small.mark = "", small.interval = 3)
}
.formatDfNums <- function(df, nams = names(df)) {
numNames <- names(df)[sapply(df, is.numeric)]
nams2 <- intersect(numNames, nams)
if(length(nams2)) df[nams2] <- lapply(df[nams2], .formatNumber)
return(df)
}
defaultOps <- list(pageLength = 20,
bPaginate = FALSE, bSearch = FALSE)
output$dtNoFormat <- DT::renderDataTable({
DT::datatable(df, options = defaultOps)
}, server = FALSE)
output$dtFormat1 <- DT::renderDataTable({
DT::datatable(
df |> .formatDfNums(),
options = c(defaultOps,
list(language.thousands = markerThousand,
language.decimal = markerDecimal,
columnDefs = list(list(targets = "numCol",
type = "num-fmt")))))
}, server = FALSE)
output$dtFormat2 <- DT::renderDataTable({
DT::datatable(df, options = defaultOps) |>
DT::formatRound(
columns = names(df)[sapply(df, is.numeric)],
digits = 2,
interval = 3,
mark = markerThousand,
dec.mark = markerDecimal
)
}, server = FALSE)
}
shinyApp(ui, server, options = list(launch.browser = TRUE))
如何更新应用程序,以便使用给定的格式显示数字,例如自定义千位和小数,并且排序保留为数字排序?
您可以使用
DT::formatSignif
效果更好。但是,它可能使用科学记数法,您可能也想避免使用科学记数法。
您最好的选择是提供自定义格式化程序。如果您查看DT::formatSignif
但是你可以利用它并执行以下操作:
DTWidget
formatSignif2
,它基本上是
DT::formatSignif
的克隆,但调用您的自定义格式化程序而不是预定义的格式化程序。
formatSignif2
。
library(shiny)
library(jsonlite)
library(htmlwidgets)
markerDecimal <- ","
markerThousand <- "."
js <- HTML("
$(function() {
DTWidget.formatSignif2 = function (data, digits, interval, mark, decMark, zeroPrint) {
const value = parseFloat(data);
if (isNaN(value)) {
return '';
}
if (zeroPrint !== null && value === 0.0) {
return zeroPrint;
}
// Round to significant digits
let roundedValue = Number(value).toPrecision(digits);
// Convert scientific notation
if (roundedValue.includes('e')) {
const [base, exponent] = roundedValue.split('e').map(Number);
const mantissa = base * Math.pow(10, exponent);
const base10 = Math.log10(Math.abs(mantissa));
roundedValue = mantissa.toFixed(Math.max(0, digits - Math.floor(base10) + 1));
}
// Remove trailing zeros by parsing back to a number
roundedValue = parseFloat(roundedValue).toString();
// Replace decimal marker with own marker
let [integerPart, fractionalPart] = roundedValue.split('.');
// Add thousand separators to the integer part
const re = new RegExp('\\\\B(?=(\\\\d{' + interval + '})+(?!\\\\d))', 'g');
integerPart = integerPart.replace(re, mark);
return fractionalPart ? `${integerPart}${decMark}${fractionalPart}` : integerPart;
}
})
")
formatSignif2 <- function(table, columns, digits = 2, interval = 3, mark = ",",
dec.mark = getOption("OutDec"), zero.print = NULL,
rows = NULL) {
tplSignif2 <- function(digits, interval, mark, dec.mark, zero.print, ...) {
sprintf("DTWidget.formatSignif2(data, %d, %d, %s, %s, %s);",
digits, interval, toJSON(unbox(mark)),
toJSON(unbox(dec.mark)),
if (is.null(zero.print)) "null" else toJSON(unbox(zero.print)))
}
DT:::formatColumns(table, columns, tplSignif2, digits, interval,
mark, dec.mark, zero.print, rows = rows)
}
df <- data.frame(
"numCol" = c(1900,2.111,0.9),
"someStrings" = c("A","BB","CCC")
)
ui <- fluidPage(
tags$head(tags$script(js)),
h1("Default formatSignif"),
DT::dataTableOutput(outputId = "dtFormat1"),
h1("Custom formatSignif"),
DT::dataTableOutput(outputId = "dtFormat2")
)
server <- function(input, output, session) {
defaultOps <- list(pageLength = 20,
bPaginate = FALSE, bSearch = FALSE)
output$dtFormat1 <- DT::renderDataTable({
DT::datatable(df, options = defaultOps) |>
DT::formatSignif(
columns = names(df)[sapply(df, is.numeric)],
digits = 2,
interval = 3,
mark = markerThousand,
dec.mark = markerDecimal
)
}, server = FALSE)
output$dtFormat2 <- DT::renderDataTable({
DT::datatable(df, options = defaultOps) |>
formatSignif2(
columns = names(df)[sapply(df, is.numeric)],
digits = 2,
interval = 3,
mark = markerThousand,
dec.mark = markerDecimal
)
}, server = FALSE)
}
shinyApp(ui, server, options = list(launch.browser = TRUE))
两种解决方案都保持了数字的内在顺序,并且自定义函数避免了科学记数法,但使用了internalDT
函数,必须小心使用。您可能希望进一步调整自定义格式化程序以满足您的需求(尤其要考虑数字量),但这应该可以帮助您继续前进。