在Shiny中,加载数据,然后预处理到全局环境,然后显示UI

时间:2019-07-19 01:02:29

标签: r shiny

我正在编写一个Shiny应用程序,该应用程序将根据数据值和相同数据的预处理来填充UI。此预处理还向服务器提供了一些对象。只要在启动ui.R和server.R之前加载并预处理数据,此应用程序就可以正常工作。当前结构是

  1. data_preprocessing.R从本地计算机加载数据
  2. source(data_preprocessing.R)可以同时加载到ui.R和server.R中
  3. 运行app.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)

1 个答案:

答案 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.ymax.ymean.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.ymax.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$file1NULL,则反应式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.ymax.y来更新滑块,则可以取消min.ymax.y反应堆,方法如下:

observe({
  df <- mydata()
  if (is.null(df) || nrow(df) == 0)
    return()

  updateSliderInput(session, "y.value", min=min(df$mpg), max=max(df$mpg))
})

但这是代码的品味和模块化的问题。