我正在编写一个Shiny应用程序,该应用程序将根据数据值和相同数据的预处理来填充UI。此预处理还向服务器提供了一些对象。只要在启动ui.R和server.R之前加载并预处理数据,此应用程序就可以正常工作。当前结构是
此玩具代码举例说明了这种情况:
# Scenario A
# run on local machine
df <- mtcars
# processe
min.y <- min(df$mpg)
max.y <- max(df$mpg)
mean.y <- mean(df$mpg)
ui <- shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput(
inputId = "y.value",
label = "Filter mpg",
min = min.y,
max = max.y,
value = c(mean.y - 1, mean.y + 1),
step = 0.5
)
),
mainPanel(
plotOutput("plot")
)
)
))
server <- function(input, output) {
filtered_df <- reactive({
df[which(df$mpg >= input$y.value[1] & df$mpg <= input$y.value[2]), ]
})
output$plot <- renderPlot({
ggplot(filtered_df(), aes(x = hp, y = mpg)) + geom_line()
})
}
shinyApp(ui, server)
当我想将这种方法推广到用户在一个基本用户界面(例如ui.R中的一个选项卡)上载一次数据集,然后才在ui.R中的主要用户界面上载一次数据集的情况下,我的问题就出现了。发射。另外,预处理为服务器提供了几个对象。代码的结构类似于以下内容(实际上不起作用...):
# Scenario B
# run in the Internet
# df <- mtcars
# # processe
# min.y <- min(df$mpg)
# max.y <- max(df$mpg)
# mean.y <- mean(df$mpg)
ui <- shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File",
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv")
),
tags$hr(),
checkboxInput("header", "Header", TRUE),
actionButton(inputId = "go",
label = "Process this data")
#actionButton("submit", label = "Submit")
),
mainPanel(
tableOutput("contents")
)
),
sidebarLayout(
sidebarPanel(
sliderInput(
inputId = "y.value",
label = "Filter mpg",
min = min.y,
max = max.y,
value = c(mean.y - 1, mean.y + 1),
step = 0.5
)
),
mainPanel(
plotOutput("plot")
)
)
)
)
server <- function(input, output) {
mydata <- eventReactive(input$go, {
inFile <- input$file1
if (is.null(inFile))
return(NULL)
# read this file in via a browser!
#df <- read.csv(inFile$datapath, header = input$header)
# for this example load mtcars
df <- mtcars
# process
min.y <- min(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
max.y <- max(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
mean.y <- mean(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
df # PREFERABLY, SHOULD ALSO BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
})
filtered_df <- reactive({
df1 <- mydata()
df1[which(df1$mpg >= input$y.value[1] & df1$mpg <= input$y.value[2]), ]
})
output$plot <- renderPlot({
ggplot(filtered_df(), aes(x = hp, y = mpg)) + geom_line()
})
}
shinyApp(ui, server)
我可能会将所有预处理的对象存储为反应性对象,但这将很快使代码变得笨拙。
一个“简单”的解决方案是,如果我能以某种方式使所有这些预处理对象在全局环境中可用。其中许多只需要计算一次。我尝试对相关对象使用“ <<-”,但这不起作用。 R抗议“错误<<-:无法更改'df'的锁定绑定的值”。
因此,如何解决此问题的想法?
基于@MrGumble输入的更新(2019-07-19):
全局环境对于同一应用程序上的所有用户都是全局的。因此,如果用户1上载有关豆形糖消费量的数据集,并将min.y,max.y和mean.y保存到全局环境中,然后用户2启动该应用程序,则将向这两个用户显示这些数据!然后,用户2上传有关学生表演的数据集时,它将覆盖用户1的数据!真是一团糟!
您是对的!因此,应该将其保存在会话环境中,而不是全局环境,以便该会话中的所有功能都可以使用它。
A)会话参数有什么作用?我在Google周围搜索,但是找不到明确的答案。
B)使用“ <<-”
使用“ <<-”不起作用。我尝试过在服务器函数内以及在服务器函数外定义它。但是没有办法。看到什么地方了吗?
ui <- shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
# fileInput("file1", "Choose CSV File",
# accept = c(
# "text/csv",
# "text/comma-separated-values,text/plain",
# ".csv")
# ),
# tags$hr(),
# checkboxInput("header", "Header", TRUE),
actionButton(inputId = "go",
label = "Process this data")
#actionButton("submit", label = "Submit")
),
mainPanel(
tableOutput("contents")
)
),
sidebarLayout(
sidebarPanel(
sliderInput(
inputId = "y.value",
label = "Filter mpg",
min = 1,
max = 30,
value = c(10 - 1, 15 + 1),
step = 0.5
)
),
mainPanel(
plotOutput("plot")
)
)
)
)
server <- function(input, output, session) {
df2 <- NA
# # define reactivevalues
# min.y <- reactiveVal()
# max.y <- reactiveVal()
mydata <- eventReactive(input$go, {
df # PREFERABLY, SHOULD ALSO BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
})
# update min max when the data loads
observeEvent(input$go, {
df2 <<- mydata()
})
output$plot <- renderPlot({
ggplot(df2, aes(x = hp, y = mpg)) + geom_line()
})
}
shinyApp(ui, server)
C)如果首先运行服务器功能或ui功能,与用户界面中显示的内容无关吗?我猜不是,但是我有一种潜伏的感觉。
D)最后,最重要的是,根据您的评论,我更新了我的代码。 ui无法捕获最小y和最大y
# Scenario C
# run in the Internet
server <- function(input, output, session) {
# define reactivevalues
min.y <- reactiveVal()
max.y <- reactiveVal()
mydata <- eventReactive(input$go, {
inFile <- input$file1
if (is.null(inFile))
return(NULL)
# read this file in via a browser!
#df <- read.csv(inFile$datapath, header = input$header)
# for this example load mtcars
df <- mtcars
# # process
# min.y <- min(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
# max.y <- max(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
# mean.y <- mean(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
#
df # PREFERABLY, SHOULD ALSO BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
})
# update min max when the data loads
observeEvent(mydata, {
min.y(min(mydata()$mpg))
max.y(max(mydata()$mpg))
})
observe({
updateSliderInput(session, "go", min=min.y(), max=max.y())
})
filtered_df <- reactive({
df1 <- mydata()
df1[which(df1$mpg >= input$y.value[1] & df1$mpg <= input$y.value[2]), ]
})
output$plot <- renderPlot({
ggplot(filtered_df(), aes(x = hp, y = mpg)) + geom_line()
})
}
ui <- shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File",
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv")
),
tags$hr(),
checkboxInput("header", "Header", TRUE),
actionButton(inputId = "go",
label = "Process this data")
#actionButton("submit", label = "Submit")
),
mainPanel(
tableOutput("contents")
)
),
sidebarLayout(
sidebarPanel(
sliderInput(
inputId = "y.value",
label = "Filter mpg",
min = min.y,
max = max.y,
value = c(10 - 1, 15 + 1),
step = 0.5
)
),
mainPanel(
plotOutput("plot")
)
)
)
)
shinyApp(ui, server)
答案 0 :(得分:0)
我将从“攻击”您的假设开始
# process
min.y <- min(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
max.y <- max(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
mean.y <- mean(df$mpg) # SHOULD BE MADE AVAILABLE IN THE GLOBAL ENVIROMENT SO ui CAN USE IT!
全局环境对于同一应用程序上的所有用户都是全局。因此,如果用户1上载有关豆形糖消费量的数据集,并将min.y
,max.y
和mean.y
保存到全局环境中,然后用户2启动该应用程序,则将显示两个用户这些数据!然后,用户2上传有关学生表演的数据集时,它将覆盖用户1的数据!真是一团糟!
因此,全局环境中的数据保持不变在所有会话中共享!对于预加载在整个使用过程中保持不变的数据很有用。
如果您想在会话中共享数据 ,请将变量放在server
函数中:
constant.var <- readRDS('some-precalculation.rds')
server <- function(input, output) {
my_users_name <- ''
observeEvent(input$txtName, {
my_users_name <<- input$txtName
})
}
在您的代码mydata
中,对于该会话是唯一的。它在server
中定义。
当您想在UI中使用min.y
时,ui
的定义在应用程序的整个使用过程中都不会改变。我相信runApp()
启动时它只会执行一次。之后,您可以随意更改min.y
,并且UI不变。 (在上面的示例中,请注意,我使用<<-
为在外部作用域中定义的变量分配值。这样做是为了在全局环境中重新定义min.y
。)
如何更新滑块的范围?
1)将限制声明为反应变量。这样,Shiny就能识别何时进行更新。
server <- function(input, output) {
min.y <- reactiveVal()
max.y <- reactiveVal()
}
2)min.y
和max.y
仅在上传的数据集更新时更新:
observeEvent(mydata, {
min.y(min(mydata()$mpg))
max.y(max(mydata()$mpg))
})
实际上,我们可以将1)和2)简化为对已上传数据集的直接反应:
mydata <- reactiveVal(data.frame())
observeEvent(input$go, {
if (is.null(input$file1))
return(NULL)
df <- read.csv(input$file1$datapath, header=input$header)
# do some checking?
mydata(df)
})
min.y <- reactive(min(mydata()$mpg))
max.y <- reactive(max(mydata()$mpg))
我已经更新了例程,因此mydata
是一种反应式,只有在检查并确定一切后才能更新。在您的代码中,如果input$file1
为NULL
,则反应式mydata
将更新为NULL
,当您希望将其作为数据帧时会在下游引起问题。
那么,如何更新UI?参见https://shiny.rstudio.com/reference/shiny/1.2.0/updateSliderInput.html
这导致我们得出以下结论。首先更新您的server
函数以接受session
参数:
server <- function(input, output, session) {
,然后对更新的最小和最大做出反应:
observe({
updateSliderInput(session, "y.value", min=min.y(), max=max.y())
})
当然,如果仅使用min.y
和max.y
来更新滑块,则可以取消min.y
和max.y
反应堆,方法如下:
observe({
df <- mydata()
if (is.null(df) || nrow(df) == 0)
return()
updateSliderInput(session, "y.value", min=min(df$mpg), max=max(df$mpg))
})
但这是代码的品味和模块化的问题。