Construisez votre première application avec Shiny

Introduction

Shiny est un package utilisé pour créer des applications Web interactive avec R. L’application Web peut être intégrée dans un document rmarkdown, une page Web, de manière autonome ou en tant que tableau de bord.

C’est vraiment puissant et nous pouvons créer des applications et des tableaux de bord assez rapidement.

Dans cet article, je vais parcourir les bases de Shiny, comment créer des applications simples et présenter le concept de réactivité. Je vais juste donner les bases de l’interface utilisateur pour commencer et j’expliquerai plus en détail dans un autre article comment améliorer l’interface utilisateur ( retrouver l’article ici )


Application minimale


Une application Shiny a besoin d’au moins les 4 lignes suivantes :

library(shiny)

ui <- fluidPage()

server <- function(input, output){}

shinyApp(ui, server)
  • library(shiny) : Chargement de Shiny
  • ui <- fluidPage() : création de la page web, l’interface utilisateur (UX)
  • server <- function(input, output){} : la partie serveur, où la logique métier est implémentée
  • shinyApp(ui, server) : création l’application avec l’UX et la logique de serveur précédemment créées.



Afficher un simple texte


Cela fonctionne, mais l’application ne fait rien. La chose la plus simple que nous puissions faire est d’afficher un texte.

Il faut ajouter le texte à afficher dans la fluidPage(), le texte est passé en argument à la fonction :

ui <- fluidPage(
  "Hi there, this is my text to display"
)

affichage d’un simple texte

Vous pouvez ajouter différents textes, ils seront affichés ensemble :

ui <- fluidPage(
  "Hi there,",
  "this is my text to display"
)

Shiny utilise une liste de fonctions, appelées “tags”, pour recréer des balises HTML. Par exemple, pour ajouter le texte en titre h1 :

ui <- fluidPage(
  tags$h1("Hi there, this is my text to display")
)

afficher le texte avec un tag H1


Syntaxe pour les tags:

Les balises html les plus courantes ont des fonctions wrapper et peuvent être utilisées comme décrit ci-dessus, en les appelant comme une fonction h1("title"), mais pour la plupart des balises, la syntaxe est tags$h1("title" )

Les balises html les plus courantes pour formater le texte sont :

  • titres hiérarchiques: h1(), h2(), h3(), h4(), h5(), h6()
  • italique: em()
  • mettre en gras: strong()
  • bloc de code: code()

La liste de toutes les balises html disponibles peut être trouvée here


Ajouter un graphique - utilisation de la fonction serveur


Maintenant, nous connaissons les bases pour afficher quelque chose, mais le but est de travailler avec des données et d’afficher nos résultats. Pour comprendre comment cela fonctionne, nous allons ajouter un graphique.

Pour ajouter un graphique, il y a 2 étapes :

  • Créer le graphique : cela se fait dans la fonction server()
    • l’objet créé dans la fonction serveur doit être construit à l’intérieur d’un render (ici renderPlot(), pour un texte renderText())
    • l’objet doit être enregistré dans une liste output$<outputId>
  • Afficher le graphique : cela se fait dans la fonction fluidPage()
    • créer la sortie en utilisant son outputId, ici dans une fonction plotOutput()
library(shiny)
library(palmerpenguins)

ui <- fluidPage(
  
  code("My first plot in a Shiny app :"),
  plotOutput(outputId = "penguin_plot")
  
)
server <- function(input, output){
  
  output$penguin_plot <- renderPlot({
    library(palmerpenguins)
    plot(penguins$bill_depth_mm, penguins$bill_length_mm, col = penguins$species)
  })
  
}

shinyApp(ui, server)

affichage d’un simple graphique

Pas à pas :

  1. Créer le graphique dans une fonction renderPlot() dans la fonction server()
  2. Affecter le résultat à la variable de sortie “penguin_plot” output$penguin_plot <- renderPlot({ ... }) dans la fonction server()
  3. Affichez le graphique plotOutput(outputId = "penguin_plot"), en utilisant simplement la variable outputId (son identifiant, pas output$) dans la fonction fluidPage()

Maintenant que nous avons construit une première application capable d’afficher un texte et un graphique, il est temps d’ajouter de l’interactivité et de générer du contenu en fonction des entrées.


Application interactive


Le but de la création d’une application Web est de pouvoir ajuster les paramètres et d’obtenir une sortie différente en fonction des nouvelles entrées. Nous allons créer une telle application, où l’utilisateur peut entrer une donnée, et l’application fournira une sortie basée sur cette donnée.

La structure de l’application est proche de la précédente, il n’y a que 2 étapes à ajouter :

  • Demander à l’utilisateur d’entrer une valeur dans l’interface utilisateur fluidPage()
  • Lire cette valeur dans le server() pour l’utiliser et adapter la sortie


Entrées


Pour demander à l’utilisateur d’interagir, nous devons utiliser une fonction d’entrée (input function). La fonction d’entrée ressemble à *Input(inputID, label, ...) où * est le type des données, par exemple :

  • textInput() pour du text
  • numericInput() pour une valeur numériques
  • dateInput() pour une date

Ils ont deux paramètres obligatoires et quelques paramètres supplémentaires :

  • inputId : l’identifiant de l’entrée pour y accéder dans le server()
  • label : le texte qui s’affiche avec la saisie dans l’UI
  • … : paramètres supplémentaires, selon le type d’entrée

numericInput() a 4 paramètres supplémentaires, nous pouvons définir une valeur minimale et maximale, un pas incrémentiel et une largeur pour afficher la zone de saisie. textInput() a 3 paramètres supplémentaires, une valeur initiale, la largeur de la zone de saisie et un espace réservé pour donner un indice.

ui <- fluidPage(
  textInput(inputId = "name", label = "Enter your name", placeholder = "First name"),
  numericInput(inputId = "age", label = "How old are you?", value = 18, min = 18)
)


Accéder à l’input dans la fonction server()


La valeur saisie ne peut être lue que dans une fonction render. Pour y accéder, utilisez simplement la variable input$<inputId>. Par exemple, pour accéder au nom et à l’âge dans une fonction render*(), utilisez input$name et input$age.

server <- function(input, output){
  
  output$sentence <- renderText({
    paste("Hello", input$name, "You are", input$age, "years old.")
  })
  
}

Pour fonctionner, nous devons ajouter l’affichage de la sortie dans la fonction fluidPage(). Le code complet de cette application est :

library(shiny)

ui <- fluidPage(
  textInput(inputId = "name", label = "Enter your name", placeholder = "First name"),
  numericInput(inputId = "age", label = "How old are you?", value = 18, min = 18),
  textOutput("sentence")
)

server <- function(input, output, session) {
  output$sentence <- renderText({
    paste("Hello", input$name, "you are", input$age, "years old.")
  })
}

shinyApp(ui, server)

Voici l’application complète: afficher les données saisies

Décomposons l’application pour comprendre comment fonctionne l’interaction :

comprendre les interactions avec Shiny

  1. Afficher les champs de saisie,
  2. Entrer les valeurs, ça déclenche la fonction render qui utilise ces input,
  3. La variable output$sentence est mise à jour, ce qui crée une réaction dans textOuput() dans la fluidPage()
  4. Le texte s’affiche.


Réactivité

Les bases de la réactivité


Nous avons vu dans l’exemple précédent que dès que nous modifions l’entrée, la sortie réagit au changement et est mise à jour. Shiny utilise la programmation réactive, si une modification est apportée à une variable, tout ce qui utilise cette variable est réévalué.

Les entrées sont toujours des variables réactives, et elles ne peuvent être utilisées que dans un contexte réactif.

Les fonctions render* sont des contextes réactifs, nous pouvons donc utiliser input$variable dans n’importe quelle fonction render*. Mais la fonction server() ne l’est pas, et la variable d’entrée n’est pas accessible directement à l’intérieur de la fonction server() :

server <- function(input, output){
  print(input$name)
}

Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)

observe({…})

Heureusement, il existe d’autres contextes réactifs. observe() est un contexte réactif et est utilisé pour accéder à une variable d’entrée en dehors d’une fonction render*. Si on revient à l’exemple précédent, la bonne façon d’imprimer la variable serait :

server <- function(input, output){
  observe({
      print(input$bill_depth)
  })
}

Pourquoi faire ça ? Cela peut être très utile par exemple pour déboguer le code, et imprimer les valeurs de variable dans la console.

reactive({…})

reactive() est un autre contexte réactif et est utilisé pour créer une nouvelle variable réactive.

Si vous devez évaluer une variable intermédiaire, basée sur un calcul, qui sera utilisée dans plusieurs fonctions render*, utiliser reactive() pour éviter de dupliquer le calcul dans chaque fonction.

Syntaxe :

Pour créer une variable réactive :

new_variable <- reactive({
  input$variable * 2
})

Pour utiliser la nouvelle variable, vous devez ajouter des parenthèses comme si vous appeliez une fonction :

observe({
  print( new_variable() )
})

Note : ceci est important à retenir car cela peut être une source d’erreur - si vous accédez à la variable d’entrée, appelez simplement le input$<variable> (sans parenthèse) dans un contexte réactif - si vous accédez à une variable réactive créée avec reactive(), appelez variable() (avec parenthèse) dans un contexte réactif

Regardons un exemple pour illustrer cela. Votre application demande de sélectionner une plage pour la longueur du bec des pingouins, et d’afficher un tableau et un graphique correspondant aux données sélectionnées.

Sans reactive(), vous devez filtrer les données dans chaque fonction render* :

  output$penguin_plot <- renderPlot({
    data <- subset(penguins, bill_length_mm > input$bill_length[1] & bill_length_mm < input$bill_length[2])
    plot(data$bill_depth_mm, data$bill_length_mm, col = data$species)
  })
  
  output$penguin_table <- renderTable({
    data <- subset(penguins, bill_length_mm > input$bill_length[1] & bill_length_mm < input$bill_length[2])
    data
  })

Dans cet exemple, il s’agit d’une simple sélection de données, mais nous pouvons voir comment cela peut affecter la vitesse de l’application si nous devons dupliquer un calcul complexe. Ici, la sélection est effectuée deux fois.

Avec reactive(), la sélection n’est faite qu’une seule fois, et la nouvelle variable contenant uniquement le résultat est ensuite utilisée dans chaque fonction render*. Les calculs sont plus efficaces, et en cas de modification, il n’y a pas de code dupliqué, la modification se fait uniquement dans la fonction réactive.

  data <- reactive({
      subset(penguins, bill_length_mm > input$bill_length[1] & bill_length_mm < input$bill_length[2])
  })
  
  output$penguin_plot <- renderPlot({
    plot(data()$bill_depth_mm, data()$bill_length_mm, col = data()$species)
  })
  
  output$penguin_table <- renderTable({
    data()
  })

Notez la parenthèse pour accéder aux données variables, car elles ont été créées avec reactive().

Ceci est l’application complète :

library(shiny)
library(palmerpenguins)

ui <- fluidPage(
  sliderInput(inputId = "bill_length", label = "Select the range of bill length?", value = c(40,50), min = 32, max = 60),
  plotOutput("penguin_plot"),
  tableOutput("penguin_table")
  
)
server <- function(input, output){
  
  observe({
      print(input$bill_length)
  })
  
  data <- reactive({
      subset(penguins, bill_length_mm > input$bill_length[1] & bill_length_mm < input$bill_length[2])
  })
  
  output$penguin_plot <- renderPlot({
    plot(data()$bill_depth_mm, data()$bill_length_mm, col = data()$species)
  })
  
  output$penguin_table <- renderTable({
    data()
  })
}

shinyApp(ui, server)

Ce qui affiche : afficher l’application pingouins complète


Disposition


Jusqu’à présent, chaque fois que nous ajoutons un élément, ils sont affichés les uns sur les autres. Shiny fournit des fonctions pour améliorer la présentation et modifier la mise en page.

Une façon courante d’afficher une application Web consiste à avoir une barre latérale avec les entrées et à afficher le contenu dans un panneau principal. Shiny a une fonction pour cette mise en page classique qui s’appelle sidebarLayout().

Le sidebarLayout utilise deux arguments, le sidebarPanel() et le mainPanel(). Vous placerez le code des entrées dans le sidebarPanel() et la sortie de rendu dans le mainPanel().

Vous pouvez afficher un titre pour l’application en utilisant titlePanel()

Voici l’application précédente utilisant le sidebarLayout() :

ui <- fluidPage(
  
  titlePanel("My analysis"),
   
  sidebarLayout(
    
    sidebarPanel(
      sliderInput(inputId = "bill_length", label = "Select the range of bill length?", value = c(40,50), min = 32, max = 60)
    ),
    
    mainPanel(
      plotOutput("penguin_plot"),
      tableOutput("penguin_table")
    )
    
  )
  
)

ajouter une barre latérale à l’application


Utilisation de différents onglets


Nous pouvons afficher la sortie dans différents onglets pour faciliter la navigation et éviter trop de choses à afficher.

Cela se fait facilement en utilisant la fonction tabsetPanel() pour envelopper tout le code des onglets, et tabPanel() pour chaque onglet.

tabPanel() a deux paramètres :

  • le nom de l’onglet à afficher
  • le code de la sortie
ui <- fluidPage(
  
  titlePanel("My analysis"),
   
  sidebarLayout(
    
    sidebarPanel(
      sliderInput(inputId = "bill_length", label = "Select the range of bill length?", value = c(40,50), min = 32, max = 60)
    ),
    
    mainPanel(
      tabsetPanel(
        tabPanel(title = "Plot",
                 plotOutput("penguin_plot")
                 ),
        tabPanel(title = "Table",
                 tableOutput("penguin_table")
                 )
      )
    )
    
  )
  
)

utilisation des onglets


Structure des fichiers


Jusqu’à présent, pour les besoins de cet article, tout le code était dans un seul fichier. Il est préférable d’organiser l’application en au moins 3 fichiers :

  • ui.R avec l’interface utilisateur
  • server.R pour la logique du serveur
  • app.R qui rassemble les 2 et lance l’application.

J’ai écrit “au moins 3 fichiers” car vous voudrez peut-être utiliser un fichier nommé “global.R”. Ce fichier est appelé une fois lors du lancement de l’application, vous pouvez donc exécuter le code dont votre application aura besoin (comme le chargement et la manipulation des données) sans ajouter de complexité aux fichiers de votre application.

Le niveau suivant est d’utiliser des modules, qui ajoutent un autre niveau de réutilisabilité et d’organisation. Mais ce sera pour un prochain post.


Conclusion


Dans cet article, j’ai couvert le développement d’une application Shiny pour montrer les différents principes clés pour démarrer. Nous avons vu le rôle du serveur et de l’interface utilisateur dans une application Shiny et avons appris à exécuter une application simple. Nous avons ajouté des entrées pour permettre l’interaction de l’utilisateur et avons parlé du concept de réactivité. Enfin, nous avons amélioré l’interface utilisateur en ajoutant une barre latérale et des onglets.

C’est suffisant pour démarrer et construire une vraie application. Mais bien sûr, il y a plus de possibilités avec Shiny, et dans le prochain article, je couvrirai spécifiquement l’interface utilisateur pour créer des applications plus complexes et améliorer leur apparence. Vous pouvez le trouver ici

Christophe Nicault
Christophe Nicault
Stratégie des Systèmes d’Information
Transformation Numérique
Data Science

Je travaille sur la stratégie des systèmes d’information, les projets informatiques et la science des données.

Sur le même sujet