【问题标题】:Persistent Selections Using DT and R Shiny使用 DT 和 R Shiny 的持久选择
【发布时间】:2017-08-09 14:05:13
【问题描述】:

我有一个 Shiny 用例,我希望允许用户通过选择列并查看某些摘要统计信息来过滤他们的数据。这个想法是让他们能够快速深入到更细化的组并查看结果。它工作得很好,除非用户在更高级别进行选择,然后所有过滤器和选择都被重置并需要再次选择。我在使这些过滤器持久化并且仅在某些情况下更新时遇到了一些麻烦。

例如,用户想要查看瑞士和德国(第 2 级)工程师(第 1 级)的收入中位数,并按年龄(第 3 级)显示。他们将按每个表上方的selectInput 值进行排序以选择类别,然后选择表中的值以包含“工程师”等变量,如下图所示。

如果他们想查看“试点”如何改变结果,国家过滤器将会消失。我希望那些都留在原地,这就是让我适应的部分。

关于如何解决这个问题的任何想法?本示例代码如下:

服务器:

library(shiny)
library(DT)
library(plyr)
library(dplyr)

# Generate income data

n <- 1000
age <- sample(20:60, n, replace=TRUE)
sex <- sample(c("M", "F"), n, replace=TRUE)
country <- sample(c("US", "CA", "UK", "DE", "CH", "NL"), n, replace=TRUE)
occupation <- sample(c("Engineer", "Doctor", "Retail", "Pilot"), n, replace=TRUE)
income <- sample(20000:120000, n, replace=TRUE)

df <- data.frame(age, sex, country, income, occupation)
categories <- c("None", "age", "sex", "country", "occupation")

shinyServer(function(input, output, session) {

  output$selection_1 <- renderUI({
    selectInput("selection_1", "Level 1 Selection", selected = "None",
                choices = categories)
  })

  output$selection_2 <- renderUI({
    selectInput("selection_2", "Level 2 Selection", selected = "None",
                choices = categories)
  })

  output$selection_3 <- renderUI({
    selectInput("selection_3", "Level 3 Selection", selected = "None",
                choices = categories)
  })

  table_1 <- reactive({
    validate(
      need(input$selection_1 != "None", "Select a variable for aggregation.")
    )
    ddply(df, input$selection_1, summarize,
          Count = length(income),
          Med_Income = median(income))
    })

  output$table_1_agg <- DT::renderDataTable(
    table_1(),
    rownames = TRUE,
    selection = list(selected = "")
    )

  # Get values to match on subsequent tables
  table_1_vals <- reactive({
    table_1()[input$table_1_agg_rows_selected, 1]
  })

  # Filter table 2
  table_2 <- reactive({
    validate(
      need(input$selection_2 != "None", "Select a variable for aggregation.")
    )
    # Filter selected values from table_1
    if(length(table_1_vals())>0){
      sel_1_col <- grep(input$selection_1, names(df))
      df2 <- df[df[,sel_1_col] %in% table_1_vals(),]
    }else{
      df2 <- df
    }
    ddply(df2, input$selection_2, summarize,
          Count = length(income),
          Med_Income = median(income))
  })

  output$table_2_agg <- DT::renderDataTable(
    table_2(),
    rownames = TRUE,
    selection = list(selected = "")
  )

  # Get values to match on subsequent tables
  table_2_vals <- reactive({
    table_2()[input$table_2_agg_rows_selected, 1]
  })

  # Filter table 3
  table_3 <- reactive({
    validate(
      need(input$selection_3 != "None", "Select a variable for aggregation.")
    )
    df3 <- df
    # Filter selected values from table_1
    if(length(table_1_vals())>0){
      sel_1_col <- grep(input$selection_1, names(df))
      df3 <- df3[df3[,sel_1_col] %in% table_1_vals(),]
    }
    if(length(table_2_vals())>0){
        sel_2_col <- grep(input$selection_2, names(df))
        df3 <- df3[df3[,sel_2_col] %in% table_2_vals(),]
    }
    ddply(df3, input$selection_3, summarize,
          Count = length(income),
          Med_Income = median(income))
  })

  output$table_3_agg <- DT::renderDataTable(
    table_3(),
    rownames = TRUE,
    selection = list(selected = "")
  )
})

用户界面:

shinyUI(fluidPage(
  fluidRow(
    column(6,
           uiOutput("selection_1"),
           DT::dataTableOutput("table_1_agg")),
    column(6,
           uiOutput("selection_2"),
           DT::dataTableOutput("table_2_agg"))
  ),
  fluidRow(
    column(6,
           br(),
           uiOutput("selection_3"),
           DT::dataTableOutput("table_3_agg"))
  )
))

谢谢!

【问题讨论】:

    标签: r shiny dt


    【解决方案1】:

    一种选择是存储选定的行并在稍后重绘表格时使用。这可以使用额外的renderUI 来创建表并使用参数selection 来指示要选择的行。

    library(shiny)
    library(DT)
    library(dplyr)
    library(plyr)
    
    # Generate income data
    
    n <- 1000
    age <- sample(20:60, n, replace=TRUE)
    sex <- sample(c("M", "F"), n, replace=TRUE)
    country <- sample(c("US", "CA", "UK", "DE", "CH", "NL"), n, replace=TRUE)
    occupation <- sample(c("Engineer", "Doctor", "Retail", "Pilot"), n, replace=TRUE)
    income <- sample(20000:120000, n, replace=TRUE)
    
    df <- data.frame(age, sex, country, income, occupation)
    categories <- c("None", "age", "sex", "country", "occupation")
    
    ui <- shinyUI(fluidPage(
      fluidRow(
        column(6,
               uiOutput("selection_1"),
               DT::dataTableOutput("table_1_agg")),
        column(6,
               uiOutput("selection_2"),
               uiOutput("table_2_aggUI")
        )
      ),
      fluidRow(
        column(6,
               br(),
               uiOutput("selection_3"),
               uiOutput("table_3_aggUI")
        )
      )
    ))
    
    server <- shinyServer(function(input, output, session) {
    
      table2_selected <- NULL
      table3_selected <- NULL
    
      output$selection_1 <- renderUI({
        selectInput("selection_1", "Level 1 Selection", selected = "None",
                    choices = categories)
      })
    
      output$selection_2 <- renderUI({
        selectInput("selection_2", "Level 2 Selection", selected = "None",
                    choices = categories)
      })
    
      output$selection_3 <- renderUI({
        selectInput("selection_3", "Level 3 Selection", selected = "None",
                    choices = categories)
      })
    
      table_1 <- reactive({
        validate(
          need(input$selection_1 != "None", "Select a variable for aggregation.")
        )
        ddply(df, input$selection_1, summarize,
              Count = length(income),
              Med_Income = median(income))
      })
    
      output$table_1_agg <- DT::renderDataTable(
        table_1(),
        rownames = TRUE,
        selection = list(selected = "")
      )
    
      # Get values to match on subsequent tables
      table_1_vals <- reactive({
        table_1()[input$table_1_agg_rows_selected, 1]
      })
    
      # Filter table 2
      table_2 <- reactive({
        validate(
          need(input$selection_2 != "None", "Select a variable for aggregation.")
        )
        # Filter selected values from table_1
        if(length(table_1_vals())>0){
          sel_1_col <- grep(input$selection_1, names(df))
          df2 <- df[df[,sel_1_col] %in% table_1_vals(),]
        }else{
          df2 <- df
        }
        ddply(df2, input$selection_2, summarize,
              Count = length(income),
              Med_Income = median(income))
      })
    
    
      output$table_2_aggUI <- renderUI({
        # to redraw UI if data on table_2() change
        table_2()
        output$table_2_agg <- DT::renderDataTable(
          isolate(table_2()),
          rownames = TRUE,
          selection = list(target = 'row', selected = table2_selected)
        )
        DT::dataTableOutput("table_2_agg")
      })
    
      # keep record of selected rows
      observeEvent(input$table_2_agg_rows_selected, {
        table2_selected <<- as.integer(input$table_2_agg_rows_selected)
        cat("Table 2 selected:", table2_selected, "\n")
      })
    
      # Get values to match on subsequent tables
      table_2_vals <- reactive({
        table_2()[input$table_2_agg_rows_selected, 1]
      })
    
      # Filter table 3
      table_3 <- reactive({
        validate(
          need(input$selection_3 != "None", "Select a variable for aggregation.")
        )
        df3 <- df
        # Filter selected values from table_1
        if(length(table_1_vals())>0){
          sel_1_col <- grep(input$selection_1, names(df))
          df3 <- df3[df3[,sel_1_col] %in% table_1_vals(),]
        }
        if(length(table_2_vals())>0){
          sel_2_col <- grep(input$selection_2, names(df))
          df3 <- df3[df3[,sel_2_col] %in% table_2_vals(),]
        }
        ddply(df3, input$selection_3, summarize,
              Count = length(income),
              Med_Income = median(income))
      })
    
    
      output$table_3_aggUI <- renderUI({
        # to redraw UI if data on table_3() change
        table_3()
        output$table_3_agg <- DT::renderDataTable(
          isolate(table_2()),
          rownames = TRUE,
          selection = list(target = 'row', selected = table3_selected)
        )
        DT::dataTableOutput("table_3_agg")
      })
    
      # keep record of selected rows
      observeEvent(input$table_3_agg_rows_selected, {
        table3_selected <<- as.integer(input$table_3_agg_rows_selected)
        cat("Table 3 selected:", table3_selected, "\n")
      })
    
    })
    
    shinyApp(ui = ui, server = server)
    

    【讨论】:

      【解决方案2】:

      您可以通过添加以下功能来实现此目的:

      1. 初始化一个临时反应变量。在 t0 时刻,此变量将以 NULL 或 0 值开始,但在重绘之前,它将临时捕获当前选定的行并过滤表的选项

        prev_selections = reactiveValues(table2 = NULL, prev_rows_t2 = NULL, 
        new_rows_t2 = NULL, filterop_t2 = 0, table3 = NULL, prev_rows_t3 = NULL, 
        new_rows_t3 = NULL, filterop_t3 = 0)
        
      2. 因为您在表 N 中选择的行将过滤掉表 N+1,...您需要在重绘下游表之前创建它们的副本。使用observeEvent 捕获应用过滤器的表和值(下表为表2)

        observeEvent(input$table_2_agg_rows_selected,{ 
                 prev_selections$table2 = table_2()
                 prev_selections$filterop_t2 = input$selection_2
               })
        
      3. 为每个表创建第二个observeEvent 集合,以在重绘表之前和之后捕获当前选定的行。 observeEvent 的集合将由上游表中发生的行选择触发(表 2 如下)

        observeEvent({input$table_1_agg_rows_selected
          input$selection_2}, 
          {
            prev_selections$prev_rows_t2 = isolate(prev_selections$table2[input$table_2_agg_rows_selected,][1])    
            prev_selections$new_rows_t2 = isolate(if ( input$selection_2 == prev_selections$filterop_t2 ) 
            {which(table_2()[,1] %in% prev_selections$prev_rows_t2[,1])} else {NULL})
          })
        
      4. 使用步骤 3 中的值作为 DT::renderDataTableselection = list(selected = ) 参数中的输入。不要忘记在DT::renderDataTable 内拨打datatable HubertL's answer here

      完整代码如下:

      library(shiny)
      library(DT)
      library(plyr)
      library(dplyr)
      
      # Generate income data
      
      n <- 1000
      age <- sample(20:60, n, replace=TRUE)
      sex <- sample(c("M", "F"), n, replace=TRUE)
      country <- sample(c("US", "CA", "UK", "DE", "CH", "NL"), n, replace=TRUE)
      occupation <- sample(c("Engineer", "Doctor", "Retail", "Pilot"), n, replace=TRUE)
      income <- sample(20000:120000, n, replace=TRUE)
      
      df <- data.frame(age, sex, country, income, occupation)
      categories <- c("None", "age", "sex", "country", "occupation")
      
      server <- shinyServer(function(input, output, session) {
      
        output$selection_1 <- renderUI({
          selectInput("selection_1", "Level 1 Selection", selected = "None",
                      choices = categories)
        })
      
        output$selection_2 <- renderUI({
          selectInput("selection_2", "Level 2 Selection", selected = "None",
                      choices = categories)
        })
      
        output$selection_3 <- renderUI({
          selectInput("selection_3", "Level 3 Selection", selected = "None",
                      choices = categories)
        })
      
        table_1 <- reactive({
          validate(
            need(input$selection_1 != "None", "Select a variable for aggregation.")
          )
          ddply(df, input$selection_1, summarize,
                Count = length(income),
                Med_Income = median(income))
        })
      
        output$table_1_agg <- DT::renderDataTable(
          table_1(),
          rownames = TRUE,
          selection = list(selected = "")
        )
      
        # Get values to match on subsequent tables
        table_1_vals <- reactive({
          table_1()[input$table_1_agg_rows_selected, 1]
        })
      
        # Filter table 2
        table_2 <- reactive({
          validate(
            need(input$selection_2 != "None", "Select a variable for aggregation.")
          )
          # Filter selected values from table_1
          if(length(table_1_vals())>0){
            sel_1_col <- grep(input$selection_1, names(df))
            df2 <- df[df[,sel_1_col] %in% table_1_vals(),]
          }else{
            df2 <- df
          }
          ddply(df2, input$selection_2, summarize,
                Count = length(income),
                Med_Income = median(income))
        })
      
        output$table_2_agg <- DT::renderDataTable(
          datatable(table_2(),
          rownames = TRUE,
          selection = list(target = 'row', selected = prev_selections$new_rows_t2))
        )
      
        # Get values to match on subsequent tables
        table_2_vals <- reactive({
          table_2()[input$table_2_agg_rows_selected, 1]
        })
      
        # Filter table 3
        table_3 <- reactive({
          validate(
            need(input$selection_3 != "None", "Select a variable for aggregation.")
          )
          df3 <- df
          # Filter selected values from table_1
          if(length(table_1_vals())>0){
            sel_1_col <- grep(input$selection_1, names(df))
            df3 <- df3[df3[,sel_1_col] %in% table_1_vals(),]
          }
          if(length(table_2_vals())>0){
            sel_2_col <- grep(input$selection_2, names(df))
            df3 <- df3[df3[,sel_2_col] %in% table_2_vals(),]
          }
          ddply(df3, input$selection_3, summarize,
                Count = length(income),
                Med_Income = median(income))
        })
      
        output$table_3_agg <- DT::renderDataTable(
          datatable(table_3(),
          rownames = TRUE,
          selection = list(target = 'row', selected = prev_selections$new_rows_t3))
        )
      
      
        ## Retain highlighted rows in temp variables and enable persistent filtering
      
        #initialize temp variables
        prev_selections = reactiveValues(table2 = NULL, prev_rows_t2 = NULL, new_rows_t2 = NULL, filterop_t2 = 0,
                                         table3 = NULL, prev_rows_t3 = NULL, new_rows_t3 = NULL, filterop_t3 = 0)
      
        #Capture current selections/highlights in Table N
        observeEvent(input$table_2_agg_rows_selected, 
                     {
                       prev_selections$table2 = table_2()
                       prev_selections$filterop_t2 = input$selection_2
                     })
      
        observeEvent(input$table_3_agg_rows_selected, 
                     {
                       prev_selections$table3 = table_3()
                       prev_selections$filterop_t3 = input$selection_3
                     })
      
        #Observe upstream events (e.g. highlights in Table N-1,...) and enable persistent selection
        #Table 2
        observeEvent({input$table_1_agg_rows_selected
          input$selection_2}, 
          {
            prev_selections$prev_rows_t2 = isolate(prev_selections$table2[input$table_2_agg_rows_selected,][1])
            prev_selections$new_rows_t2 = isolate(if ( input$selection_2 == prev_selections$filterop_t2 ) 
            {which(table_2()[,1] %in% prev_selections$prev_rows_t2[,1])} else {NULL})
      
          })
      
        #Table 3
        observeEvent({
          input$table_1_agg_rows_selected
          input$table_2_agg_rows_selected
          input$selection_3
        }, 
        {
          prev_selections$prev_rows_t3 = isolate(prev_selections$table3[input$table_3_agg_rows_selected,][1])
          prev_selections$new_rows_t3 = isolate(if ( input$selection_3 == prev_selections$filterop_t3 ) 
          {which(table_3()[,1] %in% prev_selections$prev_rows_t3[,1])} else {NULL})
      
        })
      
      
      })
      
      
      ui <- shinyUI(fluidPage(
        fluidRow(
          column(6,
                 uiOutput("selection_1"),
                 DT::dataTableOutput("table_1_agg")),
          column(6,
                 uiOutput("selection_2"),
                 DT::dataTableOutput("table_2_agg"))
        ),
        fluidRow(
          column(6,
                 br(),
                 uiOutput("selection_3"),
                 DT::dataTableOutput("table_3_agg"))
        )
      ))
      
      shinyApp(ui = ui, server = server)
      

      【讨论】:

        猜你喜欢
        • 2021-01-16
        • 2021-03-25
        • 2015-09-13
        • 2021-02-04
        • 1970-01-01
        • 2019-07-17
        • 2017-01-09
        • 1970-01-01
        • 2016-05-12
        相关资源
        最近更新 更多