%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#f4f4f4", "textColor": "#333", "lineColor": "#666"}, "flowchart": {"nodeSpacing": 200, "rankSpacing": 150, "diagramPadding": 50, "htmlLabels": true}}}%%
graph LR
A[Texte brut] --> B[Nettoyage]
B --> C[Tokenisation]
C --> D[Analyse]
D --> E[Visualisation]
Introduction aux expressions régulières
Qu’est-ce qu’on essaie de faire?
Trouver des patterns dans du texte
Exemple : Trouver tous les numéros de téléphone dans un document
Exemple : Extraire tous les courriels d’un texte
Un exemple concret
Imaginons que vous ayez ce texte:
Contact: Marie (514-555-1234) Courriel: marie@udem.caContact: Pierre (438-555-5678)Courriel: pierre@udem.ca
Comment trouver automatiquement tous les numéros de téléphone?
La solution “manuelle”
Chercher des chiffres
Regroupés par 3 ou 4
Séparés par des tirets
Commençant par 514 ou 438
La solution avec regex
# Un pattern qui trouve les numéros de téléphone"\\d{3}-\\d{3}-\\d{4}"
Décomposons le pattern
\d : un chiffre (0-9)
{3} : exactement 3 fois
- : un tiret
Donc \d{3}-\d{3}-\d{4} trouve: “514-555-1234”
Pourquoi c’est utile?
Automatise la recherche de patterns
Fonctionne sur n’importe quelle quantité de texte
Plus rapide et fiable que la recherche manuelle
Essentiel pour le nettoyage de données textuelles
Utilisation dans R
Le package stringr
# Installation si nécessaireinstall.packages("stringr")# Chargementlibrary(stringr)
Fait partie du tidyverse
Fonctions simples et cohérentes
Documentation claire avec de nombreux exemples
Fonctions principales de stringr
# Détecter un patternstr_detect(string, pattern)# Extraire un patternstr_extract(string, pattern)# Remplacer un patternstr_replace(string, pattern, replacement)# Séparer selon un patternstr_split(string, pattern)
Exemple pratique
library(stringr)# Notre textetext <-"Contact: Marie (514-555-1234), Pierre (438-555-5678)"# Trouver tous les numérosnumeros <-str_extract_all(text, "\\d{3}-\\d{3}-\\d{4}")# Résultatprint(numeros)# [[1]]# [1] "514-555-1234" "438-555-5678"
Les briques de base des regex
Rechercher des chiffres avec \d
texte <-"Je suis né en 1990 et j'ai 33 ans"str_extract_all(texte, "\\d+")# Résultat: [1] "1990" "33"
Rechercher des lettres avec [A-Z] et [a-z]
texte <-"QUÉBEC est une ville du Canada"# Trouver les mots en majusculesstr_extract(texte, "[A-Z]+")# Résultat: "QUÉBEC"
Rechercher des espaces avec \s
texte <-"mot clé"# Remplacer les espaces multiples par un seul espacestr_replace_all(texte, "\\s+", " ")# Résultat: "mot clé"
Les quantificateurs : Combien de fois?
Le + : “un ou plusieurs”
# Trouver les nombres (un ou plusieurs chiffres)texte <-"J'ai 1 chat et 22 poissons"str_extract_all(texte, "\\d+")# Résultat: [1] "1" "22"
Le * : “zéro ou plusieurs”
# Trouver les mots avec ou sans 's' à la fintexte <-"chat chats chien chiens"str_extract_all(texte, "chat[s]*")# Résultat: [1] "chat" "chats"
Le ? : “optionnel (zéro ou un)”
# Trouver 'behaviour' ou 'behavior'texte <-"behaviour and behavior"str_extract_all(texte, "behaviou?r")# Résultat: [1] "behaviour" "behavior"
Exemples pratiques pour les sciences sociales
Extraire des codes postaux canadiens
adresse <-"Mon adresse est H2X 1Y6"str_extract(adresse, "[A-Z]\\d[A-Z]\\s?\\d[A-Z]\\d")# Résultat: "H2X 1Y6"
Extraire des identifiants Twitter
tweet <-"Suivez-moi @MonProfR et @UdeM"str_extract_all(tweet, "@\\w+")# Résultat: [1] "@MonProfR" "@UdeM"
Identifier des URLs dans un texte
texte <-"Visitez https://www.umontreal.ca pour plus d'infos"str_extract(texte, "https?://[\\w\\.]+\\.\\w+")# Résultat: [1] "https://www.umontreal.ca"
Comment utiliser les regex en R?
Les numéros de téléphone?
Les adresses courriel?
Les pourcentages?
L’intelligence artificielle 😉
Les fonctions
Qu’est-ce qu’une fonction en R?
Une fonction est un bloc de code réutilisable
Elle effectue une tâche spécifique
Elle peut accepter des inputs (arguments)
Elle produit généralement un output
# Structure de base d'une fonctionma_fonction <-function(argument1, argument2) {# Corps de la fonction resultat <- argument1 + argument2return(resultat)}
Pourquoi utiliser des fonctions?
Réutiliser du code: Évite de copier-coller et répéter le même code
Modularité: Décompose des problèmes complexes en parties gérables
Fonctions de packages - dplyr::filter() - ggplot2::geom_point() - sondr::topdown_fa()
Fonctions personnalisées - Créées par vous pour des besoins spécifiques
Anatomie d’une fonction
calculer_moyenne <-function(nombres) {# Documentation: Calcule la moyenne d'un vecteur de nombres# Corps de la fonction somme <-sum(nombres) # Somme de tous les nombres taille <-length(nombres) # Nombre d'éléments moyenne <- somme / taille # Calcul de la moyenne# Valeur de retourreturn(moyenne)}# Utilisationmoyenne_classe <-calculer_moyenne(c(85, 90, 78, 92, 88))print(moyenne_classe) # Affiche: 86.6
Nom: Choisir un nom descriptif, utiliser des verbes
Arguments: Paramètres d’entrée, peuvent avoir des valeurs par défaut
Corps: Les opérations à effectuer
Retour: La valeur ou l’objet que la fonction renvoie
Bonnes pratiques pour les fonctions
Donner des noms clairs et descriptifs
Utiliser des verbes pour les fonctions (calculer_, analyser_)
Une fonction = une tâche unique et précise
Ajouter des commentaires expliquant le but et les arguments
Analyse de sentiment
La ligne rouge
Super good kebab! The portions are generous, the prices are really reasonable, and the quality is there. Tasty meat, fresh bread, and everything is well seasoned. An excellent address for a meal that is good without breaking the bank. I recommend!
Sentiment: Positif
Thèmes: Nourriture, Prix
Note: 5/5
Étape 1: Création du dataframe
# Créer un data.frame avec notre reviewreview <-data.frame(restaurant ="La ligne rouge",text ="Super good kebab! The portions are generous, the prices are really reasonable, and the quality is there. Tasty meat, fresh bread, and everything is well seasoned. An excellent address for a meal that is good without breaking the bank. I recommend!",stringsAsFactors =FALSE)
Super good kebab!The portions are generous, the prices are really reasonable, and the quality is there.Tasty meat, fresh bread, and everything is well seasoned.An excellent address for a meal that is good without breaking the bank.I recommend!
super good kebab the portions are generous the prices are really reasonable and the quality is there tasty meat fresh bread and everything is well seasoned an excellent address for a meal that is good without breaking the bank i recommend
r$> head(tokens, 10)
restaurant word
1 La ligne rouge super
2 La ligne rouge good
3 La ligne rouge kebab
4 La ligne rouge the
5 La ligne rouge portions
6 La ligne rouge are
7 La ligne rouge generous
8 La ligne rouge the
9 La ligne rouge prices
10 La ligne rouge are
Étape 4: Retrait des mots vides
# Obtenir les stop wordsstop_words <- tidytext::get_stopwords(language ="en")# Retirer les stop words avec dplyr anti_jointokens_clean <- dplyr::anti_join( tokens, stop_words,by ="word")
r$> head(tokens_clean, 10)
restaurant word
1 La ligne rouge super
2 La ligne rouge good
3 La ligne rouge kebab
4 La ligne rouge portions
5 La ligne rouge generous
6 La ligne rouge prices
7 La ligne rouge really
8 La ligne rouge reasonable
9 La ligne rouge quality
10 La ligne rouge tasty
Étape 5: Analyse de sentiment (AFINN)
# Obtenir le lexique AFINNafinn <- tidytext::get_sentiments("afinn")# Joindre avec nos tokenssentiment_scores <- dplyr::inner_join( tokens_clean, afinn,by ="word")# Voir les scoresarranged_scores <- sentiment_scores %>% dplyr::select(word, value) %>% dplyr::arrange(dplyr::desc(value))
r$> head(arranged_scores, 10)
word value
1 super 3
2 good 3
3 excellent 3
4 good 3
5 generous 2
6 recommend 2
7 fresh 1
Nothing exceptional, just edible. I had good feedback about the food and I was very, very disappointed. Not to mention cash only which for me is unacceptable. Too many good restaurants in the neighborhood, I won’t go back there
Food is good and price is ok. The only issu is the attitude of the staff. The lady at he cash register and the guy that takes the orders seriously lack client service skills. Both are very rude. Hygiene is another issue, there are flies all over the place. In addition to all this, they only take cash.
# Créer un data.frame avec plusieurs reviewsreviews <-data.frame(restaurant ="La ligne rouge",text =c("Super good kebab! The portions are generous, the prices are really reasonable, and the quality is there. Tasty meat, fresh bread, and everything is well seasoned. An excellent address for a meal that is good without breaking the bank. I recommend!","Nothing exceptional, just edible. I had good feedback about the food and I was very, very disappointed. Not to mention cash only which for me is unacceptable. Too many good restaurants in the neighborhood, I won't go back there","Food is good and price is ok. The only issu is the attitude of the staff. The lady at he cash register and the guy that takes the orders seriously lack client service skills. Both are very rude. Hygiene is another issue, there are flies all over the place. In addition to all this, they only take cash." ),stringsAsFactors =FALSE) %>% dplyr::mutate(id =1:nrow(.))
r$> head(tokens, 10)
restaurant id word
1 La ligne rouge 1 super
2 La ligne rouge 1 good
3 La ligne rouge 1 kebab
4 La ligne rouge 1 the
5 La ligne rouge 1 portions
6 La ligne rouge 1 are
7 La ligne rouge 1 generous
8 La ligne rouge 1 the
9 La ligne rouge 1 prices
10 La ligne rouge 1 are
Étape 4: Retrait des mots vides
# Obtenir les stop wordsstop_words <- tidytext::get_stopwords(language ="en")# Retirer les stop words avec dplyr anti_jointokens_clean <- dplyr::anti_join( tokens, stop_words,by ="word")
r$> head(tokens_clean, 10)
restaurant id word
1 La ligne rouge 1 super
2 La ligne rouge 1 good
3 La ligne rouge 1 kebab
4 La ligne rouge 1 portions
5 La ligne rouge 1 generous
6 La ligne rouge 1 prices
7 La ligne rouge 1 really
8 La ligne rouge 1 reasonable
9 La ligne rouge 1 quality
10 La ligne rouge 1 tasty
Étape 5: Analyse de sentiment (AFINN)
# Obtenir le lexique AFINNafinn <- tidytext::get_sentiments("afinn")# Joindre avec nos tokenssentiment_scores <- dplyr::inner_join( tokens_clean, afinn,by ="word")
r$> head(sentiment_scores, 10)
restaurant id word value
1 La ligne rouge 1 super 3
2 La ligne rouge 1 good 3
3 La ligne rouge 1 generous 2
4 La ligne rouge 1 fresh 1
5 La ligne rouge 1 excellent 3
6 La ligne rouge 1 good 3
7 La ligne rouge 1 recommend 2
8 La ligne rouge 2 good 3
9 La ligne rouge 2 disappointed -2
10 La ligne rouge 2 unacceptable -2
Étape 6: Score total
# Calculate summary statistics per reviewsentiment_summary <- sentiment_scores %>%group_by(id, restaurant) %>%summarise(total_sentiment =sum(value), # Sum of all sentiment scoresmean_sentiment =mean(value), # Average sentimentword_count =n(), # Number of sentiment wordsmin_sentiment =min(value), # Most negative wordmax_sentiment =max(value) # Most positive word ) %>%ungroup()
Voici les résultats
r$> print(sentiment_summary)
# A tibble: 3 × 7
id restaurant total_sentiment mean_sentiment word_count min_sentiment max_sentiment
<int> <chr> <dbl> <dbl> <int> <dbl> <dbl>
1 1 La ligne rouge 17 2.43 7 1 3
2 2 La ligne rouge 2 0.5 4 -2 3
3 3 La ligne rouge 1 0.5 2 -2 3
Réassembler les données
# First, let's create a dataframe with just id and textoriginal_texts <- reviews %>%select(id, text)# Then merge it with your sentiment summarysentiment_summary_with_text <- sentiment_summary %>%left_join(original_texts, by ="id")
En réunissant le text original avec les analyses:
r$> sentiment_summary_with_text
# A tibble: 3 × 8
id restaurant total_sentiment mean_sentiment word_count min_sentiment max_sentiment text
<int> <chr> <dbl> <dbl> <int> <dbl> <dbl> <chr>
1 1 La ligne rouge 17 2.43 7 1 3 Super good kebab! [...]
2 2 La ligne rouge 2 0.5 4 -2 3 Nothing exceptional [...]
3 3 La ligne rouge 1 0.5 2 -2 3 Food is good and price is ok. [...]
Lexicoder (LSD)
Spécialisé pour textes politiques
Gestion avancée des négations
Validation académique extensive
Plus précis pour l’analyse politique
Pourquoi LSD pour la science politique?
Contexte politique
Vocabulaire politique spécifique
Expressions complexes
Nuances importantes
Gestion des négations
“not bad” ≠ “bad”
“no support” vs “support”
Validation scientifique
Testé sur des textes politiques
Utilisé dans des publications majeures
Benchmark en science politique
Étape 1 : Préparation des données pour LSD
library(quanteda)library(pbapply)library(dplyr)library(tidytext)library(stringr)source("R/lsd_prep_functions.R")# Charger les exemples de reviews de restaurantsreviews <-data.frame(restaurant ="La ligne rouge",text =c("Super good kebab! The portions are generous, the prices are really reasonable, and the quality is there. Tasty meat, fresh bread, and everything is well seasoned. An excellent address for a meal that is good without breaking the bank. I recommend!","Nothing exceptional, just edible. I had good feedback about the food and I was very, very disappointed. Not to mention cash only which for me is unacceptable. Too many good restaurants in the neighborhood, I won't go back there","Food is good and price is ok. The only issu is the attitude of the staff. The lady at he cash register and the guy that takes the orders seriously lack client service skills. Both are very rude. Hygiene is another issue, there are flies all over the place. In addition to all this, they only take cash." ),stringsAsFactors =FALSE) %>% dplyr::mutate(id =1:nrow(.))
Étape 2 : Division en phrases
# Diviser en phrases et créer ID uniquereviews_sentences <- reviews %>% tidytext::unnest_sentences(text, text) %>%mutate(id_sentence =row_number())
Prétraitement LSD : Fonctionnalités
Ordre des fonctions
L’ordre d’application est important
Chaque fonction a un rôle spécifique
Fonctions spéciales
LSDprep_contr : Gère les contractions (don’t → do not)
LSDprep_negation : Traite les négations
mark_proper_nouns : Identifie les noms propres
Avantages
Prétraitement standardisé
Meilleure gestion des négations
Plus précis pour l’analyse de sentiment
Étape 3 : Application des fonctions de préparation
Étape 3 : Application des fonctions de préparation
Avant
nothing exceptional, just edible.
Après
(nothing) not exceptional , just edible .
Étape 4 : Création du corpus et tokenisation
# Convertir en dataframe (pour s'assurer du type)reviews_sentences <-as.data.frame(reviews_sentences)# Création du corpus et tokenisationcorpus_text <- quanteda::tokens(reviews_sentences$text_prepped)
# Application du dictionnaire LSD pour l'analyse de sentimentmatrice_sentiment <- quanteda::dfm( quanteda::tokens_lookup(corpus_text, data_dictionary_LSD2015, nested_scope ="dictionary"))
# Conversion de la matrice de fréquence des termes en dataframeresultats_sentiment <- quanteda::convert(matrice_sentiment, to ="data.frame")# Combinaison des résultats de sentiment avec les données originalesarticles_sentiment <-cbind(reviews_sentences, resultats_sentiment)# Calcul des métriques de sentimentarticles_sentiment <- articles_sentiment %>%mutate(total_words =str_count(text_prepped, "\\S+"),# Calcul des proportions de termes positifs et négatifsproportion_positive = (positive + neg_negative) / total_words,proportion_negative = (negative + neg_positive) / total_words,tone_index = proportion_positive - proportion_negative )
Étape 6 : Calcul des scores de sentiment
doc_id
positive
negative
neg_pos
neg_neg
tone_index
text1
2
0
0
0
0.500
text2
3
0
0
0
0.176
text3
2
0
0
0
0.167
text4
2
0
0
1
0.200
text5
0
0
0
0
0.000
text6
0
0
1
0
-0.143
Étape 7 : Agrégation et interprétation des résultats
# Agrégation des résultats par review (id)sentiment_par_review <- articles_sentiment %>%group_by(id) %>%summarise(text_original =first(reviews$text[id]), # Texte original complettotal_positive =sum(positive), # Somme des termes positifstotal_negative =sum(negative), # Somme des termes négatifstotal_neg_positive =sum(neg_positive), # Somme des négations de termes positifstotal_neg_negative =sum(neg_negative), # Somme des négations de termes négatifstotal_words =sum(total_words), # Nombre total de mots# Calcul des proportions globalesproportion_positive =sum(positive) /sum(total_words),proportion_negative =sum(negative) /sum(total_words),# Indice de ton global normalisétone_index = (sum(positive) -sum(negative)) / (sum(positive) +sum(negative)),.groups ="drop" ) %>%select(id, text_original, tone_index)
Comparaison des scores de sentiment
Scores par review
Review
Positif
Négatif
Score final
Review #1
9
0
+1.0
Review #2
2
3
-0.2
Review #3
2
2
0.0
Conseils pratiques
Validation
Vérifier manuellement les résultats
Comparer les dictionnaires
Documenter les choix méthodologiques
Limites
Aucun dictionnaire n’est parfait
Contexte est toujours important
Valider avec analyses qualitatives
Quel lexique choisir?
Lexique
Type de score
Forces
Utilisation idéale
Discipline
AFINN
-5 à +5
Nuancé, simple
Médias sociaux, avis
Marketing
BING
Pos/Neg
Simple, précis
Analyses générales
Sciences sociales
NRC
8 émotions
Riche en contexte
Analyse émotionnelle
Psychologie
Lexicoder
Binaire + thèmes
Validé académiquement
Discours politiques
Science politique
VADER
-1 à +1
Gère emojis/web
Médias sociaux
Communications
LDA : Allocation de Dirichlet Latente
Qu’est-ce que c’est?
Une méthode pour découvrir automatiquement des thèmes dans une collection de textes
Développée en 2003 (Blei, Ng, Jordan)
Apprentissage non supervisé : l’algorithme découvre seul les structures
L’intuition derrière LDA
L’idée centrale
Chaque document contient plusieurs sujets
Chaque sujet est une collection de mots liés
Les mots apparaissent avec différentes probabilités dans chaque sujet
Comment fonctionne LDA?
Approche non supervisée
Aucune connaissance préalable
Pas de liste de mots prédéfinie
Pas d’étiquettes nécessaires
Découverte automatique
Les relations entre mots émergent des données
Les thèmes se forment naturellement
Le processus en pratique
Étapes simplifiées
Transformer les textes en chiffres
Commencer par des assignations aléatoires
Répéter des milliers de fois:
Pour chaque mot: quel sujet est le plus probable?
Mettre à jour les distributions
Aboutir à des groupes cohérents
Avantages pour les sciences sociales
Pourquoi utiliser LDA en sciences sociales?
Analyse de grandes quantités de texte (discours politiques, médias sociaux)
Découverte objective de thèmes sans biais d’interprétation préalable
Suivi de l’évolution des discours dans le temps
Combinaison possible avec l’analyse de sentiment
Limites à connaître
Défis du LDA
Nombre de sujets à définir à l’avance
Les sujets nécessitent une interprétation humaine
Besoin d’un volume suffisant de données
Modélisation de sujets (Topic Modeling)
# Chargement des bibliothèqueslibrary(tidytext)library(tidyr)library(dplyr)library(ggplot2)library(topicmodels)# Charger les exemples de reviews de restaurantsreviews <-data.frame(restaurant ="La ligne rouge",text =c("Super good kebab! The portions are generous, the prices are really reasonable, and the quality is there. Tasty meat, fresh bread, and everything is well seasoned. An excellent address for a meal that is good without breaking the bank. I recommend!","Nothing exceptional, just edible. I had good feedback about the food and I was very, very disappointed. Not to mention cash only which for me is unacceptable. Too many good restaurants in the neighborhood, I won't go back there","Food is good and price is ok. The only issu is the attitude of the staff. The lady at he cash register and the guy that takes the orders seriously lack client service skills. Both are very rude. Hygiene is another issue, there are flies all over the place. In addition to all this, they only take cash." ),stringsAsFactors =FALSE) %>% dplyr::mutate(id =1:nrow(.))
Préparation des données pour le topic modeling
# Tokenisation et nettoyagereviews_tokens <- reviews %>%mutate(doc_id =paste0("doc_", id)) %>% tidytext::unnest_tokens(word, text) %>%anti_join(stop_words) # Supprimer les mots vides# Voir les premiers tokenshead(reviews_tokens, 10)
Création de la matrice document-terme
# Créer la matrice document-terme (DTM)reviews_dtm <- reviews_tokens %>%count(doc_id, word) %>%cast_dtm(doc_id, word, n)# Afficher un aperçu de la matricereviews_dtm
# Exécuter le modèle LDAset.seed(123) # Pour la reproductibilitélda_model <-LDA(reviews_dtm, k =2, control =list(seed =123, verbose =TRUE))# Examiner la distribution des sujets pour chaque documenttopics <-tidy(lda_model, matrix ="gamma")topics_wide <- topics %>%pivot_wider(names_from = topic, values_from = gamma)print(topics_wide)
Extraction des termes principaux pour chaque sujet
# Extraire les termes principaux de chaque sujettop_terms <-tidy(lda_model, matrix ="beta") %>%group_by(topic) %>%slice_max(beta, n =10) %>%arrange(topic, -beta)# Afficher les termes principaux pour chaque sujetprint(top_terms)
# Afficher la distribution des sujets pour chaque reviewdocument_topics <-augment(lda_model, data = reviews_dtm)# Mettre en forme pour faciliter la lecturereview_classifications <- document_topics %>%select(document, .topic) %>%distinct() %>%mutate(review_id =as.integer(gsub("doc_", "", document)),review_text = reviews$text[review_id],primary_topic =ifelse(.topic ==1, "Nourriture", "Service/Expérience") ) %>%select(document, review_text, primary_topic) %>%arrange(document)# Afficher la classification des reviewshead(review_classifications, 3)
Classification des reviews par sujet
Sujet : Nourriture
Review 1
“Super good kebab! The portions are generous, the prices are really reasonable…” Mots-clés: kebab, meat, bread, portions
Sujet : Service/Expérience
Review 2
“Nothing exceptional, just edible. I had good feedback about the food and I was very, very disappointed…” Mots-clés: disappointed, cash only, exceptional
Review 3
“Food is good and price is ok. The only issu is the attitude of the staff…” Mots-clés: staff, attitude, hygiene
Comment utiliser les regex en R?
L’intelligence artificielle 😉