web-dev-qa-db-fra.com

Comment organiser de grandes applications Shiny?

Quelles sont les meilleures pratiques pour organiser de plus grandes applications Shiny?
Je pense que les meilleures pratiques R sont également applicables à Shiny.
Les meilleures pratiques R sont discutées ici: Comment organiser de grands programmes R
Lien vers le Guide de style R de Google: Guide de style

Mais quels sont les trucs et astuces uniques dans le contexte Shiny que je peux adopter pour rendre mon code Shiny plus beau (et plus lisible)? Je pense à des choses comme:

  • Exploiter la programmation orientée objet dans Shiny
  • Dans server.R quelles pièces doivent être achetées?
  • Hiérarchie des fichiers du projet contenant des documents de démarque, des images, des fichiers XML et des fichiers source

Par exemple, si j'utilise navbarPage et tabsetPanel dans chaque tabPanel, mon code commence à sembler assez compliqué après l'ajout de plusieurs éléments d'interface utilisateur.

Exemple de code:

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)

Pour organiser ui.R code J'ai trouvé une solution plutôt sympa de GitHub: code radiant
La solution consiste à utiliser renderUI pour rendre chaque tabPanel et dans server.R les onglets proviennent de différents fichiers.

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)
57
Mikael Jumppanen

Après l'ajout de modules à R brillant. La gestion de structures complexes dans des applications brillantes est devenue beaucoup plus facile.

Description détaillée des modules brillants: ici

Avantages de l'utilisation des modules:

  • Une fois créés, ils sont facilement réutilisables
  • Les collisions d'identité sont plus faciles à éviter
  • Organisation du code basée sur les entrées et sorties des modules

Dans une application brillante basée sur des onglets, un onglet peut être considéré comme un module qui a entrées et sorties . Les sorties des onglets peuvent ensuite être transmises à d'autres onglets en tant qu'entrées.

Application à fichier unique pour une structure basée sur des onglets qui exploite la pensée modulaire. L'application peut être testée en utilisant cars dataset. Certaines parties du code ont été copiées du Joe Cheng (premier lien). Tous les commentaires sont les bienvenus.

# 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)
23
Mikael Jumppanen

J'aime vraiment la façon dont Matt Leonawicz organise ses applications. J'ai adopté son approche en apprenant à utiliser Shiny, car nous savons tous qu'il peut être assez dispersé s'il n'est pas correctement géré. Jetez un œil à sa structure, il donne un aperçu de la façon dont il organise les applications dans l'application appelée run_alfresco

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

23
Pork Chop

J'ai écrit Radiant. Je n'ai pas encore entendu de gens dire de mauvaises choses sur l'organisation du code, mais je suis sûr que cela pourrait être mieux. Une option serait de séparer l'interface utilisateur et la logique comme le fait Joe Cheng dans les partiels brillants.

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

Un autre pourrait être d'essayer la programmation OO, par exemple, en utilisant R6 http://rpubs.com/wch/17459

8
Vincent