Accessibility Tools

- Le blog participatif de bioinformatique francophone depuis 2012 -

Vous avez un script que vous sou­hai­tez par­ta­ger avec une équipe expé­ri­men­tale ? Vous ne vou­lez pas que les uti­li­sa­teurs modi­fient le code pour para­mé­trer votre pro­gramme ? Vous codez avec R ? Alors cet article est fait pour vous ! Nous allons voir com­ment créer une appli­ca­tion web avec R et per­mettre à votre uti­li­sa­teur d’exécuter votre code sans le voir.

Shiny

Le package que nous uti­li­se­rons est shi­ny. Il est pro­po­sé par Rstu­dio (https://​shi​ny​.rstu​dio​.com/) et dis­po­nible sur le CRAN. Ce package per­met de construire des appli­ca­tions web très sim­ple­ment sans connais­sances par­ti­cu­lières en HTML et CSS. Les fonc­tions que nous appel­le­rons dans R vont être tra­duites en HTML. Par exemple,

sera trans­for­mé en

. Il n’est donc pas indis­pen­sable de savoir coder en HTML, mais des connais­sances dans les lan­gages web pour­ront vous être utiles dans des cas par­ti­cu­liers, puisqu'il est pos­sible d’intégrer dans l’application shi­ny du code HTML brut. 

Une appli­ca­tion shi­ny se divise en 2 par­ties :

  • l’UI : Il s’agit de l’interface uti­li­sa­teur visible dans une page web. Nous pour­rons y retrou­ver des graphes, des tableaux, du texte, etc. L’utilisateur pour­ra inter­agir avec cette inter­face par le biais de bou­tons, de sli­ders, de cases, etc.
  • le ser­veur : Il s’agit de la « zone de tra­vail ». Tous les cal­culs, pré­pa­ra­tions de don­nées ou ana­lyses que R réa­li­se­ra seront faits côté ser­veur.

Nous allons voir dans cet article toutes les étapes pour créer une appli­ca­tion com­plète. Elle sera capable de lire un fichier en fonc­tion de para­mètres enre­gis­trés par l’utilisateur puis d'afficher :

  • Un tableau avec de la colo­ra­tion condi­tion­nelle
  • 4 gra­phiques obte­nus par des approches dif­fé­rentes

L’ensemble du code per­met­tant de réa­li­ser l’application est dis­po­nible sur github : https://​github​.com/​b​i​o​i​n​f​o​-​f​r​/​b​i​o​i​n​f​o​-​f​r​_​S​h​iny

Pré-requis

Toutes les étapes pour créer une appli­ca­tion Shi­ny seront détaillées dans ce post. Connaître la syn­taxe de R sim­pli­fie­ra gran­de­ment la lec­ture de l'article mais n’est pas indis­pen­sable.

Pour réa­li­ser cette appli­ca­tion, il vous fau­dra une ver­sion à jour de RStu­dio (plus simple que la console R). Pour l’installer, sui­vez les étapes sui­vantes (l’ordre est impor­tant) :

  1. ins­tal­ler R : https://​cran​.​r​-pro​ject​.org/
  2. Ins­tal­ler RStu­dio : https://​www​.rstu​dio​.com/​p​r​o​d​u​c​t​s​/​r​s​t​u​d​i​o​/​d​o​w​n​l​o​ad/

Note : pour les uti­li­sa­teurs de R les plus avan­cés, l’application peut être déve­lop­pée dans un envi­ron­ne­ment vir­tuel comme docker (sujet de mon pro­chain post).

Les données

Les don­nées uti­li­sées pour cette appli­ca­tion pro­viennent du tableau IRIS regrou­pant des mesures sur des fleurs (dis­po­nible dans Rda­ta­set et décrit ici https://​archive​.ics​.uci​.edu/​m​l​/​d​a​t​a​s​e​t​s​/​i​ris ). Ce jeu de don­nées est très uti­li­sé pour illus­trer les fonc­tions dans R et pour le machine lear­ning. Le tableau est com­po­sé de 5 colonnes :

  • la lon­gueur des sépales ;
  • la lar­geur des sépales ;
  • la lon­gueur des pétales ;
  • la lar­geur des pétales ;
  • l’espèce de fleurs.

Un fichier au for­mat txt est dis­po­nible ici :
https://​github​.com/​b​i​o​i​n​f​o​-​f​r​/​b​i​o​i​n​f​o​-​f​r​_​S​h​i​n​y​/​b​l​o​b​/​m​a​s​t​e​r​/​d​a​t​a​s​e​t​I​r​i​s​.​txt .

Les packages R

Les packages uti­li­sés pour réa­li­ser l’application sont dis­po­nibles sur le CRAN. Ils s’installent avec la com­mande :

. Les packages que nous uti­li­se­rons sont :

  • shi­ny [1] : Il per­met­tra de construire l’application web
  • shi­ny­da­sh­board [2]: Il per­met­tra de créer une archi­tec­ture dyna­mique à la page web avec une zone de titre, une menu rabat­table et une zone prin­ci­pale
  • shi­ny­Wid­gets [3] : Il per­met­tra de mettre un mes­sage d’alerte pour confir­mer la lec­ture cor­recte du tableau
  • DT [4] : Il per­met­tra de créer un tableau dyna­mique avec de la colo­ra­tion condi­tion­nelle
  • plot­ly [5] , ggplot2 [6] et goo­gle­Vis [7] : Ils nous per­met­tront de réa­li­ser des gra­phiques
  • colour­pi­cker [8] : Il per­met­tra à l’utilisateur de sélec­tion­ner une cou­leur.

Nous uti­li­se­rons pour les ins­tal­ler et les char­ger un autre package : any­lib [9]. Ce package est très pra­tique car il per­met d'installer (si besoin) et de char­ger une liste de package. En plus, il a été créé par un des auteurs de Bioin­fo-fr : Aure­lien Cha­tei­gner. Que deman­der de plus !

install.packages("anyLib")
anyLib::anyLib(c("shiny", "shinydashboard", "shinyWidgets", "DT", "plotly", "ggplot2", "googleVis", "colourpicker"))

Création de l’architecture

Mise en place d’un dashboard

Pour mettre en forme notre appli­ca­tion web (la par­tie UI visible par l’utilisateur), nous allons uti­li­ser le package shi­ny­da­sh­board. La docu­men­ta­tion est pré­sente ici : https://​rstu​dio​.github​.io/​s​h​i​n​y​d​a​s​h​b​o​a​r​d​/​i​n​d​e​x​.​h​tml .

L'architecture mini­male avec shi­ny­das­bord est zone de titre (bleue), une barre laté­rale (noir) et une zone prin­ci­pale (grise).

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody()
)

server <- function(input, output) { }

shinyApp(ui, server)
Visua­li­sa­tion de l’application

Test de l’application

Pour tes­ter l’application, il faut sau­ve­gar­der le code puis appuyer sur le bou­ton

au des­sus à droite de l’éditeur de texte de Rstu­dio. Un point impor­tant, Rstu­dio recon­naît par défaut les appli­ca­tions qui se nomme app.R. Je vous conseille vive­ment de nom­mer votre fichier app.R.

Si vous tra­vaillez avec la console R, vous pou­vez lan­cer la com­mande sui­vante :

runApp()

Ajouter un titre

Dans la fonc­tion dash­board­Hea­der, nous ajou­tons un titre à l’application (ici bioin­fo-fr). Ce titre sera affi­ché en haut à gauche.

ui <- dashboardPage(
  dashboardHeader(title = "bioinfo-fr"),
  dashboardSidebar( ),
  dashboardBody( )
)
Visua­li­sa­tion de l’application

Ajouter des pages

La pre­mière étape est d’ajouter des élé­ments (item) dans la barre de menu laté­rale (par­tie noire). Nous uti­li­sons pour cela la fonc­tion

. Nous y ajou­tons la fonc­tion

qui contient les items du menu.

Ensuite, il faut indi­quer que la par­tie body aura plu­sieurs pages (des

). Chaque

cor­res­pond à une page acces­sible par le menu. Le

doit avoir le même nom que l’argument

de la fonc­tion

pour y accé­der (exemple : read­Da­ta). Dans chaque page, nous ajou­tons un titre de niveau 1 (h1). Vous pou­vez remar­quer l'utilisation de la fonc­tion

(

). L'argument de la fonc­tion est un nom d'icône que nous pou­vons trou­ver sur ces deux sites :
https://​fon​ta​we​some​.com/ et
https://​get​boots​trap​.com/​d​o​c​s​/​4​.​3​/​c​o​m​p​o​n​e​n​t​s​/​a​l​e​r​ts/ . En uti­li­sant cette fonc­tion, vous aurez une petite image (icônes) à gauche du nom de l'élément (par exemple un livre pour la lec­ture des don­nées). Il est aus­si pos­sible de l'utiliser pour des bou­tons .

ui <- dashboardPage(
  dashboardHeader(title = "bioinfo-fr"),
  dashboardSidebar(
    sidebarMenu(
      menuItem("Lecture des données", tabName = "readData", icon = icon("readme")),
      menuItem("Visualisation des données", tabName = "visualization", icon = icon("poll"))
    )
  ),
  dashboardBody(
    tabItems(
      # Read data
      tabItem(tabName = "readData",
              h1("Lecture des données")
      ),
      
      # visualization
      tabItem(tabName = "visualization",
              h1("Visualisation des données")
      )
    )
  )
)
Visua­li­sa­tion de l'application

Création d’un lecteur de fichier

L’objectif est de pro­po­ser une inter­face simple pour lire un fichier dans l’application et qui per­mette à l’utilisateur de para­mé­trer la lec­ture et d’avoir une pré­vi­sua­li­sa­tion du fichier lu.

Importer un fichier

Pour impor­ter un fichier, shi­ny pro­pose la fonc­tion

. Il est pos­sible de faire du “drag and drop” dans la zone de l’import ou de sélec­tion­ner un fichier dans l’explorateur de fichiers. Le type de fichier visible est para­mé­trable dans les argu­ments. Ici, nous uti­li­se­rons le para­mé­trage par défaut.

ui <- dashboardPage(
  dashboardHeader(title = "bioinfo-fr"),
  dashboardSidebar(
    sidebarMenu(
      menuItem("Lecture des données", tabName = "readData", icon = icon("readme")),
      menuItem("Visualisation des données", tabName = "visualization", icon = icon("poll"))
    )
  ),
  dashboardBody(
    tabItems(
      # Read data
      tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected")
      ),
      
      # visualization
      tabItem(tabName = "visualization",
              h1("Visualisation des données")
      )
    )
  )
)
Visua­li­sa­tion de l’application

Zone de paramétrage

Nous sou­hai­tons main­te­nant para­mé­trer 3 points lors de la lec­ture du fichier : type de sépa­ra­teur (vir­gule, tabu­la­tion, espace), type de quote (simple, double, aucune) et la présence/​absence des noms de colonnes (hea­der). Nous uti­li­sons pour cela des radio bou­tons. La fonc­tion uti­li­sée est

. 5 argu­ments sont uti­li­sés :

  • id : iden­ti­fiant du groupe de radio bou­tons (ici nous avons 3 groupes de radio bou­tons pour nos 3 para­mètres),
  • label : le titre pré­sent au des­sus du groupe de radio bou­tons,
  • choices : les choix pos­sibles dans le groupe de radio bou­tons. A noter, la zone située à gauche du "=" contient les infor­ma­tions qui seront affi­chées dans l'application alors que la par­tie droite indique ce que com­prend R côté ser­veur. Pour le hea­der par exemple, il sera affi­ché “Yes” côté UI et nous récu­pé­re­rons côté ser­veur (“Yes” = TRUE) lorsque que nous récu­pé­re­rons la valeur du radio bou­ton côté ser­veur.
  • Selec­ted : Nom du radio bou­ton sélec­tion­né au lan­ce­ment de l’application
  • inline = T : pour avoir les radio bou­tons ali­gnés
[…]
tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected"),
              
              h3("Parameters"),
              
              # Input: Checkbox if file has header
              radioButtons(id = "header", 
                           label = "Header",
                           choices = c("Yes" = TRUE,
                                       "No" = FALSE),
                           selected = TRUE, inline=T),
              
              # Input: Select separator ----
              radioButtons(id = "sep", 
                           label = "Separator",
                           choices = c(Comma = ",",
                                       Semicolon = ";",
                                       Tab = "\t"),
                           selected = "\t", inline=T),
              
              # Input: Select quotes ----
              radioButtons(id = "quote", 
                           label= "Quote",
                           choices = c(None = "",
                                       "Double Quote" = '"',
                                       "Single Quote" = "'"),
                           selected = "", inline=T)
              
      ),

[…]
Visua­li­sa­tion de l'application

Zone de prévisualisation

Dans cette zone, nous allons visua­li­ser les pre­mières lignes du fichier que nous sou­hai­tons lire. Il faut donc :

  • Créer une zone d’affichage dans l’UI
  • Lire les don­nées côté ser­veur et envoyer les don­nées dans la zone d’affichage

Côté UI

Pour affi­cher le tableau, nous uti­li­sons la fonc­tion

. Une zone va être créée pour affi­cher un tableau. Nous don­nons à cette zone un iden­ti­fiant en uti­li­sant l’argument

. Cet iden­ti­fiant est indis­pen­sable pour retrou­ver la zone côté ser­veur.

 tabItems(
      # Read data
      tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected"),
              
              h3("Parameters"),
              
              # Input: Checkbox if file has header
              radioButtons(inputId = "header", 
                           label = "Header",
                           choices = c("Yes" = TRUE,
                                       "No" = FALSE),
                           selected = TRUE, inline=T),
              
              # Input: Select separator ----
              radioButtons(inputId = "sep", 
                           label = "Separator",
                           choices = c(Comma = ",",
                                       Semicolon = ";",
                                       Tab = "t"),
                           selected = "t", inline=T),
              
              # Input: Select quotes ----
              radioButtons(inputId = "quote", 
                           label= "Quote",
                           choices = c(None = "",
                                       "Double Quote" = '"',
                                       "Single Quote" = "'"),
                           selected = "", inline=T),
              h3("File preview"),
              dataTableOutput(outputId = "preview")
              
      ),

Côté Ser­ver

Nous sou­hai­tons à pré­sent affi­cher de l’information. Du côté ser­veur, pour envoyer de l’information, la syn­taxe com­mence qua­si­ment tou­jours par

puis l’ID de la zone de sor­tie (ici notre tableau de pré­vi­sua­li­sa­tion avec l’id "pre­view"). Ce que nous sou­hai­tons lui envoyer est un tableau. Nous uti­li­sons donc la fonc­tion

. Dans cette der­nière fonc­tion, nous allons lire le tableau qui va être ren­voyé. Pour récu­pé­rer de l’information du côté UI, il faut uti­li­ser la syn­taxe sui­vante :

. Par exemple, nous sou­hai­tons récu­pé­rer le choix de l'utilisateur concer­nant le hea­der :

.

output$preview <-  renderDataTable({
    
    req(input$dataFile)
    
    df <- read.csv(input$dataFile$datapath,
                   header = as.logical(input$header),
                   sep = input$sep,
                   quote = input$quote,
                   nrows=10
    )
  },  options = list(scrollX = TRUE , dom = 't'))

Si nous détaillons le code :

  • req(input$dataFile) : bloque la suite du code si la zone d’import de fichier est vide
  • df <- read.csv() : on stocke dans df la lec­ture du fichier
  • input$dataFile$datapath : che­min d’accès au fichier impor­té
  • hea­der = as.logical(input$header) : récu­pé­ra­tion de la réponse de l’utilisateur pour savoir si pré­sence ou absence d’un hea­der. Le as.logical per­met de conver­tir un TRUE ou FALSE en boo­léen.
  • sep = input$sep, quote = input$quote : récu­pé­ra­tion du para­mé­trage de l’utilisateur pour le sépa­ra­teur et les quotes. Ces infor­ma­tions sont don­nées aux argu­ments de la fonc­tion read.csv()
  • nrows=10 : Nous ne sou­hai­tons pas lire tout le fichier. Seules les pre­mières lignes sont néces­saires pour savoir si le tableau est lu cor­rec­te­ment ou non. Nous lisons donc les 10 pre­mières lignes.
  • options = list(scrollX = TRUE , dom = 't') : Si le tableau a de nom­breuses colonnes, cette option per­met d’avoir un scroll hori­zon­tal

Vous pou­vez main­te­nant tes­ter sur un fichier texte conte­nant un tableau. Le chan­ge­ment de para­mé­trage a un effet direct sur la visua­li­sa­tion.

Visua­li­sa­tion de l'application

Organisation des éléments

Pour les connais­seurs de boots­trap, Shi­ny intègre son code. Pour les autres, il est pos­sible d’organiser le conte­nu d’une page à l’aide d’une grille. La grille est com­po­sée de lignes (

) elles-mêmes com­po­sées de 12 blocs. Nous allons pla­cer les para­mètres et la pré­vi­sua­li­sa­tion sur une même ligne. Nous sou­hai­tons sto­cker les para­mètres dans  3 blocs (

: la colonne aura une taille de 3 blocs) et 9 blocs pour la pré­vi­sua­li­sa­tion (

).

tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected"),
              
              fluidRow(
                column(3,
                       h3("Parameters"),
                       
                       # Input: Checkbox if file has header
                       radioButtons(inputId = "header", 
                                    label = "Header",
                                    choices = c("Yes" = TRUE,
                                                "No" = FALSE),
                                    selected = TRUE, inline=T),
                       
                       # Input: Select separator ----
                       radioButtons(inputId = "sep", 
                                    label = "Separator",
                                    choices = c(Comma = ",",
                                                Semicolon = ";",
                                                Tab = "t"),
                                    selected = "t", inline=T),
                       
                       # Input: Select quotes ----
                       radioButtons(inputId = "quote", 
                                    label= "Quote",
                                    choices = c(None = "",
                                                "Double Quote" = '"',
                                                "Single Quote" = "'"),
                                    selected = "", inline=T)
                ),
                column(9,
                       h3("File preview"),
                       dataTableOutput(outputId = "preview")
                )
              )
Visua­li­sa­tion de l'application

Bouton de lecture

Pour finir avec cette page, nous allons créer un bou­ton pour vali­der le para­mé­trage de la lec­ture du tableau. En cli­quant sur ce bou­ton, l’ensemble du fichier sera lu. Nous ne réa­li­sons pas une lec­ture dyna­mique comme pré­cé­dem­ment. En effet,  à chaque chan­ge­ment de para­mètre, l’ensemble du fichier est relu. Si le fichier est gros, le temps de lec­ture sera long.

Côté UI

Nous ajou­tons un

. L’identifiant de notre bou­ton est "actBtn­Vi­sua­li­sa­tion".

[...]
actionButton(inputId = "actBtnVisualisation", label = "Visualisation",icon = icon("play") )
[...]

Pour une ques­tion esthé­tique, nous ajou­tons un saut de ligne avant le bou­ton et nous met­tons le bou­ton dans une divi­sion pour pou­voir le cen­trer :

[...]
tags$br(),
div(actionButton(inputId = "actBtnVisualisation", label = "Visualisation",icon = icon("play") ), align = "center")
[...]

Côté ser­veur

Lorsque que le bou­ton est cli­qué, nous sou­hai­tons à pré­sent que le conte­nu du fichier soit sto­cké dans une variable. Il s’agit d’une variable par­ti­cu­lière. Elle doit être visible par toutes les fonc­tions côté ser­veur et relan­cer toutes les fonc­tions qui l’utilisent si elle change. Il s’agit d’une variable réac­tive (

). Si nous détaillons le code :

  • Nous décla­rons une reac­ti­ve­Va­lue avec comme nom .
  • Nous allons uti­li­ser une fonc­tion qui per­met d’attendre une action par­ti­cu­lière. Ici nous atten­dons que l’utilisateur clique sur le bou­ton. Une fois que le bou­ton a été cli­qué, le code entre les { } sera exé­cu­té. Ici, l’objectif sera de sto­cker le conte­nu du fichier impor­té dans la reac­ti­ve­Va­lue sous le nom (data$table)
data = reactiveValues()

  observeEvent(input$actBtnVisualisation, {
    data$table = read.csv(input$dataFile$datapath,
                          header = as.logical(input$header),
                          sep = input$sep,
                          quote = input$quote,
                          nrows=10)
  })

Ain­si, à chaque clic du bou­ton,

sera mis à jour ain­si que toutes les fonc­tions qui l’utilise (ex : des gra­phiques).
Nous pou­vons aus­si ajou­ter un mes­sage pour confir­mer la lec­ture du fichier. Nous uti­li­se­rons

pro­po­sé dans le package shi­ny­Wid­gets. La docu­men­ta­tion est dis­po­nible ici : https://​github​.com/​d​r​e​a​m​R​s​/​s​h​i​n​y​W​i​d​g​ets .

observeEvent(input$actBtnVisualisation, {
    data$table = read.csv(input$dataFile$datapath,
                          header = as.logical(input$header),
                          sep = input$sep,
                          quote = input$quote,
                          nrows=10)
    sendSweetAlert(
      session = session,
      title = "Done !",
      text = "Le fichier a bien été lu !",
      type = "success"
    )  
  })
Visua­li­sa­tion du mes­sage

Chan­ge­ment de page

Enfin, notre appli­ca­tion étant com­po­sée de 2 pages, nous sou­hai­tons chan­ger de page une fois que le fichier est lu pour arri­ver sur la page de visua­li­sa­tion.

updateTabItems(session, "tabs", selected = "visualization")

Pour rap­pel, “tabs” est l’identifiant de notre side­bar­Me­nu. Nous allons avec cette com­mande cher­cher dans la side­bar­Me­nu la page qui a comme iden­ti­fiant “visua­li­za­tion” et chan­ger de page.

Visualisation

Exploration du tableau

Nous allons à pré­sent affi­cher le tableau com­plet. Nous uti­li­sons pour cela le package DT (https://​rstu​dio​.github​.io/​DT/ ). Il per­met de recher­cher, sélec­tion­ner ou trier les infor­ma­tions d'un tableau de don­nées. Il faut pour cela créer une zone où sera affi­ché le tableau dans l’UI.

Côté UI

tabItem(tabName = "visualization",
              h1("Visualisation des données"),
              h2("Exploration du tableau"),
              dataTableOutput('dataTable')
      )

Puis du côté ser­veur, il ne reste plus qu’à envoyer le conte­nu de notre fichier dans ce tableau par le biais de la

. Ain­si, le tableau sera auto­ma­ti­que­ment mis à jour si un nou­veau fichier est lu.

output$dataTable = DT::renderDataTable(data$table)
Visua­li­sa­tion de l'application

Il est pos­sible de faire de la mise en forme condi­tion­nelle comme dans excel. Le code pro­po­sé par la suite est dépen­dant du tableau uti­li­sé. En effet, nous allons cibler les colonnes d’intérêt par leur nom pour une ques­tion de lisi­bi­li­té.

Voi­ci une pro­po­si­tion de mise en forme condi­tion­nelle de notre tableau (ins­pi­ré de l’exemple pro­po­sé dans la docu­men­ta­tion du package DT).

  • His­to­gramme des valeurs pour les colonnes Sepal.length et Petal.length
  • Colo­ra­tion par seuils mul­tiples pour les colonnes Sepal.width et Petal.width (fond blanc écri­ture noire, fond rouge écri­ture blanche et fond rouge fon­cé écri­ture blanche)
  • Colo­ra­tion du fond en fonc­tion de l’espèce pour la colonne espèce.

output$dataTable = DT::renderDataTable({
    datatable(data$table, filter = 'top') %>% 
      formatStyle('Sepal.Length', 
                  background = styleColorBar(data$table$Sepal.Length, 'lightcoral'),
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center'
      ) %>%
      formatStyle(
        'Sepal.Width',
        backgroundColor = styleInterval(c(3,4), c('white', 'red', "firebrick")),
        color = styleInterval(c(3,4), c('black', 'white', "white"))
      ) %>%
      formatStyle(
        'Petal.Length',
        background = styleColorBar(data$table$Petal.Length, 'lightcoral'),
        backgroundSize = '100% 90%',
        backgroundRepeat = 'no-repeat',
        backgroundPosition = 'center'
      ) %>%
      formatStyle(
        'Petal.Width',
        backgroundColor = styleInterval(c(1,2), c('white', 'red', "firebrick")),
        color = styleInterval(c(1,2), c('black', 'white', "white"))
      ) %>%
      formatStyle(
        'Species',
        backgroundColor = styleEqual(
          unique(data$table$Species), c('lightblue', 'lightgreen', 'lavender')
        )
      )
  })
Visua­li­sa­tion de l'application

Enfin, pour amé­lio­rer l’exploration, il est pos­sible d’ajouter des filtres par colonnes. Pour les valeurs numé­riques, les don­nées sont fil­trées par un sli­der. Pour les colonnes conte­nant du texte, il y a deux pos­si­bi­li­tés :

  • Peu de varia­bi­li­té entre les élé­ments. Par exemple, la colonne Spe­cies ne contient que 3 élé­ments dif­fé­rents : seto­sa, ver­si­co­lor et vir­gi­ni­ca. Dans ce cas, le filtre sera com­po­sé des élé­ments uniques de cette colonne qui seront cli­quables. En les cli­quant, toutes les lignes avec cet élé­ment seront sélec­tion­nées.  
  • Grande varia­bi­li­té entre les élé­ments. Dans ce cas, une zone pour entrer du texte sera pro­po­sée. Le texte sai­si sera recher­ché dans la colonne.
output$dataTable = DT::renderDataTable({
    datatable(data$table, filter = 'top') %>% 
     [...]
Visua­li­sa­tion de l'application

Visualisation graphique

Nous allons créer de 4 façons dif­fé­rentes des gra­phiques et les affi­cher dans l’application shi­ny :

  • des gra­phiques sta­tiques
  • des gra­phiques dyna­miques

Les gra­phiques seront repré­sen­tés sur la même ligne avec une flui­dRow et 4 colonnes (comme nous avons fait pré­cé­dem­ment).

Graphique R

R pro­pose une grande palette de gra­phiques de base. Cepen­dant, il s'agit uni­que­ment de gra­phiques sta­tiques.

Côté UI

Il faut comme pré­cé­dem­ment créer une zone pour indi­quer où va être affi­ché le gra­phique. La fonc­tion uti­li­sée est plo­tOut­put.

tabItem(tabName = "visualization",
              h1("Visualisation des données"),
              h2("Exploration du tableau"),
              dataTableOutput('dataTable'),
              h2("Graphiques"),
             fluidRow(
                column(3,plotOutput("plotAvecR") )
              )
      )

Côté ser­veur

Nous allons pour ce gra­phique com­pa­rer la cor­ré­la­tion entre la taille des sépales et des pétales. Comme pour le tableau, la syn­taxe est la sui­vante pour envoyer de l’information du côté UI :

. Pour envoyer un plot, nous uti­li­sons la fonc­tion ren­der­Plot. Dans cette fonc­tion, vous pou­vez mettre n’importe quel gra­phique de R. Afin de mettre à jour auto­ma­ti­que­ment les gra­phiques, nous uti­li­sons notre reac­ti­ve­Va­lue :

. Chaque fois que

chan­ge­ra, le plot sera géné­ré de nou­veau. Pour accé­der au conte­nu du fichier lu qui est sto­cké dans la reac­ti­ve­Va­lue

sous le nom de

, nous uti­li­sons de nou­veau la syn­taxe sui­vante :

. Il s’agit d’un data­frame (la lec­ture par read.csv2 ren­voie un data­frame). Les colonnes sont donc acces­sibles par un $ puis le nom. Au final, pour obte­nir le vec­teur conte­nant les valeurs de lon­gueur des pétales, nous uti­li­se­rons la syn­taxe sui­vante :

.

output$plotAvecR <- renderPlot({
    plot(data$table$Petal.Length,data$table$Sepal.Length, 
         main = "Sepal length vs Petal length (R)",
         ylab = "Sepal length",
         xlab = "Petal length")
  })

Le para­mé­trage du plot est libre et n’est pas contraint par shi­ny.

Visua­li­sa­tion de l'application

Graphique par ggplot2

Ggplot2 est une librai­rie gra­phique de plus en plus uti­li­sée. Elle pro­pose des gra­phiques plus évo­lués que ceux de base dans R. Vous trou­ve­rez une docu­men­ta­tion très bien faite ici : https://​ggplot2​.tidy​verse​.org/. Nous allons com­pa­rer les lar­geurs et les lon­gueurs des sépales. Une colo­ra­tion en fonc­tion de l’espèce est pro­po­sée.

Côté UI

Comme tou­jours, nous allons créer une zone où sera affi­ché le gra­phique. La fonc­tion uti­li­sée est encore

.

fluidRow(
                column(3,plotOutput("plotAvecR")),
                column(3, plotOutput("plotAvecGgplot2"))
              )

Côté ser­veur

Nous allons pro­cé­der de la même façon que pré­cé­dem­ment. La dif­fé­rence est liée au conte­nu de la fonc­tion

. Nous allons cette fois-ci uti­li­ser les fonc­tions de ggplot2.

output$plotAvecGgplot2 <- renderPlot({
    ggplot(data=data$table, aes(x = Sepal.Length, y = Sepal.Width)) + 
      geom_point(aes(color=Species, shape=Species)) +
      xlab("Sepal Length") +  ylab("Sepal Width") +
      ggtitle("Sepal Length-Width (ggplot2")
  })
Visua­li­sa­tion de l'application

Graphique Plotly

Plot­ly est un package de j’affectionne par­ti­cu­liè­re­ment. Il pro­pose énor­mé­ment d’outils pré­pro­gram­més (enre­gis­tre­ment de l’image, zoom, infor­ma­tions sup­plé­men­taires). De plus, il n’est pas exclu­si­ve­ment réser­vé à R. Il est pos­sible de l’utiliser aus­si dans des pro­jets en JS et en python (aus­si simple d’utilisation).

Côté UI

De nou­veau, nous allons créer une zone pour affi­cher le gra­phique. Atten­tion, nous chan­geons de fonc­tion. Nous uti­li­se­rons cette fois plot­lyOut­put.

fluidRow(
                column(3, plotOutput("plotAvecR")),
                column(3, plotOutput("plotAvecGgplot2")),
                column(3, plotlyOutput("plotAvecPlotly"))
              )

Côté ser­veur

Je n’expliquerai pas ici la syn­taxe pour réa­li­ser un gra­phique avec Plot­ly. La docu­men­ta­tion sur le site est extrê­me­ment bien faite avec de très nom­breux exemples (https://​plot​.ly/r/). Vous pou­vez mettre n’importe quel gra­phique plot­ly dans la fonc­tion. Ici, nous com­pa­rons la lar­geur et la lon­gueur des pétales.

plot_ly(data = data$table, x = ~ Petal.Length, y = ~ Petal.Width, color = ~ Species) %>%
        layout(title = 'Petal Length-Width (plotly)',
               yaxis = list(title = "Petal width"),
               xaxis = list(title = "Petal length"))

Je vous invite lorsque vous lan­ce­rez l’application à sur­vo­ler ce gra­phique. Il y a énor­mé­ment d’informations dis­po­nibles et d’outils d’exploration.

Graphique Google

Pour finir, les gra­phiques de Google sont de plus en plus popu­laires et offrent un plus large choix de repré­sen­ta­tions que Plot­ly (calen­drier, etc.). Ici, nous allons réa­li­ser un his­to­gramme de la lar­geur des pétales.

Côté UI

Nous créons de nou­veau une zone pour affi­cher le gra­phique. La fonc­tion uti­li­sée est htm­lOut­put. Cette fonc­tion est capable d’interpréter du code HTML venant du ser­veur. Si vous sou­hai­tez écrire du HTML direc­te­ment dans la par­tie UI, il vous suf­fit d’utiliser la fonc­tion HTML (ex : HTML(“<h1>Titre 1</h1>”) ) .

fluidRow(
                column(3, plotOutput("plotAvecR")),
                column(3, plotOutput("plotAvecGgplot2")),
                column(3, plotlyOutput("plotAvecPlotly")),
                column(3, htmlOutput("plotAvecGoogle"))
              )

Côté ser­veur

Pour les gra­phiques Google, nous uti­li­sons les fonc­tions gra­phiques com­men­çant par gvis et le ren­du est fait avec la fonc­tion

. Elles sont détaillées à la page sui­vante https://​cran​.​r​-pro​ject​.org/​w​e​b​/​p​a​c​k​a​g​e​s​/​g​o​o​g​l​e​V​i​s​/​v​i​g​n​e​t​t​e​s​/​g​o​o​g​l​e​V​i​s​_​e​x​a​m​p​l​e​s​.​h​tml .

output$plotAvecGoogle <- renderGvis({
      gvisHistogram(as.data.frame(data$table$Petal.Width),
                    options=list(title ="Petal width (Google)",
                                 height=400)
      )
  })

Visua­li­sa­tion de l’application

Visua­li­sa­tion du l'application

Gérer le tableau vide

En lan­çant l’application, si vous vous ren­dez sur la par­tie visua­li­sa­tion, vous trou­ve­rez plein d’erreurs. Ces erreurs sont la cause de l’utilisation d’une

. En effet, lorsque rien n’a encore été lu,

est NULL (vide). Or toutes les fonc­tions que nous uti­li­sons ne gèrent pas les NULL. Nous ajou­te­rons pour le tableau et les gra­phiques un peu de code pour lui dire de ren­voyer NULL si le tableau est vide.

if (!is.null(data$table)) {
    [représentation graphique ou le tableau]
} else {
    NULL
}

Interagir avec les graphique

Nous allons voir deux types d'interactions avec les gra­phiques pour illus­trer la sim­pli­ci­té pour l’utilisateur d'interagir avec les don­nées et les repré­sen­ta­tions :

  • Sélec­tion­ner les don­nées à affi­cher à l’aide du tableau
  • Chan­ger des para­mètres gra­phiques sur le plot de base pro­po­sé par R (le pre­mier gra­phique). Tous ces chan­ge­ments peuvent bien sûr être appli­qués sur tous les gra­phiques.

Sélectionner les données à afficher à l’aide du tableau

Grâce à Shi­ny, il est pos­sible de faire com­mu­ni­quer le tableau avec les gra­phiques. Nous pro­fi­tons pour cela de la puis­sance du package DT qui génère le tableau. Les modi­fi­ca­tions que nous allons réa­li­ser seront uni­que­ment côté ser­veur. L'objectif est de récu­pé­rer les lignes qui sont affi­chées dans le tableau et de n'utiliser que ces lignes dans les gra­phiques. Comme pré­cé­dem­ment, pour récu­pé­rer de l’information dans l’UI, il faut uti­li­ser

. Nous sou­hai­tons récu­pé­rer de l'information de notre tableau qui a comme iden­ti­fiant

. Ensuite, nous ajou­tons

à la fin de l’ID pour obte­nir les lignes. Ain­si, avec

, nous avons les lignes affi­chées dans le tableau. Il ne reste plus qu’à les sélec­tion­ner dans le vec­teur de don­nées.  Le gra­phique est à pré­sent dyna­mique.

output$plotAvecR <- renderPlot({
    if (!is.null(data$table)) {
      plot(data$table$Petal.Length[input$dataTable_rows_all],
           data$table$Sepal.Length[input$dataTable_rows_all], 
           main = "Sepal length vs Petal length (R)",
           ylab = "Sepal length",
           xlab = "Petal length")
    } else {
      NULL
    }
  })

La même démarche est ensuite appli­quée aux autres gra­phiques. Grâce aux filtres du tableau, nous avons ain­si la pos­si­bi­li­té de sélec­tion­ner par les don­nées numé­riques (lon­gueur et lar­geur) et par l’espèce.

Changement de couleur pour le graphique de base R

L’objectif est de vous mon­trer une autre façon d'interagir avec les gra­phiques. En effet, il se peut que vous n’utilisiez pas de tableau dans votre appli­ca­tion. De très nom­breux exemples sont dis­po­nibles en ligne (ici par exemple : https://​shi​ny​.rstu​dio​.com/​g​a​l​l​e​ry/). Nous allons implé­men­ter 4 chan­ge­ments sur ce gra­phique pour vous don­ner des exemples d’utilisation d’inputs :

  • Chan­ge­ment de la cou­leur des points (avec l’utilisation d’un colour picker capable de gérer la trans­pa­rence)
  • Chan­ge­ment du type de point
  • Chan­ge­ment de la taille des points
  • Chan­ge­ment du titre

Côté UI

Pour plus de lisi­bi­li­té lors de l’utilisation, nous avons chan­gé la dis­po­si­tion des gra­phiques pour avoir sur une ligne le gra­phique R avec ses para­mètres et sur une seconde les trois autres gra­phiques. Vous pou­vez ain­si voir la sim­pli­ci­té de la réor­ga­ni­sa­tion d’une page à l’aide du sys­tème de Grid.

tabItem(tabName = "visualization",
              h1("Visualisation des données"),
              h2("Exploration du tableau"),
              dataTableOutput('dataTable'),
              h2("Graphiques"),
              fluidRow(
                column(4, plotOutput("plotAvecR")),
                column(4, colourpicker::colourInput("colR", "Couleur graphique R", "black",allowTransparent = T),
                       sliderInput("cex", "Taille",
                                   min = 0.5, max = 3,
                                   value = 1,step = 0.2
                                  )),
                column(4, selectInput(inputId = "pch", choices = 1:20, label = "Type de points",selected = 1),
                       textInput("title", "Titre", "Sepal length vs Petal length (R)") )
              ),
              tags$br(), 
              fluidRow(
                column(4, plotOutput("plotAvecGgplot2")),
                column(4, plotlyOutput("plotAvecPlotly")),
                column(4, htmlOutput("plotAvecGoogle"))
              )
      )

Pour faire entrer de l’information, nous avons besoin de 4 fonc­tions

:

pour la cou­leur (du package colourp­ci­cker),

pour la taille des points,

pour le type de points et

pour le titre du gra­phique.

Côté ser­veur

Nous allons récu­pé­rer les entrées et les inté­grer dans notre plot.

      plot(data$table$Petal.Length[input$dataTable_rows_all],
           data$table$Sepal.Length[input$dataTable_rows_all], 
           main = input$title,
           ylab = "Sepal length",
           xlab = "Petal length",
           pch = as.numeric(input$pch),
           col = input$colR, 
           cex = input$cex)

Visua­li­sa­tion dans l’application

Visua­li­sa­tion de l'application

Conclusion

Et voi­là ! Vous avez réa­li­sé une appli­ca­tion com­plète capable de lire un fichier en fonc­tion de para­mètres et d'explorer ses don­nées. Vous trou­ve­rez l’ensemble du code sur github ici :
https://​github​.com/​b​i​o​i​n​f​o​-​f​r​/​b​i​o​i​n​f​o​-​f​r​_​S​h​iny . A tra­vers ce post, nous avons vu com­ment rendre inter­ac­tive l’exploration d’un tableau de don­nées à l’aide de Shi­ny. Vos uti­li­sa­teurs n’auront plus à voir votre code. Ils auront sim­ple­ment à appuyer sur Run App. Il existe de nom­breuses solu­tions de par­tage (https://​shi​ny​.rstu​dio​.com/​t​u​t​o​r​i​a​l​/​w​r​i​t​t​e​n​-​t​u​t​o​r​i​a​l​/​l​e​s​s​o​n7/).  De nom­breuses autres pos­si­bi­li­tés sont dis­po­nibles et pour­ront être détaillées dans d’autres articles (conca­té­mé­ri­sa­tion et inté­gra­tion conti­nue d’une appli­ca­tion Shi­ny, par exemple).

Mer­ci à mes relec­teurs Auré­lien C. et Ismaël P. pour leur aide !

Versions des outils utilisés


R version 3.5.1 (2018-07-02)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 9 (stretch)

Matrix products: default
BLAS: /usr/lib/openblas-base/libblas.so.3
LAPACK: /usr/lib/libopenblasp-r0.2.19.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=C             
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2.2              shinycssloaders_0.2.0       shinyjs_1.0                 colourpicker_1.0            shinyWidgets_0.4.4          reshape2_1.4.3             
 [7] plotly_4.8.0                ggplot2_3.1.0               FactoMineR_1.41             DT_0.5                      DESeq2_1.22.2               SummarizedExperiment_1.12.0
[13] DelayedArray_0.8.0          BiocParallel_1.16.5         matrixStats_0.54.0          Biobase_2.42.0              GenomicRanges_1.34.0        GenomeInfoDb_1.18.1        
[19] IRanges_2.16.0              S4Vectors_0.20.1            BiocGenerics_0.28.0         shinydashboard_0.7.1        shiny_1.2.0                

loaded via a namespace (and not attached):
 [1] bitops_1.0-6           bit64_0.9-7            RColorBrewer_1.1-2     httr_1.4.0             tools_3.5.1            backports_1.1.3        R6_2.3.0              
 [8] rpart_4.1-13           Hmisc_4.1-1            DBI_1.0.0              lazyeval_0.2.1         colorspace_1.3-2       nnet_7.3-12            withr_2.1.2           
[15] tidyselect_0.2.5       gridExtra_2.3          bit_1.1-14             compiler_3.5.1         htmlTable_1.13.1       flashClust_1.01-2      scales_1.0.0          
[22] checkmate_1.9.0        genefilter_1.64.0      stringr_1.3.1          digest_0.6.18          foreign_0.8-70         XVector_0.22.0         base64enc_0.1-3       
[29] pkgconfig_2.0.2        htmltools_0.3.6        htmlwidgets_1.3        rlang_0.3.0.1          rstudioapi_0.8         RSQLite_2.1.1          bindr_0.1.1           
[36] jsonlite_1.6           acepack_1.4.1          dplyr_0.7.8            RCurl_1.95-4.11        magrittr_1.5           GenomeInfoDbData_1.2.0 Formula_1.2-3         
[43] leaps_3.0              Matrix_1.2-14          Rcpp_1.0.0             munsell_0.5.0          yaml_2.2.0             scatterplot3d_0.3-41   stringi_1.2.4         
[50] MASS_7.3-50            zlibbioc_1.28.0        plyr_1.8.4             grid_3.5.1             blob_1.1.1             promises_1.0.1         crayon_1.3.4          
[57] miniUI_0.1.1.1         lattice_0.20-35        splines_3.5.1          annotate_1.60.0        locfit_1.5-9.1         knitr_1.21             pillar_1.3.1          
[64] geneplotter_1.60.0     XML_3.98-1.16          glue_1.3.0             latticeExtra_0.6-28    data.table_1.11.8      httpuv_1.4.5           gtable_0.2.0          
[71] purrr_0.2.5            tidyr_0.8.2            assertthat_0.2.0       xfun_0.4               mime_0.6               xtable_1.8-3           later_0.7.5           
[78] viridisLite_0.3.0      survival_2.42-3        tibble_1.4.2           AnnotationDbi_1.44.0   memoise_1.1.0          cluster_2.0.7-1

Bibliographie

[1]Wins­ton Chang, Joe Cheng, JJ Allaire, Yihui Xie and Jona­than McPher­son (2018). shi­ny : Web Appli­ca­tion Fra­me­work for R. R package ver­sion 1.2.0.https://​CRAN​.​R​-pro​ject​.org/​p​a​c​k​a​g​e​=​s​h​iny
[2]Wins­ton Chang and Bar­ba­ra Borges Ribei­ro (2018). shi­ny­da­sh­board : Create Dash­boards with 'Shi­ny'. R package ver­sion 0.7.1. https://​CRAN​.​R​-pro​ject​.org/​p​a​c​k​a​g​e​=​s​h​i​n​y​d​a​s​h​b​o​ard
[3]Vic­tor Per­rier, Fan­ny Meyer and David Gran­jon (2018). shi­ny­Wid­gets : Cus­tom Inputs Wid­gets for Shi­ny. R package ver­sion 0.4.4. https://​CRAN​.​R​-pro​ject​.org/​p​a​c​k​a​g​e​=​s​h​i​n​y​W​i​d​g​ets
[4]Yihui Xie, Joe Cheng and Xia­nying Tan (2018). DT : A Wrap­per of the JavaS­cript Libra­ry 'Data­Tables'. R package ver­sion 0.5. https://​CRAN​.​R​-pro​ject​.org/​p​a​c​k​a​g​e​=DT
[5]Car­son Sie­vert (2018) plot­ly for R. https://​plot​ly​-book​.cpsie​vert​.me
[6]H. Wick­ham. ggplot2 : Ele­gant Gra­phics for Data Ana­ly­sis. Sprin­ger-Ver­lag New York, 2016.
[7]Mar­kus Ges­mann and Die­go de Cas­tillo. Using the Google Visua­li­sa­tion API with R. The R Jour­nal, 3(2):40-44, Decem­ber 2011.
[8]Dean Atta­li (2017). colour­pi­cker : A Colour Picker Tool for Shi­ny and for Selec­ting Colours in Plots. R package ver­sion 1.0. https://​CRAN​.​R​-pro​ject​.org/​p​a​c​k​a​g​e​=​c​o​l​o​u​r​p​i​c​ker
[9]Aure­lien Cha­tei­gner (2018). any­Lib : Ins­tall and Load Any Package from CRAN, Bio­con­duc­tor or Github. R package ver­sion 1.0.5.
https://​CRAN​.​R​-pro​ject​.org/​p​a​c​k​a​g​e​=​a​n​y​Lib

Vous avez aimé ? Dites-le nous !

Moyenne : 4.7 /​ 5. Nb de votes : 3

Pas encore de vote pour cet article.

We are sor­ry that this post was not use­ful for you !

Let us improve this post !

Tell us how we can improve this post ?




Commentaires

5 réponses à “Rendre ses projets R plus accessibles grâce à Shiny”

  1. Super article ! Mer­ci Tho­mas.

  2. Avatar de Malo Bonfils
    Malo Bonfils

    Bon­jour,
    je me sers de votre code pour me for­mer sur Rshi­ny, il est très com­plet. Petit pro­blème, quand je rentre mon fichier en lec­ture et que je sou­haite le vali­der pour l'afficher sur la page sui­vante, le bou­ton visua­li­sa­tion ne me per­met pas de le faire, et R stu­dio ne me met aucune erreur spé­ci­fique par rap­port à cela. Je me demande si vous avez déja ren­con­tré ça ?

    1. Avatar de Thomas Denecker
      Thomas Denecker

      Bon­jour,
      Je vous remer­cie pour votre retour et je vous prie de m'excuser pour le délai de réponse lié aux fêtes de fin d'année.

      Concer­nant votre pro­blème, serait-il pos­sible d'avoir plus de détails ?
      Il peut y avoir plu­sieurs expli­ca­tions pos­sibles :
      - La taille du fichier. Shi­ny a un para­mé­trage par défaut pour ne pas lire des fichiers trop gros. Il est pos­sible d'ajouter cette com­mande au début du code

      options(shiny.maxRequestSize = 1000*1024^2)

      . Elle per­met de pas­ser la taille des fichiers acceptés/​importés à 1Go.
      - Le temps. Shi­ny ne tra­vaille pas plus vite de R /​ Rstu­dio. Si la lec­ture du fichier est longue sans shi­ny, elle le sera avec shi­ny.
      - Un pro­blème de lec­ture du fichier. Il fau­drait tes­ter de voir si la lec­ture est cor­rec­te­ment para­mé­trée.

      Un bon moyen de savoir s'il y a un bug dans une appli­ca­tion shi­ny est le pas­sage de la fenêtre en gris et l'apparition d'un ban­deau pro­po­sant de rechar­ger le ser­veur. Si c'est le cas, il faut explo­rer la console Rstu­dio si l'application a été lan­cé avec Rstu­dio ou les fichiers log.

  3. Bon­jour Tho­mas,
    Mer­ci de votre tuto­riel.
    Hyper com­plet, pas-à-pas et très didac­tique.
    Thumb up 🙂
    Encore mer­ci.

  4. Avatar de Marc Scho
    Marc Scho

    Bon­jour,

    Je ne par­viens pas à affi­cher la seconde page , j'ai une erreur dans send­Swee­tA­lert, "object 'ses­sion' not found.

Laisser un commentaire

Pour insérer du code dans vos commentaires, utilisez les balises <code> et <\code>.