闪亮的数据表自定义数字样式和排序

问题描述 投票:0回答:1

使用

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

在一张图片中: enter image description here

如何更新应用程序,以便使用给定的格式显示数字,例如自定义千位和小数,并且排序保留为数字排序?

r shiny datatable dt
1个回答
0
投票

您可以使用

DT::formatSignif
效果更好。但是,它可能使用科学记数法,您可能也想避免使用科学记数法。

您最好的选择是提供自定义格式化程序。如果您查看DT::formatSignif

的源代码,您会发现在幕后调用了一个自定义 JavaScript 格式化函数这是此格式化程序的源代码)。

我认为这是一种黑客行为,

但是你可以利用它并执行以下操作:

    添加自定义 JS 格式化函数
  1. DTWidget
    
    
  2. 创建一个 R 函数
  3. formatSignif2
    ,它基本上是 
    DT::formatSignif
     的克隆,但调用您的自定义格式化程序而不是预定义的格式化程序。
  4. 在渲染中使用
  5. 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

函数,必须小心使用。

您可能希望进一步调整自定义格式化程序以满足您的需求(尤其要考虑数字量),但这应该可以帮助您继续前进。

© www.soinside.com 2019 - 2024. All rights reserved.