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:
server.R
quelles pièces doivent être achetées?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)
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)
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
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