【问题标题】:filter data in shiny app but keeping values in selectInput when updating table在闪亮的应用程序中过滤数据,但在更新表时将值保留在 selectInput
【发布时间】:2020-06-24 19:06:44
【问题描述】:

我有一个闪亮的应用程序,它要求用户上传一个文件(一个带有数据的表格文件),然后它将这个文件呈现到一个表格中,用户可以根据numericInputselectInputtextAreaInput。用户必须选择过滤器,然后按下按钮以过滤表。

没有顺序过滤,即用户可以填写所有过滤器或只填写一个。每次用户选择一个过滤器时,其他过滤器的值都会更新(selectInput 输入),这就是我想要的行为。但是,一旦按下 Filter 按钮,我就看不到之前的选择,也无法重置过滤器。

我想要实现的是在更新过滤器时保持实际行为,即,一旦我选择一个过滤器并按下过滤器按钮,其他selectInput 选择就会自动更新,但是我想跟踪过滤器的选择,以便用户可以看到他/她选择的过滤器。这正是我所期待的,但每次我按下按钮 Filter 时,似乎都会再次呈现过滤器选项卡。

这是我的应用程序,

library(shiny)
library(vroom)
library(dplyr)
library(shinycssloaders)
library(shinydashboard)
library(shinydashboardPlus)
library(tidyr)


header <- dashboardHeader()

sidebar <- dashboardSidebar(width = 450,

  sidebarMenu(id="tabs", 
    menuItem("Filtros", tabName="filtros", icon = icon("bar-chart-o")),
      uiOutput("filtros")

  )
)

body <- dashboardBody(

  tabItems(
    tabItem(tabName="filtros",
          fluidRow(
          column(12,dataTableOutput("tabla_julio") %>% withSpinner(color="#0dc5c1"))
        )
    )  
   )
 )

ui <- dashboardPagePlus(enable_preloader = TRUE, sidebar_fullCollapse = TRUE, header, sidebar, body)

server = function(input, output, session) {

    #Create the choices for sample input
    vals <- reactiveValues(data=NULL)
    vals$data <- iris



  output$filtros <- renderUI({

    datos <- vals$data
      conditionalPanel("input.tabs == 'filtros'",
        tagList(        
            div(style="display: inline-block;vertical-align:top; width: 221px;",numericInput(inputId="Sepal.Length", label="Sepal.Length", value=NA, min = NA, max = NA, step = NA)),                      
            div(
              div(style="display: inline-block;vertical-align:top; width: 224px;", selectInput(inputId = "Species", label = "Species", width = "220",  choices=unique(datos$Species), 
              selected = NULL, multiple = TRUE, selectize = TRUE, size = NULL))
              )
            ),
            actionButton("filtrar", "Filter")
          )
    })

# create reactiveValues

  vals <- reactiveValues(data=NULL)
  vals$data <- iris


# Filter data

observeEvent(input$filtrar, {

      tib <- vals$data

      if (!is.na(input$Sepal.Length)){
        tib <- tib %>% dplyr::filter(!Sepal.Length >= input$Sepal.Length)
        print(head(tib))
      } else { tib <- tib }

      # Filter
      if (!is.null(input$Species)){
        toMatch <- paste0("\\b", input$Species, "\\b")
        matches <- unique(grep(paste(toMatch,collapse="|"), tib$Species, value=TRUE))
        tib <- tib %>% dplyr::filter(Species %in% matches)
      } else { tib <- tib}

      tib -> vals$data
      print(head(tib, n=15))

    })


  # Reactive function creating the DT output object
  output$tabla_julio <- DT::renderDataTable({        
      DT::datatable(vals$data) 
    })

}

shinyApp(ui, server)

【问题讨论】:

  • 我会试试@bretauv
  • @bretauv,我已经把例子缩短了
  • @SeGa,我已经用 iris 数据集更新了示例

标签: r shiny


【解决方案1】:

另一个更新:

library(shiny)
library(vroom)
library(dplyr)
library(shinycssloaders)
library(shinydashboard)
library(shinydashboardPlus)
library(tidyr)

header <- dashboardHeader()

sidebar <- dashboardSidebar(width = 450,
                            sidebarMenu(id = "tabs",
                                        menuItem(
                                          "Filtros",
                                          tabName = "filtros",
                                          icon = icon("bar-chart-o")
                                        ),
                                        uiOutput("filtros")
                            ))

body <- dashboardBody(tabItems(tabItem(tabName = "filtros",
                                       fluidRow(
                                         column(12,
                                                DT::dataTableOutput("tabla_julio") # %>% withSpinner(color = "#0dc5c1")
                                         )
                                       ))))

ui <-
  dashboardPagePlus(
    enable_preloader = FALSE,
    sidebar_fullCollapse = TRUE,
    header,
    sidebar,
    body
  )

server = function(input, output, session) {

  # Create the choices for sample input
  vals <- reactiveValues(data = iris, filtered_data = iris)

  output$filtros <- renderUI({
    datos <- isolate(vals$data)
    conditionalPanel(
      "input.tabs == 'filtros'",
      tagList(
        div(
          style = "display: inline-block;vertical-align:top; width: 221px;",
          numericInput(
            inputId = "SepalLength",
            label = "Sepal.Length",
            value = NA,
            min = NA,
            max = NA,
            step = NA
          )
        ),
        div(
          div(
            style = "display: inline-block;vertical-align:top; width: 224px;",
            selectInput(
              inputId = "Species",
              label = "Species",
              width = "220",
              choices = unique(isolate(datos$Species)),
              selected = NULL,
              multiple = TRUE,
              selectize = TRUE,
              size = NULL
            )
          )
        )
      ),
      actionButton("filtrar", "Filter", style = "width: 100px;"),
      actionButton("reset", "Reset", style = "width: 100px;")
    )
  })


  # Filter data
  observeEvent(input$filtrar, {
    tib <- vals$data

    if (!is.na(input$SepalLength)) {
      tib <- tib %>% dplyr::filter(Sepal.Length < input$SepalLength)
      print(head(tib))
    } else {
      tib
    }

    # Filter
    if (!is.null(input$Species)) {
      tib <- tib %>% dplyr::filter(Species %in% input$Species)
    } else {
      tib
    }

    print(head(tib, n = 15))

    vals$filtered_data <- tib

    updateSelectInput(session, inputId = "Species", selected = input$Species, choices = unique(vals$filtered_data$Species))

  })

  observeEvent(input$reset, {
    updateNumericInput(session, inputId = "SepalLength", value = NA)
    updateSelectInput(session, inputId = "Species", selected = "")
  })

  # Reactive function creating the DT output object
  output$tabla_julio <- DT::renderDataTable({
    DT::datatable(vals$filtered_data)
  }, server = FALSE)

}

shinyApp(ui, server)

更新:这是我认为你所追求的。最重要的一步是isolaterenderUI 中的输入,这样它们就不会在每次输入更改时重新渲染。

library(shiny)
library(vroom)
library(dplyr)
library(shinycssloaders)
library(shinydashboard)
library(shinydashboardPlus)
library(tidyr)

header <- dashboardHeader()

sidebar <- dashboardSidebar(width = 450,
                            sidebarMenu(id = "tabs",
                                        menuItem(
                                          "Filtros",
                                          tabName = "filtros",
                                          icon = icon("bar-chart-o")
                                        ),
                                        uiOutput("filtros")
                            ))

body <- dashboardBody(tabItems(tabItem(tabName = "filtros",
                                       fluidRow(
                                         column(12,
                                                DT::dataTableOutput("tabla_julio") # %>% withSpinner(color = "#0dc5c1")
                                         )
                                       ))))

ui <-
  dashboardPagePlus(
    enable_preloader = FALSE,
    sidebar_fullCollapse = TRUE,
    header,
    sidebar,
    body
  )

server = function(input, output, session) {

  # Create the choices for sample input
  vals <- reactiveValues(data = iris, filtered_data = iris)

  output$filtros <- renderUI({
    datos <- isolate(vals$data)
    conditionalPanel(
      "input.tabs == 'filtros'",
      tagList(
        div(
          style = "display: inline-block;vertical-align:top; width: 221px;",
          numericInput(
            inputId = "SepalLength",
            label = "Sepal.Length",
            value = NA,
            min = NA,
            max = NA,
            step = NA
          )
        ),
        div(
          div(
            style = "display: inline-block;vertical-align:top; width: 224px;",
            selectInput(
              inputId = "Species",
              label = "Species",
              width = "220",
              choices = unique(isolate(datos$Species)),
              selected = NULL,
              multiple = TRUE,
              selectize = TRUE,
              size = NULL
            )
          )
        )
      ),
      actionButton("filtrar", "Filter", style = "width: 100px;"),
      actionButton("reset", "Reset", style = "width: 100px;")
    )
  })


  # Filter data
  observeEvent(input$filtrar, {
    tib <- vals$data

    if (!is.na(input$SepalLength)) {
      tib <- tib %>% dplyr::filter(Sepal.Length < input$SepalLength)
      print(head(tib))
    } else {
      tib
    }

    # Filter
    if (!is.null(input$Species)) {
      tib <- tib %>% dplyr::filter(Species %in% input$Species)
    } else {
      tib
    }

    print(head(tib, n = 15))

    vals$filtered_data <- tib

  })

  observeEvent(input$reset, {
    updateNumericInput(session, inputId = "SepalLength", value = NA)
    updateSelectInput(session, inputId = "Species", selected = "")
  })

  # Reactive function creating the DT output object
  output$tabla_julio <- DT::renderDataTable({
    DT::datatable(vals$filtered_data)
  }, server = FALSE)

}

shinyApp(ui, server)

初步答案:

我建议使用库中的selectizeGroup-module(shinyWidgets)。

它创建了一个

一组相互依赖的selectizeInput进行过滤 data.frame 的列(如 Excel)。

除此之外,它只使用selectizeInput 似乎满足您的要求,并为我们节省了大量的打字。

以下是使用iris 数据集的示例:

library(shiny)
library(DT)
library(shinyWidgets)
library(datasets)

DF <- iris
names(DF) <- gsub("\\.", "", names(DF))

ui <- fluidPage(
  fluidRow(
    column(width = 10, offset = 1, tags$h3("Filter data with selectize group")),
    column(width = 3, offset = 1, 
           selectizeGroupUI(
             id = "my-filters",
             params = list(
               SepalLength = list(inputId = "SepalLength", title = "SepalLength:"),
               SepalWidth = list(inputId = "SepalWidth", title = "SepalWidth:"),
               PetalLength = list(inputId = "PetalLength", title = "PetalLength:"),
               PetalWidth = list(inputId = "PetalWidth", title = "PetalWidth:"),
               species = list(inputId = "Species", title = "Species:")
             ),
             inline = FALSE
           )),
    column(
      width = 10, offset = 1,DT::dataTableOutput(outputId = "table")
    )
  )
)

server <- function(input, output, session) {
  filtered_table <- callModule(
    module = selectizeGroupServer,
    id = "my-filters",
    data = DF,
    vars = names(DF)
  )
  output$table <- DT::renderDataTable(filtered_table())
}

shinyApp(ui, server)

【讨论】:

  • 感谢@ismirsehregal,但在我的真实数据中,我有selectInputsnumericInputs 需要使用高于和小于numericIpunt 的值进行过滤。我也想过使用selectizeGroup,但在我的真实数据中不符合我的要求
  • 好吧,这很合理——我将把答案留在这里,因为它可能适合其他人的用例。有没有想过用DT的column filters
  • 是的,那是另一种方法,问题是用户有时想要进行负面过滤,他们正在寻找更直观的东西。我有点挣扎,因为我能够根据不同的选择更新过滤,但每次我这样做(触发按钮)过滤器都会刷新,用户无法返回
  • 是的,这就是我要找的东西,我唯一失去的功能是,一旦您选择过滤器,selectInput 中的选择不会反映真实值,即您仅使用 setosa 过滤 setosa应该显示,但我认为这是因为isolate 如果没有简单的解决方案,我会选择您的选择。非常感谢
  • 嗨@ismirsehregal,很抱歉用这么多问题打扰你,我想我没有正确解释自己。让我举个例子:应用程序启动,然后我过滤Sepan.Length,值为4.5,表格只显示4行,唯一的Species是setosa,所以我想获得的行为是在selectInput 只有 setosa 物种应该出现。我真正的应用程序,有几个 selectInputs 其中一些有几个选择,这个想法是一旦用户应用一些过滤器(不是按顺序),选择就会减少。
【解决方案2】:

如果我正确理解了您的问题,那么您几乎达到了目标。在这种情况下,您将在运行时覆盖您的数据。这会导致过滤器无效,并且反应式 UI 似乎在每次点击时都会检查这一点。

一个简单的解决方案是将原始数据集和过滤后的数据集分开存储。另一种方法是将过滤器存储在反应值中,并在运行时使用原始表上的过滤器重新渲染DataTable。在这里,我将举第一个例子。

下面我更改了以下内容:

  1. 添加了 data_printfilters 作为打印和过滤器的反应值
  2. 更改了过滤器的过滤方法,使用data_print,并添加了一些格式并更改了几行代码,作为可能更容易适应给定用户输入的代码示例
  3. 删除了一些不必要的代码(renderDataTable 自动将输入更改为 DT)
server = function(input, output, session) {
  #Create the choices for sample input
  vals <- reactiveValues(
                         #raw data
                         data = iris,
                         #Exists only in order to print.
                         data_print = iris,
                         #for filtering data
                         filters = list(Species = c(), 
                                        Sepal.Length = c()
                                        )
                         )
  #in case of many filters, or filters expanding depending on input data, it might be worth adding this to reactiveValues
  ## Unchanged
  output$filtros <- renderUI({
    datos <- vals$data
    conditionalPanel("input.tabs == 'filtros'",
                     tagList(        
                       div(style="display: inline-block;vertical-align:top; width: 221px;",
                           numericInput(inputId="Sepal.Length", label="Sepal.Length", 
                                        value=NA, min = NA, max = NA, step = NA)),                      
                       div(
                         div(style="display: inline-block;vertical-align:top; width: 224px;", 
                             selectInput(inputId = "Species", label = "Species", width = "220",  
                                         choices=unique(datos$Species),  
                                         selected = NULL, multiple = TRUE, selectize = TRUE, size = NULL))
                       )
                     ),
                     actionButton("filtrar", "Filter")
    )
  })

  # Filter data
  observeEvent(input$filtrar, {
    nm <- names(vals$filters)
    for(i in nm){
      if(is.na(input[[i]]) || is.null(input[[i]]))
        vals$filters[[i]] <- unique(vals$data[[i]]) #If unfiltered use all values
      else
        vals$filters[[i]] <- input[[i]] #if filtered choose the filtered value
    }
    #Overwrite data_print instead of data. Creds to https://stackoverflow.com/a/47171513/10782538 
    vals$data_print <- vals$data %>% dplyr::filter((!!as.symbol(nm[1])) %in% vals$filters[[1]], 
                                         (!!as.symbol(nm[2]) %in% vals$filters[[2]]))

  })

  # Reactive function creating the DT output object
  output$tabla_julio <- DT::renderDataTable(        
    vals$data_print #<====renderDataTable changes to data.
  )
}

【讨论】:

  • 非常感谢@Oliver,我的真实数据在 numericInput 和 selectInput 之间有 12 个过滤器,您能否发布第二个解决方案的示例?谢谢
  • 感谢@Oliver,行为几乎就在那里,但是当您选择上一个过滤器时,selectInput 选项不会更新。例如,如果您在Sepal.Length 中选择 5.1,然后进行过滤,则物种 selectInput 中的唯一选项应该是 setosaversicolor
猜你喜欢
  • 2017-11-18
  • 2019-01-30
  • 2019-01-29
  • 1970-01-01
  • 2021-02-04
  • 2014-12-29
  • 2019-03-18
  • 2014-04-11
  • 2016-04-09
相关资源
最近更新 更多