R/NpsVoixClients.R

Defines functions NpsVoixClients

Documented in NpsVoixClients

#Macro de traitement
NpsVoixClients <- function(donnee, nps_client, commentaire, nom_sondage) {

  #Vérification des requis
  require(tcltk)
  if (tk_messageBox(icon = 'warning',type = c("okcancel"), message = "Pour utiliser cet outil, vous devez pointer vers le sondage CSV et indiqué les colonnes de NPS et de commentaires.\n\nDe plus, les catégories de NPS autorisées doivent être libellées ainsi 'Promoteur', 'Passif' ou 'Détracteur'",caption = 'Avertissement') == 'cancel')
    stop("Arrêté par l'utilisateur")

  #Importation des transformers
  transformers <- reticulate::import('transformers')

  #Importation des modèles de classification
  classifier_tokenizer <- transformers$AutoTokenizer$from_pretrained('camembert-base')
  classifier_zsl <- transformers$pipeline("zero-shot-classification", model="BaptisteDoyen/camembert-base-xnli", tokenizer = classifier_tokenizer, use_fast = TRUE)

  #Définition des mégas catégories
  prix <- c('prix', 'prime', 'renouvellement', 'rabais', 'soumission', 'cher', 'augmentation', 'tarif', 'coût', 'taux')
  employe_agent <- c('employé', 'personne', 'courtoisie', 'parler', 'réponse', 'personnel', 'professionnel', 'clair', 'explication', 'demande', 'information', 'agent')
  efficacite_processus_delai <- c('efficacité', 'processus', 'délai', 'rapide', 'attente', 'temps', 'mois', 'problème', 'réclamation', 'dossier', 'contrat', 'satisfaction')
  produits_et_services <- c('produit','service', 'besoin', 'police', 'qualité', 'compagnie', 'téléphone', 'couverture', 'la capitale', 'ssq', 'beneva')

  categories_zsl <- c(prix, employe_agent, efficacite_processus_delai, produits_et_services)

  #Lecture des données sources
  donnee <- read.csv2(donnee)
  #Conserve le jeu de données, le nps et le commentaire déclaré dans la fonction
  donnee <- dplyr::select(donnee, nps_client, commentaire)
  #Renomme les colonnes
  colnames(donnee) <- c('nps','commentaire')

  #Retrait des lignes vides
  donnee <- donnee[!is.na(donnee$nps) && !is.na(donnee$commentaire)]

  #Retrait des stop word
  donnee$commentaire <- tm::removeWords(donnee$commentaire, tm::stopwords("french"))

  #Tout en minuscule, retrait des nombres, retrait des ponctuations, retrait des stop words, retrait des espaces superflus
  donnee$commentaire <- tm::stripWhitespace(tm::removeWords(tm::removePunctuation(tm::removeNumbers(tolower(donnee$commentaire))), tm::stopwords("french")))

  #Stemmatisation
  donnee$commentaire <- SnowballC::wordStem(donnee$commentaire, language = "french")

  #Analyse des sentiments avec syuzhet
  donnee$syuzhet_temp <-syuzhet::get_nrc_sentiment(donnee$commentaire, language = "french")

  donnee$colere <- as.numeric(donnee$syuzhet_temp$anger)
  donnee$anticipation <- as.numeric(donnee$syuzhet_temp$anticipation)
  donnee$degout <- as.numeric(donnee$syuzhet_temp$disgust)
  donnee$peur <- as.numeric(donnee$syuzhet_temp$fear)
  donnee$joie <- as.numeric(donnee$syuzhet_temp$joy)
  donnee$tristesse <- as.numeric(donnee$syuzhet_temp$sadness)
  donnee$surprise <- as.numeric(donnee$syuzhet_temp$surprise)
  donnee$confiance <- as.numeric(donnee$syuzhet_temp$trust)
  donnee$syuzhet_negatif <- as.numeric(donnee$syuzhet_temp$negative)
  donnee$syuzhet_positif <- as.numeric(donnee$syuzhet_temp$positive)
  donnee <- donnee[,-c(3)]

  #Conserve le nombre de ligne
  nb_ligne <- nrow(donnee)

  #Zero shot classification
  #Chaque commentaire est traité individuellement
  donnee$ligne <- seq.int(nrow(donnee))
  #On boucle sur toutes les lignes, une à la fois
  for (i in 1:max(donnee$ligne)) {
    #Cette étape est la plus longue, c'est pourquoi j'y mets un suivi
    print(glue::glue("Traitement de la ligne {i} de {max(donnee$ligne)}"))
    #On conserve que la ligne en cours
    tempo <- donnee[c(i),]
    #On isole le commentaire
    tempo2 <- NLP::as.String(tempo[tempo$ligne == i, c("commentaire")])
    #On passe le commentaire dans la classification
    tempo3 <- classifier_zsl(tempo2, categories_zsl)
    #Les labels et scores obtenus sont convertis en caractères pour l'instant
    tempo3$labels <- as.character(tempo3$labels)
    tempo3$scores <- as.character(tempo3$scores)

    #On recrée le jeu de données en 'recollant' les lignes
    if (i == 1){
      final = tempo3
    } else {
      final <- rbind(final, tempo3)
    }
    i <- i + 1
  }
  rownames(final) <- NULL

  categorie <- as.data.frame(final)
  donnee <- merge(x = donnee, y = categorie, by.x = 'commentaire', by.y = 'sequence', all.x = TRUE)

  #Création des colonnes vides pour accueillir les résultats
  donnee[,prix] <- as.numeric(NA)
  donnee[,employe_agent] <- as.numeric(NA)
  donnee[,efficacite_processus_delai] <- as.numeric(NA)
  donnee[,produits_et_services] <- as.numeric(NA)

  #Traitement pour reclasser les labels et les scores
  #Le modèle classe chaque ligne en fonction du plus gros score
  #Chaque ligne est indépendante
  #Il faut donc tout remettre en ordre
  nom_col <- colnames(donnee)
  #Boucle à travers toutes les lignes
  for (i in 1:max(donnee$ligne)) {
    #On isole les données de la ligne en cours, plus particulièrement les labels et les scores
    tempo4 <- donnee[i,]
    a <- donnee[i,c("labels")][[1]]
    b <- donnee[i,c("scores")][[1]]
    #On scanne parmi tous les labels
    for (j in 1:length(a)) {
      #On cherche à matcher le nom de la colonne avec le label en cours
      for (k in 16 : length(nom_col)) {
        if (a[j] == nom_col[k]) {
          #Une fois trouvé, on lui attribue la valeur
          modif <- glue::glue("tempo4$`{a[j]}` <- {b[j]}")
          eval(parse(text = modif))
          #On ramène les données ensemble
          if (i == 1){
            final = tempo4
          }
          final <- rbind(final, tempo4)
          i <- i + 1
        }
      }
    }
  }

  #On enlève les colonnes de labels et de scores
  donnee <- na.omit(final[,-c(13:15)])
  rownames(donnee) <- NULL

  #Pour chaque catégorie, on additionneles valeurs de la classification
  #et on retire les valeurs qui l'ont composées
  donnee$categorie_prix <- rowSums(donnee[13:(13+length(prix)-1)], na.rm=TRUE)
  donnee <- donnee[,-c(13:(13+length(prix)-1))]
  donnee$categorie_employe_agent <- rowSums(donnee[13:(13+length(employe_agent)-1)], na.rm=TRUE)
  donnee <- donnee[,-c(13:(13+length(employe_agent)-1))]
  donnee$categorie_efficacite_processus_delai <- rowSums(donnee[13:(13+length(efficacite_processus_delai)-1)], na.rm=TRUE)
  donnee <- donnee[,-c(13:(13+length(efficacite_processus_delai)-1))]
  donnee$categorie_produits_et_services<- rowSums(donnee[13:(13+length(produits_et_services)-1)], na.rm=TRUE)
  donnee <- donnee[,-c(13:(13+length(produits_et_services)-1))]

  #On génère la moyenne et l'erreur type des variables
  donnee <- as.data.frame(sqldf::sqldf("select distinct nps,
                                avg(colere) as colere_moyenne,	stdev(colere) as colere_erreur_type,
                                avg(anticipation) as anticipation_moyenne,	stdev(anticipation) as anticipation_erreur_type,
                                avg(degout) as degout_moyenne,	stdev(degout) as degout_erreur_type,
                                avg(peur) as peur_moyenne,	stdev(peur) as peur_erreur_type,
                                avg(joie) as joie_moyenne,	stdev(joie) as joie_erreur_type,
                                avg(tristesse) as tristesse_moyenne,	stdev(tristesse) as tristesse_erreur_type,
                                avg(surprise) as surprise_moyenne,	stdev(surprise) as surprise_erreur_type,
                                avg(confiance) as confiance_moyenne,	stdev(confiance) as confiance_erreur_type,
                                avg(syuzhet_negatif) as syuzhet_negatif_moyenne,	stdev(syuzhet_negatif) as syuzhet_negatif_erreur_type,
                                avg(syuzhet_positif) as syuzhet_positif_moyenne,	stdev(syuzhet_positif) as syuzhet_positif_erreur_type,
                                avg(categorie_prix) as prix_moyenne,	stdev(categorie_prix) as prix_erreur_type,
                                avg(categorie_employe_agent) as employe_agent_moyenne,	stdev(categorie_employe_agent) as employe_agent_erreur_type,
                                avg(categorie_efficacite_processus_delai) as efficacite_processus_delai_moyenne,	stdev(categorie_efficacite_processus_delai) as efficacite_processus_delai_erreur_type,
                                avg(categorie_produits_et_services) as produits_et_services_moyenne,	stdev(categorie_produits_et_services) as produits_et_services_erreur_type

                                from donnee

                                group by nps

                                order by nps"))

  donnee$colere_erreur_type <- donnee$colere_erreur_type / nb_ligne
  donnee$anticipation_erreur_type <- donnee$anticipation_erreur_type / nb_ligne
  donnee$degout_erreur_type <- donnee$degout_erreur_type / nb_ligne
  donnee$peur_erreur_type <- donnee$peur_erreur_type / nb_ligne
  donnee$joie_erreur_type <- donnee$joie_erreur_type / nb_ligne
  donnee$tristesse_erreur_type <- donnee$tristesse_erreur_type / nb_ligne
  donnee$surprise_erreur_type <- donnee$surprise_erreur_type / nb_ligne
  donnee$confiance_erreur_type <- donnee$confiance_erreur_type / nb_ligne
  donnee$syuzhet_negatif_erreur_type <- donnee$syuzhet_negatif_erreur_type / nb_ligne
  donnee$syuzhet_positif_erreur_type <- donnee$syuzhet_positif_erreur_type / nb_ligne
  donnee$prix_erreur_type <- donnee$prix_erreur_type / nb_ligne
  donnee$employe_agent_erreur_type <- donnee$employe_agent_erreur_type / nb_ligne
  donnee$efficacite_processus_delai_erreur_type <- donnee$efficacite_processus_delai_erreur_type / nb_ligne
  donnee$produits_et_services_erreur_type <- donnee$produits_et_services_erreur_type / nb_ligne

  #Le format des données passe sous un format long
  donnee <- tidyr::gather(data = donnee, key = variables, value = valeurs, -c(nps))

  #On crée la table finale
  donnee <- as.data.frame(sqldf::sqldf("select distinct nps,
                                replace(replace(variables, '_moyenne', ''), '_erreur_type', '') as variables,
                                sum(case when variables like '%moyenne%' then valeurs else 0 end) as moyenne,
                                sum(case when variables like  '%erreur_type%' then valeurs else 0 end) as erreur_type

                                from donnee

                                group by nps, replace(replace(variables, '_moyenne', ''), '_erreur_type', '')

                                order by 1, 2"))

  #Génération du graphique GGplot
  donnee <- dplyr::filter(.data = donnee, variables %in% c("efficacite_processus_delai", "employe_agent", "produits_et_services", "prix"))
  donnee <-  ggplot2::ggplot(data = donnee) +
    ggplot2::aes(x = variables, fill = nps, weight = moyenne) +
    ggplot2::geom_bar(position = "dodge") +
    ggplot2::labs(
      x = "Thématiques exprimées",
      y = "Valeurs en pourcentage",
      title = "Voix des clients - ce que nos clients disent",
      caption = "Barres d'erreur ± 1 erreur type",
      subtitle = nom_sondage,
      fill = "NPS") +
    ggplot2::theme_minimal() +
    ggplot2::theme(legend.position = "bottom")+
    ggplot2::geom_errorbar(ggplot2::aes(ymin = moyenne - erreur_type, ymax = moyenne + erreur_type), width=.2,
                  position = ggplot2::position_dodge(.9)) +
    ggplot2::scale_x_discrete(labels=c("Efficacité du processus et des délais", "Employ / agent", "Prix",
                              "Produits et services")) +
    ggplot2::scale_y_continuous(labels = scales::percent)

    ggplot2::ggplot_build(donnee)

    print(donnee)
    plot(donnee)
    print("Donnes par NPS et variables")
    print(donnee$data)
}
pomercierbeneva/NpsVoixClients documentation built on Dec. 22, 2021, 9:47 a.m.