如何将RShiny reactionFileReader与reactUI和不存在的文件一起使用?

时间:2018-06-28 16:50:40

标签: r shiny

我该如何构建一个反应式用户界面,以响应具有不同数据输入的reactFileFileReader?

我对将reactFileFileer集成到一个应用程序中感兴趣,该应用程序以图形方式显示数据中的组并按组显示选定的点。

挑战:

  1. 并非我可以从前缀和后缀中识别出的每个文件都存在。
  2. 每个文件有不同数量的组。

我遇到崩溃/失败

  1. 尝试打开一个不存在的文件。
  2. 更新文件(因此它确实会检测到更改)

潜在的解决方案:

  1. 在读取数据后放慢/延迟下一步,以便重新加载数据。 通过reactive()req()
  2. 修复
  3. isolate()从属UI,因此它仅在首次加载文件时更改组数。

我包括了模拟数据(及其生成),一个UI,损坏的服务器以及一个没有反应式文件读取器的工作服务器。

更新

剩下的唯一事情是在重读文件后不重置renderUI'group'。通常这是一件好事,但在这里我不想要那。

包装

library(tidyr); library(dplyr); library(ggplot2); library(readr); library(stringr)
library(shiny)
#library(DT)

模拟数据

a1 <- structure(list(Group = c("alpha_1", "alpha_1", "alpha_2", "alpha_2", "alpha_3", "alpha_3"), Sample = c("ps_1", "ps_2", "ps_1", "ps_2", "ps_1", "ps_2"), x = c(1, 1.1, 4, 4.1, 6.8, 7), y = c(2.1, 2, 7.3, 7, 10, 9.7)), .Names = c("Group", "Sample", "x", "y"), row.names = c(NA,-6L), class = c("tbl_df", "tbl", "data.frame"), spec = structure(list(cols = structure(list(Group = structure(list(), class = c("collector_character", "collector")),Sample = structure(list(), class = c("collector_character","collector")), x = structure(list(), class = c("collector_double", "collector")), y = structure(list(), class = c("collector_double", "collector"))), .Names = c("Group", "Sample", "x", "y")), default = structure(list(), class = c("collector_guess", "collector"))), .Names = c("cols", "default"), class = "col_spec"))
a2 <- structure(list(Group = c("alpha_6", "alpha_6", "alpha_7", "alpha_7", "alpha_9", "alpha_9", "alpha_10", "alpha_10"), Sample = c("ps_1", "ps_2", "ps_1", "ps_2", "ps_1", "ps_2", "ps_1", "ps_2"), x = c(3,3.2, 5, 5.1, 1, 1.1, 5, 5.1), y = c(8.1, 7, 3, 4, 14, 15, 4,3)), .Names = c("Group", "Sample", "x", "y"), row.names = c(NA, -8L), class = c("tbl_df", "tbl", "data.frame"), spec = structure(list(cols = structure(list(Group = structure(list(), class = c("collector_character", "collector")), Sample = structure(list(), class = c("collector_character","collector")), x = structure(list(), class = c("collector_double", "collector")), y = structure(list(), class = c("collector_double", "collector"))), .Names = c("Group", "Sample", "x", "y")), default = structure(list(), class = c("collector_guess", "collector"))), .Names = c("cols", "default"), class = "col_spec"))
b2 <- structure(list(Group = c("beta_3", "beta_3", "beta_4", "beta_4", "beta_6", "beta_6"), Sample = c("ps_1", "ps_2", "ps_1", "ps_2", "ps_1", "ps_2"), x = c(3, 3.2, 5, 5.1, 1, 1.1), y = c(8.1, 7, 3, 4, 14, 15)),.Names = c("Group", "Sample", "x", "y"), row.names = c(NA, -8L), class = c("tbl_df", "tbl", "data.frame"), spec = structure(list(cols = structure(list(Group = structure(list(), class = c("collector_character", "collector")), Sample = structure(list(), class = c("collector_character","collector")), x = structure(list(), class = c("collector_double", "collector")), y = structure(list(), class = c("collector_double", "collector"))), .Names = c("Group", "Sample", "x", "y")), default = structure(list(), class = c("collector_guess", "collector"))), .Names = c("cols", "default"), class = "col_spec"))
b3 <- structure(list(Group = c("beta_3", "beta_3", "beta_4", "beta_4", "beta_6", "beta_6"), Sample = c("ps_1", "ps_2", "ps_1", "ps_2", "ps_1", "ps_2"), x = c(3, 3.2, 5, 5.1, 1, 1.1), y = c(8.1, 7, 3, 4, 14, 15)), .Names = c("Group", "Sample", "x", "y"), row.names = c(NA, -8L), class = c("tbl_df", "tbl", "data.frame"), spec = structure(list(cols = structure(list(Group = structure(list(), class = c("collector_character", "collector")), Sample = structure(list(), class = c("collector_character","collector")), x = structure(list(), class = c("collector_double", "collector")), y = structure(list(), class = c("collector_double", "collector"))), .Names = c("Group", "Sample", "x", "y")), default = structure(list(), class = c("collector_guess", "collector"))), .Names = c("cols", "default"), class = "col_spec"))

# Data export to simulate the problem
lz_write <- function(input) {
  write_csv(input, paste0(substitute(input), ".csv"))
}
lz_write(a1); lz_write(a2); lz_write(b2); lz_write(b3) # Messed up function for lapply...
# rm(list = ls()) # Clean the environment

UI

ui <- fluidPage(
  titlePanel("Minimal Example"),
  fluidRow(
    column(width = 2, class = "well", 
           # File selection
           HTML(paste("Which file?")),
           # Prefix:
           selectInput(inputId = "p",
                       label = "Prefix:",
                       choices = c("a", "b", "c"),  
                       selected = "a"), 

           # Suffix:
           numericInput(inputId = "s",
                        label = "Suffix:",
                        min = 1,
                        max = 3,
                        value = 1,
                        step = 1)), 
    column(width = 10,
           plotOutput(outputId = "scatterplot",
                      dblclick = "plot_dblclick",  # Might not be necessary, but it's not more work to include but more work to exclude
                      brush = brushOpts(id =  "plot_brush", resetOnNew = TRUE)))
    ), 
  fluidRow(
    column(width = 3,
           br(),
           uiOutput(outputId = "group_n")), 
    column(width = 9, 
           fixedRow( 
             column(width = 3,
                    HTML(paste0("Arg 1"))),
             column(width = 3,
                    HTML(paste0("Arg 2"))),
             column(width = 3,
                    uiOutput(outputId = "num_2"))
             )
    )
  ),
  fluidRow(
    br(), br(), br(), #Lets add some gaps or spacing
    DT::dataTableOutput(outputId = "Table")) # Summary table
)  # Not sure if actually necessary for this example

服务器损坏   现在唯一的问题是用户界面会在重新读取文件时重置...

server_broken <- function(input, output, session) { # Broken version

  #Larger subset: A Reactive Expression # May be used later...
  args <- reactive({
    list(input$p, input$s)  #which file do we wish to input. This was our tag
  })
  # Reactive File-reader Subset
  path <- reactive({
    paste0(input$p, input$s, ".csv")
  }) # Reactive Filename, kinda like our args... 



  filereader <- function(input) { # The function we pass into a reactive filereader. 
    suppressWarnings(read_csv(input, col_types = cols(
      Group = col_character(),
      Sample = col_character(),
      x = col_double(),
      y = col_double())
    ))
  }

  ##BROKEN REACTIVE FILE READER HERE##
  data_1 <- reactiveValues() # The function we use for livestream data
  observe({
    if(file.exists(path()) == TRUE) {
      fileReaderData <-  reactiveFileReader(500, session, path(), filereader) 
    }  else { 
      message("This file does not exist") 
    ## OR DO I DO SOMETHING ELSE HERE??##
    }
    data_1$df <- reactive({ 
     ## STOPS APP CRASHING, BUT NO LONGER REFRESHES CONSTANTLY ##
      req(fileReaderData()) 
      fileReaderData()
    })   
  }) # Honestly don't understand still

  data <- reactive(data_1$df()) # Pulling things out just so the rest of our code can stay the same. 

  ## END OF BROKEN FILE READER##
  ## Reactive UI HERE##
  data_m <- reactive({
    req(data()) 
    args()
    tmp <- isolate(select(data(), Group))
    tmp %>% distinct()
  }) # number of groups

  output$num_2 <- renderUI({
    req(data())
    numericInput(inputId = "n",
                 label = "Group:",
                 min = 1,
                 max = length(data_m()$Group), 
                 value = 1 
    )
  }) #This is our 'reactive' numeric input for groups. This caps the max of our function based on the number of groups there are per file

  n <- reactive(input$n) #which marker number we are dealing with. 
  ## End of reactive UI##
  data_n <- reactive({
    req(data()); req(data_m())
    dt <- filter(data(), Group == data_m()[[1]][input$n])
  }) 


  # Create scatterplot object the plotOutput function is expecting ----
  ranges <- reactiveValues(x = NULL, y = NULL)


  output$scatterplot <- renderPlot({
    validate(need(data(), "The specified file does not exist. Please try another"))
    p <- as.numeric(input$p)
    plot <- ggplot(data_n(), aes(x, y)) +  
      labs(title = paste0("Group ", data_n()$Group[1])) + 
      labs(x = "X vals", y = "Y vals") + 
      geom_point() + theme_bw() # I already have customized aesthetics. Removed for minimalism  
    plot + coord_cartesian(xlim = ranges$x, ylim = ranges$y, expand = TRUE) # So we see all points more readily. messes up the zoom but oh well
  })

  # When a double-click happens, check if there's a brush on the plot.
  # If so, zoom to the brush bounds; if not, reset the zoom.
  observeEvent(input$plot_dblclick, {
    brush <- input$plot_brush
    if (!is.null(brush)) {
      ranges$x <- c(brush$xmin, brush$xmax)
      ranges$y <- c(brush$ymin, brush$ymax)
    } else {
      ranges$x <- NULL
      ranges$y <- NULL
    }
  })  


  #Creating text ----
  output$group_n <- renderText({
    req(data())
    paste0("There are ", length(data_m()$Group), " groups in this file.",
           tags$br("This is Group: ", data_m()$Group[n()])
    )
  }) 

  #Building a table for you to visibly see points. You may need to update the DT to the github version ----
  output$Table <- DT::renderDataTable({
    req(data())
    brushedPoints(data_n(), brush = input$plot_brush) %>%
      select(Sample) 
  })

}

功能服务器

它已被删除,因为损坏的至少不会崩溃,并且问题很明显。请参阅以前的修改。

已咨询来源

会话信息

  • R版本3.4.2(2017-09-28)
  • 平台:x86_64-w64-mingw32 / x64(64位)
  • 在以下环境中运行:Windows 7 x64(内部版本7601)Service Pack 1

更新

Observe()内放置反应堆可阻止应用程序崩溃,并且确实会更新文件(忘记删除某些内容)。剩下的就是将依赖的UI保存在某处...

1 个答案:

答案 0 :(得分:1)

简而言之,问题是由于没有正确理解observers的逻辑,在反应式响应之后缺少()以及没有调用req来阻止某些部分重新执行(参见Quick Tour Primer)。

可以通过在下面的##CHANGE:中查找特定的逐行更新...最重要的更改(无明显顺序)是:

  1. isolate()用于renderUI
  2. req()中使用renderUI使其变慢,直到#个组中有更新时才运行,而是调用args()使其取决于文件选择< / li>
  3. 预先计算renderUI以外的组数

更新的服务器

server_fixed <- function(input, output, session) { 

  #Larger subset: A Reactive Expression # May be used later...
  args <- reactive({
    list(input$p, input$s)  #which file do we wish to input. This was our tag
  })
  # Reactive File-reader Subset
  path <- reactive({
    paste0(input$p, input$s, ".csv")
  }) # Reactive Filename, kinda like our args... 



  filereader <- function(input) { # The function we pass into a reactive filereader. 
    suppressWarnings(read_csv(input, col_types = cols(
      Group = col_character(),
      Sample = col_character(),
      x = col_double(),
      y = col_double())
    ))
  }

  data_1 <- reactiveValues() # The function we use for livestream data
  observe({
    if(file.exists(path()) == TRUE) {
      fileReaderData <-  reactiveFileReader(500, session, path(), filereader) 
    }  else { 
      message("This file does not exist")
    }
    data_1$df <- reactive({
      # if(exists(fileReaderData())) {
      #   fileReaderData()
      # } # Crashed from the beginning
      req(fileReaderData()) 
      fileReaderData()
    })   
  }) 

  data <- reactive(data_1$df()) ##CHANGE: FORGOT THE ()##

  # Group setting...
  data_m <- reactive({
    req(data()) 
    args()
    tmp <- isolate(select(data(), Group))
    tmp %>% distinct()
  }) #number of markers, keeping only the marker name

  data_m_length <- reactive({ ##CHANGE: TOOK OUT OF output$num_2## 
  ##CHANGE: ADDED AN ISOLATE to fix the # of groups per file ##

    isolate(length(data_m()$Group))
  })

  output$num_2 <- renderUI({
    req(data_m_length()) ## CHANGE: ONLY EXECUTE ONCE WE HAVE OUR isolated data_m_length##
    args() ## CHANGE: DEPENDENT UPON changing files##
    isolate(
    numericInput(inputId = "n",
                 label = "Group:",
                 min = 1,
                 max = data_m_length(), 
                 value = 1 # THIS SHOULD BE CACHED! 
    )) ##CHANGE: ADDED IT IN ISOLATE when testing. NOT SURE IF STILL NEEDED##
  }) #This is our 'reactive' numeric input for groups. This caps the max of our function based on the number of groups there are per file

  n <- reactive(input$n) #which marker number we are dealing with. 

  data_n <- reactive({
    req(data()); req(data_m())
    dt <- filter(data(), Group == data_m()[[1]][n()])
  }) 


  # Create scatterplot object the plotOutput function is expecting ----
  ranges <- reactiveValues(x = NULL, y = NULL)


  output$scatterplot <- renderPlot({
    validate(need(data(), "The specified file does not exist. Please try another"))
    p <- as.numeric(input$p)
    plot <- ggplot(data_n(), aes(x, y)) +  
      labs(title = paste0("Group ", data_n()$Group[1])) + 
      labs(x = "X vals", y = "Y vals") + 
      geom_point() + theme_bw() # I already have customized aesthetics. Removed for minimalism  
    plot + coord_cartesian(xlim = ranges$x, ylim = ranges$y, expand = TRUE) # So we see all points more readily. messes up the zoom but oh well
  })

  # When a double-click happens, check if there's a brush on the plot.
  # If so, zoom to the brush bounds; if not, reset the zoom.
  observeEvent(input$plot_dblclick, {
    brush <- input$plot_brush
    if (!is.null(brush)) {
      ranges$x <- c(brush$xmin, brush$xmax)
      ranges$y <- c(brush$ymin, brush$ymax)
    } else {
      ranges$x <- NULL
      ranges$y <- NULL
    }
  })  


  #Creating text ----
  output$group_n <- renderText({
    req(data())
    paste0("There are ", length(data_m()$Group), " groups in this file.",
           tags$br("This is Group: ", data_m()$Group[n()])
    )
  }) 

  #Building a table for you to visibly see points. You may need to update the DT to the github version ----
  output$Table <- DT::renderDataTable({
    req(data())
    brushedPoints(data_n(), brush = input$plot_brush) %>%
      select(Sample) 
  })

}

剩下的就是适当地使用suppressErrorvalidate