Avantages
Inconvénients...
library(shiny)
# Define UI for application that plots an histogram
shinyUI(fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput(inputID = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 25)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput(outputId = "distPlot")
)
)
))
! inputId, outputId
# Faire le lien input <--> output
server <- function(input, output) {
output$distPlot <- renderPlot({
# Generate bins based on input$bins from ui.R
x <- faithful[,2]
bins <- seq(min(x), max(x), length.out = input$bins+1)
# Draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
Parties réactives : renderPlot, renderImage,renderTable, ...
→ Bloc ré-éxecuté quand une des entrées utilisée est modifiée.
withMathJax(helpText("Some math here $$\\alpha+\\beta$$"))
# now we can just write "static" content without withMathJax()
div("more math here $$\\sqrt{2}$$")
Note : si on charge du contenu HTML plus tard via renderUI(), alors il faut entourer chaque expression LaTeX par "withMathJax(...)".
fluidPage(..., title = NULL, theme = NULL, lang = NULL)
A fluid page layout consists of rows which in turn include columns. Rows exist for the purpose of making sure their elements appear on the same line. Columns exist for the purpose of defining how much horizontal space within a 12-unit wide grid its elements should occupy.
Chaque balise HTML courante a un équivalent "fonction shiny" : voir la section "Interface builder functions" dans l'aide.
La partie serveur définit ce qui se passe après chaque action utilisateur. En général, elle met à jour le contenu des éléments de l'UI.
→ Mix entre le contrôleur et modèle vus au cours précédent.
Structurer l'exécution pour éviter de dupliquer des traitements :
data = reactive({ ... })
data()
data = reactive({ ... })
renderPlot(data())
observe({
input$saveButton
write.csv(data())
})
Note : observe() retourne un objet "observer" que l'on peut mettre en pause par exemple.
Isoler une partie du code :
observe({
input$saveButton #take a dependency on input$saveButton
# Isolate a whole block
data <- isolate({
a <- input$valueA #no dependency on input$valueA or input$valueB
b <- input$valueB
c(a=a, b=b)
})
writeToDatabase(data)
})
Exécuter un bloc de manière conditionelle au changement d'une ou plusieurs entrées.
shinyApp(
ui = fluidPage(
column(4,
numericInput("x", "Value", 5),
br(),
actionButton("button", "Show")
),
column(8, tableOutput("table"))
),
server = function(input, output) {
# Take an action every time button is pressed;
# here, we just print a message to the console
observeEvent(input$button, {
cat("Showing", input$x, "rows\n")
})
# Take a reactive dependency on input$button, but
# not on any of the stuff inside the function
df <- eventReactive(input$button, {
head(cars, input$x)
})
output$table <- renderTable({
df()
})
}
)
Ajoute ou supprime des éléments du DOM (cf. cours précédent)
shinyApp(
ui = basicPage(actionButton("go", "Go")),
server = function(input, output, session) {
observeEvent(input$go, {
insertUI("#go", "afterEnd",
actionButton("dynamic", "click to remove"))
# Set up an observer that depends on the dynamic
# input, so that it doesn't run when the input is
# created, and only runs once after that (since
# the side effect is remove the input from the DOM)
observeEvent(input$dynamic, {
removeUI("#dynamic")
}, ignoreInit = TRUE, once = TRUE)
})
}
)
ui <- fluidPage(
sliderInput("n", "Number of observations", 2, 1000, 500),
plotOutput("plot")
)
server <- function(input, output) {
# Anything that calls autoInvalidate will automatically invalidate
# every 2 seconds.
autoInvalidate <- reactiveTimer(2000)
observe({
# Re-execute this reactive expression every time the timer fires.
autoInvalidate()
# Do something each time this is invalidated.
# The isolate() makes this observer _not_ get invalidated and re-executed
# when input$n changes.
print(paste("The value of input$n is", isolate(input$n)))
})
# Generate a new histogram each time the timer fires, but not when
# input$n changes.
output$plot <- renderPlot({
autoInvalidate()
hist(rnorm(isolate(input$n)))
})
}
Un reactiveTimer est pratique pour par exemple demander une mise à jour au serveur toutes les $n$ minutes/secondes.
Mais, plus efficace de laisser un serveur notifier le client :
...Voir plus loin :)
Réaliser une interface shiny permettant d'explorer au choix :
"Shiny's architecture consists of a client (the browser) and a server (an R process). The two are connected by a websocket that receives state changes from the client, such as new values for input controls, and distributes state changes from the server, such as new output values."
"In some cases (for instance, if you're writing your own custom bindings) it's helpful to see exactly what's going across the wire. You can watch the JSON emitted to and received from the websocket by turning on tracing:"
options(shiny.trace = TRUE)
Voir les diverses possibilité de debugging.
Dashboard ?
Développer une petite appli Shiny pas à pas, l'objectif étant de classer des données :
Point important : tout le monde dispose de l'ensemble des fichiers et de l'historique $\Rightarrow$ pas de perte de données en cas de plantage du serveur.
Trois "arbres" représentant l'état actuel des fichiers
coexistent localement...
Objectif = faciliter le partage de votre code, l'ouvrir à de nouvelles collaborations spontanées ou non.
"Code" au sens large : tout ensemble de fichiers texte est OK.
Il existe plusieurs plateformes alternatives, mais GitHub est le plus populaire, et elles fonctionnent toutes un peu de la même façon.
Premières étapes :
Ensuite
git clone <url_du_repo> <nom_local>
# Modifier des fichiers, en ajouter, supprimer, etc
# Ajout dans l’index :
git add -A #ajoute tout à l'index
# Ajout dans le repo local :
git commit #affiche un éditeur de texte
# Alternative:
git commit -m "Message décrivant les modifications"
# Envoi sur le repo principal (souvent GitHub)
git push [origin main]
git pull [origin main]
# Résoudre les conflits éventuels, etc...
Suivre ce tutoriel et faire les exercices.
Concernant la gestion des conflits, vous pouvez par exemple développer à deux sur un même repo.
Pour un repo GitHub : git-lfs.
Solution très simple mais fonctionnelle (un seul fichier) : git-fat.
Version simple :
# Création du squelette depuis R package.skeleton(name = "monpackage")
# Ajout d’une fonction (3 prédéfinies: f, g, h) cd monpackage echo "increment <- function(x) { x + 1 }" > R/increment.R echo "export(increment)" >> NAMESPACE cd .. R CMD INSTALL monpackage
# Utilisation du package library(monpackage) increment(32) #presque... :)
Version plus élaborée (recommandée) : développer un package.
Suivre le tutoriel du slide précédent : créer un package avec une fonction prenant en argument un jeu de données (fichier CSV), et retournant une liste avec deux champs :
Utiliser ce package dans une application Shiny permettant d'uploader un jeu de données et ensuite de choisir parmi les variables à représenter (histogramme).
Note : on peut inclure l'app Shiny comme illustration du package, comme indiqué ici.