如何在 R 中正确地聚合具有反应性的嵌套行?

How can I correctly aggregate nested rows with reactable in R?

我正在开发一个闪亮的应用程序,其中 reactable 包由于我的数据的嵌套结构而非常有用。它允许我折叠和汇总更高级别类别中的行,并且仅展开以显示所需的 'subrows' if/when。

对于只有 1 个嵌套级别(例如,品牌中的汽车模型),来自 reactable 的聚合函数(uniquecount 等)开箱即用.然而,当添加额外的嵌套级别时,事情就会崩溃,甚至像 unique 这样的聚合器也会呈现重复的值(!)。我怀疑这是因为子类别并没有全部汇集在一个平面结构中,只对所有子类别执行 1 个聚合步骤,而是唯一值保持子类别特定,然后只是连接在一起,导致重复。此问题也影响其他聚合器,而不仅仅是 unique

我在下面的 R 中添加了一个 MWE,因为我一直无法解决这个问题。由于 JS 也远不是我的强项,我一直无法插入任何 JS 来更灵活地解决这个问题,正如建议的那样 here。我怎样才能调整下面的聚合器以获得正确显示的输出(即不重复)?

library(shiny)
library(shinydashboard)
library(reactable)
library(stringr)

if (interactive()) {
  
  ui <- shinyUI(basicPage(
    box(title = "mtcars data",
        width = 12,
        reactableOutput("car_tab")
    )
  ))
  
  server <- function(input, output, session) {
    output$car_tab <- renderReactable({
      
      df <- mtcars
      df$make <- str_extract(rownames(df), "[[:alpha:]]+")
      df$model <- rownames(df)
      rownames(df) <- NULL
      df <- df[ , c("make", "model", "mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear", "carb")]
      
      reactable(df,
                groupBy = c("cyl", "am", "make"),
                columns = list(
                  cyl = colDef(name = "Number of cylinders"),
                  am = colDef(name = "Transmission",
                              aggregate = "frequency"),
                  make = colDef(name = "Make",
                                aggregate = "frequency"),
                  model  = colDef(name = "Model",
                                  aggregate = "unique"),
                  mpg = colDef(name = "Miles/gallon",
                               aggregate = "mean",
                               format = colFormat(digits = 2)),
                  disp = colDef(name = "Displacement"),
                  hp = colDef(name = "Horsepower"),
                  drat = colDef(name = "Rear axle ratio"),
                  wt = colDef(name = "Weight"),
                  qsec = colDef(name = "1/4 mile time"),
                  vs = colDef(name = "Engine",
                              aggregate = "count"),
                  gear = colDef(name = "Number of forward gears"),
                  carb = colDef(name = "Number of carburetors")
                )
      )
    })
  }
  
  shinyApp(ui = ui, server = server)
  
}

要获得0(3), 1(8),您需要

groupBy = c("cyl", "am")

我认为这可以通过使用聚合器来解决,因为您刚才正在使用它们,然后通过 colDefaggregated 参数提供自定义 JavaScript 渲染器。这个想法是 reactable 将通过内置运算符进行聚合,并将使用自定义渲染器在聚合单元格中渲染输出。

自定义 JavaScript 渲染器应该采用看起来像 0, 0(2), 1(6), 1(2) 的字符串并进行一些字符串操作以产生像 0(3), 1(8).

这样的输出

如果像这样定义JavaScript函数并保存在R变量中,就可以重复使用:

jsRenderer <- "
    function(cellInfo) {
        const handleBrackets = (item) => {
            const currentName = item.replace(/\([0-9]+\)/g, '')
            const currentCountsArr = item.match(/\(([0-9]+)\)/)
            let currentCount = 1
            if (currentCountsArr && currentCountsArr.length === 2) {
                currentCount = parseInt(currentCountsArr[1], 10)
            }
            return {
                currentName,
                currentCount
            }
        }

        const getCounts = (input) => {
            const trimmedInput = input.replace(/\s+/g, '')
            const items = trimmedInput.split(',')
            const namesWithoutBrackets = trimmedInput.replace(/\(([0-9]+)\)/g, '').split(',')
            const itemObj = items.reduce((prev, current, index) => {
                const itemWithoutBrackets = handleBrackets(current)
                let {
                    currentName,
                    currentCount
                } = itemWithoutBrackets

                if (namesWithoutBrackets.indexOf(currentName) !== index) {
                    currentCount += prev[currentName]
                }

                return {
                    ...prev,
                    ...{
                        [currentName]: currentCount
                    }
                }

            }, {})

            const stringToSanitize = Object.entries(itemObj).reduce((prevString, currentKeyValue) => {
                return prevString.concat(`${currentKeyValue[0]}(${currentKeyValue[1]}), `)
            }, '')

            return stringToSanitize.slice(0, -2)
        }

        return (getCounts(cellInfo.value))
    }
"

然后您可以像这样将此渲染提供给 colDef

colDef(name = "Transmission", aggregate = "frequency", aggregated = jsRenderer)

在您的 MWE 中,结果显示如下:

Merc(2),
Toyota(2),
Datsun(1),
Fiat(2),
Honda(1),
Porsche(1),
Lotus(1),
Volvo(1)