R Shiny:根据单独文件中的输入创建过滤函数
我正在编写一个闪亮的程序来操作用户上传的数据集。 该数据集具有固定的列名称,我创建了几个 UI 元素 (selectInputs) 来过滤该数据集。
Reprex 看起来像这样:
ui <- fluidPage(
fluidRow(selectInput("filter_a","label",choices = c("a","b","c"),multiple = T),
selectInput("filter_b","label",choices = c("x","z","y"),multiple = T),
dataTableOutput("o1"),
br(),
dataTableOutput("o2")
)
)
server <- function(input, output) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_function_1 <- reactive({
req(data)
df <- df()
if(!is.null(input$filter_a)){
df <- df %>%
filter(df$a %in% input$filter_a)
}
if(!is.null(input$filter_b)){
df <- df %>%
filter(df$b %in% input$filter_b)
}
return(df)
})
output$o1 <- renderDataTable({filter_function_1()})
虽然这有效,但看起来是非常糟糕的做法。在我的实际程序中,我有一组 14 个过滤器,并将其包装 14 次,并且应用相同的过滤器对我来说看起来并不正确。
为了简化我想出了这个。我有一种感觉,这也不是最佳实践(通过连接字符串来寻址 input$filter_a 似乎并不正确)。
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("input$", filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
filter_function_2 <- reactive({
df <- df()
df <- df %>%
filter_func(arg="a") %>%
filter_func(arg="b")
return(df)
})
output$o2 <- renderDataTable({filter_function_2()})
}
现在,这对我来说看起来更干净,但我仍然想进一步模块化代码,并将过滤器函数和代码放在文件中。涉及更多数据准备步骤,我希望能够轻松调试它们,因此需要单独的文件/函数。
代码现在可能如下所示:
filter_data.R
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("input$", filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
这是它不再工作的点,因为它在函数范围内找不到输入 - 这至少是我最好的猜测。我想以多种方式重写函数,这些是我的想法:
让 filer_data.R 函数接受我想要过滤的所有列的命名参数。这看起来很简单,但对我来说也非常多余
在服务器端访问闪亮的输入变量,收集以“filter_”开头的所有“列”并将它们传递到过滤器函数。然后过滤器函数应用必要的过滤器。
我很确定我在某个地方搞砸了,但我一直无法弄清楚。这里什么不起作用?
I am writing a Shiny program which manipulates a dataset the user uploads.
The dataset has fixed column names and I create several UI elements (selectInputs) to filter that dataset.
Reprex looks like this:
ui <- fluidPage(
fluidRow(selectInput("filter_a","label",choices = c("a","b","c"),multiple = T),
selectInput("filter_b","label",choices = c("x","z","y"),multiple = T),
dataTableOutput("o1"),
br(),
dataTableOutput("o2")
)
)
server <- function(input, output) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_function_1 <- reactive({
req(data)
df <- df()
if(!is.null(input$filter_a)){
df <- df %>%
filter(df$a %in% input$filter_a)
}
if(!is.null(input$filter_b)){
df <- df %>%
filter(df$b %in% input$filter_b)
}
return(df)
})
output$o1 <- renderDataTable({filter_function_1()})
While this works it looks like very bad practice. In my actual program I have a set of 14 filters and wrapping it 14 times and applying the same just doesnt look right to me.
Wanting to simplify I came up with this. I have a feeling that this is also not best practice (addressing the input$filter_a by concatenating strings doesnt seem right).
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("inputquot;, filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
filter_function_2 <- reactive({
df <- df()
df <- df %>%
filter_func(arg="a") %>%
filter_func(arg="b")
return(df)
})
output$o2 <- renderDataTable({filter_function_2()})
}
Now, this looks cleaner to me, but I still want to modulize the code even more and have the filter function and code resign in a file. There are more data prep steps involved and I want to be able to debug them easily, hence the separate files / functions.
Code might look now like this:
filter_data.R
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("inputquot;, filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
This is the point where it doesn't work anymore, since it can't find the input while in the function scope - that would be at least my best guess. I though of rewriting function in several ways, these are my ideas:
Have the filer_data.R function take in named arguments for all columns I want to filter. This seems straight-forward but also very redundant to me
Access shiny input variable on the server side, collect all "columns" that start with "filter_" and pass them onto the filter function. The filter function then applies the necessary filters.
I'm pretty sure I mess up somewhere, but I haven't been able to figure it out. What's not working here?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
首先,让我们解决如何根据多个输入连续调用多个过滤器的问题。我们可以使用 purrr:reduce2 来实现此目的:
在下面的示例中,reduce2 采用名为
myfilter
的自定义函数,该函数具有三个参数:初始的data.frame
列名和我们要过滤的值。调用reduce2时,向.init
参数提供data.frame
非常重要。然后我们可以创建一个带有两个参数的单独函数
filter_function_1
:react_dat
和input
。通过将代码放入外部函数/文件中来清理代码的另一种方法是使用闪亮的模块。有多种方法可以进行设置,具体取决于该模块与应用程序其他部分的交互方式。一种方法是将所有内容放入模块中:
First, lets solve the problem how to call several filter in a row based on multiple inputs. We can use
purrr:reduce2
for this:In the example below
reduce2
takes a custom function calledmyfilter
with three arguments: the initialdata.frame
the column name and the value we want to filter. When callingreduce2
it is important to supply thedata.frame
to the.init
argument.Then we could create a separate function
filter_function_1
with two arguments:react_dat
andinput
.And another way cleaning the code by putting it in external function / files would be to use shiny modules. There are many ways to set this up depending on how this module interacts with other parts of your app. One way of doing this is putting everything into the module: