library(knitr) library(rmdformats) ## Global options options(max.print="75") opts_chunk$set(echo=TRUE, cache=TRUE, prompt=FALSE, tidy=TRUE, comment=NA, message=FALSE, warning=FALSE) opts_knit$set(width=75)
Shiny est un framework d'applications web pour R
Le succès du package (pour les utilisateurs R) réside dans :
sa facilité d'écriture : aucune compétence en développement web n'est requise
le potentiel de calcul et de modélisation de R est facilement mobilisable : il est aisé de partir d'un script d'analyse R pour en réaliser une application
les applications sont publiées en local ou sur les internets via un serveur shiny
Une application Shiny contient 2 fichiers :
ui.R pour "user interface" : ce fichier définit l'apparence de la page web et les inputs
server.R : ce fichier explique comment, côté serveur, R doit se comporter et quels calculs il doit réaliser à renvoyer sous forme d'outputs en fonction du comportement utilisateur
Nota Bene : parfois on trouve seulement un seul fichier nommé app.R qui contient l'ui et le serveur sous forme d'objets R et l'application est lancée via l'instruction shinyApp(ui,server)
Dans RStudio,
File > New File > Shiny Web App
donner un nom à votre application
choisir multiple file pour séparer les dévelopements côté UI du côté serveur
choisir une adresse où positionner le projet
Un fois le projet créé, cliquez sur le bouton "Run app" ou exécutez runApp('nom_de_lapplication')
!
Dans sa version "unique file", le template d'une application Shiny est de la forme :
library(shiny) ui <- fluidPage() server <- function(input, output) {} shinyApp(ui = ui, server = server)
2 types de fonctions : xInput et xOutput
Ces deux grandes familles de fonctions s'intéressent aux inputs que l'utilisateur va vouloir passer au serveur et les output que le serveur lui restituera.
A saisir :
numericInput(inputId = "Numeric", label = "Choisissez un nombre entre 0 et 100", value = 50, min = 0, max = 100, step = 10)
ou sous forme de slider :
sliderInput("slider", "Nombre :", min = 0, max = 1000, value = 500)
sliderInput("slider", "Nombre :", min = 0, max = 1000, value = c(100,300))
textInput(inputId = "texte", label = "Saisissez du texte", value = "exemple")
textAreaInput(inputId = "texte", label = "Une plus grosse boîte de texte")
Avec un choix unique...
selectInput(inputId = "idSelect", label = "Choisissez: ", selected = 3, choices = c("choix 1" = 1, "choix 2" = 2, "choix 3" = 3))
ou à choix multiples :
selectInput(inputId = "idSelect", label = "Choisissez: ", selected = 3, multiple = TRUE, choices = c("choix 1" = 1, "choix 2" = 2, "choix 3" = 3))
checkboxInput(inputId = "check1", label = "Cochez (ou pas): ", value = TRUE)
checkboxGroupInput(inputId = "checkmultiple", label = "Cochez (ou pas)", choices = c("check 1", "check 2", "check 3", "check 4"),selected = "check 1",inline = TRUE)
dateInput(inputId = "ladate", label = "Une date", value = Sys.Date()-10, format = "dd/mm/yy", language = "fr", startview = "year", weekstart = 1)
ou une plage de temps :
dateRangeInput(inputId = "plagedate", label = "Une plage de temps : ", language = "fr")
fileInput(inputId = "fichier_importe", label = "Fichier à importer", multiple = FALSE, accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv"))
actionButton(inputId = "idActionButton", label = "Il clique à gauche")
FYI : la liste des fonctions qui finissent par Input dans le package Shiny :
ls('package:shiny', pattern = "Input$")
htmlOutput(outputId = "renduhtml")
imageOutput(outputId = "image")
plotOutput(outputId = "graphe")
tableOutput(outputId = "table1")
dataTableOutput(outputId ="table2")
textOutput(outputId = "texte")
verbatimTextOutput(outputId = "untextepluslong")
uiOutput(outputId = "unelementui")
FYI : la liste des fonctions qui finissent par Output dans le package Shiny :
ls('package:shiny', pattern = "Output$")
Le serveur assemble les inputs pour les restituer à l'utilisateur sous forme d'output. L'art de faire du Shiny consiste à réaliser ce montage côté serveur. A cet effet, 3 règles à observer impérativement pour ne pas avoir d'ennuis :
output$x
côté serveur : Exemple :
ui <- fluidPage( plotOutput("unplot") ) server <- function(input, output) { output$unplot <- # du code R pour faire le plot en question) } shinyApp(ui = ui, server = server)
output$x <-
dans le serveur, l'instruction en question est nécessairement suivie d'une fonction render*()
L'exemple devient donc :
Exemple :
ui <- fluidPage( plotOutput("unplot") ) server <- function(input, output) { output$unplot <- renderPlot({# du code R}) } shinyApp(ui = ui, server = server)
Nota Bene : attention aux accolades à l'intérieur de cette famille de fonctions
A disposition, la liste de toutes les fonctions render*()
ls('package:shiny', pattern = "^render")
Elles fonctionnent main dans la main avec les fonctions *Output()
de l'UI.
render*()
, la syntaxe est de la forme : input$x
ui <- fluidPage( # l'input fourni par l'utilisateur est un nombre entier renseigné par sliderInput() : sliderInput(inputId = "classes", "Nombre de classes", min = 1, max =50, value = 30), # l'output retourné à l'utilisateur est un plot nommé "unplot" plotOutput("unplot") ) server <- function(input, output) { output$unplot <- renderPlot({ # l'objet unplot est transmis à plotOutput via output$unplot #selection de la variable dans le jeu de données x <- faithful[, 2] # définition du nombre de classes avec l'input classes <- seq(min(x), max(x), length.out = input$classes + 1) # construction du plot hist(x, breaks = classes, col = 'darkgray', border = 'white', main = "Histogramme des temps d'attente entre 2 éruptions du geyser 'Old Faithful' ") }) } shinyApp(ui = ui, server = server)
Exercice 1 :
Ajouter une boîte de texte sous le graphique qui nous renseigne sur le temps moyen d'attente entre 2 éruptions.
Les fonctions render*()
ont une réactivité immédiate. C'est à dire qu'à chaque couple input\$inputId <-> output\$outputId la mise à jour est instantanée dès lors que input\$inputId change de valeur.
Les valeurs réactives marchent de pair avec les fonctions réactives.
Dès que l'input change, les valeurs dites "réactives" influent sur les fonctions qui utilisent pour leur notifier que les paramètres qu'ils utilisent sont invalides : tous les blocs de code qui contiennent l'input sont donc (ré)évalués. Les fonctions réactives mettent à jour les output$outputId
Les inputs sont en fait une liste de valeurs réactives et il n'est possible d'appeler des ces valeurs que dans des fonctions réactives : les valeurs réactives notifient les fonctions réactives qui y répondent.
render*()
Chaque fois que les inputs utilisés dans une fonction render*()
changent c'est l'intégralité du bloc de code qui est (ré)évalué.
Question :
Dans l'exemple précédent, le calcul de la moyenne est-il recalculé à un moment ?
Exercice 2 :
Ajouter à l'application précédente un input qui serait du texte à saisir et qui permettrait à l'utilisateur de changer le titre du graphique
reactive()
Les fonctions reactive()
servent à découper les applications shiny en petits modules qui optimisent le flux de valeurs réactives et des fonctions associées
Elle permettent de créer des objets, réactifs, liés à un input qui seront utilisés à plusieurs endroits de l'application.
variable_a_reutiliser <- reactive({expression impliquant des inputs}) ... output$nom_de_loutput <- render*({fonction1(paramètre = variable_a_reutiliser())}) output$nom_d_un_autre_output <- render*({fonction2(paramètre = variable_a_reutiliser())})
Nota Bene : attention aux parenthèses de la fonction réactive nouvellement générée. En cas d'oubli, il se passe ce qui se passe quand on demande à R une fonction sans parenthèses == l'affichage du code source
Exercice 3 :
Ajouter un bloc de texte sous le graphique qui compte le nombre de caractères contenus dans le titre du graphique avec une fonction reactive()
isolate()
isolate()
est une fonction qui retourne une valeur non réactive.
La syntaxe est semblable à celle de reactive*()
:
isolate({expression impliquant des inputs})
Avec des parenthèses car il s'agit d'une fonction et des accolades car il s'agit d'une expression R.
Au lancement de l'application, la valeur prise par le résultat de la fonction isolate()
est celle qui correspond à l'évaluation de l'expression entre accolades avec les inputs correspondants.
Si les inputs changent au sein des accolades, l'output n'est pas mis à jour.
Par contre, et c'est tout son intérêt, si isolate
est au sein d'un bloc de code qui contient des variables réactives, chaque fois que ces valeurs changent, le bloc est réévalué, et l'expression au sein d'isolate()
remise à jour en quelque sorte.
isolate()
est un moyen de faire un refresh "local", conditionné par des variables environnantes qui elles sont réactives.
Exercice 4 :
Utiliser isolate()
pour isoler la réactivité de la mise à jour du titre du graphique : le comptage du nombre de caractères soit rester réactif, mais la mise à jour du titre ne doit se faire que quand le slider est modifié.
observeEvent()
et observe()
Ces deux fonctions ont une syntaxe différente pour une réaction identique :
observe({expression à évaluer liée à un input}) observeEvent(input$evenement, {expression à évaluer liée à l'input "evemenent"})
Ces fonctions attendent des interactions avec l'utilisateur, des "observations" au niveau des inputs avant d'exécuter les expressions entre accolades.
Il est possible d'assigner leur valeur à des outputs pour que ces derniers ne se (ré)exécute que quand l'utilisateur le demande et non pas sous forme d'un refresh instantané comme pour les valeurs réactives au sein des fonctions render*()
ui <- fluidPage( sliderInput(inputId = "num", label = "Choisir un nombre", min = 1, max = 100, value = 25), actionButton(inputId = "go", label = "Renvoyer la valeur prise par le bouton dans la console") ) server <- function(input, output) { observeEvent(input$go, { print(as.numeric(input$num)) }) } shinyApp(ui = ui, server = server)
Souvent, les événements qui déclenchent du code ailleurs dans l'application sont des actionButton()
comme dans l'exemple ci-dessus ou des
eventReactive()
Dans le flux de réactivité et de mise à jour des valeurs du côté serveur et UI, il est parfois intéressant de temporiser ce flux en laissant la main au client putôt qu'en faisant les refresh instantanément, à l'image de reactive()
La fonction qui qui permet de conditionner la chaîne de réactivité à un événement est la fonction eventReactive()
Sa syntaxe est de la forme :
eventReactive(input qui donne le go, {expression à évaluer après le go})
Comme pour les fonctions reactive()
, la fonctions eventReactive()
renvoie une fonction et invite donc à utiliser les parenthèses à chaque fois qu'on souhaite s'y référer :
variable_a_reutiliser <- eventReactive(input qui donne le go, {expression impliquant des inputs}) ... output$nom_de_loutput <- render*({fonction1(paramètre = variable_a_reutiliser())}) output$nom_d_un_autre_output <- render*({fonction2(paramètre = variable_a_reutiliser())})
Exercice 5:
A partir d'un template vide, créer une application qui utilise la fonction actionButton
comme déclencheur à afficher un chiffre au hasard compris entre 1 et 100 (fonction sample
).
Exercice 6:
Ajouter à votre application un bouton de mise à jour du titre du graphique et du nombre de caractères qu'il contient au moyen d'un actionButton
dès lors que l'utilisateur juge sa saisie terminée
un schéma récapitulatif :
Un aide-mémoire : https://www.rstudio.com/wp-content/uploads/2015/08/shiny-french-cheatsheet.pdf
tags$
Les tags
sont la liste de fonctions R à disposition pour écrire du html depuis R :
library(magrittr) names(tags) %>% head(50)
Ainsi...
tags$blockquote("une citation")
...est une fonction qui insère les balises correspondantes
Il est donc possible de rajouter tous les éléments statiques nécessaires à la construction de l'UI avec les tags
simplement en les ajoutant dans l'ui :
ui <- fluidPage( tags$h1("Titre h1"), tags$p(tags$code("I <3 R")), tags$p(tags$code("I ",tags$strong("really"), "<3 R")) ) server <- function(input, output) { } shinyApp(ui = ui, server = server)
Avec la fonction HTML()
!
ui <- fluidPage( HTML("<p> <code> I <strong>really</strong> <3 R </code> </p>") ) server <- function(input, output) { } shinyApp(ui = ui, server = server)
Pour découper la page avec des div
:
fluidRow() column(3)
fluidRow("Ligne", column(3, "Colonne 1"), column(3, "Colonne 2") )
ui <- fluidPage( fluidRow("Ligne", column(width = 3, "Colonne 1", sliderInput(inputId = "slider", label = "slider", min=1, max = 5, value = 2)), column(width = 3, "Colonne 2", sliderInput(inputId = "slider", label = "slider", min=1, max = 5, value = 2)) ) ) server <- function(input, output) { } shinyApp(ui = ui, server = server)
ou avec des fonctions *panel
:
ls('package:shiny', pattern = "Panel$")
La fonction classiquement utilisée étant sidebarPanel
couplée à mainPanel
ui <- fluidPage( mainPanel( fluidRow("Ligne",wellPanel( column(width = 3, "Colonne 1", sliderInput(inputId = "slider", label = "slider", min=1, max = 5, value = 2))), column(width = 3, "Colonne 2", sliderInput(inputId = "slider", label = "slider", min=1, max = 5, value = 2)) ) ) ) server <- function(input, output) { } shinyApp(ui = ui, server = server)
Dès lors que l'on s'oriente vers une customisation avancée de l'apparence de la page, il est de bon ton de créer un répertoire à la racine de app.R
ou ui.R
et server.R
afin d'y hoster la boîte à outils nécessaire au design
Ce répertoire doit s'appeler www
:
dir.create("www")
C'est dans ce répertoire que viendra piocher le navigateur les images et le teplate css
...et en le déposant dans le répertoire www
Exercice pour la prochaine fois : Réaliser une application Shiny de votre package de SEO !
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.