[Etude de marché - Article 3]


Dans le précédent article, nous avons programmé notre questionnaire. Nous allons voir maintenant comment enregistrer les réponses sur un server Amazon S3 et comment poster son questionnaire en ligne.


3. Sauvegarde des réponses.

Notre questionnaire est programmé, mais pour l’instant, lorsque le répondant appuie sur le dernier bouton “Terminé”, rien ne se passe. C’est dans cette partie que nous allons connecter notre application à un server Amazon S3 pour y sauvegarder les réponses.

Pour cette partie, je me suis inspiré de cet article de Sean Kross pour m’enregistrer et faire fonctionner mon bucket Amazon S3.

Et j’ai repris de cet article - de Dean Attali - les fonctions qu’il a créé pour enregistrer les réponses dans notre bucket (sur un serveur Amazon S3).


3.1. Amazon S3.

Amazon Simple Storage Service (Amazon S3) est un service de stockage d’objet. Dans notre cas, les objets seront un fichier csv contenant les réponses des personnes sondées. Plus globalement, ce service permet de stocker et protéger des données pour des sites web, des applications mobiles, de l’archivage ou pour sauvegarder des données issues d’appareils IoT.


3.1.1. Création d’un utilisateur IAM.

Le première étape est la création d’un compte Amazon AWS. Après vous êtes connecté(e), il faut maintenant vous créer un utilisateur IAM (Identity and Access Management). C’est ce qui vous permet de contrôler en toute sécurité l’accès aux ressources AWS.

Pour cela, il faut aller dans la partie “Security, Identity, & Compliance” et cliquer sur “IAM”. Sur cette page, il faut cliquer sur “Users” et puis sur “Add Users”.

  • Remplir le “User Name”.
  • Cocher “Programmatic access”.

Sur la page suivante, il faut :

  • Cliquer sur “Attach existing policies directly”.
  • Chercher “AmazonS3FullAccess” et cocher le.

Il faut ensuite passer la page “Tags” et cliquer sur “Create User”. Il faut enfin télécharger le csv. Les informations contenues dans ce fichier doivent rester secrètes (ne pas les diffuser).

L’Excel contient plusieurs informations : User name, Password, Access key ID, Secret access key, Console login link. Avec ces informations, vous pourrez connecter votre application à votre compte Amazon AWS pour y stocker les réponses.


3.1.2. Création d’un Bucket.

Avant de faire le lien entre votre application et votre compte Amazon S3, il faut créer un Bucket pour y stocker les réponses.

Ainsi, sur la page d’accueil AMS, il faut aller dans la partie “Storage” et cliquer sur “S3”. Puis cliquer sur “Créer un nouveau Bucket”. Il faut remplir “Bucket name”. Dans mon exemple, je l’ai appelé “svyapp”.

Vous venez de créer votre Bucket. Il faut maintenant connecter votre application à votre compte Amazon S3.


3.1.3. Connecter votre compte Amazon S3 à votre application.

Pour connecter votre compte Amazon S3 à votre application, il faut installer le package “aws.s3”.

install.packages("aws.s3")

Il faut ensuite dans votre script “App.R” ajouter les éléments d’identifications (avant le début de l’application –> avant la ligne de code shiny::shinyApp(…). On a ainsi :

library(aws.s3)

# Set up your keys and your region here.
Sys.setenv("AWS_ACCESS_KEY_ID" = "XXXXXXXXXXXX",
           "AWS_SECRET_ACCESS_KEY" = "XXXXXXXXXXXX",
           "AWS_DEFAULT_REGION" = "eu-west-3")


3.2. Sauvegarder les réponses dans le Bucket.

Notre compte Amazon S3 est connecté à notre application. Le bucket pour recevoir les réponses est créé également. Il faut maintenant définir les réponses à enregistrer et écrire la fonction qui va envoyer les réponses sur le Bucket lorsque le répondant aura cliqué sur “Terminé”. Pour cela, j’ai repris la fonction écrite par Dean Attali.


3.2.1. Définir les réponses à enregistrer.

Pour commencer, il faut définir deux objets (toujours avant le début de l’application):

  • Le nom du Bucket (celui qu’on a créé depuis notre compte Amazon S3).
  • Les réponses à enregistrer (l’ID des questions).
s3BucketName <- "svyapp"

Responses <- c("Q1", "Q2", "Q3", "Q4", "Q5_Oui", "Q5_pe", "Q5_non")


Dans la partie “Server” de notre application, il faut maintenant aggréger les réponses : les réponses sont enregistrées en ligne (une ligne = un répondant) et où chaque colonne est une question. Pour faire cela, il faut utiliser la fonction sapply qui va vectoriser les réponses x en colonne [x].

On en profite également pour déclencher la fonction “saveData” (programmée ci-dessous) lorsque le bouton “Terminé” est activé.

    formData <- reactive({
      data <- sapply(Responses, function(x) input[[x]])
      data
    })
    
    observeEvent(input$submit, {
      saveData(formData())
    })


3.2.2. Ecrire la fonction pour envoyer le fichier des réponses sur notre Bucket

Ensuite, il faut créer deux fonctions pour enregistrer les réponses dans notre Bucket (la première va être utilisée dans la seconde). On a ainsi :

  • La fonction “get_time_human” qui permet de fixer la date et l’heure de création du fichier.
  • La fonction “saveData” qui va coller les données dans un fichier, qui va lui attribuer un nom et qui va le placer à l’intérieur de notre Bucket.


La fonction “get_time_human”.

La fonction “get_time_human” va nous permette de voir comment construire une fonction avec R. Créer une fonction permet d’automatiser une tâche répétitive ou d’en réduire sa compléxité.

Il y a différente partie dans une fonction :

  • Le nom de la fonction: c’est ce qui va nous permettre d’appeler la fonction par la suite. Il faut la stocker comme un objet. Elle est construite via la fonction “function”.
  • Les arguments (optionnels): c’est les arguments nécessaires pour faire fonctionner la fonction. C’est les Inputs de la fonction.
  • Le corps de la fonction (entre les crochets): c’est les intructions que la fonction va effectuer.

On a donc :

nom_de_fonction <- function(arguments) {
    instructions
}


Ci-dessous un exemple (repris d’ici pour mieux comprendre la création d’une fonction. Cette fonction “Puissance d’un nombre (pun)” permet de calculer automatiquement la puissance d’un nombre (= c’est le résultat de la multiplication répétée de ce nombre avec lui-même).

Il y a deux arguments (Inputs) pour cette fonction :

  • Un nombre “a” quelconque.
  • Un entier naturel “n” non nul: l’exposant.

Après avoir calculé le résultat (a^n), ce dernier va afficher le résultat via la fonction paste. Cette fonction va concaténer l’ensemble des vecteurs après les avoir transformer en caractère.

pun <- function(a, n) {
# function to print x raised to the power y
resultat <- a^n
paste(a,"puissance", n, "donne", resultat)
}


Ce qui donne :

> pun(2,4)
[1] "2 puissance 4 donne 16"


Revenons à la fonction “get_time_human”. Cette fonction va être utilisé pour nommer le fichier que l’on va ensuite enregistrer dans notre Bucket. Cette fonction va ainsi renvoyer la date et l’heure via la fonction Sys.time(). On peut modeler le résultat de cette fonction avec plusieurs arguments pour avoir la date et/ou l’heure :

  • %Y : année sur 4 chiffres.
  • %m : mois de 01 à 12.
  • %d : jour du mois.
  • %H : heure de 00 à 23.
  • %M : minute de 00 à 59.
get_time_human <- function() {
  format(Sys.time(), "%Y%m%d-%H%M%OS")}


Ce qui donne (au moment au j’écris cette ligne de code) :

> get_time_human()
[1] "20200730-122315"


La fonction “saveData”.

Nous allons maintenant voir comment construire la fonction “saveData” qui va créer un fichier csv, lui donner un nom et l’enregistrer sur notre Bucket.

La première étape est de créer une dataframe et d’y coller le nom des colonnes et les réponses associées en les séparant grâce au point-virgule.

Pour le nom du fichier csv, il se construit comme ceci :

  • La date et l’heure via notre fonction “get_time_human()”.
  • Une suite composée de chiffre et de lettre tirée au hasard via la fonction “digest”. Ainsi, si deux répondants cliquent en même temps sur le bouton “Terminé” de différencier les deux fichiers (et de pouvoir enregistrer les deux).
  • .csv : pour signifier que c’est un fichier csv.

Enfin, la dernière étape est d’enregistrer ce fichier dans notre Bucket via la fonction “put_object” du package AWS. On a ainsi :

saveData <- function(data) {

    data <- paste0(
    paste(names(data), collapse = ";"), "\n",
    paste(unname(data), collapse = ";")
  )
  
  file_name <- paste0(
    paste(
      get_time_human(),
      digest(data, algo = "md5"),
      sep = "_"
    ),
    ".csv"
  )
  
  put_object(file = charToRaw(data), object = file_name, bucket = s3BucketName)}


Vous pouvez maintenant tester votre application (le script complet est remis en conclusion de cette partie –> Partie 3.3.) et vous connecter à votre compte Amazon S3. Vous avez bien dans votre Bucket la réponse. Bravo.


3.2.3. Fermer l’App après avoir cliqué sur terminé.

Lorsque le répondant clique sur le bouton “Terminé”, les réponses sont maintenant sauvegardées. Cependant, rien ne se passe ensuite. Le répondant ne sait pas si sa réponse a été prise en compte ou pas. Pour cela, je vous propose une fois le bouton “Terminé” cliqué de clore l’application via la fonction stopApp (). Ce qui donne :

    observe({
      if (input$submit) 
        stopApp()})


3.3. Conclusion.

Pour conclure, je vous propose de vous partager le code complet de l’application. C’est ce fichier App.R que nous allons dans la partie suivante publier en ligne.

library(shiny)
library(shinyWidgets)
library(sortable)
library(shinyjs)
library(aws.s3)
library(digest)
library(rsconnect)

##### Gestion de la sauvegarde #####

s3BucketName <- "svyapp"

Sys.setenv("AWS_ACCESS_KEY_ID" = "XXXXXXXXX",
           "AWS_SECRET_ACCESS_KEY" = "XXXXXXXXX",
           "AWS_DEFAULT_REGION" = "eu-west-3")

# Les réponses à sauvegarder
Responses <- c("Q1", "Q2", "Q3", "Q4", "Q5_Oui", "Q5_pe", "Q5_non")

# Fonctions nécessaires
get_time_human <- function() {
  format(Sys.time(), "%Y%m%d-%H%M%OS")}

saveData <- function(data) {

    data <- paste0(
    paste(names(data), collapse = ";"), "\n",
    paste(unname(data), collapse = ";")
  )
  
  file_name <- paste0(
    paste(
      get_time_human(),
      digest(data, algo = "md5"),
      sep = "_"
    ),
    ".csv"
  )
  
  put_object(file = charToRaw(data), object = file_name, bucket = s3BucketName)}

# Début de l'App. 

shinyApp(
  
  ui = fluidPage(
    style = "width:800px",
    shinyjs::useShinyjs(),
    titlePanel(img(src="myLogo.png")),
    
    panel(
      
##### Questionnaire #####
      
      # Introduction
      div(id = "Intro",
          
          h3("Bienvenue sur notre questionnaire en ligne. Nous vous remercions d’accepter de répondre à cette 
       étude en ligne menée par l’Institut d’études et de sondages Su-R-vey. Cette étude sera traitée de façon 
       anonyme. Nous vous garantissons la confidentialité de cette interview. Ce questionnaire vous prendra 5
       minutes."),
          
          actionButton("button0", "Suivant")), 
      
      # Question Q1. 
      hidden(div(id = "Q1",
                 
                 radioGroupButtons("Q1", label = p("Vous êtes :"),
                                   choices = list("Un Homme" = 1, "Une femme" = 2), 
                                   selected = "", direction = "vertical", justified = TRUE), 
                 
                 actionButton("button1", "Suivant"))), 
      
      # Question Q2. 
      hidden(div(id = "Q2",
                 
                 numericInput("Q2", label = p("Quel âge avez-vous ?"), min = 1, max = 110, value = ""), 
                 
                 hidden(div(id = "Q2_out", p("La réponse doit être comprise entre 1 et 110"))),
                 
                 actionButton("button2", "Suivant"))),
      
      
      # Question Q3.
      hidden(div(id = "Q3",
                 
                 checkboxGroupButtons("Q3", label = "Sur quel réseau social êtes-vous inscrit(e) ?",
                                      choices = c("Facebook" = 1,
                                                  "Google +" = 2, 
                                                  "Instagram" = 3, 
                                                  "Linkedin" = 4, 
                                                  "Periscope" = 5,
                                                  "Pinterest" = 6,
                                                  "Reddit" = 7,
                                                  "Snapchat" = 8,
                                                  "TikTok" = 9, 
                                                  "Tumblr" = 10,
                                                  "Twitter" = 11,
                                                  "Viadeo" = 12, 
                                                  "Aucun de ces réseaux" = 99), 
                                      direction = "vertical", selected = "", justified = TRUE),
                 
                 actionButton("button3", "Suivant"))), 
      
      
      # Question Q4. 
      hidden(div(id = "Q4",
                 
                 textInput("Q4", label = ("Quels sont pour vous les inconvéniants de Facebook ?"), 
                           value = "Ecrire ici", width = "600px"),
                 
                 actionButton("button4", "Suivant"))), 
      
      
      # Question Q5. 
      hidden(div(id = "Q5",
                 
                 bucket_list(
                   header = c("Quels appareils prévoyez vous d'acheter lors du prochain mois?"),
                   add_rank_list(
                     input_id = "Q5_Liste", 
                     text = "Liste des appareils",
                     labels = c("Une console", "Une montre", "Un drone", "Une tablette",
                                "Un casque audio", "Un smartphone")
                   ),
                   add_rank_list(
                     input_id = "Q5_Oui", 
                     text = "Oui"
                   ),
                   add_rank_list(
                     input_id = "Q5_pe", 
                     text = "Peut-Etre"
                   ), 
                   add_rank_list(
                     input_id = "Q5_non", 
                     text = "Non"
                   )),
                 
                 actionButton("button5", "Suivant"))), 
      
      # Fin du questionnaire 
      hidden(div(id = "Fin", 
                 
                 p("Ce questionnaire est à présent terminé. Je vous remercie vivement 
                   pour votre participation et je vous souhaite une bonne fin de 
                   journée / soirée"),
                 
                 actionButton("submit", "Terminé"))))),
  
  server = function(input, output, session) {
    
    ##### L'enchainement des questions#####
    
    observeEvent(input$button0, {
      shinyjs::hide(id = "Intro")
      shinyjs::show(id = "Q1")
    })
    
    observe(if(input$button1 && !is.null(input$Q1)) {
      shinyjs::hide("Q1")
      shinyjs::show("Q2")
    })
    
    # Obliger une réponse numérique. 
    observe(if(input$button2 && is.numeric(input$Q2) && input$Q2 <= 110) {
      shinyjs::hide("Q2")
      shinyjs::show("Q3")
    })
    
    # Mise en place d'un message si le répondant ne respecte pas la borne.   
    observeEvent(input$Q2, {
      if ((!is.na(input$Q2) && input$Q2 > 110) | (!is.na(input$Q2) && input$Q2 < 1 )) {
        shinyjs::show("Q2_out")
      } else {
        shinyjs::hide("Q2_out")
      }}, ignoreInit = TRUE)

    # Programmation du choix 99 de la question 3 (Si Q3 = 99 --> déselectionner tous les autres)
    observeEvent(input$Q3, {
      if (99 %in% input$Q3) {
        updateCheckboxGroupButtons(session, "Q3", selected = "99")
      } else {
        updateCheckboxGroupButtons(session, "Q3", selected = input$Q3)
      }
    }, ignoreInit = TRUE)
    
    # Question 4 est filtrée : 
    observe(if(input$button3 && input$Q3 == 1) {
      shinyjs::hide("Q3")
      shinyjs::show("Q4")
    })
    
    observe(if(input$button3 && input$Q3 != 1) {
      shinyjs::hide("Q3")
      shinyjs::show("Q5")
    })

    # Reprise Q5
    observe(if(input$button4 && !is.null(input$Q4)) {
      shinyjs::hide("Q4")
      shinyjs::show("Q5")
    })
    
    observeEvent(input$button5, {
      shinyjs::hide("Q5")
      shinyjs::show("Fin")
    })
    
    ##### Gestion de la Sauvegarde - Partie Server
    
    formData <- reactive({
      data <- sapply(Responses, function(x) input[[x]])
      data
    })
    
    observeEvent(input$submit, {
      saveData(formData())
    })
    
    # Fermer la page. 
    observe({
      if (input$submit) 
        stopApp()
    })})


4. Mise en ligne du questionnaire.

Pour la mise en ligne du questionnaire, nous allons utiliser le site ShinyApps. Il faut tout d’abord vous créer un compte. Puis, sur la page “Dashboard”, copier les autorisations nécessaires pour pouvoir vous connecter à ce service.

Ouvrez un nouveau script et installer la package “rsconnect”.

install.packages("rsconnect")

Puis, vous pouvez faire tourner le script ci-dessous (en indiquant le chemin jusqu’au dossier de votre application) :

library(rsconnect)

rsconnect::setAccountInfo(name='aureliendaval',
                          token='XXXXXXXXXXXXX',
                          secret='XXXXXXXXXXXXX')

rsconnect::deployApp('C:/Path/Application Su-R-vey')


Bravo, vous venez de mettre en ligne votre application. Une page internet doit s’ouvrir avec le lien. Félicitations.


Conclusion.

Le questionnaire est maintenant en ligne. Le lien de l’application construite ensemble est disponible ici Vous pouvez diffuser le lien pour commencer à récolter des réponses. La prochaine étape est normalement le suivi du terrain avec l’avancée des différents quotas. Cependant, ce point sera géré par la suite avec le création d’un Dashboard. Le prochain article portera donc sur le traitement statistique des réponses.