Shiny : visualisation / exploration interactive

Avantages

  • Interface interactive / dashboard.
  • Assez facile à mettre en oeuvre.
  • Tout en R.

Inconvénients...

  • Debuggage parfois délicat car javascript-HTML.
  • Nécessite un serveur Shiny pour être déployé.

2 composants principaux (obligatoires)

  • UI : décrit l'interface. Mise en page, boutons, sliders, plots, ...
    Inputs : actionButton, dateInput, actionLink, sliderInput, radioButtons, ...
    Outputs : plotOutput, dataTableOutput, textOutput, ...
  • Server : décrit la logique applicative réactive.Met à jour les sorties quand les entrées changent.

Exemple - UI

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

  • Permet de référencer les entrées / sorties.
  • Fait le lien avec le code serveur.

Exemple - Serveur

# 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.

MathJax

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(...)".

Grid system...

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.

En résumé :

  • la partie UI correspond à une page HTML,
  • vous pouvez définir des styles inline,
  • ou bien chargés depuis une feuille CSS.

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.

Serveur, réactivité avancée

Structurer l'exécution pour éviter de dupliquer des traitements :

data = reactive({ ... })
data()
  • reactive : bloc réactif retournant une expression reactive
  • observe : bloc réactif ne retournant rien (effet de bord i/o)
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)
})

observeEvent / eventReactive

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()
    })
  }
)

insertUI / removeUI

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)
    })
  }
)

Reactive timers

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)))
  })
}

Websockets : le retour

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 :)

Déploiement

  1. shinyapp.io → création d'un compte.
  2. Bouton "publish document".
  3. Récupération de la clé et du secret.
  4. Penser à regrouper l'ensemble des données utiles dans un fichier .RData dans le répertoire de l'application.
  5. Chargé avec la fonction load.

Projets

Réaliser une interface shiny permettant d'explorer au choix :

  • les données Game Of Throne du TP (got/data/*.csv, *.geojson)
  • les données du service "dans ma rue" de la Mairie de Paris (dsviz/data/dansmarue.RData)
  • les stats de joueurs NBA https://www.nba.com/stats/ (*)
  • ...vos données d'un autre projet.

Digression : architecture d'une application shiny

"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.

App Shiny = interface à une API Plumber

Par exemple.

TODO ?

Scaling Shiny

Dashboard ?

Slides 1
Slides 2
Tutoriel

Exercice

Développer une petite appli Shiny pas à pas, l'objectif étant de classer des données :

  1. Effectuer un clustering k-means prenant en entrée le paramètre $K$, et afficher le résultat (graphique, coloré).
  2. Laisser le choix de l'algorithme (au moins CAH et k-means).
  3. Effectuer une classification supervisée données $\rightarrow$ résultat du clustering et afficher le taux de bien classés. Idéalement, laissez le choix de la méthode (kNN et tree par exemple).
  4. Afficher la courbe des erreurs en fonction de K pour l'input particulier K = 0 (cette courbe peut remplacer l'affichage du clustering).

Ébauche de corrigé

Contrôle de Version: Git

Plan

  1. Pourquoi Git ?!
  2. Vue d'ensemble
  3. Créer un repository sur GitHub
  4. Utilisation en ligne de commandes
  5. Client GitHub Desktop ou GitKraken ou ...
  6. Traitement des gros fichiers (binaires)

Motivations

Alternatives

  • CVS: logiciel de contrôle de version utilisé dans les années 90.
  • Dropbox: conserve l'historique des 30 derniers jours seulement. Possibilité de ramener un fichier à un état précédent, mais pas de gestion de versions globale : fichier par fichier.
  • SVN : "ancêtre de Git", encore en développement. Utilisé surtout dans les années 2000. Principal problème : centralisé.
  • Mercurial : similaire à Git.
  • Perforce/helix-core : sans doute plus complet, logiciel commercial.
  • ...

Vue d'ensemble

  1. Création d'un dossier spécial avec "git init"
  2. Envoi d'une copie de ce dossier sur un serveur.
  3. ...
  4. Des collaborateurs récupèrent le code : "git clone".
  5. Modifications apportées au dossier : sauvegardées via "git push".
  6. Les collaborateurs récupèrent les modifications : "git pull".
  7. ...

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.

Vue plus précise

Trois "arbres" représentant l'état actuel des fichiers
coexistent localement...

GitHub : premiers pas

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 :

  1. Ouvrir un compte : https://github.com/signup
  2. S'assurer d'avoir une clé SSH : documentation

GitHub : créer un dépôt

  1. Depuis le menu en haut à droite, cliquer sur "Your repositories" puis sur "New" en vert à droite.
  2. Le seul champ obligatoire est le nom du dépôt : choisissez bien, il n'est pas modifiable (à moins de supprimer/recréer).
  3. Concernant les autres champs, indiquez une brève description, et laissez les choix par défaut.

Ensuite

  • récupérer votre repo localement :
    git clone <url_du_repo> <nom_local>
  • Faire des modifications, le mettre à jour : voir suite.

Publier des modifications

# 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]

Mettre à jour votre copie de travail

git pull [origin main]

# Résoudre les conflits éventuels, etc...

Suite

Suivre ce tutoriel et faire les exercices.

Concernant la gestion des conflits, vous pouvez par exemple développer à deux sur un même repo.

Clients

Faites votre choix :)

Gestion des fichiers binaires

Pour un repo GitHub : git-lfs.

Solution très simple mais fonctionnelle (un seul fichier) : git-fat.

Ce dont j'envisageais de parler (mais finalement non)

Créer un package R

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.

Exercice

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 :

  • Le vecteur des noms de variables (colonnes).
  • Une fonction prenant une variable en argument (ou son index) retournant un histogramme.

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.