Brève introduction aux modules Shiny

La construction d’application(s) Shiny complexes peut mener à dupliquer du code.
Les modules Shiny sont initialement la réponse à cette duplication.

Un module Shiny est un composant réutilisable au sein d’application(s) plus complexes.

On peut ainsi voir un module comme une fonction que l’on pourrait appeler n fois plutôt que de dupliquer du code.

Une application Shiny est composée :

  • D’un fichier ui.R pour l’interface.
  • D’un fichier server.R pour la logique serveur.
  • OU bien d’un seul fichier app.R qui défini l’interface et la logique serveur.

Tout comme l’application, le module est défini par 2 fonctions :

  • Une pour l’interface (suffixé par ‘_UI’ ou ‘UI’, ex = monModule_UI).
  • Une pour la logique serveur (ex = monModule).

Nous verrons ici les autres aspects positifs des modules, les situations favorables à leurs utilisations ainsi qu’une brève introduction à leur fonctionnement au travers d’un exemple hello-world.

NB : Cet article de Joe Cheng est une bonne introduction à la modularisation Shiny.

Répétition d’un module au sein d’une application

Dès lors que l’on a besoin de dupliquer un bloc de code UI et/ou des fonctions serveur on peut définir un module et l’utiliser autant de fois que nécessaire.

Dans l’exemple ci-dessous, le module createPlot est utilisé avec les jeux de données :

  • dataset_1
  • dataset_2
  • dataset_3

NB : Le module createPlot est défini par les fonctions :

  • createPlot pour la logique serveur.
  • createPlotUI pour l’interface.

Module within Shiny application

Répétition d’un module entre différentes applications

En cours de développement d’une application, il peut arriver que l’on ait besoin d’une “partie d’application” déjà créée ailleurs. Voici les étapes à suivre dans ce cas :

  1. Convertir ces “parties d’application” en modules (cf 2 fonctions : interface & logique serveur).
  2. Rassembler ces fonctions dans un package R.
  3. Utiliser les modules à partir du package créé.

Rassembler les modules nous offre les avantages du packaging comme :

  • La seule mise à jour du package suffit à maintenir l’ensemble des applications Shiny.
  • On peut ajouter des tests au package avec testthat.
  • Ajout simplifié de la documentation.
  • etc…

NB : Pour toutes ces raisons, cette solution peut également être appropriée même dans le cas où les modules ne sont utilisés que dans une seule application.

Module across Shiny application

Pour une meilleure organisation des fichiers Shiny

L’ensemble des codes liés à une application Shiny peut être stocké dans les fichiers ui.R & server.R ou dans le seul fichier app.R.
Les modules permettent de séparer le code en plusieurs fichiers, ce qui facilite :

  • La compréhension de l’architecture.
  • La collaboration entre différents développeurs.

Cas 1 : réutilisation dans une seule application

Exemple d’application non modularisée :

application1
|___ server.R (1000 lines)
|___ ui.R     (500 lines)

Création de 2 modules (module_1 & module_2), réutilisés chacun n fois dans l’application1 :

application1
|___ server.R (300 lines)
|___ ui.R (200 lines)
|
|___ modules
    |___ module_1.R (200 lines)
    |___ module_2.R (200 lines)

On observe dans ce cas, une meilleure organisation des fichiers ainsi qu’un gain de lignes de codes (si les modules sont réutilisés au moins 2 fois).

Cas 2 : Mise à disposition d’un module au travers d’un package

Exemple d’applications non modularisées :

application1                    application2                    application3
|___ server.R (400 lines)       |___ server.R (400 lines)       |___ server.R (400 lines)
|___ ui.R     (250 lines)       |___ ui.R     (250 lines)       |___ ui.R     (250 lines)

Création d’un package qui contient le module_1 réutilisé dans les 3 applications :

packageModules
|___ module_1.R (350 lines)

application1                    application2                    application3
|___ server.R (200 lines)       |___ server.R (200 lines)       |___ server.R (200 lines)
|___ ui.R     (150 lines)       |___ ui.R     (150 lines)       |___ ui.R     (150 lines)

On observe un gain du nombre de lignes de code. On bénéficie également dans ce cas des avantages du packaging exprimés au chapitre précédent.

Résumé

Les bonnes raisons d’utiliser les modules Shiny :

  • Répliquer facilement une “petite application” au sein d’une application Shiny elle-même.
  • Eviter la réplication de code entre différentes applications Shiny.
  • Réorganiser les fichiers de code et gagner en lisibilité.
  • Rassembler ses modules afin de bénéficier des avantages du packaging R.

Hello Shiny module world

L’application ci-dessous décrit un exemple minimaliste Hello World. Son code est disponible dans ce répertoire GitHub. L’application est accessible en ligne.

Vous pouvez lancer l’application localement avec le code ci-dessous :

shiny::runGitHub(repo = "ardata-fr/shinyapps", subdir = "modules-hello-world")

app.R

Notez ici l’appel aux fonctions du module hello-world :

  • hello_worldUI dans la partie ui.
  • hello_world dans la partie server au travers de la fonction callModule.
library(shiny)

# load module functions
source("hello_world.R")

ui <- fluidPage(
    
    titlePanel("Using of Shiny modules"),
    
    fluidRow(
        # Call interface function of module "hello_world"
        hello_worldUI(id = "id_1")
    )
    
)

server <- function(input, output, session) {
    
    # Call logic server function of module "hello_world"
    callModule(module = hello_world, id = "id_1")
    
}

shinyApp(ui = ui, server = server)

hello_world.R

Notez ici la définition des 2 fonctions :

  • hello_worldUI qui définit l’interface
  • hello_world qui définit la logique serveur
# Function for module UI
hello_worldUI <- function(id) {
    ns <- NS(id)
    
    fluidPage(
        fluidRow(
            column(2, textInput(ns("TI_username"), label = NULL, placeholder = "your name")),
            column(2, actionButton(ns("AB_hello"), label = "Hello !"))
        ),
        hr(),
        fluidRow(
            column(12, textOutput(ns("TO_Hello_user")))
        )
    )
    
}

# Function for module server logic
hello_world <- function(input, output, session) {
    
    # When user clicks on "Hello" button : Update reactive variable "name"
    name <- eventReactive(input$AB_hello, {
        return(input$TI_username)
    })
    
    # Show greetings
    output$TO_Hello_user <- renderText({
        if (name() %in% "") {
            return("Hello world !")
        } else {
            return(paste("Hello", name(), "!"))
        }
    })
    
}

A suivre

Nous avons vu l’importance des modules Shiny et décrit un premier exemple simple.
Le mécanisme du transfert de données entre l’application Shiny et ses modules fera l’objet d’un prochain article.


Suivez nous:  -  Sites recommandés: R-bloggers R weekly Twitter #rstats Jobs for R-users