-
Notifications
You must be signed in to change notification settings - Fork 0
/
QO_Pee_K1.qmd
462 lines (304 loc) · 17.5 KB
/
QO_Pee_K1.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
---
title: "Pas à pas pour analyser une question ouverte avec R.temis"
---
## Appel du package R.temis
```{r packages,warning=FALSE,results='hide',message=FALSE}
library(R.temis)
```
## Données
Le *corpus* utilisé dans cet exemple contient un extrait des réponses à une question ouverte issue de l'enquête Populations, Espaces de Vie, Environnements (Ined, 1992). L'intitulé de la question est celui-ci :*Si je vous dis Environnement, qu'est ce que cela évoque pour vous ?*.
Pour chaque enquêté, on dispose de caractéristiques socio-démographiques (<https://data.ined.fr/index.php/catalog/41>).
Les données sont stockées dans un tableau, dit *individus x variables*, contenant 2017 lignes (le nombre de répondants) et 16 colonnes. Ces colonnes correspondent aux variables suivantes : identifiant du questionnaire, caractéristiques des répondants (ou *métadonnées*) et en 16ème colonne la variable correspondant à la question ouverte ( *variable textuelle*). Les textes des réponses constituent le *corpus*.
```{r import}
corpus <- import_corpus("data/PEE_K1_extract.csv", format="csv",textcolumn=16,language="fr")
```
# Création du tableau lexical
La fonction `build_dtm` crée le tableau lexical (*Document Term Matrix* dans R).
```{r tle}
dtm <-build_dtm(corpus, remove_stopwords=F,min_length=2)
dtm
```
Les lignes du *tableau lexical* correspondent ici aux 2017 réponses extraites de l'enquête "Populations, Espaces de Vie, Environnements (PEE), Ined 1992) et les colonnes aux 1157 mots distincts du corpus.
## Affichage
```{r voir, eval=FALSE}
# des metadonnées
View(meta(corpus))
# des réponses à la QO
View(sapply(corpus,as.character))
# d'un extrait du Tableau lexical
inspect(dtm)
#as.matrix(dtm[1:10, c("de", "abus")])
```
# Occurrences
## Explorer le lexique
La fonction `dictionary` crée le dictionnaire associé au lexique. On peut explorer via RStudio : affichage des mots par ordre alphabétique ou par fréquence.
On affiche la liste des mots les plus fréquents avec la fonction `frequent_terms`.
```{r freqT}
dic<-dictionary(dtm,remove_stopwords = F)
frequent_terms(dtm, n=20)
```
Le mot *nature* est évoqué 888 fois dans cet extrait des réponses à la question ouverte (PEE) et représente plus de 7% des occurrences.
# Afficher le nuage de mots !
Ici affiche les mots de plus de 20 occurrences et on choisit de ne pas afficher les mots-outils (fonction `word_cloud`),
```{r nuage}
cloud<-word_cloud(dtm, color="black", n=100, min.freq=20,remove_stopwords=T)
```
Les *occurrences* de quelques mots doivent être précisées dans la légende de ce graphique pour aider sa compréhension.
# Concordances
Les *concordances* permettent, pour chaque mot, de rassembler l'ensemble des textes (ici les réponses à la question ouverte) dans lesquels il apparaît (fonction `concordances`).
Ici par exemple on cherche comprendre pourquoi le mot *enquêté* apparait dans les réponses.
```{r concordances}
concordances(corpus,dtm,"enquêté")
#concordances(corpus,dtm,"alentour")
#concordances(corpus,dtm,"sur")
#concordances(corpus,dtm,"que")
```
On doit *afficher les concordances à chaque étape* d'interprétation des résultats.
# Cooccurrences
## Terme le plus associé (positivement ou négativement) à un terme
On utilise la fonction `cooc_terms` qui affiche les termes coocurrents à un mots choisi (ici logement) dans l'ensemble du corpus.
```{r ccoc_t}
cooc_terms(dtm,"logement", n=10)
```
Parmi les réponses contenant *logement*, *mon* représente 3,5% de l'ensemble des occurrences. Plus de 10% des occurrences de *mon* sont présentes quand *logement* est aussi donné dans les réponses.
On verra par la suite qu'on peut produire un *graphe de mots*.
## Analyse factorielle sur un tableau lexical entier
L'analyse factorielle sur un tableau lexical met en évidence les mots les plus *cooccurrents*. La lecture des mots les plus contributifs aux axes et les concordances permettent d'identifier des champs lexicaux. Par la suite, elle peut aider à de justifier (ou non) la lemmatisation de certains mots (partie suivante).
```{r AFC_TLE}
resTLE <-corpus_ca(corpus,dtm, sparsity=0.985)
#explor(resTLE)
res <- explor::prepare_results(resTLE)
explor::CA_var_plot(res, xax = 1, yax = 2, lev_sup = TRUE, var_sup = FALSE,
var_sup_choice = , var_hide = "Row", var_lab_min_contrib = 0, col_var = "Position",
symbol_var = "Type", size_var = "Contrib", size_range = c(23.4375, 312.5),
labels_size = 10, point_size = 25, transitions = TRUE, labels_positions = NULL,
xlim = c(-3, 2.91), ylim = c(-2.42, 3.49))
```
Les aides à l'interprétation *classiques* (valeurs propres, contributions, coordonnées, ...) sont stockées dans *resTLE* que l'on explore avec grâce à la fonction `explor`.
# Mettre les QO en regard avec les métadonnées
On dispose des variables suivantes : sexe, age en classes (aget), situation matrimoniale(matrim), pratique religieuse (pratique), avoir des enfants oui/non(enf), activite, profession regroupée (prof), diplôme (dipl), *vote écolo* oui/non (vote), type d'habitat, type de localité (local), revenu, region.
## Répartitions
Vérifier la répartition des caractéristiques des enquêté pour chaque métadonnée.
```{r repart, eval=FALSE}
library (questionr)
table(meta(corpus)$sexe)
table(meta(corpus)$aget)
table(meta(corpus)$matrim)
table(meta(corpus)$pratique)
table(meta(corpus)$enf)
table(meta(corpus)$activite)
table(meta(corpus)$prof)
table(meta(corpus)$dipl)
table(meta(corpus)$vote)
table(meta(corpus)$habitat)
table(meta(corpus)$localite)
table(meta(corpus)$revenu)
table(meta(corpus)$region)
```
## Nombre de mots par sous-corpus
Ici on compte les mots de chaque sous-corpus (ou catégorie) crée à partir de la variable *Region* de l'enquête.
```{r BilanLex}
lexical_summary(dtm, corpus,"region", unit = "global")
```
Le corpus contient 12 222 mots dont 1 157 mots distincts, alors que le corpus des enquêtés de la région Seine contient 2 242 mots dont 472 mots distints. On pourrait comparer ici le % de mots distincts (indicateur de richesse du vocabulaire) 9,5% vs 21% en région Seine.
## Specificités
### Mots spécifiques par modalités
```{r Mspec}
# selon le région
specific_terms(dtm,meta(corpus)$aget, n=10)
```
*% Term/Level* indique le % du mot par rapport au total des mots de la catégorie (ici *pollution* représente 2,31 % de tous les mots cités par les personnes de 20 à 29 ans).
*% Level/Term* indique le % du mot pour cette catégorie par rapport à l'ensemble (ici *pollution* représente 21,92 % (32/146)).
*Global* indique le % du mot par rapport à l'ensemble des mots du corpus.
La *t value* permet d'évaluer la significativité du test.
Les mots sont ordonnés selon leur significativité et une valeur négative de la significativité indique un sous-emploi du mot dans la catégorie (par exemple *espaces* pour les 40-49ans).
Les mots *pollution* et *nature* sont plus employés par les répondants les plus jeunes (moins de 30ans)...
### Réponses spécifiques par modalités
La fonction `characteristic_docs` affiche les documents représentatifs d'une catégorie donnée de répondants ici.
Remarque : Le calcul de la distance est fondé sur la métrique du Khi2 et mesure l'écart entre le profil d'une réponse (ici) et le profil moyen de la réponse de la catégorie. Ce critère a tendance à favoriser les textes longs car plus ils contiennent de "mots", plus ils ont de chance de contenir de "mots" communs, et donc de se rapprocher du profil moyen.
```{r Rspec}
#Réponses spécifiques selon la région
characteristic_docs(corpus,dtm,meta(corpus)$region, ndocs=5)
```
# Analyse factorielle sur tableau de contingence
La fonction `corpus_ca` permet d'effectuer une AFC qui met en relation les "mots" et un ensemble de *variables qualitatives* (les métadonnées).
On choisit les variables (ici : sexe, aget, matrim, pratique, enf, activite, prof, dipl, vote, habitat, localite, revenu) pour créer le tableau de contingence croisant les *mots* du corpus et les modalités des métadonnées sur lequel on calcule l'AFC.
## Choix des metadonnées
```{r AFC_TLA}
resTLA<-corpus_ca(corpus,dtm, variables =c("sexe","aget", "matrim","pratique","enf","activite","prof","dipl","vote", "habitat" ,"localite","revenu"),sparsity=0.98)
#explor(resTLA)
res <- explor::prepare_results(resTLA)
explor::CA_var_plot(res, xax = 1, yax = 2, lev_sup = FALSE, var_sup = FALSE,
var_sup_choice = , var_hide = "None", var_lab_min_contrib = 2, col_var = "Position",
symbol_var = "Type", size_var = "Contrib", size_range = c(26.25, 350), labels_size = 10,
point_size = 28, transitions = TRUE, labels_positions = NULL, xlim = c(-0.393,
0.447), ylim = c(-0.413, 0.426))
```
Ici encore on regarde les aides à l'interprétation *classiques* (valeurs propres, contributions, coordonnées, ...) stockées dans *resTLA* avec la fonction `explor`
On affiche les réponses les plus contributives aux axes 1 à 3 (fonction `extreme_docs`).
```{r extr_doc}
#Documents les plus illustratifs par axe
extreme_docs(corpus,resTLA,axis=1,ndocs=2)
extreme_docs(corpus,resTLA,axis=2,ndocs=2)
extreme_docs(corpus,resTLA,axis=3,ndocs=2)
```
# Lemmatiser
La lemmatisation (Lebart, Salem) consite à regrouper les mots qui ont une signification proche. Cette opération permet de réduire la liste de mots distincts et donc la taille du lexique.
Nous allons voir dans cette partie que cette opération peut se faire de différentes façons et nécessite de faire des choix ...
## De façon personnalisée
On voit ici comment créer son *lemmatiseur*.
### Suppression de mots
On crée une liste de mots que l'on a choisi de supprimer du tableau lexical et du dictionnaire.
Ici, comme à tout moment de l'analyse, il est important d'afficher les *concordances* d'un mot pour ensuite faire un choix (le supprimer, le regrouper avec un autre, ...).
Pour l'exemple, on supprime les mots *sur*, *que* et *qu*.
```{r nettoyage}
#concordances(corpus,dtm,"sur")
# liste des mots à supprimer
asupp <- c("sur","que","qu")
# on enlève les mots de la liste dans le tableau lexical
dtm2 <-dtm[, !colnames(dtm) %in% asupp]
```
On crée le nouveau *dictionnaire* associé au lexique avec la fonction `dictionnary`sans les mots-outils et sans les mots que l'on a choisit de ne pas garder pour l'analyse.
```{r dico}
dic<-dictionary(dtm,remove_stopwords = T)
# on enlève les mots de la liste dans le dictionnaire
dic2 <- dic[!rownames(dic) %in% asupp,]
```
Pour créér son propre lemmatiseur, on exporte ce dictionnaire (ici dic2.csv) afin de le modifier si besoin, mots après mot (par exemple avec un tableur).
```{r Export_dic, eval=FALSE}
write.csv2(dic2, file="dic2_lem.csv")
```
### Correction de la lemmatisation de R
On pourrait utiliser la colonne Terms du dictionnaire pour lemmatiser mais celle-ci ne nous satisfait pas. On décide alors de remplacer les racines de certains mots issues de la lemmatisation automatique et on enregiste ce nouveau dictionnaire qui sera notre *lemmatiseur personnalisé*.
Ici encore, on s'aide des concordances. Par exemple : `concordances(corpus,dtm,c("écologie","écolo","écologique","écologiques","écologiste","écologistes","écolos"))`
Pour cette analyse, on utilise un lemmatiseur déjà produit lors d'une analyse précédente (avec Spad). On importe ce lemmatiseur dans R et on utilise la fonction `combine_terms` pour lemmatiser.
```{r import_lem1}
dic_lem1 <- read.csv2("data/Pee_dic_lem_extract.csv",row.names=1)
#setdiff(rownames(dic2), rownames(dic_lem1))
dtmlem1 <-combine_terms(dtm2, dic_lem1)
```
## Lexique après lemmatisation
```{r freq_lem1}
frequent_terms(dtmlem1, n=30)
```
## Utilisation de lexique3
### Pour selectionner les mots à analyser
Ici on reprend le tableau lexical et le dictionnaire initiaux (dtm et dic) càd sans suppression de mots au choix ni de lemmatisation personnalisée.
On utilise le lemmatiseur crée à partir de lexique 3 (https://chrplr.github.io/openlexicon/datasets-info/Lexique382/README-Lexique.html) qui nous permet dans un premier temps de repérer la catégorie grammaticale de chaque mot du corpus. On peut ensuite ne garder que les mots des catégories qui nous intéressent pour l'analyse (ici les adverbes *ADV*, les verbes *VER*, les adjectifs *ADJ*, les noms *NOM*, et les pronoms personnels et possessifs *PRO:per*,*PRO:pos*). On gardera pour l'analyse également les mots qui n'ont pas été identifiés grâce à Lexique3 ( mis dans la liste *nr*).
```{r gram}
library(dplyr)
lexique3 <- read.csv("data/Lexique383_simplifie.csv", fileEncoding="UTF-8")
lexique3 <- arrange(lexique3, desc(freqlivres))
lexique3 <- lexique3[!duplicated(lexique3$ortho),]
voc_actif <- lexique3[lexique3$cgram %in% c("ADV", "VER", "ADJ", "NOM", "PRO:per","PRO:pos"),]
dic_total <- merge(dic, voc_actif, by.x="row.names", by.y="ortho", all.x=TRUE)
dic_total <- mutate(dic_total, Term=coalesce(lemme, Term))
rownames(dic_total) <- dic_total$Row.names
# Lister les mots non reconnus par Lexique 3
nr <- filter(dic_total, is.na(lemme))
```
## Lemmatisation automatique avec lexique3
Cette opération permet à la fois de ne retenir que certaines catégories de mots mais aussi à les remplacer par leur forme racine.
```{r lem_lex3}
dtmlem2 <- combine_terms(dtm, dic_total)
```
## Lexique après lemmatisation avec Lexique3
```{r freq_lex3}
frequent_terms(dtmlem2, n=30)
```
## Graphe de mots
```{r tree,, eval=FALSE}
arbre <- terms_graph(dtmlem2, min_occ = 30, interactive=F)
```
![](images/graphe_mots.png)
# Nouvelle analyse factorielle avec métadonnées
Suite à la lemmatisation avec lexique3
On choisit les mêmes varaibles que plus haut
```{r AFC_TLAlem2}
resTLAlem2<-corpus_ca(corpus,dtmlem2, variables =c("sexe","aget", "matrim","pratique","enf","activite","prof","dipl","vote", "habitat" ,"localite","revenu"),sparsity=0.98)
#explor(resTLAlem2)
res <- explor::prepare_results(resTLAlem2)
explor::CA_var_plot(res, xax = 1, yax = 2, lev_sup = FALSE, var_sup = FALSE,
var_sup_choice = , var_hide = "None", var_lab_min_contrib = 0, col_var = "Position",
symbol_var = "Type", size_var = "Contrib", size_range = c(22.5, 300), labels_size = 10,
point_size = 24, transitions = TRUE, labels_positions = NULL, xlim = c(-0.544,
0.517), ylim = c(-0.482, 0.579))
```
# Et pour aller plus loin
## Correction de la lemmatisation de Lexique3
La lemmatisation automatique produite avec Lexique3 affiche les racines des mots pour certains (ex. bretagn ou tranquil). On remanie ce lemmatiseur (`dic_total`) en créant un autre dictionnaire (`dicor`) avec une colonne contenant les mots qui n'ont pas été lemmatisés automatiquement. Puis on lemmatisera le tableau lexical avec les "mots" (Term) de cette nouvelle colonne.
```{r corr_lemm2}
dic_total_c <- dic_total
dic_total_c$Term[dic_total_c$Term == "bretagn"] <- "bretagne"
dic_total_c$Term[dic_total_c$Term == "tranquil"] <- "tranquilité"
dtmlem22 <- combine_terms(dtm, dic_total_c)
```
## Analyser des sous-corpus
On créer des sous corpus à l'aide des métadonnées ou de mots au choix.
```{r sscorpus, eval=FALSE, include=FALSE}
#Ici par exemple par les réponses des enquêtes dans la région Bretagne
corpus_bret <- corpus[meta(corpus,"region") == "Bretagne"]
# Ici les questions ouvertes contenant le mot entourage
corpus_entourage <- subset_corpus(corpus,dtm,"entourage")
```
## Classifications du tableau lexical
Va regrouper les réponses contenant des mots cooccurrents et permet ainsi de repérer des champs lexicaux.
### Avec R.temis
```{r clus}
clusTLE <- corpus_ca(corpus, dtmlem2,variables = NULL, ncp = 6, sparsity = 0.98)
#Afficher le dendrogramme
#plot(clusTLE$call$tree)
#clusTLE$desc.var
```
Ici on selectionne 7 classes
```{r cah_cut}
clus <- corpus_clustering(clusTLE,7)
```
## Description des classes
Pour chaque numéro de classe déterminé dans l'étape précédente, R affiche les mots et textes spécifiques qui vont permettre de déterminer les champs lexicaux de chacune.
On ajoute la variable de classe (clus) aux métadonnées initiales.
```{r add_clus}
corpus_cl <- add_clusters(corpus,clus)
#View(meta(corpus_cl))
#clusTLE$desc.var
```
### Mots spécifiques
On se sert de cette variable issue de la classification pour calculer les spécificités
```{r cah_desc }
specific_terms(dtmlem2,meta(corpus_cl)$clus, n=5)
```
### Réponses specifiques
specific_terms(dtmlem2,meta(corpus_cl)\$clus, n=5)
```{r doc_char2}
characteristic_docs(corpus_cl,dtmlem2,meta(corpus_cl)$clu, ndocs=3)
```
### Avec Rainette (type Alceste)
On pourrait également faire une classification avec le package *Rainette*. En savoir plus sur <https://juba.github.io/rainette/articles/introduction_usage.html>
Son utilisation nesessite aussi d'appeler le package `quanteda`.
Attention risque de conflit entre les packages tm (chargé par R.temis) et quanteda : ne pas lancer `library(quanteda)`.
Ici on utilise le corpus non lemmatisé.
```{r reinert, eval=FALSE,message=FALSE}
library(rainette)
corpus
# On adapte le TLE pour quanteda
dfm <- quanteda::as.dfm(dtm)
quanteda::docvars(dfm, "doc_id") <- 1:nrow(dfm)
# On exécute rainette
resrai <- rainette(dfm, min_uc_size = 3, k = 10, doc_id = "doc_id", cc_test = 0.3,
tsj = 3)
```
Exploration interactive
```{r rai_explo, eval=FALSE,message=FALSE}
rainette_explor(resrai,dfm)
groups <-cutree_rainette(resrai, k = 10)
```
![](images/dendro_rainette.png)
Description des classes
```{r rai_stat, eval=FALSE,message=FALSE}
rainette_stats(groups, dfm, n_terms = 5)
```
On récupère la variable issue de la classification Rainette pour des traitements ultérieurs
```{r rain_v_cl , eval=FALSE,message=FALSE}
meta(corpus, "classe") <- cutree_rainette(resrai, k = 10)
#View(meta(corpus))
```