【问题标题】:How to organize large Shiny apps?如何组织大型闪亮应用程序?
【发布时间】:2015-01-20 16:54:22
【问题描述】:

组织大型 Shiny 应用程序的最佳做法是什么?
我认为最佳 R 实践也适用于 Shiny。
此处讨论了最佳 R 实践:How to organize large R programs
链接到 Google 的 R 风格指南:Style Guide

但是在 Shiny 上下文中,我可以采用哪些独特的技巧和窍门来使我的 Shiny 代码看起来更好(并且更具可读性)? 我在想这样的事情:

  • 在 Shiny 中利用面向对象编程
  • server.R 中应该采购哪些部件?
  • 项目的文件层次结构包含 Markdown 文档、图片、 xml 和源文件

例如,如果我在每个tabPanel 中都使用navbarPagetabsetPanel,那么在添加几个UI 元素后,我的代码开始看起来很混乱。

示例代码:

server <- function(input, output) {

 #Here functions and outputs..

}

ui <- shinyUI(navbarPage("My Application",
  tabPanel("Component 1",
             sidebarLayout(
                sidebarPanel(
                    # UI elements..
                ),
                mainPanel(
                    tabsetPanel(
                        tabPanel("Plot", plotOutput("plot")
                                 # More UI elements..
                                 ), 
                        tabPanel("Summary", verbatimTextOutput("summary")
                                 # And some more...
                                 ), 
                        tabPanel("Table", tableOutput("table")
                                 # And...
                                 )
                    )
                )
    )           
  ),
  tabPanel("Component 2"),
  tabPanel("Component 3")
))

shinyApp(ui = ui, server = server)

为了组织 ui.R 代码,我从 GitHub 找到了非常好的解决方案:radiant code
解决方案是使用renderUI 来渲染每个tabPanel,并且在server.R 中,标签来自不同的文件。

server <- function(input, output) {

  # This part can be in different source file for example component1.R
  ###################################
  output$component1 <- renderUI({
        sidebarLayout(
                sidebarPanel(
                ),
                mainPanel(
                    tabsetPanel(
                        tabPanel("Plot", plotOutput("plot")), 
                        tabPanel("Summary", verbatimTextOutput("summary")), 
                        tabPanel("Table", tableOutput("table"))
                    )
                )
    )
  })
 #####################################  

}
ui <- shinyUI(navbarPage("My Application",
  tabPanel("Component 1", uiOutput("component1")),
  tabPanel("Component 2"),
  tabPanel("Component 3")
))

shinyApp(ui = ui, server = server)

【问题讨论】:

    标签: r shiny


    【解决方案1】:

    现在还有golem 包,它提供了组织闪亮代码的框架。它主要使用模块,但也提供了如何组织的结构,例如辅助函数和 css/javascript 文件。还有一个accompanying book

    【讨论】:

      【解决方案2】:

      模块 添加到 R 闪亮之后。在闪亮的应用程序中管理复杂结构变得容易得多。

      闪亮模块详细说明:Here

      使用模块的优点:

      • 创建后,它们很容易重复使用
      • ID 冲突更容易避免
      • 基于模块输入和输出的代码组织

      在基于标签的闪亮应用中,一个标签可以被视为一个具有输入输出的模块。然后可以将选项卡的输出作为输入传递给其他选项卡。

      利用模块化思维的基于选项卡结构的单文件应用程序。可以使用cars 数据集测试应用程序。从 Joe Cheng(第一个链接)复制的部分代码。欢迎所有的cmets。

      # Tab module
      # This module creates new tab which renders dataTable
      
      dataTabUI <- function(id, input, output) {
        # Create a namespace function using the provided id
        ns <- NS(id)
      
        tagList(sidebarLayout(sidebarPanel(input),
      
                              mainPanel(dataTableOutput(output))))
      
      }
      
      # Tab module
      # This module creates new tab which renders plot
      plotTabUI <- function(id, input, output) {
        # Create a namespace function using the provided id
        ns <- NS(id)
      
        tagList(sidebarLayout(sidebarPanel(input),
      
                              mainPanel(plotOutput(output))))
      
      }
      
      dataTab <- function(input, output, session) {
        # do nothing...
        # Should there be some logic?
      
      
      }
      
      # File input module
      # This module takes as input csv file and outputs dataframe
      # Module UI function
      csvFileInput <- function(id, label = "CSV file") {
        # Create a namespace function using the provided id
        ns <- NS(id)
      
        tagList(
          fileInput(ns("file"), label),
          checkboxInput(ns("heading"), "Has heading"),
          selectInput(
            ns("quote"),
            "Quote",
            c(
              "None" = "",
              "Double quote" = "\"",
              "Single quote" = "'"
            )
          )
        )
      }
      
      # Module server function
      csvFile <- function(input, output, session, stringsAsFactors) {
        # The selected file, if any
        userFile <- reactive({
          # If no file is selected, don't do anything
          validate(need(input$file, message = FALSE))
          input$file
        })
      
        # The user's data, parsed into a data frame
        dataframe <- reactive({
          read.csv(
            userFile()$datapath,
            header = input$heading,
            quote = input$quote,
            stringsAsFactors = stringsAsFactors
          )
        })
      
        # We can run observers in here if we want to
        observe({
          msg <- sprintf("File %s was uploaded", userFile()$name)
          cat(msg, "\n")
        })
      
        # Return the reactive that yields the data frame
        return(dataframe)
      }
      basicPlotUI <- function(id) {
        ns <- NS(id)
        uiOutput(ns("controls"))
      
      }
      # Functionality for dataselection for plot
      # SelectInput is rendered dynamically based on data
      
      basicPlot <- function(input, output, session, data) {
        output$controls <- renderUI({
          ns <- session$ns
          selectInput(ns("col"), "Columns", names(data), multiple = TRUE)
        })
        return(reactive({
          validate(need(input$col, FALSE))
          data[, input$col]
        }))
      }
      
      ##################################################################################
      # Here starts main program. Lines above can be sourced: source("path-to-module.R")
      ##################################################################################
      
      library(shiny)
      
      
      ui <- shinyUI(navbarPage(
        "My Application",
        tabPanel("File upload", dataTabUI(
          "tab1",
          csvFileInput("datafile", "User data (.csv format)"),
          "table"
        )),
        tabPanel("Plot", plotTabUI(
          "tab2", basicPlotUI("plot1"), "plotOutput"
        ))
      
      ))
      
      
      server <- function(input, output, session) {
        datafile <- callModule(csvFile, "datafile",
                               stringsAsFactors = FALSE)
      
        output$table <- renderDataTable({
          datafile()
        })
      
        plotData <- callModule(basicPlot, "plot1", datafile())
      
        output$plotOutput <- renderPlot({
          plot(plotData())
        })
      }
      
      
      shinyApp(ui, server)
      

      【讨论】:

      • 很好,但您示例中的前几个函数(尤其是dataTabUIplotTabUI)只是普通函数,并未真正演示任何 模块 功能。在这两种情况下,对ns &lt;- NS(id) 的调用完全是多余的。它没有任何用处,因为:(1) 生成的 ns 函数不会在函数中的任何位置调用,并且; (b) 在服务器端没有使用相应的函数来引用此类对ns 的调用会产生的命名空间对象。如果不包括函数dataTabUIplotTabUIdataTab,这个例子会更清楚。
      • @JoshO'Brien 至少 dataTabUI 等。遵循 Shiny 应用程序的形式,并为将来需要的动态组件做好准备。
      【解决方案3】:

      我非常喜欢 Matt Leonawicz 组织应用程序的方式。我采用他的方法学习如何使用 Shiny,因为我们都知道如果管理不当,它会变得非常分散。看看他的结构,他概述了他在名为 run_alfresco 的应用程序中组织应用程序的方式

      https://github.com/ua-snap/shiny-apps

      【讨论】:

      • 感谢@Pork Chop 的这个建议——Matt 是否有充分的理由将 io 和 reactives 放在 appSourceFiles 子目录中,而不是将其留在 externals 目录中?跨度>
      • @micstr,我猜他采用了他过去完成的大型项目的结构。我建议查看用于开发和构建应用程序的 Visual Studio 项目设置(仅用于洞察力)。你也可以在你的 Rshiny 中采用MVC 模型结构,并像他一样按文件夹分解它
      • @jangorecki 因为它被提出问题的用户接受,我认为它完成了工作,或者你可以发布一些东西,所以不要咸
      • 您的帖子不是答案,而是对答案的引用,当回购将被删除时,您的帖子将一文不值。你应该发表评论。这就是 SO 的工作方式。
      • 是的,但是 4 年过去了,github 帐户仍然存在,您真的不应该否决遗留问题
      【解决方案4】:

      我写了辐射。我还没有听到人们对代码组织说坏话(还),但我相信它可能会更好。一种选择是像 Joe Cheng 在闪亮的部分中所做的那样将 ui 和逻辑分开。

      https://github.com/jcheng5/shiny-partials

      另一个可能是尝试 OO 编程,例如,使用 R6 http://rpubs.com/wch/17459

      【讨论】:

      • 一篇关于您的组织方法的博文会很好。此外,它将允许更多关于闪亮应用架构的对话。如果更多的人在谈论/思考它,每个人都会受益
      • @MySchizoBuddy 我会看看我能做什么。不过可能不会很快。今年教授两个新课程。
      • 你有时间写一篇关于闪亮应用组织的文章
      • 很遗憾没有。我建议看看shiny modules。另一种选择是创建一个闪亮的应用程序作为一组 R 包。这就是我现在用于Radiant 的方法。
      猜你喜欢
      • 2011-12-21
      • 1970-01-01
      • 2010-11-18
      • 1970-01-01
      • 2017-07-27
      • 1970-01-01
      • 2018-05-12
      • 2018-12-29
      • 2013-07-08
      相关资源
      最近更新 更多