简体   繁体   English

如何在 Shiny 应用程序中编写模块化或更多 DRY selectizeInput 检查空值/全部

[英]How to write modular or more DRY selectizeInput in a Shiny app checking for nulls / all

Assume we have a shiny app that utilizes several selectizeInput controls with ( multiple = TRUE ).假设我们有一个shiny应用程序,它使用多个带有 ( multiple = TRUE ) 的selectizeInput控件。 I would like to have a reactive table that filters for those selected choices, but also does not filter when "nothing" is selected.我想要一个反应表来过滤那些选定的选项,但在选择“无”时也不会过滤 I know I could create an "All" bucket/choice, but I would prefer to just leave this blank.我知道我可以创建一个“全部”存储桶/选择,但我宁愿将其留空。 I am able to do this with a simple is.null pattern of if (.is.null(input$something) column %in% input$something else TRUE , but this is not very DRY once I have more inputs.我可以用一个简单的is.null模式if (.is.null(input$something) column %in% input$something else TRUE来做到这一点,但是一旦我有更多的输入,这就不是很干了。

Is there a way to make this code more dry?有没有办法让这段代码更干? Or even just shorten this code via a function?或者甚至只是通过 function 缩短此代码? Or are shiny modules the way to go?或者是 shiny 模块到 go 的方式?

library(shiny)
library(tidyverse)

df_test <- tibble::tribble(
    ~ Region, ~ Category,
    "West",       "A",
    "West",       "A",
    "West",       "B",
    "East",       "D",
    "East",       "E",
    "North",       "A",
    "North",       "B",
    "North",       "C"
) %>% 
    mutate_all(as.factor)

ui <- fluidPage(
    selectizeInput(
        "region", "Select region(s):", 
        choices = levels(df_test$Region),
        multiple = TRUE
    ),
    selectizeInput(
        "category", "Select category/categories:",
        choices = levels(df_test$Category),
        multiple = TRUE
    ),
    tableOutput("table")
)


server <- function(input, output, session) {
    output$table <- renderTable(
        df_test %>%
            filter(
                # This pattern works, but is not very DRY and there are many 
                # more inputs...
                if (!is.null(input$region)) Region %in% input$region else TRUE,
                if (!is.null(input$category)) Category %in% input$category else TRUE
            )
    )
}

shinyApp(ui, server)

I think if it is especially for dplyr , then the easiest way is to write a custom filter function. Here is one proposal, however so far it only works for one filter option at a time.我认为如果它特别适用于dplyr ,那么最简单的方法是编写一个自定义filter function。这是一个建议,但到目前为止它一次只适用于一个过滤器选项。 I have to think a bit more how to generalise it.我必须多考虑一下如何概括它。

library(dplyr)

filter_shiny <- function(.data, var, input, ...) {
  if (is.null(input)) {
    dplyr::filter(.data, TRUE, ...)
  } else {
    dplyr::filter(.data, {{var}} %in% input, ...)
  }
}

test_input <- "virginica"

iris %>% 
  filter_shiny(Species, test_input) %>% 
  head()
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
#> 1          6.3         3.3          6.0         2.5 virginica
#> 2          5.8         2.7          5.1         1.9 virginica
#> 3          7.1         3.0          5.9         2.1 virginica
#> 4          6.3         2.9          5.6         1.8 virginica
#> 5          6.5         3.0          5.8         2.2 virginica
#> 6          7.6         3.0          6.6         2.1 virginica

test_input <- NULL

iris %>% 
  filter_shiny(Species, test_input) %>% 
  head()
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa

Edit编辑

I've tried to come up with a function that takes an arbitrary amount of arguments. I'm a beginner in metaprogramming, so for now I've built the filtering function by myself instead of manipulating the input for dplyr::filter:我试图想出一个 function,它占用任意数量的 arguments。我是元编程的初学者,所以现在我自己构建了过滤 function,而不是操纵 dplyr::filter 的输入:

library(dplyr)

# this function takes pairs of input:
# 1. the variable
# 2. the filter condition
#
# example:
# iris %>% 
#   filter_shiny_2(Species, c("versicolor", "virginica"))
filter_shiny_2 <- function(.data, ...) {
  
  # capture the user provided input
  vars <- rlang::enquos(...)
  
  if (length(vars) %% 2 != 0) stop("You need to provide pairs of variables and filter conditions.")
  
  # discard all filter conditions where the condition is NULL
  index_delete <- unlist(lapply(seq(from = 2, to = length(vars), by = 2), function(i) {
    is.null(rlang::eval_tidy(vars[[i]]))
  }))
  
  # if the second input gets deleted, then also the associated variable
  # therefore expand the index
  index_delete <- rep(index_delete, each = 2)
  vars[index_delete] <- NULL
  
  if (length(vars) > 0) {
    # do the filtering for every supplied variable
    filter_index <- TRUE
    for (j in seq(from = 1, to = length(vars) - 1, by = 2)) {
      # generate the index which rows fullfill the filter condition
      # rlang::eval_tidy uses the provided .data to get the values of the variable
      # use the corresponding values for filtering in rhs of %in%
      filter_index <- (rlang::eval_tidy(vars[[j]], data = .data) %in%
                         rlang::eval_tidy(vars[[j + 1]])) & filter_index
      
    }
    .data[filter_index, ]
    
  } else {
    .data
  }
}

test_data <- data.frame(type = rep(c("mac", "windows", "linux"), each = 4),
                        used = rep(c("new", "used"), each = 2))

test_var1 <- c("mac", "linux")
test_var2 <- c("new")

test_data %>% 
  filter_shiny_2(type, test_var1,
                 used, test_var2)
#>     type used
#> 1    mac  new
#> 2    mac  new
#> 9  linux  new
#> 10 linux  new

test_var2 <- NULL

test_data %>% 
  filter_shiny_2(type, test_var1,
                 used, test_var2)
#>     type used
#> 1    mac  new
#> 2    mac  new
#> 3    mac used
#> 4    mac used
#> 9  linux  new
#> 10 linux  new
#> 11 linux used
#> 12 linux used

Created on 2020-09-02 by the reprex package (v0.3.0)reprex package (v0.3.0) 创建于 2020-09-02

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 带有所有组合的 if 语句 selectizeInput Shiny - if statement with all combinations selectizeInput Shiny 如何在 selectizeInput 加载所有选项之前添加微调器? [闪亮的] - How to add a spinner before a selectizeInput has loaded all the choices? [Shiny] 如何使R程序(使用Shiny)更具模块化 - How to make R program (with Shiny) more modular 闪亮的selectizeInput功能“一次性删除” - Shiny selectizeInput with feature “remove all-at-once” Shiny:使用 fileInput() 和 selectizeInput() 自动化 Web 应用程序 - Shiny : Automate a web app with fileInput() and selectizeInput() 如果未满足条件,如何隐藏selectizeInput - How to hide selectizeInput if condition is not met in shiny 在一个闪亮的应用程序中,您如何明确引用来自 selectizeInput 的用户选择以读取特定的 csv 文件? - In a shiny app, how do you explicitly refer to user selections from selectizeInput to read in specific csv files? 具有可重复使用页面的“模块化”闪亮应用程序 - “Modular” Shiny App with Reusable Page Rstudio中的模块化闪亮应用程序工作流程 - Modular Shiny app workflow in Rstudio 如何在闪亮的应用程序中从UI中选择数据后,将值(选项)传递给selectizeInput()? - How to pass values (choices) to selectizeInput() after selecting data from UI in shiny app?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM