Spaces:
Sleeping
Sleeping
Upload 41 files
Browse files- .gitattributes +1 -0
- Dockerfile +43 -0
- app.R +639 -0
- cles_analyse_iramuteq.png +3 -0
- dictionnaires/lexique_fr.csv +0 -0
- gitattributes +43 -0
- gitignore +4 -0
- help/chd.md +101 -0
- help/chd_iramuteq.md +144 -0
- help/help.md +115 -0
- help/helpafc.md +61 -0
- help/helpchi2.md +28 -0
- help/ner.md +85 -0
- help/nettoyage.md +48 -0
- help/pos_spacy.md +82 -0
- help/rapport_audit_iramuteq_like.md +156 -0
- help/rapport_dendrogramme_iramuteq.md +22 -0
- help/rapport_mapping_chd_iramuteq_like.md +112 -0
- help/rapport_tokenisation_iramuteq_clone_v3.md +148 -0
- iramuteq-like/CHD.R +407 -0
- iramuteq-like/afc_helpers_iramuteq.R +39 -0
- iramuteq-like/afc_iramuteq.R +672 -0
- iramuteq-like/affichage_iramuteq-like.R +42 -0
- iramuteq-like/anacor.R +114 -0
- iramuteq-like/chd_afc_pipeline_iramuteq.R +316 -0
- iramuteq-like/chd_engine_iramuteq.R +117 -0
- iramuteq-like/chd_iramuteq.R +892 -0
- iramuteq-like/chdtxt.R +724 -0
- iramuteq-like/concordancier-iramuteq.R +182 -0
- iramuteq-like/cooccurrences_iramuteq.R +76 -0
- iramuteq-like/dendogramme_iramuteq.R +45 -0
- iramuteq-like/nettoyage_iramuteq.R +37 -0
- iramuteq-like/nlp_lexique_iramuteq.R +200 -0
- iramuteq-like/pipeline_iramuteq_analysis_iramuteq.R +33 -0
- iramuteq-like/pipeline_lexique_analysis_iramuteq.R +91 -0
- iramuteq-like/server_events_lancer_iramuteq.R +1133 -0
- iramuteq-like/stats_chd.R +136 -0
- iramuteq-like/textprepa_iramuteq.py +137 -0
- iramuteq-like/ui_options_iramuteq.R +36 -0
- iramuteq-like/wordcloud_iramuteq.R +69 -0
- penguins.csv +345 -0
- ui.R +421 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
cles_analyse_iramuteq.png filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: Dockerfile porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
|
| 4 |
+
FROM rocker/r2u:22.04
|
| 5 |
+
|
| 6 |
+
ENV LANG=C.UTF-8
|
| 7 |
+
ENV LC_ALL=C.UTF-8
|
| 8 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 9 |
+
|
| 10 |
+
# Python pour spaCy
|
| 11 |
+
RUN apt-get update && \
|
| 12 |
+
apt-get install -y --no-install-recommends \
|
| 13 |
+
ca-certificates \
|
| 14 |
+
python3 \
|
| 15 |
+
python3-pip \
|
| 16 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 17 |
+
|
| 18 |
+
# Paquets R (r2u installe en binaires via apt/bspm, donc très rapide)
|
| 19 |
+
RUN install.r shiny quanteda wordcloud RColorBrewer igraph dplyr htmltools remotes irlba
|
| 20 |
+
|
| 21 |
+
# FactoMineR depuis GitHub (sans tirer les Suggests)
|
| 22 |
+
RUN R -q -e "options(repos=c(CRAN='https://cloud.r-project.org')); remotes::install_github('husson/FactoMineR', dependencies=NA, upgrade='never')"
|
| 23 |
+
|
| 24 |
+
# Utilisateur non-root compatible Hugging Face
|
| 25 |
+
RUN set -eux; \
|
| 26 |
+
if ! id -u user >/dev/null 2>&1; then \
|
| 27 |
+
if getent passwd 1000 >/dev/null 2>&1; then \
|
| 28 |
+
useradd -m -u 1001 user; \
|
| 29 |
+
else \
|
| 30 |
+
useradd -m -u 1000 user; \
|
| 31 |
+
fi; \
|
| 32 |
+
fi
|
| 33 |
+
|
| 34 |
+
ENV HOME=/home/user
|
| 35 |
+
WORKDIR /home/user/app
|
| 36 |
+
|
| 37 |
+
COPY . /home/user/app
|
| 38 |
+
|
| 39 |
+
RUN chown -R user:user /home/user/app
|
| 40 |
+
|
| 41 |
+
USER user
|
| 42 |
+
EXPOSE 7860
|
| 43 |
+
CMD ["Rscript", "/home/user/app/rainette/start.R"]
|
app.R
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: app.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
|
| 4 |
+
###############################################################################
|
| 5 |
+
# Script CHD - version beta 0.4 - 18-02-2026 #
|
| 6 |
+
# A partir d'un corpus texte formaté aux exigences IRAMUTEQ #
|
| 7 |
+
# Stéphane Meurisse #
|
| 8 |
+
# wwww.codeandcortex.fr #
|
| 9 |
+
# #
|
| 10 |
+
# 1.Réalise la CHD sur le corpus, sans rainette_explor #
|
| 11 |
+
# 2.Extrait chi2, lr, freq, docprop dans un CSV #
|
| 12 |
+
# 3.AFC #
|
| 13 |
+
# 4.Recherche de NER avec Spacy (md) #
|
| 14 |
+
# 5.Génère nuages de mots et graphes de cooccurrences par classe #
|
| 15 |
+
# 6.Exporte les segments de texte par classe au format text #
|
| 16 |
+
# 7.Creation d'un concordancier au format html #
|
| 17 |
+
# 8.Recherche de coocurrences #
|
| 18 |
+
###############################################################################
|
| 19 |
+
|
| 20 |
+
library(shiny)
|
| 21 |
+
library(quanteda)
|
| 22 |
+
library(wordcloud)
|
| 23 |
+
library(RColorBrewer)
|
| 24 |
+
library(igraph)
|
| 25 |
+
library(dplyr)
|
| 26 |
+
library(htmltools)
|
| 27 |
+
|
| 28 |
+
options(shiny.maxRequestSize = 300 * 1024^2)
|
| 29 |
+
options(shinygadgets.viewer = shiny::browserViewer())
|
| 30 |
+
options(bspm.sudo = TRUE)
|
| 31 |
+
|
| 32 |
+
if (file.exists("help.md")) {
|
| 33 |
+
ui_aide_huggingface <- function() {
|
| 34 |
+
tagList(
|
| 35 |
+
tags$h2("Aide"),
|
| 36 |
+
includeMarkdown("help.md")
|
| 37 |
+
)
|
| 38 |
+
}
|
| 39 |
+
} else {
|
| 40 |
+
ui_aide_huggingface <- function() {
|
| 41 |
+
tagList(
|
| 42 |
+
tags$h2("Aide"),
|
| 43 |
+
tags$p("Le fichier help.md est introuvable. Ajoute help.md à la racine du projet.")
|
| 44 |
+
)
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
source("iramuteq-like/nettoyage_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 50 |
+
source("iramuteq-like/concordancier-iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 51 |
+
source("spacy_ner/concordancier_ner.R", encoding = "UTF-8", local = TRUE)
|
| 52 |
+
source("iramuteq-like/afc_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 53 |
+
source("iramuteq-like/ui_options_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 54 |
+
source("iramuteq-like/affichage_iramuteq-like.R", encoding = "UTF-8", local = TRUE)
|
| 55 |
+
source("iramuteq-like/wordcloud_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 56 |
+
source("ui.R", encoding = "UTF-8", local = TRUE)
|
| 57 |
+
|
| 58 |
+
source("iramuteq-like/chd_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 59 |
+
source("iramuteq-like/dendogramme_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 60 |
+
source("iramuteq-like/stats_chd.R", encoding = "UTF-8", local = TRUE)
|
| 61 |
+
source("iramuteq-like/chd_engine_iramuteq.R", encoding = "UTF-8", local = TRUE)
|
| 62 |
+
|
| 63 |
+
server <- function(input, output, session) {
|
| 64 |
+
|
| 65 |
+
rv <- reactiveValues(
|
| 66 |
+
logs = "",
|
| 67 |
+
statut = "En attente.",
|
| 68 |
+
progression = 0,
|
| 69 |
+
|
| 70 |
+
base_dir = NULL,
|
| 71 |
+
export_dir = NULL,
|
| 72 |
+
segments_file = NULL,
|
| 73 |
+
stats_file = NULL,
|
| 74 |
+
html_file = NULL,
|
| 75 |
+
ner_file = NULL,
|
| 76 |
+
zip_file = NULL,
|
| 77 |
+
|
| 78 |
+
res = NULL,
|
| 79 |
+
res_chd = NULL,
|
| 80 |
+
dfm_chd = NULL,
|
| 81 |
+
dfm = NULL,
|
| 82 |
+
filtered_corpus = NULL,
|
| 83 |
+
res_stats_df = NULL,
|
| 84 |
+
clusters = NULL,
|
| 85 |
+
max_n_groups = NULL,
|
| 86 |
+
max_n_groups_chd = NULL,
|
| 87 |
+
|
| 88 |
+
res_type = "simple",
|
| 89 |
+
|
| 90 |
+
exports_prefix = paste0("exports_", session$token),
|
| 91 |
+
|
| 92 |
+
spacy_tokens_df = NULL,
|
| 93 |
+
lexique_fr_df = NULL,
|
| 94 |
+
textes_indexation = NULL,
|
| 95 |
+
|
| 96 |
+
ner_df = NULL,
|
| 97 |
+
ner_nb_segments = NA_integer_,
|
| 98 |
+
|
| 99 |
+
afc_obj = NULL,
|
| 100 |
+
afc_erreur = NULL,
|
| 101 |
+
|
| 102 |
+
afc_vars_obj = NULL,
|
| 103 |
+
afc_vars_erreur = NULL,
|
| 104 |
+
|
| 105 |
+
afc_dir = NULL,
|
| 106 |
+
afc_table_mots = NULL,
|
| 107 |
+
afc_table_vars = NULL,
|
| 108 |
+
afc_plot_classes = NULL,
|
| 109 |
+
afc_plot_termes = NULL,
|
| 110 |
+
afc_plot_vars = NULL,
|
| 111 |
+
|
| 112 |
+
explor_assets = NULL,
|
| 113 |
+
stats_corpus_df = NULL,
|
| 114 |
+
stats_zipf_df = NULL
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
register_outputs_status(input, output, session, rv)
|
| 118 |
+
|
| 119 |
+
output$ui_afc_statut <- renderUI({
|
| 120 |
+
if (!is.null(rv$afc_erreur) && nzchar(rv$afc_erreur)) {
|
| 121 |
+
return(tags$p("AFC : erreur (voir ci-dessous)."))
|
| 122 |
+
}
|
| 123 |
+
if (is.null(rv$afc_obj) || is.null(rv$afc_obj$ca)) {
|
| 124 |
+
return(tags$p("AFC non calculée. Lance une analyse pour calculer l'AFC classes × termes."))
|
| 125 |
+
}
|
| 126 |
+
ncl <- nrow(rv$afc_obj$table)
|
| 127 |
+
nt <- ncol(rv$afc_obj$table)
|
| 128 |
+
tags$p(paste0("AFC calculée sur ", ncl, " classes et ", nt, " termes (table Classes × Termes)."))
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
output$ui_afc_erreurs <- renderUI({
|
| 132 |
+
messages <- Filter(
|
| 133 |
+
nzchar,
|
| 134 |
+
list(
|
| 135 |
+
rv$afc_erreur,
|
| 136 |
+
rv$afc_vars_erreur
|
| 137 |
+
)
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
if (length(messages) == 0) {
|
| 141 |
+
return(NULL)
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
tags$div(
|
| 145 |
+
style = "display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px;",
|
| 146 |
+
lapply(messages, function(msg) {
|
| 147 |
+
tags$div(
|
| 148 |
+
style = "border: 1px solid #f5c2c7; background: #f8d7da; color: #842029; border-radius: 4px; padding: 10px; white-space: pre-wrap;",
|
| 149 |
+
msg
|
| 150 |
+
)
|
| 151 |
+
})
|
| 152 |
+
)
|
| 153 |
+
})
|
| 154 |
+
|
| 155 |
+
output$ui_spacy_langue_detection <- renderUI({
|
| 156 |
+
if (identical(input$source_dictionnaire, "lexique_fr")) {
|
| 157 |
+
return(NULL)
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
output$ui_ner_lexique_incompatibilite <- renderUI({
|
| 161 |
+
if (!isTRUE(input$activer_ner) || !identical(input$source_dictionnaire, "lexique_fr")) {
|
| 162 |
+
return(NULL)
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
output$ui_corpus_preview <- renderUI({
|
| 166 |
+
fichier <- input$fichier_corpus
|
| 167 |
+
if (is.null(fichier) || is.null(fichier$datapath) || !file.exists(fichier$datapath)) {
|
| 168 |
+
return(tags$p("Aucun corpus importé pour le moment."))
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
lignes <- tryCatch(
|
| 172 |
+
readLines(fichier$datapath, encoding = "UTF-8", warn = FALSE),
|
| 173 |
+
error = function(e) NULL
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
if (is.null(lignes) || length(lignes) == 0) {
|
| 177 |
+
return(tags$p("Le corpus importé est vide ou illisible."))
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
max_lignes <- 250
|
| 181 |
+
extrait <- lignes[seq_len(min(length(lignes), max_lignes))]
|
| 182 |
+
texte <- paste(extrait, collapse = "\n")
|
| 183 |
+
|
| 184 |
+
if (length(lignes) > max_lignes) {
|
| 185 |
+
texte <- paste0(
|
| 186 |
+
texte,
|
| 187 |
+
"\n\n… Aperçu limité aux ", max_lignes,
|
| 188 |
+
" premières lignes (", length(lignes), " lignes au total)."
|
| 189 |
+
)
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
tags$div(
|
| 193 |
+
tags$p(
|
| 194 |
+
style = "margin-bottom: 8px;",
|
| 195 |
+
paste0("Fichier : ", fichier$name)
|
| 196 |
+
),
|
| 197 |
+
tags$pre(
|
| 198 |
+
style = "white-space: pre-wrap; max-height: 70vh; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: #fafafa;",
|
| 199 |
+
texte
|
| 200 |
+
)
|
| 201 |
+
)
|
| 202 |
+
})
|
| 203 |
+
|
| 204 |
+
output$ui_table_stats_corpus <- renderUI({
|
| 205 |
+
req(rv$stats_corpus_df)
|
| 206 |
+
|
| 207 |
+
definitions <- c(
|
| 208 |
+
"Nom du corpus" = "Nom du fichier corpus importé.",
|
| 209 |
+
"Nombre de textes" = "Nombre d'unités de texte détectées dans le corpus.",
|
| 210 |
+
"Nombre de mots dans le corpus" = "Total des occurrences de mots (tokens).",
|
| 211 |
+
"Nombre de formes" = "Nombre de formes lexicales distinctes (types), différent des hapax.",
|
| 212 |
+
"Nombre de segments de texte" = "Nombre de segments après découpage pour l'analyse.",
|
| 213 |
+
"Nombre d'Hapax" = "Nombre de formes apparaissant une seule fois dans le corpus.",
|
| 214 |
+
"Loi de Zpif" = "Indicateur de conformité approximative à la loi de Zipf."
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
lignes <- lapply(seq_len(nrow(rv$stats_corpus_df)), function(i) {
|
| 218 |
+
metrique <- as.character(rv$stats_corpus_df$Metrique[i])
|
| 219 |
+
valeur <- as.character(rv$stats_corpus_df$Valeur[i])
|
| 220 |
+
definition <- unname(definitions[[metrique]])
|
| 221 |
+
if (is.null(definition) || !nzchar(definition)) definition <- ""
|
| 222 |
+
|
| 223 |
+
tags$tr(
|
| 224 |
+
tags$td(
|
| 225 |
+
tags$div(metrique),
|
| 226 |
+
if (nzchar(definition)) tags$div(
|
| 227 |
+
style = "font-size: 0.85em; color: #c62828; margin-top: 2px;",
|
| 228 |
+
definition
|
| 229 |
+
)
|
| 230 |
+
),
|
| 231 |
+
tags$td(valeur)
|
| 232 |
+
)
|
| 233 |
+
})
|
| 234 |
+
|
| 235 |
+
tags$table(
|
| 236 |
+
class = "table table-striped table-condensed",
|
| 237 |
+
tags$thead(
|
| 238 |
+
tags$tr(
|
| 239 |
+
tags$th("Metrique"),
|
| 240 |
+
tags$th("Valeur")
|
| 241 |
+
)
|
| 242 |
+
),
|
| 243 |
+
tags$tbody(lignes)
|
| 244 |
+
)
|
| 245 |
+
})
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
output$plot_stats_zipf <- renderPlot({
|
| 249 |
+
req(rv$stats_zipf_df)
|
| 250 |
+
df <- rv$stats_zipf_df
|
| 251 |
+
if (is.null(df) || nrow(df) < 2) {
|
| 252 |
+
plot.new()
|
| 253 |
+
text(0.5, 0.5, "Données insuffisantes pour tracer la loi de Zpif.", cex = 1.1)
|
| 254 |
+
return(invisible(NULL))
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
x_lim <- range(df$log_rang, na.rm = TRUE)
|
| 258 |
+
y_lim <- range(c(df$log_frequence, df$log_pred), na.rm = TRUE)
|
| 259 |
+
|
| 260 |
+
plot(
|
| 261 |
+
x = df$log_rang,
|
| 262 |
+
y = df$log_frequence,
|
| 263 |
+
pch = 16,
|
| 264 |
+
cex = 0.8,
|
| 265 |
+
col = grDevices::adjustcolor("#2C7FB8", alpha.f = 0.7),
|
| 266 |
+
xlab = "log(rang)",
|
| 267 |
+
ylab = "log(fréquence)",
|
| 268 |
+
main = "Loi de Zpif",
|
| 269 |
+
xlim = x_lim,
|
| 270 |
+
ylim = y_lim,
|
| 271 |
+
asp = 1
|
| 272 |
+
)
|
| 273 |
+
grid(col = "#E6E6E6", lty = "dotted")
|
| 274 |
+
|
| 275 |
+
ord <- order(df$log_rang)
|
| 276 |
+
lines(df$log_rang[ord], df$log_pred[ord], col = "#D7301F", lwd = 2.5)
|
| 277 |
+
|
| 278 |
+
legend(
|
| 279 |
+
"topright",
|
| 280 |
+
legend = c("Données", "Régression log-log"),
|
| 281 |
+
col = c("#2C7FB8", "#D7301F"),
|
| 282 |
+
pch = c(16, NA),
|
| 283 |
+
lty = c(NA, 1),
|
| 284 |
+
lwd = c(NA, 2),
|
| 285 |
+
bty = "n"
|
| 286 |
+
)
|
| 287 |
+
})
|
| 288 |
+
|
| 289 |
+
output$ui_chd_statut <- renderUI({
|
| 290 |
+
if (is.null(rv$res)) {
|
| 291 |
+
return(tags$p("CHD non disponible. Lance une analyse."))
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
nb_classes <- NA_integer_
|
| 295 |
+
if (!is.null(rv$clusters)) nb_classes <- length(rv$clusters)
|
| 296 |
+
|
| 297 |
+
if (identical(rv$res_type, "iramuteq")) {
|
| 298 |
+
return(tags$p(paste0("CHD disponible (moteur IRaMuTeQ-like) - classes détectées : ", nb_classes, ".")))
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
if (identical(rv$res_type, "double")) {
|
| 302 |
+
return(tags$p("CHD disponible (classification double rainette2)."))
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
tags$p(paste0("CHD disponible (classification simple rainette) - classes détectées : ", nb_classes, "."))
|
| 306 |
+
})
|
| 307 |
+
|
| 308 |
+
register_events_lancer(input, output, session, rv)
|
| 309 |
+
register_rainette_explor_affichage(input, output, session, rv)
|
| 310 |
+
|
| 311 |
+
output$plot_afc_classes <- renderPlot({
|
| 312 |
+
if (!is.null(rv$afc_erreur) && nzchar(rv$afc_erreur)) {
|
| 313 |
+
plot.new()
|
| 314 |
+
text(0.5, 0.5, "AFC indisponible (erreur).", cex = 1.1)
|
| 315 |
+
return(invisible(NULL))
|
| 316 |
+
}
|
| 317 |
+
if (is.null(rv$afc_obj) || is.null(rv$afc_obj$ca)) {
|
| 318 |
+
plot.new()
|
| 319 |
+
text(0.5, 0.5, "AFC non disponible. Lance une analyse.", cex = 1.1)
|
| 320 |
+
return(invisible(NULL))
|
| 321 |
+
}
|
| 322 |
+
tracer_afc_classes_seules(rv$afc_obj, axes = c(1, 2), cex_labels = 1.05)
|
| 323 |
+
})
|
| 324 |
+
|
| 325 |
+
panneaux <- lapply(classes, function(cl) {
|
| 326 |
+
output_id <- paste0("table_stats_chd_iramuteq_cl_", cl)
|
| 327 |
+
|
| 328 |
+
output[[output_id]] <- renderTable({
|
| 329 |
+
extraire_stats_chd_classe(
|
| 330 |
+
rv$res_stats_df,
|
| 331 |
+
classe = cl,
|
| 332 |
+
n_max = 100,
|
| 333 |
+
show_negative = FALSE,
|
| 334 |
+
max_p = if (isTRUE(input$filtrer_affichage_pvalue)) input$max_p else 1,
|
| 335 |
+
seuil_p_significativite = input$max_p,
|
| 336 |
+
style = "iramuteq_clone"
|
| 337 |
+
)
|
| 338 |
+
}, rownames = FALSE, sanitize.text.function = function(x) x)
|
| 339 |
+
|
| 340 |
+
tabPanel(
|
| 341 |
+
title = paste0("Classe ", cl),
|
| 342 |
+
tableOutput(output_id)
|
| 343 |
+
)
|
| 344 |
+
})
|
| 345 |
+
|
| 346 |
+
do.call(tabsetPanel, c(id = "tabs_stats_chd_iramuteq", panneaux))
|
| 347 |
+
})
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
if (is.null(rv$exports_prefix) || !nzchar(rv$exports_prefix)) {
|
| 351 |
+
return(tags$div(
|
| 352 |
+
style = "padding: 12px;",
|
| 353 |
+
tags$p("Préfixe de ressources invalide."),
|
| 354 |
+
tags$p("Relance l'analyse pour régénérer les exports.")
|
| 355 |
+
))
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
if (!(rv$exports_prefix %in% names(shiny::resourcePaths()))) {
|
| 359 |
+
shiny::addResourcePath(rv$exports_prefix, rv$export_dir)
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
candidats_html <- c(
|
| 363 |
+
rv$html_file,
|
| 364 |
+
file.path(rv$export_dir, "segments_par_classe.html"),
|
| 365 |
+
file.path(rv$export_dir, "concordancier.html")
|
| 366 |
+
)
|
| 367 |
+
candidats_dyn <- list.files(
|
| 368 |
+
rv$export_dir,
|
| 369 |
+
pattern = "(segments.*classe|concord).*\\.html$",
|
| 370 |
+
ignore.case = TRUE,
|
| 371 |
+
full.names = TRUE
|
| 372 |
+
)
|
| 373 |
+
candidats_html <- c(candidats_html, candidats_dyn)
|
| 374 |
+
candidats_html <- unique(candidats_html[!is.na(candidats_html) & nzchar(candidats_html)])
|
| 375 |
+
html_existant <- candidats_html[file.exists(candidats_html)]
|
| 376 |
+
|
| 377 |
+
if (length(html_existant) == 0) {
|
| 378 |
+
return(tags$div(
|
| 379 |
+
style = "padding: 12px;",
|
| 380 |
+
tags$p("Le fichier du concordancier HTML n'est pas disponible pour cette analyse."),
|
| 381 |
+
tags$p("Relance l'analyse puis vérifie les logs si le problème persiste.")
|
| 382 |
+
))
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
src_html <- html_existant[[1]]
|
| 386 |
+
nom_html <- basename(src_html)
|
| 387 |
+
src_dans_exports <- file.path(rv$export_dir, nom_html)
|
| 388 |
+
|
| 389 |
+
if (!isTRUE(file.exists(src_dans_exports))) {
|
| 390 |
+
ok_copy <- tryCatch(file.copy(src_html, src_dans_exports, overwrite = TRUE), error = function(e) FALSE)
|
| 391 |
+
if (isTRUE(ok_copy)) src_html <- src_dans_exports
|
| 392 |
+
} else {
|
| 393 |
+
src_html <- src_dans_exports
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
tags$iframe(
|
| 397 |
+
src = paste0("/", rv$exports_prefix, "/", basename(src_html)),
|
| 398 |
+
style = "width: 100%; height: 70vh; border: 1px solid #999;"
|
| 399 |
+
)
|
| 400 |
+
})
|
| 401 |
+
|
| 402 |
+
tags$div(
|
| 403 |
+
style = "text-align: center;",
|
| 404 |
+
tags$img(
|
| 405 |
+
src = paste0("/", rv$exports_prefix, "/", src_rel),
|
| 406 |
+
style = "max-width: 100%; height: auto; border: 1px solid #999; display: inline-block;"
|
| 407 |
+
)
|
| 408 |
+
)
|
| 409 |
+
})
|
| 410 |
+
|
| 411 |
+
output$table_stats_classe <- renderTable({
|
| 412 |
+
req(input$classe_viz, rv$res_stats_df)
|
| 413 |
+
classe_norm <- normaliser_id_classe_ui(input$classe_viz)
|
| 414 |
+
classe_stats <- if (is.na(classe_norm)) input$classe_viz else classe_norm
|
| 415 |
+
|
| 416 |
+
extraire_stats_chd_classe(
|
| 417 |
+
rv$res_stats_df,
|
| 418 |
+
classe = classe_stats,
|
| 419 |
+
n_max = 50,
|
| 420 |
+
max_p = if (isTRUE(input$filtrer_affichage_pvalue)) input$max_p else 1,
|
| 421 |
+
seuil_p_significativite = input$max_p,
|
| 422 |
+
style = "iramuteq_clone"
|
| 423 |
+
)
|
| 424 |
+
}, rownames = FALSE, sanitize.text.function = function(x) x)
|
| 425 |
+
|
| 426 |
+
output$plot_afc <- renderPlot({
|
| 427 |
+
if (!is.null(rv$afc_erreur) && nzchar(rv$afc_erreur)) {
|
| 428 |
+
plot.new()
|
| 429 |
+
text(0.5, 0.5, "AFC indisponible (erreur).", cex = 1.1)
|
| 430 |
+
return(invisible(NULL))
|
| 431 |
+
}
|
| 432 |
+
if (is.null(rv$afc_obj) || is.null(rv$afc_obj$ca)) {
|
| 433 |
+
plot.new()
|
| 434 |
+
text(0.5, 0.5, "AFC non disponible. Lance une analyse.", cex = 1.1)
|
| 435 |
+
return(invisible(NULL))
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
activer_repel <- TRUE
|
| 439 |
+
if (!is.null(input$afc_reduire_chevauchement)) activer_repel <- isTRUE(input$afc_reduire_chevauchement)
|
| 440 |
+
|
| 441 |
+
taille_sel <- "frequency"
|
| 442 |
+
if (!is.null(input$afc_taille_mots) && nzchar(as.character(input$afc_taille_mots))) {
|
| 443 |
+
taille_sel <- as.character(input$afc_taille_mots)
|
| 444 |
+
}
|
| 445 |
+
if (!taille_sel %in% c("frequency", "chi2")) taille_sel <- "frequency"
|
| 446 |
+
|
| 447 |
+
top_termes <- 120
|
| 448 |
+
if (!is.null(input$afc_top_termes) && is.finite(input$afc_top_termes)) top_termes <- as.integer(input$afc_top_termes)
|
| 449 |
+
|
| 450 |
+
tracer_afc_classes_termes(rv$afc_obj, axes = c(1, 2), top_termes = top_termes, taille_sel = taille_sel, activer_repel = activer_repel)
|
| 451 |
+
})
|
| 452 |
+
|
| 453 |
+
output$ui_table_afc_mots_par_classe <- renderUI({
|
| 454 |
+
if (is.null(rv$afc_table_mots)) {
|
| 455 |
+
output$table_afc_mots_message <- renderTable({
|
| 456 |
+
data.frame(Message = "AFC mots : non disponible.", stringsAsFactors = FALSE)
|
| 457 |
+
}, rownames = FALSE)
|
| 458 |
+
return(tableOutput("table_afc_mots_message"))
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
df <- rv$afc_table_mots
|
| 462 |
+
colonnes <- intersect(c("Terme", "Classe_max", "frequency", "chi2", "p_value", "Segment_texte"), names(df))
|
| 463 |
+
df <- df[, colonnes, drop = FALSE]
|
| 464 |
+
if ("p_value" %in% names(df)) {
|
| 465 |
+
df$p_value <- ifelse(
|
| 466 |
+
is.na(df$p_value),
|
| 467 |
+
NA_character_,
|
| 468 |
+
formatC(df$p_value, format = "f", digits = 6)
|
| 469 |
+
)
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
classes <- unique(as.character(df$Classe_max))
|
| 473 |
+
classes <- classes[!is.na(classes) & nzchar(classes)]
|
| 474 |
+
classes <- sort(classes)
|
| 475 |
+
|
| 476 |
+
if (length(classes) == 0) {
|
| 477 |
+
output$table_afc_mots_message <- renderTable({
|
| 478 |
+
data.frame(Message = "AFC mots : aucune classe disponible.", stringsAsFactors = FALSE)
|
| 479 |
+
}, rownames = FALSE)
|
| 480 |
+
return(tableOutput("table_afc_mots_message"))
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
ui_tables <- lapply(seq_along(classes), function(i) {
|
| 484 |
+
cl <- classes[[i]]
|
| 485 |
+
id <- paste0("table_afc_mots_", i)
|
| 486 |
+
|
| 487 |
+
output[[id]] <- renderUI({
|
| 488 |
+
sous_df <- df[df$Classe_max == cl, , drop = FALSE]
|
| 489 |
+
colonnes <- intersect(c("Terme", "frequency", "chi2", "p_value", "Segment_texte"), names(sous_df))
|
| 490 |
+
sous_df <- sous_df[, colonnes, drop = FALSE]
|
| 491 |
+
|
| 492 |
+
if ("p_value" %in% names(sous_df)) {
|
| 493 |
+
sous_df$p_value <- ifelse(
|
| 494 |
+
is.na(sous_df$p_value),
|
| 495 |
+
NA_character_,
|
| 496 |
+
formatC(sous_df$p_value, format = "f", digits = 6)
|
| 497 |
+
)
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
if ("chi2" %in% names(sous_df)) {
|
| 501 |
+
sous_df <- sous_df[order(-sous_df$chi2), , drop = FALSE]
|
| 502 |
+
sous_df$chi2 <- ifelse(
|
| 503 |
+
is.na(sous_df$chi2),
|
| 504 |
+
NA_character_,
|
| 505 |
+
formatC(sous_df$chi2, format = "f", digits = 6)
|
| 506 |
+
)
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
sous_df <- head(sous_df, 100)
|
| 510 |
+
generer_table_html_afc_mots(sous_df)
|
| 511 |
+
})
|
| 512 |
+
|
| 513 |
+
tagList(
|
| 514 |
+
tags$h5(cl),
|
| 515 |
+
uiOutput(id)
|
| 516 |
+
)
|
| 517 |
+
})
|
| 518 |
+
|
| 519 |
+
do.call(tagList, ui_tables)
|
| 520 |
+
})
|
| 521 |
+
|
| 522 |
+
output$plot_afc_vars <- renderPlot({
|
| 523 |
+
if (!is.null(rv$afc_vars_erreur) && nzchar(rv$afc_vars_erreur)) {
|
| 524 |
+
plot.new()
|
| 525 |
+
text(0.5, 0.5, "AFC variables étoilées indisponible (erreur).", cex = 1.1)
|
| 526 |
+
return(invisible(NULL))
|
| 527 |
+
}
|
| 528 |
+
if (is.null(rv$afc_vars_obj) || is.null(rv$afc_vars_obj$ca)) {
|
| 529 |
+
plot.new()
|
| 530 |
+
text(0.5, 0.5, "AFC variables étoilées non disponible. Lance une analyse.", cex = 1.1)
|
| 531 |
+
return(invisible(NULL))
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
activer_repel <- TRUE
|
| 535 |
+
if (!is.null(input$afc_reduire_chevauchement)) activer_repel <- isTRUE(input$afc_reduire_chevauchement)
|
| 536 |
+
|
| 537 |
+
top_mod <- 120
|
| 538 |
+
if (!is.null(input$afc_top_modalites) && is.finite(input$afc_top_modalites)) top_mod <- as.integer(input$afc_top_modalites)
|
| 539 |
+
|
| 540 |
+
tracer_afc_variables_etoilees(rv$afc_vars_obj, axes = c(1, 2), top_modalites = top_mod, activer_repel = activer_repel)
|
| 541 |
+
})
|
| 542 |
+
|
| 543 |
+
output$table_afc_vars <- renderTable({
|
| 544 |
+
if (is.null(rv$afc_table_vars)) {
|
| 545 |
+
return(data.frame(Message = "AFC variables étoilées : non disponible.", stringsAsFactors = FALSE))
|
| 546 |
+
}
|
| 547 |
+
df <- rv$afc_table_vars
|
| 548 |
+
colonnes <- intersect(c("Modalite", "Classe_max", "frequency", "chi2", "p_value"), names(df))
|
| 549 |
+
df <- df[, colonnes, drop = FALSE]
|
| 550 |
+
if ("p_value" %in% names(df)) {
|
| 551 |
+
p_values <- df$p_value
|
| 552 |
+
df$p_value <- ifelse(
|
| 553 |
+
is.na(p_values),
|
| 554 |
+
NA_character_,
|
| 555 |
+
ifelse(
|
| 556 |
+
p_values > 0.05,
|
| 557 |
+
sprintf("<span style='color:#d97706;font-weight:600;'>%s</span>", formatC(p_values, format = "f", digits = 6)),
|
| 558 |
+
formatC(p_values, format = "f", digits = 6)
|
| 559 |
+
)
|
| 560 |
+
)
|
| 561 |
+
}
|
| 562 |
+
if ("chi2" %in% names(df)) df <- df[order(-df$chi2), , drop = FALSE]
|
| 563 |
+
if ("chi2" %in% names(df)) {
|
| 564 |
+
df$chi2 <- ifelse(
|
| 565 |
+
is.na(df$chi2),
|
| 566 |
+
NA_character_,
|
| 567 |
+
formatC(df$chi2, format = "f", digits = 6)
|
| 568 |
+
)
|
| 569 |
+
}
|
| 570 |
+
head(df, 200)
|
| 571 |
+
}, rownames = FALSE, sanitize.text.function = function(x) x)
|
| 572 |
+
|
| 573 |
+
output$table_afc_eig <- renderTable({
|
| 574 |
+
if (!is.null(rv$afc_erreur) && nzchar(rv$afc_erreur)) {
|
| 575 |
+
return(data.frame(Message = "AFC indisponible (erreur).", stringsAsFactors = FALSE))
|
| 576 |
+
}
|
| 577 |
+
if (is.null(rv$afc_obj) || is.null(rv$afc_obj$ca)) {
|
| 578 |
+
return(data.frame(Message = "AFC non disponible.", stringsAsFactors = FALSE))
|
| 579 |
+
}
|
| 580 |
+
eig <- rv$afc_obj$ca$eig
|
| 581 |
+
if (is.null(eig)) return(data.frame(Message = "Valeurs propres indisponibles.", stringsAsFactors = FALSE))
|
| 582 |
+
df <- as.data.frame(eig)
|
| 583 |
+
df$Dim <- rownames(df)
|
| 584 |
+
rownames(df) <- NULL
|
| 585 |
+
df <- df[, c("Dim", names(df)[1], names(df)[2], names(df)[3]), drop = FALSE]
|
| 586 |
+
names(df) <- c("Dim", "Valeur_propre", "Pourcentage_inertie", "Pourcentage_cumule")
|
| 587 |
+
df
|
| 588 |
+
}, rownames = FALSE)
|
| 589 |
+
|
| 590 |
+
output$dl_segments <- downloadHandler(
|
| 591 |
+
filename = function() "segments_par_classe.txt",
|
| 592 |
+
content = function(file) {
|
| 593 |
+
req(rv$segments_file)
|
| 594 |
+
file.copy(rv$segments_file, file, overwrite = TRUE)
|
| 595 |
+
}
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
output$dl_stats <- downloadHandler(
|
| 599 |
+
filename = function() "stats_par_classe.csv",
|
| 600 |
+
content = function(file) {
|
| 601 |
+
req(rv$stats_file)
|
| 602 |
+
file.copy(rv$stats_file, file, overwrite = TRUE)
|
| 603 |
+
}
|
| 604 |
+
)
|
| 605 |
+
|
| 606 |
+
output$dl_html <- downloadHandler(
|
| 607 |
+
filename = function() "segments_par_classe.html",
|
| 608 |
+
content = function(file) {
|
| 609 |
+
req(rv$html_file)
|
| 610 |
+
file.copy(rv$html_file, file, overwrite = TRUE)
|
| 611 |
+
}
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
output$dl_zip <- downloadHandler(
|
| 615 |
+
filename = function() "exports_rainette.zip",
|
| 616 |
+
content = function(file) {
|
| 617 |
+
req(rv$zip_file)
|
| 618 |
+
file.copy(rv$zip_file, file, overwrite = TRUE)
|
| 619 |
+
}
|
| 620 |
+
)
|
| 621 |
+
|
| 622 |
+
output$dl_afc_zip <- downloadHandler(
|
| 623 |
+
filename = function() "afc_exports.zip",
|
| 624 |
+
content = function(file) {
|
| 625 |
+
req(rv$afc_dir)
|
| 626 |
+
zip_tmp <- tempfile(fileext = ".zip")
|
| 627 |
+
ancien <- getwd()
|
| 628 |
+
on.exit(setwd(ancien), add = TRUE)
|
| 629 |
+
setwd(dirname(rv$afc_dir))
|
| 630 |
+
if (file.exists(zip_tmp)) unlink(zip_tmp)
|
| 631 |
+
utils::zip(zipfile = zip_tmp, files = basename(rv$afc_dir))
|
| 632 |
+
file.copy(zip_tmp, file, overwrite = TRUE)
|
| 633 |
+
}
|
| 634 |
+
)
|
| 635 |
+
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
app <- shinyApp(ui = ui, server = server)
|
| 639 |
+
app
|
cles_analyse_iramuteq.png
ADDED
|
Git LFS Details
|
dictionnaires/lexique_fr.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
gitattributes
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: gitattributes définit les règles Git appliquées au dépôt.
|
| 2 |
+
# Ce fichier contrôle le suivi des contenus et la normalisation associée.
|
| 3 |
+
# Il évite les écarts de versionnement et garantit une base de travail stable.
|
| 4 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
packages/udpipe/data/brussels_listings.RData filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
packages/udpipe/data/brussels_reviews_anno.RData filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
packages/udpipe/data/brussels_reviews_w2v_embeddings_lemma_nl.RData filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
packages/udpipe/data/brussels_reviews.RData filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
packages/udpipe/inst/dummydata/toymodel.udpipe filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
packages/udpipe/french-gsd-ud-2.5-191206.udpipe filter=lfs diff=lfs merge=lfs -text
|
gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: gitignore définit les règles Git appliquées au dépôt.
|
| 2 |
+
# Ce fichier contrôle le suivi des contenus et la normalisation associée.
|
| 3 |
+
# Il évite les écarts de versionnement et garantit une base de travail stable.
|
| 4 |
+
.DS_Store
|
help/chd.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CHD — Clarification de `mincl` et de la sélection des classes terminales
|
| 2 |
+
|
| 3 |
+
## 1) À quoi sert `mincl` ?
|
| 4 |
+
|
| 5 |
+
`mincl` est le **seuil minimal d'effectif d'une classe** (en pratique : nombre d'UCE/segments) utilisé au moment de la **sélection finale des classes terminales**.
|
| 6 |
+
|
| 7 |
+
- Si une classe terminale a un effectif `< mincl`, elle peut être écartée telle quelle.
|
| 8 |
+
- Dans la logique IRaMuTeQ, l'algorithme peut alors remonter dans l'arbre vers une classe mère pour garder une partition interprétable.
|
| 9 |
+
|
| 10 |
+
Autrement dit, `mincl` ne règle pas directement la segmentation du texte : il intervient surtout dans le **post-traitement de l'arbre CHD**.
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 2) Comment IRaMuTeQ applique `mincl` ?
|
| 15 |
+
|
| 16 |
+
Dans les scripts historiques d'IRaMuTeQ :
|
| 17 |
+
|
| 18 |
+
- `find.terminales(...)` conserve d'abord les classes terminales dont l'effectif est `>= mincl`.
|
| 19 |
+
- Pour les classes trop petites, il existe une logique de remontée vers la classe mère (via les liens mère/filles) avant de figer la solution.
|
| 20 |
+
- `make.classes(...)` reconstruit ensuite les classes finales et l'arbre filtré.
|
| 21 |
+
|
| 22 |
+
Cette étape explique pourquoi, à corpus identique, les classes finales peuvent différer d'une implémentation CHD qui n'a pas ce post-traitement.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 3) Valeur "auto" de `mincl` dans IRaMuTeQ
|
| 27 |
+
|
| 28 |
+
### 3.1 CHD texte (`Rchdtxt`)
|
| 29 |
+
|
| 30 |
+
Convention utilisée :
|
| 31 |
+
|
| 32 |
+
- `mincl = 0`
|
| 33 |
+
→ mode automatique
|
| 34 |
+
|
| 35 |
+
Formule auto :
|
| 36 |
+
|
| 37 |
+
\[
|
| 38 |
+
mincl = round(nrow(classeuce1) / ind)
|
| 39 |
+
\]
|
| 40 |
+
|
| 41 |
+
avec :
|
| 42 |
+
|
| 43 |
+
- `ind = nbcl * 2` si `classif_mode == 0` (double classification)
|
| 44 |
+
- sinon `ind = nbcl`
|
| 45 |
+
|
| 46 |
+
et `nbcl = nbt + 1`.
|
| 47 |
+
|
| 48 |
+
### 3.2 CHD questionnaire (`Rchdquest`)
|
| 49 |
+
|
| 50 |
+
Convention différente :
|
| 51 |
+
|
| 52 |
+
- `mincl = 2`
|
| 53 |
+
→ mode automatique
|
| 54 |
+
|
| 55 |
+
Formule auto :
|
| 56 |
+
|
| 57 |
+
\[
|
| 58 |
+
mincl = round(nrow(classeuce1) / (nbt + 1))
|
| 59 |
+
\]
|
| 60 |
+
|
| 61 |
+
Puis un plancher est imposé :
|
| 62 |
+
|
| 63 |
+
- si `mincl < 3` alors `mincl = 3`.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## 4) Différence avec le script Rainette de ce projet
|
| 68 |
+
|
| 69 |
+
Dans ce projet, la classification est obtenue via `rainette(...)` / `rainette2(...)`, puis les groupes sont utilisés directement comme classes.
|
| 70 |
+
|
| 71 |
+
- Le paramètre `min_split_members` sert principalement à contraindre `k` (nombre de classes faisable).
|
| 72 |
+
- Ce n'est **pas** l'équivalent exact de `mincl` d'IRaMuTeQ.
|
| 73 |
+
|
| 74 |
+
Conséquence : sans post-traitement "classes terminales" à la manière IRaMuTeQ, le nombre et l'identité des classes finales peuvent varier.
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## 5) Recommandation d'implémentation (optionnelle)
|
| 79 |
+
|
| 80 |
+
Pour rapprocher le comportement d'IRaMuTeQ sans casser l'existant :
|
| 81 |
+
|
| 82 |
+
1. Ajouter un réglage `mode_mincl` :
|
| 83 |
+
- `manuel`
|
| 84 |
+
- `auto_iramuteq`
|
| 85 |
+
2. Ajouter `mincl_manuel` (actif seulement en mode manuel).
|
| 86 |
+
3. En mode `auto_iramuteq`, calculer `mincl` avec la formule texte ci-dessus.
|
| 87 |
+
4. Appliquer ensuite un post-traitement de classes terminales (inspiré de `find.terminales`/`make.classes`).
|
| 88 |
+
|
| 89 |
+
Ainsi, l'utilisateur peut choisir entre :
|
| 90 |
+
|
| 91 |
+
- un mode Rainette "direct" (plus simple),
|
| 92 |
+
- un mode "IRa-like" (plus proche des sorties IRaMuTeQ).
|
| 93 |
+
|
| 94 |
+
---
|
| 95 |
+
|
| 96 |
+
## 6) Vocabulaire rapide
|
| 97 |
+
|
| 98 |
+
- **UCE / segment** : unité de texte classée.
|
| 99 |
+
- **Classe terminale** : classe feuille dans l'arbre CHD.
|
| 100 |
+
- **Classe mère / filles** : relation hiérarchique dans l'arbre de partition.
|
| 101 |
+
- **`mincl`** : effectif minimal exigé pour conserver une classe terminale telle quelle.
|
help/chd_iramuteq.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CHD IRaMuTeQ-like — fonctionnement et paramètres utilisateur
|
| 2 |
+
|
| 3 |
+
Ce document explique le fonctionnement du mode **CHD IRaMuTeQ-like** dans l’application, les paramètres disponibles, et l’interprétation des sorties (dendrogramme et tableaux de statistiques).
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1) Objectif de la CHD IRaMuTeQ-like
|
| 8 |
+
|
| 9 |
+
La CHD (Classification Hiérarchique Descendante) cherche à partitionner les segments de texte en classes lexicalement homogènes et distinctes.
|
| 10 |
+
|
| 11 |
+
Dans ce mode IRaMuTeQ-like, l’algorithme suit la logique historique IRaMuTeQ :
|
| 12 |
+
- découpage hiérarchique en classes,
|
| 13 |
+
- sélection de classes terminales,
|
| 14 |
+
- calcul des termes caractéristiques par classe (chi2 signé, p-value, fréquence, proportion documentaire),
|
| 15 |
+
- visualisation via un dendrogramme de style phylogramme.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## 2) Pipeline simplifié
|
| 20 |
+
|
| 21 |
+
1. **Préparation du DFM** (matrice documents/termes) à partir des segments.
|
| 22 |
+
2. **Lancement du moteur CHD** avec un nombre de classes cible (paramètre `k`).
|
| 23 |
+
3. **Reconstruction des classes terminales** avec un seuil `mincl` (auto ou manuel).
|
| 24 |
+
4. **Calcul des statistiques par classe**:
|
| 25 |
+
- chi2 signé,
|
| 26 |
+
- p-value,
|
| 27 |
+
- fréquence (`frequency`),
|
| 28 |
+
- proportion documentaire (`docprop`),
|
| 29 |
+
- ratio de vraisemblance (`lr`).
|
| 30 |
+
5. **Rendu visuel**:
|
| 31 |
+
- dendrogramme CHD IRaMuTeQ-like,
|
| 32 |
+
- tableaux de stats par classe,
|
| 33 |
+
- vues AFC (si activées).
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 3) Paramètres utilisateur (côté interface)
|
| 38 |
+
|
| 39 |
+
> Les noms affichés peuvent légèrement varier selon l’écran, mais la logique fonctionnelle est la suivante.
|
| 40 |
+
|
| 41 |
+
### 3.1 Nombre de classes (`k`)
|
| 42 |
+
- **Libellé dans l’interface**: « Nombre de classes terminales de la phase 1 ».
|
| 43 |
+
- **Valeur par défaut**: `10`.
|
| 44 |
+
- **Rôle**: fixe le niveau de découpage attendu.
|
| 45 |
+
- **Effet**: plus `k` est élevé, plus la partition est fine (classes plus nombreuses, parfois moins stables).
|
| 46 |
+
- **Valeurs recommandées**:
|
| 47 |
+
- petits corpus: 3 à 6,
|
| 48 |
+
- corpus moyens/grands: 4 à 10.
|
| 49 |
+
|
| 50 |
+
### 3.2 Mode de classification (`classif_mode`)
|
| 51 |
+
- **Valeurs**: `simple` ou `double`.
|
| 52 |
+
- **Rôle**:
|
| 53 |
+
- `simple`: classification standard,
|
| 54 |
+
- `double`: logique de classification croisée (plus stricte, selon configuration).
|
| 55 |
+
|
| 56 |
+
### 3.3 Seuil minimal de classe (`mincl`) + mode auto/manuel (`mincl_mode`)
|
| 57 |
+
- **`mincl_mode = auto`**: le système calcule automatiquement un minimum d’effectif par classe.
|
| 58 |
+
- **`mincl_mode = manuel`**: l’utilisateur impose `mincl`.
|
| 59 |
+
- **Effet**:
|
| 60 |
+
- `mincl` plus grand = classes plus robustes, mais potentiellement moins nombreuses,
|
| 61 |
+
- `mincl` plus petit = classes plus fines, mais plus sensibles au bruit.
|
| 62 |
+
|
| 63 |
+
### 3.4 Seuil de p-value (`max_p`)
|
| 64 |
+
- **Rôle**: filtre des termes dans certaines vues statistiques.
|
| 65 |
+
- **Exemples**:
|
| 66 |
+
- `0.05` pour les termes statistiquement marqués,
|
| 67 |
+
- `1` pour ne pas filtrer (vue exhaustive).
|
| 68 |
+
|
| 69 |
+
### 3.5 Binarisation (`binariser`)
|
| 70 |
+
- **Rôle**: transforme les fréquences en présence/absence avant CHD.
|
| 71 |
+
- **Usage**: en général activée pour rester proche des pratiques historiques CHD lexicales.
|
| 72 |
+
|
| 73 |
+
### 3.6 Méthode SVD (`svd_method`)
|
| 74 |
+
- **Valeurs**: `irlba`, `svdR`.
|
| 75 |
+
- **Rôle**: méthode de décomposition utilisée dans l’étape factorielle interne.
|
| 76 |
+
- **Interprétation des méthodes**:
|
| 77 |
+
- `irlba` : appelle `irlba::irlba(...)`, une SVD tronquée itérative adaptée aux matrices creuses/volumineuses ; souvent plus rapide et plus robuste en mémoire pour la CHD,
|
| 78 |
+
- `svdR` : appelle `svd(...)` de base R ; calcul exact complet, utile comme référence de reproductibilité mais potentiellement plus coûteux.
|
| 79 |
+
- **Par défaut**: `irlba`.
|
| 80 |
+
|
| 81 |
+
### 3.7 Mode patate (`mode_patate`)
|
| 82 |
+
- **Rôle**: active/désactive l’étape de reclassement itératif des individus selon le moteur historique.
|
| 83 |
+
- **Effet**:
|
| 84 |
+
- désactivé (`FALSE`) = reclassement plus complet,
|
| 85 |
+
- activé (`TRUE`) = comportement simplifié/rapide.
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## 4) Sorties et interprétation
|
| 90 |
+
|
| 91 |
+
### 4.1 Dendrogramme CHD (IRaMuTeQ-like)
|
| 92 |
+
- Représentation en **phylogramme**:
|
| 93 |
+
- axe x: profondeur de partition,
|
| 94 |
+
- axe y: ordre des classes terminales.
|
| 95 |
+
- Les classes terminales peuvent être colorées différemment.
|
| 96 |
+
- Si la structure est incomplète (données trop pauvres ou sortie CHD invalide), un message explicite est affiché.
|
| 97 |
+
|
| 98 |
+
### 4.2 Tableau statistique par classe
|
| 99 |
+
Colonnes principales:
|
| 100 |
+
- **Terme**: forme lexicale.
|
| 101 |
+
- **chi2**: chi2 signé (positif = sur-représenté dans la classe; négatif = sous-représenté).
|
| 102 |
+
- **p**: p-value associée.
|
| 103 |
+
- **frequency**: fréquence du terme dans la classe.
|
| 104 |
+
- **docprop**: proportion de segments de la classe contenant le terme.
|
| 105 |
+
- **lr**: ratio de vraisemblance (indicateur complémentaire).
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## 5) Bonnes pratiques pour l’utilisateur
|
| 110 |
+
|
| 111 |
+
1. Commencer avec un `k` modéré (4–6).
|
| 112 |
+
2. Vérifier l’équilibre des classes (éviter des classes trop petites).
|
| 113 |
+
3. Ajuster `mincl` si beaucoup de classes instables apparaissent.
|
| 114 |
+
4. Utiliser `max_p = 0.05` pour une lecture interprétative, puis `max_p = 1` pour audit complet.
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
## 6) Dépannage rapide
|
| 119 |
+
|
| 120 |
+
### Problème: pas de dendrogramme / message d’erreur
|
| 121 |
+
- Vérifier que le corpus contient suffisamment de segments et de vocabulaire.
|
| 122 |
+
- Réduire `k`.
|
| 123 |
+
- Revenir à `mincl_mode = auto`.
|
| 124 |
+
|
| 125 |
+
### Problème: tableau de stats vide
|
| 126 |
+
- Vérifier les filtres (`max_p` trop strict).
|
| 127 |
+
- Vérifier que les classes reconstruites ne sont pas nulles.
|
| 128 |
+
|
| 129 |
+
### Problème: résultats instables entre exécutions
|
| 130 |
+
- Éviter de changer simultanément plusieurs paramètres (`k`, `mincl`, `svd_method`).
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## 7) Paramètres “utilisateur” recommandés (profil de départ)
|
| 135 |
+
|
| 136 |
+
- `k = 5`
|
| 137 |
+
- `classif_mode = simple`
|
| 138 |
+
- `mincl_mode = auto`
|
| 139 |
+
- `max_p = 0.05`
|
| 140 |
+
- `binariser = TRUE`
|
| 141 |
+
- `svd_method = irlba`
|
| 142 |
+
- `mode_patate = FALSE`
|
| 143 |
+
|
| 144 |
+
Ce profil donne généralement un bon compromis entre lisibilité des classes, stabilité et interprétabilité.
|
help/help.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[//]: # (Rôle du fichier: help.md documente une partie de l'application Rainette.)
|
| 2 |
+
[//]: # (Ce document sert de référence fonctionnelle/technique pour l'équipe.)
|
| 3 |
+
[//]: # (Il décrit le comportement attendu afin de sécuriser maintenance et diagnostics.)
|
| 4 |
+
### codeandcortex.fr - Stéphane Meurisse - version beta 0.4 - 18-02-2026
|
| 5 |
+
- <a href="https://www.codeandcortex.fr" target="_blank" rel="noopener noreferrer">codeandcortex.fr</a>
|
| 6 |
+
- <a href="https://www.codeandcortex.fr/comprendre-chd-methode-reinert/" target="_blank" rel="noopener noreferrer">Comprendre la CHD</a>
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
### IRaMuTeQ
|
| 10 |
+
IRaMuTeQ, développé par Pierre Ratinaud, est un logiciel libre devenu une référence pour l’analyse textuelle en sciences humaines et sociales. Il met en œuvre la méthode de Reinert (CHD), l’AFC, ainsi que l’analyse de similitudes de Vergès, et propose de nombreux traitements complémentaires pour explorer la structure lexicale d’un corpus. Un atout est son dictionnaire de lemmes, plus précis et performant que beaucoup d’alternatives, ce qui améliore la stabilité des classes. Depuis la version 0.4 vous avez le choix avec le dictionnaire NLP de spaCy et celui de **IRaMuTeQ - lexique_fr** (uniquement fr)
|
| 11 |
+
Ce qui change à partir de la version O.4 c'est l'utilisation du **dictionnaire** utilisé par **IRaMuTeQ** (uniquement fr). **Ce dictionnaire est plus précis que spaCy**.
|
| 12 |
+
|
| 13 |
+
- <a href="https://pratinaud.gitpages.huma-num.fr/iramuteq-website/" target="_blank" rel="noopener noreferrer">IRaMuTeQ</a>
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
### Méthode Reinert - CHD
|
| 17 |
+
|
| 18 |
+
La méthode de Reinert est une approche statistique d’analyse lexicale conçue pour dégager des « mondes lexicaux » dans un corpus.
|
| 19 |
+
L’idée est de repérer des ensembles de segments de texte qui partagent des vocabulaires proches.
|
| 20 |
+
|
| 21 |
+
La CHD, pour "classification hiérarchique descendante", est l’algorithme de partitionnement associé à cette méthode.
|
| 22 |
+
Il procède par divisions successives : on prend l’ensemble des segments, puis on le coupe en deux groupes maximisant leur différenciation lexicale.
|
| 23 |
+
Ensuite, chaque groupe peut être à nouveau subdivisé, et ainsi de suite, jusqu’à obtenir un nombre de classes jugé pertinent ou une limite imposée par les paramètres.
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
### Rainette développé par Julien Barnier
|
| 27 |
+
|
| 28 |
+
Rainette est un package R qui réalise une CHD selon la méthode Reinert.
|
| 29 |
+
- <a href="https://github.com/juba/rainette/blob/main/vignettes/introduction_usage.Rmd" target="_blank" rel="noopener noreferrer">Doc Rainette</a>
|
| 30 |
+
- <a href="https://cran.r-project.org/web/packages/rainette/vignettes/introduction_usage.html" target="_blank" rel="noopener noreferrer">Utilisation de rainette</a>
|
| 31 |
+
- <a href="https://juba.r-universe.dev/builds" target="_blank" rel="noopener noreferrer">Builds r-universe</a>
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
### Pourquoi vos fichiers peuvent disparaître sur Hugging Face
|
| 35 |
+
|
| 36 |
+
Sur Hugging Face Spaces, le stockage local de ce conteneur est temporaire : si le serveur redémarre, ou si la page est rechargée après une déconnexion, les fichiers générés pendant une analyse précédente peuvent ne plus être disponibles.
|
| 37 |
+
|
| 38 |
+
Conseil : télécharge l’archive ZIP des exports juste après la fin de l’analyse.
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# Logique générale de l’application
|
| 42 |
+
|
| 43 |
+
Uploadez un fichier texte au format IRaMuTeQ. L’app segmente, construit une matrice termes-documents (DTM), lance la CHD avec rainette, calcule les statistiques, génère un HTML surligné (concordancier), puis produit la CHD, AFC, NER, nuages de mots et réseaux de cooccurrences. L’onglet d’exploration (Explore_rainette) permet de visualiser la CHD.
|
| 44 |
+
|
| 45 |
+
### Choix de la langue du dictionnaire spaCy
|
| 46 |
+
|
| 47 |
+
Vous avez le choix entre 4 langues spaCy préinstallées : français, anglais, espagnol et allemand (modèles "large", lg). D’autres langues peuvent être ajoutées ensuite selon les besoins. Il existe quatre tailles de modèles : "sm", "md", "lg" et "trf" (basé sur la technologie "transformer"). Le script détecte la cohérence entre le choix du dictionnaire et votre corpus importé, sur la base des stopwords.
|
| 48 |
+
|
| 49 |
+
### Paramètres de l’analyse
|
| 50 |
+
|
| 51 |
+
- **segment_size** : taille des segments lors du découpage du corpus. Plus petit donne plus de segments, plus grand donne des segments plus longs.
|
| 52 |
+
- **k (nombre de classes)** : nombre de classes demandé pour la CHD.
|
| 53 |
+
- Nombre minimal de termes par segment : `min_segment_size` : Lors de la tokenisation et du calcul de la dtm, certaines formes (mots-outils, mots trop peu fréquents) ont été supprimées, les segments peuvent donc varier en taille.
|
| 54 |
+
Avec `min_segment_size = 10`, les segments comportant moins de 10 formes sont regroupés avec le segment suivant ou précédent du même document jusqu'à atteindre la taille minimale souhaitée.
|
| 55 |
+
- Effectif minimal pour scinder une classe : **min_split_members**. Nombre minimal de documents pour qu'une classe soit scindée en deux à l'étape suivante de la classification.
|
| 56 |
+
- Fréquence minimale des termes : `dfm_trim min_docfreq` : fréquence minimale en nombre de segments pour conserver un terme dans le DFM. Plus "haut" enlève les termes rares. Par exemple si vous `dfm_trim = 3` cela supprime de la matrice les termes apparaissant dans moins de 3 segments.
|
| 57 |
+
- **max_p (p-value)** : seuil de p-value pour filtrer les termes mis en avant dans les statistiques.
|
| 58 |
+
- **top_n (wordcloud)** : nombre de termes affichés dans chaque nuage de mots.
|
| 59 |
+
- **window (cooccurrences)** : taille de la fenêtre glissante pour calculer les cooccurrences.
|
| 60 |
+
- **top_feat (cooccurrences)** : nombre de termes retenus pour construire le réseau de cooccurrences.
|
| 61 |
+
|
| 62 |
+
### Options de nettoyage du texte
|
| 63 |
+
|
| 64 |
+
Ces options agissent surtout sur la **préparation linguistique** (tokenisation, DFM, CHD, stats), pas sur l’affichage "brut" des segments.
|
| 65 |
+
|
| 66 |
+
- **Nettoyage caractères (regex)** (`nettoyage_caracteres`) : supprime les caractères non autorisés par la regex interne (ex : @).
|
| 67 |
+
- **Supprimer la ponctuation** (`supprimer_ponctuation`) : active `remove_punct` lors de la tokenisation quanteda. La ponctuation est retirée des tokens utilisés pour les analyses (CHD, stats).
|
| 68 |
+
- **Supprimer les chiffres (0-9)** (`supprimer_chiffres`) : supprime les chiffres avant tokenisation.
|
| 69 |
+
- **Traiter les élisions FR** (`supprimer_apostrophes`) : enlève les élisions en début de mot (`c'`, `j'`, `l'`, `m'`, `n'`, `s'`, `t'`, `d'`, `qu'`) pour ramener par ex. `c'est` vers `est`.
|
| 70 |
+
- **Forcer en minuscules avant analyse** (`forcer_minuscules_avant`) : convertit le texte en minuscules avant la construction des tokens/termes.
|
| 71 |
+
|
| 72 |
+
#### Stopwords en mode IRaMuTeQ-like
|
| 73 |
+
|
| 74 |
+
- En mode **IRaMuTeQ-like**, la source de lemmatisation est forcée sur **Lexique (fr)**.
|
| 75 |
+
- Donc, quand l'option **Retirer les stopwords** est activée, le filtrage se fait avec les stopwords **français de quanteda** (et non avec spaCy).
|
| 76 |
+
- Le filtrage stopwords via **spaCy** n'est utilisé que lorsque la source de dictionnaire est **spaCy** (mode Rainette).
|
| 77 |
+
|
| 78 |
+
#### Effet sur le concordancier HTML
|
| 79 |
+
|
| 80 |
+
- Quand **Supprimer la ponctuation** est cochée, la ponctuation est bien retirée dans les **données d’analyse**.
|
| 81 |
+
- Le **concordancier HTML** continue d’afficher les segments issus du corpus, donc vous pouvez encore voir de la ponctuation dans le texte affiché.
|
| 82 |
+
|
| 83 |
+
### Classification double (rainette2)
|
| 84 |
+
|
| 85 |
+
- **Classification double** : l’application combine deux classifications rainette (res1 et res2) via rainette2, puis découpe l’arbre final avec k.
|
| 86 |
+
|
| 87 |
+
### Lemmatisation (option)
|
| 88 |
+
|
| 89 |
+
- **Lemmatisation** : si activée, le texte est **lemmatisé avec Spacy ou le dictionnaire de lemme provenant du logiciel IRaMuTeQ - lexique_fr**. La lemmatisation semble (beaucoup) plus efficace avec le dictionnaire IRaMuTeQ provenant de **OpenLexicon (modifié)**.
|
| 90 |
+
|
| 91 |
+
- <a href="https://openlexicon.fr/" target="_blank" rel="noopener noreferrer">OpenLexicon</a>
|
| 92 |
+
|
| 93 |
+
### Filtrage Morphosyntaxique
|
| 94 |
+
- **Tokens à conserver** : filtre les tokens conservés selon leur catégorie grammaticale (ex : NOUN, ADJ, VERB, PROPN, ADV...).
|
| 95 |
+
|
| 96 |
+
### Paramètres SpaCy/NER
|
| 97 |
+
- Activer NER (spaCy) => Détections des entités nommées (NER) par spaCy (ex : "Paris" = "LOC"). Le modele spaCy "md" est un peu léger... pour cette tâche.
|
| 98 |
+
|
| 99 |
+
### Exploration "Explore_rainette"
|
| 100 |
+
|
| 101 |
+
- **Classe** : sélection de la classe pour afficher les images et la table de statistiques associées.
|
| 102 |
+
- **CHD** : affichage graphique de la CHD.
|
| 103 |
+
- **Type** : bar (barres) ou cloud (nuage) pour l’affichage des termes par classe.
|
| 104 |
+
- **Statistiques** : chi2, lr, frequency, selon le critère utilisé pour classer les termes.
|
| 105 |
+
- Dans les exports CSV de type (`measure = "chi2"`), les colonnes suivantes sont importantes :
|
| 106 |
+
- **`n_target`** : nombre d’occurrences du terme dans la classe/cluster analysé.
|
| 107 |
+
- **`n_reference`** : nombre d’occurrences du même terme dans (tout) le corpus de référence (le reste des classes).
|
| 108 |
+
- **`chi2`** et **`p`** : test d’association entre cible et référence ; plus `chi2` est élevé et `p` petite, plus le terme est spécifiquement lié à la classe.
|
| 109 |
+
- **Nombre de termes** : nombre de termes affichés par classe dans la visualisation.
|
| 110 |
+
- **Afficher les valeurs négatives** : inclut les termes négativement associés à une classe.
|
| 111 |
+
|
| 112 |
+
### Démarrage
|
| 113 |
+
|
| 114 |
+
- L’application ne fait plus de mise à jour automatique de `rainette` au lancement.
|
| 115 |
+
- Si vous voyez un ancien message `AUTO_UPDATE_RAINETTE=true -> tentative de mise à jour`, vérifiez que l’image/conteneur a bien été reconstruit avec la dernière version du dépôt.
|
help/helpafc.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[//]: # (Rôle du fichier: helpafc.md documente une partie de l'application Rainette.)
|
| 2 |
+
[//]: # (Ce document sert de référence fonctionnelle/technique pour l'équipe.)
|
| 3 |
+
[//]: # (Il décrit le comportement attendu afin de sécuriser maintenance et diagnostics.)
|
| 4 |
+
## Aide AFC : calcul, affichage des termes, rôle de `top_termes`, calcul du `residu de Pearson`
|
| 5 |
+
|
| 6 |
+
### 1) Comment l’AFC est calculée dans le script
|
| 7 |
+
|
| 8 |
+
L’AFC classes × termes est calculée en 3 étapes :
|
| 9 |
+
|
| 10 |
+
1. Construction de la table de contingence **Classes × Termes** depuis le DFM.
|
| 11 |
+
2. Exécution de l’AFC avec `FactoMineR::CA(tab, graph = FALSE)`.
|
| 12 |
+
3. Récupération des coordonnées des classes (`rowcoord`) et des termes (`colcoord`) pour le tracé.
|
| 13 |
+
|
| 14 |
+
### 2) Qu’est-ce que `top_termes` ?
|
| 15 |
+
|
| 16 |
+
`top_termes` est **une limite d’affichage graphique** des mots sur le plan AFC. Par défaut : `top_termes = 120`
|
| 17 |
+
|
| 18 |
+
### 3) Sur quoi `top_termes` filtre ?
|
| 19 |
+
|
| 20 |
+
Le filtrage est fait dans la fonction de tracé des termes :
|
| 21 |
+
|
| 22 |
+
1. on part de `termes_stats`,
|
| 23 |
+
2. on enlève les termes vides,
|
| 24 |
+
3. on trie par `frequency` décroissante,
|
| 25 |
+
4. on garde les `top_termes` premiers.
|
| 26 |
+
|
| 27 |
+
Pourquoi top_termes est en fréquence et pas en p-value ?
|
| 28 |
+
|
| 29 |
+
Parce que top_termes est une contrainte de lisibilité graphique.
|
| 30 |
+
Le rôle de top_termes est de limiter le nombre de labels affichés dans `tracer_afc_classes_termes`, sinon le plot devient illisible (chevauchements).
|
| 31 |
+
Le code trie les termes par `frequency` qui détermine ensuite le `top_termes`.
|
| 32 |
+
En amont, dans le pipeline serveur, on construit termes_signif avec `p <= input$max_p`, puis on passe ces termes à `executer_afc_classes`, `termes_cibles` = termes_significatifs). **Donc la p-value réduit le périmètre des termes de l'AFC**.
|
| 33 |
+
|
| 34 |
+
### 4) Le CSV contient-il seulement `top_termes` ?
|
| 35 |
+
|
| 36 |
+
Le CSV `stats_termes.csv` exporte la table `rv$afc_obj$termes_stats` (jeu complet de stats AFC disponible), sans appliquer la réduction `top_termes`.
|
| 37 |
+
|
| 38 |
+
### 5) Note sur l'afc chi2/résidu de Pearson
|
| 39 |
+
|
| 40 |
+
- Les **positions AFC** viennent de `FactoMineR::CA`.
|
| 41 |
+
- Les **résidus/chi2** sont des statistiques d’association utilisés pour l’interprétation.
|
| 42 |
+
|
| 43 |
+
### 6) Comment le résidu de Pearson est calculé
|
| 44 |
+
|
| 45 |
+
Pour chaque mot et chaque classe :
|
| 46 |
+
|
| 47 |
+
- On regarde combien de fois le mot apparaît réellement dans cette classe.
|
| 48 |
+
- On calcule ensuite combien on aurait “normalement” attendu pour ce mot dans cette classe, si la répartition était neutre.
|
| 49 |
+
- On compare les deux : réel vs attendu, et transforme cet écart en une valeur (le résidu).
|
| 50 |
+
|
| 51 |
+
Le résidu ne sert pas à recalculer l’AFC.
|
| 52 |
+
|
| 53 |
+
- Le code extrait, pour chaque mot, la classe où la surreprésentation est la plus forte et sa valeur (`resid_max`).
|
| 54 |
+
- `Classe_max` = la classe où le mot apparaît le plus en quantité brute (le plus d’occurrences observées).
|
| 55 |
+
- `resid_max` = la valeur qui mesure à quel point ce mot est plus (ou moins) présent que prévu dans la classe (sur/sous-représentation)
|
| 56 |
+
|
| 57 |
+
### 7) Résidu positif / résidu négatif
|
| 58 |
+
|
| 59 |
+
- Résidu positif => le mot est plus présent que prévu dans cette classe (**surreprésenté**).
|
| 60 |
+
- Résidu négatif => le mot est moins présent que prévu dans cette classe (**sous-représenté**).
|
| 61 |
+
- Résidu proche de 0 => présence “normale”, pas d’écart fort.
|
help/helpchi2.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[//]: # (Rôle du fichier: helpchi2.md documente une partie de l'application Rainette.)
|
| 2 |
+
[//]: # (Ce document sert de référence fonctionnelle/technique pour l'équipe.)
|
| 3 |
+
[//]: # (Il décrit le comportement attendu afin de sécuriser maintenance et diagnostics.)
|
| 4 |
+
## Aide : interprétation du chi2 et de `Classe_max`
|
| 5 |
+
|
| 6 |
+
Le code calcule un chi2 global par terme sur l’ensemble des classes :
|
| 7 |
+
|
| 8 |
+
\[
|
| 9 |
+
\chi_j^2 = \sum \frac{(O - E)^2}{E}
|
| 10 |
+
\]
|
| 11 |
+
|
| 12 |
+
puis une p-value.
|
| 13 |
+
|
| 14 |
+
En plus, il calcule les résidus standardisés cellule par cellule :
|
| 15 |
+
|
| 16 |
+
\[
|
| 17 |
+
\frac{O - E}{E}
|
| 18 |
+
\]
|
| 19 |
+
|
| 20 |
+
- `Classe_max` = la classe où ce résidu est le plus grand pour le terme considéré (`which.max`).
|
| 21 |
+
- C’est la classe de surreprésentation la plus forte pour aider la lecture des résultats.
|
| 22 |
+
- Les classes elles-mêmes viennent du regroupement (`dfm_group`) et sont renommées « Classe X » pour affichage.
|
| 23 |
+
- L’interface confirme : couleur des mots selon la classe de plus forte surreprésentation (résidus), taille selon fréquence ou chi2.
|
| 24 |
+
|
| 25 |
+
## En une phrase
|
| 26 |
+
|
| 27 |
+
- `chi2` = « est-ce que la distribution du terme varie globalement selon les classes ? »
|
| 28 |
+
- `Classe_max` = « si oui, dans quelle classe l’excès est le plus marqué ? »
|
help/ner.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Named Entity Recognition NER (spaCy + règles JSON)
|
| 2 |
+
|
| 3 |
+
## Fonctionnement
|
| 4 |
+
1. spaCy détecte des entités (PER, ORG, LOC, etc...) mais le résultat nécessite bien souvent des corrections
|
| 5 |
+
2. Dans le script un mini-filtrage a été ajouté pour supprimer des faux positifs (ponctuation seule, cas bruités, etc...)
|
| 6 |
+
3. Vous pouvez ajouter un fichier au format **.json**, ses règles seront appliquées : exclusions et ajouts
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+

|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
## Format attendu du fichier JSON
|
| 13 |
+
- Le fichier doit être au **format `.json`**.
|
| 14 |
+
Exemple totalement farfellu montrant que vous pouvze exclure et inclure des mots, créer un nouveau label
|
| 15 |
+
|
| 16 |
+
```json
|
| 17 |
+
{
|
| 18 |
+
"exclude_texts": ["ça", "«", "»"],
|
| 19 |
+
"exclude_labels": ["MISC"],
|
| 20 |
+
"include": [
|
| 21 |
+
{"text": "OpenAI", "label": "ORG"},
|
| 22 |
+
{"text": "ChatGPT", "label": "PRODUCT"},
|
| 23 |
+
{"text": "regarder", "label": "VERBE"},
|
| 24 |
+
{"text": "commencer", "label": "VERBE"}
|
| 25 |
+
]
|
| 26 |
+
}
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
## Peut-on créer ses propres labels ?
|
| 31 |
+
Oui. Il faut écrire les **LABELS en MAJUSCULES**
|
| 32 |
+
|
| 33 |
+
- Les entités détectées *nativement* par spaCy gardent les labels du modèle (`PER`, `ORG`, `LOC`, etc.).
|
| 34 |
+
- Les entités ajoutées via `include` peuvent utiliser **n'importe quel label** (ex: `VOTRE_LABEL_1`, `VOTRE_LABEL_2`,...).
|
| 35 |
+
- Ces labels personnalisés apparaissent ensuite dans la sortie NER (`ent_label`).
|
| 36 |
+
|
| 37 |
+
Exemple: `{"text": "commencer", "label": "ACTION"}` forcera la présence de `commencer` avec le label `ACTION` si le mot est trouvé dans le texte.
|
| 38 |
+
|
| 39 |
+
## Labels spaCy déjà existants
|
| 40 |
+
Les labels disponibles dépendent du **modèle spaCy chargé**.
|
| 41 |
+
|
| 42 |
+
### Labels du modèle FR utilisé dans ce projet (`fr_core_news_md`)
|
| 43 |
+
- `PER` : personne
|
| 44 |
+
- `ORG` : organisation
|
| 45 |
+
- `LOC` : lieu
|
| 46 |
+
- `MISC` : catégorie diverse (autres entités)
|
| 47 |
+
|
| 48 |
+
### Labels NER officiels spaCy (Si je ne me trompe pas avec des modeles "lg" on bénéficie de catégories étendus)
|
| 49 |
+
- `PERSON`: People, including fictional.
|
| 50 |
+
- `NORP`: Nationalities or religious or political groups.
|
| 51 |
+
- `FAC`: Buildings, airports, highways, bridges, etc.
|
| 52 |
+
- `ORG`: Companies, agencies, institutions, etc.
|
| 53 |
+
- `GPE`: Countries, cities, states.
|
| 54 |
+
- `LOC`: Non-GPE locations, mountain ranges, bodies of water.
|
| 55 |
+
- `PRODUCT`: Objects, vehicles, foods, etc. (Not services.)
|
| 56 |
+
- `EVENT`: Named hurricanes, battles, wars, sports events, etc.
|
| 57 |
+
- `WORK_OF_ART`: Titles of books, songs, etc.
|
| 58 |
+
- `LAW`: Named documents made into laws.
|
| 59 |
+
- `LANGUAGE`: Any named language.
|
| 60 |
+
- `DATE`: Absolute or relative dates or periods.
|
| 61 |
+
- `TIME`: Times smaller than a day.
|
| 62 |
+
- `PERCENT`: Percentage, including ”%“.
|
| 63 |
+
- `MONEY`: Monetary values, including unit.
|
| 64 |
+
- `QUANTITY`: Measurements, as of weight or distance.
|
| 65 |
+
- `ORDINAL`: “first”, “second”, etc.
|
| 66 |
+
- `CARDINAL`: Numerals that do not fall under another type.
|
| 67 |
+
|
| 68 |
+
## Signification des champs JSON
|
| 69 |
+
- `exclude_texts` : liste de textes d'entité à **rejeter** (insensible à la casse).
|
| 70 |
+
- `exclude_labels` : liste de labels d'entité à **rejeter** (ex. `MISC`).
|
| 71 |
+
- `include` : liste d'entités à **forcer**.
|
| 72 |
+
- `text` : texte recherché dans le document.
|
| 73 |
+
- `label` : label assigné à l'entité ajoutée.
|
| 74 |
+
|
| 75 |
+
## Expressions utilisées (important)
|
| 76 |
+
Pour `include`, le script utilise une regex Python de la forme :
|
| 77 |
+
|
| 78 |
+
- `\b<text>\b` avec `re.IGNORECASE`.
|
| 79 |
+
|
| 80 |
+
Cela veut dire :
|
| 81 |
+
- recherche **insensible à la casse** ;
|
| 82 |
+
- correspondance sur des **bornes de mot** (`\b`) ;
|
| 83 |
+
- évite de matcher au milieu d'un mot.
|
| 84 |
+
|
| 85 |
+
Exemple : `"text": "Paris"` matche `Paris` mais pas `parisien`.
|
help/nettoyage.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audit des options de nettoyage (spaCy vs `lexique_fr`)
|
| 2 |
+
|
| 3 |
+
Contexte analysé : pipeline Rainette (`R/server_events_lancer.R`, `nettoyage.R`, `R/pipeline_spacy_analysis.R`, `R/pipeline_lexique_analysis.R`, `R/nlp_spacy.R`, `rainette/spacy_preprocess.py`, `R/chd_afc_pipeline.R`, `R/nlp_language.R`).
|
| 4 |
+
|
| 5 |
+
## Résumé rapide
|
| 6 |
+
|
| 7 |
+
- Toutes les options UI sont bien **lues** par le pipeline.
|
| 8 |
+
- Mais en mode **spaCy**, certaines options sont **redondantes** ou **sans effet réel** sur la sortie finale.
|
| 9 |
+
- Ton log debug (`Diagnostic pipeline: ...`) est normal : il n'affiche qu'un sous-ensemble des options.
|
| 10 |
+
|
| 11 |
+
## Vérification option par option
|
| 12 |
+
|
| 13 |
+
| Option UI | `lexique_fr` | `spaCy` | Verdict |
|
| 14 |
+
|---|---|---|---|
|
| 15 |
+
| `nettoyage_caracteres` | Appliquée avant pipeline via `appliquer_nettoyage_et_minuscules()` | Appliquée avant pipeline via `appliquer_nettoyage_et_minuscules()` | ✅ OK des deux côtés |
|
| 16 |
+
| `forcer_minuscules_avant` | Appliquée dans `appliquer_nettoyage_et_minuscules()` | Appliquée dans `appliquer_nettoyage_et_minuscules()` + passée à Python (`--lower_input`) | ⚠️ Redondante en spaCy |
|
| 17 |
+
| `supprimer_chiffres` | Appliquée dans `appliquer_nettoyage_et_minuscules()` + `tokens(..., remove_numbers=TRUE/FALSE)` | Appliquée dans `appliquer_nettoyage_et_minuscules()` + spaCy Python (`--remove_numbers`) + `tokens(..., remove_numbers=...)` | ⚠️ Très redondante en spaCy |
|
| 18 |
+
| `supprimer_apostrophes` | Appliquée dans `appliquer_nettoyage_et_minuscules()` | Appliquée dans `appliquer_nettoyage_et_minuscules()` + spaCy Python (`--strip_fr_elisions`) | ⚠️ Redondante en spaCy |
|
| 19 |
+
| `supprimer_ponctuation` | Appliquée à la tokenisation quanteda (`tokens(remove_punct=...)`) | **Déjà supprimée** dans `rainette/spacy_preprocess.py` (`tok.is_punct` ignoré), puis retokenisation quanteda | ⚠️ En spaCy, case quasi sans effet |
|
| 20 |
+
| `retirer_stopwords` | Oui (`obtenir_stopwords_analyse(..., source_dictionnaire='lexique_fr')` → quanteda FR) | Oui (`source_dictionnaire='spacy'` → stopwords spaCy) | ✅ OK des deux côtés |
|
| 21 |
+
| `filtrage_morpho` | Oui via `filtrer_textes_lexique_par_cgram()` (`c_morpho`) | Oui via `--pos_keep` et filtrage POS dans Python | ✅ OK des deux côtés |
|
| 22 |
+
|
| 23 |
+
## Point important pour ton test (`lexique_fr` + pas de case minuscules)
|
| 24 |
+
|
| 25 |
+
Même si tu ne coches pas « Passage en minuscule », la chaîne de traitement fait ensuite un `tokens_tolower(...)` lors de la construction du DFM (avec ou sans stopwords). Donc la représentation finale utilisée pour l'analyse passe en minuscules dans tous les cas.
|
| 26 |
+
|
| 27 |
+
➡️ En clair : pour l'analyse statistique, l'absence de coche « minuscules » n'empêche pas une normalisation en minuscules plus tard.
|
| 28 |
+
|
| 29 |
+
## Pourquoi ton log debug ne montre pas tout
|
| 30 |
+
|
| 31 |
+
La ligne :
|
| 32 |
+
|
| 33 |
+
`Diagnostic pipeline: dictionnaire=... | langue UI=... | filtrage_morpho=... | retirer_stopwords=...`
|
| 34 |
+
|
| 35 |
+
est volontairement courte et n'inclut pas `forcer_minuscules_avant`, `supprimer_ponctuation`, `supprimer_chiffres`, `supprimer_apostrophes`, `nettoyage_caracteres`.
|
| 36 |
+
|
| 37 |
+
## Conclusion
|
| 38 |
+
|
| 39 |
+
- ✅ **Oui**, les options principales sont prises en compte pour `lexique_fr` et spaCy.
|
| 40 |
+
- ⚠️ **Non**, elles ne sont pas toutes discriminantes en spaCy (certaines sont doublées, et `supprimer_ponctuation` n'a presque pas d'effet car spaCy retire déjà la ponctuation en amont).
|
| 41 |
+
- ⚠️ La case « Passage en minuscule » n'est pas un indicateur fiable du casing final du DFM, car le pipeline applique `tokens_tolower(...)` ensuite.
|
| 42 |
+
|
| 43 |
+
## Recommandations (si tu veux un comportement plus lisible)
|
| 44 |
+
|
| 45 |
+
1. Ajouter dans le log debug l'état de toutes les cases de nettoyage.
|
| 46 |
+
2. En mode spaCy, clarifier dans l'UI que la ponctuation est déjà retirée côté Python.
|
| 47 |
+
3. Décider d'un **seul** niveau pour la mise en minuscules (avant spaCy OU juste avant DFM), pour éviter les ambiguïtés de debug.
|
| 48 |
+
4. Éviter les doubles suppressions chiffres/apostrophes (R + Python) sauf besoin explicite.
|
help/pos_spacy.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[//]: # (Rôle du fichier: pos_spacy.md documente une partie de l'application Rainette.)
|
| 2 |
+
[//]: # (Ce document sert de référence fonctionnelle/technique pour l'équipe.)
|
| 3 |
+
[//]: # (Il décrit le comportement attendu afin de sécuriser maintenance et diagnostics.)
|
| 4 |
+
### Analyse morphosyntaxique avec spaCy et Lexique (fr)
|
| 5 |
+
|
| 6 |
+
- Documentation principale spaCy : <https://spacy.io/usage>
|
| 7 |
+
- Linguistic Features (POS, morphology) : <a href="https://spacy.io/usage/linguistic-feature/" target="_blank" rel="noopener noreferrer">Lexique POS spaCy</a>
|
| 8 |
+
- Documentation OpenLexicon : <a href="https://openlexicon.fr/" target="_blank" rel="noopener noreferrer">OpenLexicon</a>
|
| 9 |
+
|
| 10 |
+
### Traduction FR des POS (spaCy)
|
| 11 |
+
|
| 12 |
+
- **ADJ** : adjectif
|
| 13 |
+
- **ADP** : adposition (préposition)
|
| 14 |
+
- **ADV** : adverbe
|
| 15 |
+
- **AUX** : auxiliaire
|
| 16 |
+
- **CCONJ** : conjonction de coordination
|
| 17 |
+
- **DET** : déterminant
|
| 18 |
+
- **INTJ** : interjection
|
| 19 |
+
- **NOUN** : nom
|
| 20 |
+
- **NUM** : numéral
|
| 21 |
+
- **PART** : particule
|
| 22 |
+
- **PRON** : pronom
|
| 23 |
+
- **PROPN** : nom propre
|
| 24 |
+
- **PUNCT** : ponctuation
|
| 25 |
+
- **SCONJ** : conjonction de subordination
|
| 26 |
+
- **SYM** : symbole
|
| 27 |
+
- **VERB** : verbe
|
| 28 |
+
- **X** : autre / catégorie inconnue
|
| 29 |
+
|
| 30 |
+
### Filtrage morphosyntaxique spécifique lexique_fr
|
| 31 |
+
|
| 32 |
+
Le dictionnaire **lexique_fr** utilisé ici est celui d’**IRaMuTeQ**, et il semble lui-même issu d’**OpenLexicon**.
|
| 33 |
+
|
| 34 |
+
> Contrairement au logiciel IRaMuTeQ (où les niveaux sont généralement interprétés comme `1 = active` et `2 = supplémentaire`), le filtrage proposé ici est **binaire** et configurable de plusieurs façons.
|
| 35 |
+
|
| 36 |
+

|
| 37 |
+
|
| 38 |
+
Deux configurations principales dans l'interface sont possibles :
|
| 39 |
+
1. Si vous **ne cochez pas** le filtrage morphosyntaxique, **tout le corpus** est pris en compte.
|
| 40 |
+
2. Si vous **filtrez** sur des catégories morphosyntaxiques (voir la liste ci-dessous), l’analyse porte sur le **corpus filtré** par les catégories sélectionnées.
|
| 41 |
+
|
| 42 |
+
Noms des catégories de Lexique_fr
|
| 43 |
+
|
| 44 |
+
- **NOM** : nom commun
|
| 45 |
+
- **NOM_SUP** : nom
|
| 46 |
+
- **VER** : verbe
|
| 47 |
+
- **VER_SUP** : verbe
|
| 48 |
+
- **AUX** : auxiliaire
|
| 49 |
+
- **ADJ** : adjectif
|
| 50 |
+
- **ADJ_SUP** : adjectif
|
| 51 |
+
- **ADJ_DEM** : adjectif démonstratif
|
| 52 |
+
- **ADJ_IND** : adjectif indéfini
|
| 53 |
+
- **ADJ_INT** : adjectif interrogatif
|
| 54 |
+
- **ADJ_NUM** : adjectif numéral
|
| 55 |
+
- **ADJ_POS** : adjectif possessif
|
| 56 |
+
- **ADV** : adverbe
|
| 57 |
+
- **ADV_SUP** : adverbe
|
| 58 |
+
- **PRE** : préposition
|
| 59 |
+
- **CON** : conjonction
|
| 60 |
+
- **ART_DEF** : article défini
|
| 61 |
+
- **ART_IND** : article indéfini
|
| 62 |
+
- **PRO_DEM** : pronom démonstratif
|
| 63 |
+
- **PRO_IND** : pronom indéfini
|
| 64 |
+
- **PRO_PER** : pronom personnel
|
| 65 |
+
- **PRO_POS** : pronom possessif
|
| 66 |
+
- **PRO_REL** : pronom relatif
|
| 67 |
+
- **ONO** : onomatopée
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
Flux technique (mode "Lexique_fr"):
|
| 71 |
+
1. tokenisation locale (quanteda),
|
| 72 |
+
2. filtrage des tokens par présence dans lexique_fr avec les catégories `c_morpho` sélectionnées,
|
| 73 |
+
3. lemmatisation (si activée) directement via lexique_fr (forme -> lemme).
|
| 74 |
+
|
| 75 |
+
> Le filtrage morphosyntaxique lexique_fr est donc indépendant de spaCy.
|
| 76 |
+
|
| 77 |
+
### Activation de filtrage morphosyntaxique
|
| 78 |
+
|
| 79 |
+
- de choisir la langue spaCy (`fr`, `en`, `es`, `it`, `de`) quand la source est **spaCy**,
|
| 80 |
+
- de sélectionner les POS à conserver parmi la liste POS quand la source est **spaCy**,
|
| 81 |
+
- de sélectionner directement les catégories `c_morpho` à conserver quand la source est **Lexique (fr)**,
|
| 82 |
+
- de combiner ce filtrage avec la lemmatisation selon les besoins analytiques.
|
help/rapport_audit_iramuteq_like.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audit technique détaillé — pipeline IRaMuTeQ-like (tokenisation + découpage)
|
| 2 |
+
|
| 3 |
+
## 1) Ce que fait exactement le pipeline actuel
|
| 4 |
+
|
| 5 |
+
### 1.1 Entrée corpus et segmentation initiale
|
| 6 |
+
- Le serveur lance d'abord l'import du corpus via `import_corpus_iramuteq(chemin_fichier)`, puis applique la segmentation via `split_segments(corpus, segment_size = input$segment_size)`.
|
| 7 |
+
- Le nombre de segments affiché juste après cette étape est `ndoc(corpus)` après `split_segments`.
|
| 8 |
+
- **Important**: les fonctions `import_corpus_iramuteq` et `split_segments` ne sont pas redéfinies dans ce dépôt (elles viennent d'une dépendance, typiquement `rainette`), donc leur comportement exact dépend de la version installée.
|
| 9 |
+
|
| 10 |
+
### 1.2 Pré-nettoyage texte (avant tokenisation)
|
| 11 |
+
- Le texte segmenté passe ensuite par `appliquer_nettoyage_et_minuscules(...)`.
|
| 12 |
+
- Transformations possibles:
|
| 13 |
+
- normalisation espace insécable `\u00A0` -> espace,
|
| 14 |
+
- suppression des chiffres,
|
| 15 |
+
- suppression des préfixes d'élision FR (`c'`, `d'`, `l'`, `n'`, `t'`, `s'`, `j'`, `qu'`),
|
| 16 |
+
- suppression de caractères hors regex autorisée,
|
| 17 |
+
- normalisation des espaces,
|
| 18 |
+
- passage en minuscules.
|
| 19 |
+
|
| 20 |
+
### 1.3 Tokenisation réellement utilisée
|
| 21 |
+
|
| 22 |
+
#### Chemin Lexique FR
|
| 23 |
+
- La tokenisation est faite avec `quanteda::tokens(...)` sur les textes prétraités.
|
| 24 |
+
- Les options `remove_punct` et `remove_numbers` dépendent de l'UI.
|
| 25 |
+
- Ensuite, stopwords optionnels puis `dfm` + `dfm_trim(min_docfreq=...)`.
|
| 26 |
+
|
| 27 |
+
#### Chemin spaCy
|
| 28 |
+
- Le script Python `rainette/spacy_preprocess.py` lit `doc_id,text`, puis pour chaque token spaCy:
|
| 29 |
+
- ignore espaces et ponctuation,
|
| 30 |
+
- optionnellement ignore nombres (`tok.like_num`),
|
| 31 |
+
- optionnellement retire préfixes d'élision FR,
|
| 32 |
+
- filtre POS (si demandé),
|
| 33 |
+
- prend surface ou lemme, en minuscules,
|
| 34 |
+
- recompose le document par `" ".join(tokens_sortie)`.
|
| 35 |
+
- Ensuite côté R, **il y a une seconde tokenisation** quanteda sur ce texte reconstruit.
|
| 36 |
+
|
| 37 |
+
### 1.4 CHD IRaMuTeQ-like
|
| 38 |
+
- Quand `modele_chd == "iramuteq"`, le pipeline force `source_dictionnaire = "lexique_fr"`.
|
| 39 |
+
- Les stats classes sont calculées via `construire_stats_classes_iramuteq(...)` (contingence présence/absence doc-terme), avec exclusion des classes 0.
|
| 40 |
+
- Le moteur IRaMuTeQ-like (`lancer_moteur_chd_iramuteq`) s'appuie sur les scripts historiques `CHD.R`, `anacor.R`, `chdtxt.R`.
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## 2) Où une divergence de nombre de segments peut apparaître
|
| 45 |
+
|
| 46 |
+
## 2.1 Niveau segmentation primaire (le plus probable)
|
| 47 |
+
1. **Version de dépendance différente** pour `split_segments`.
|
| 48 |
+
2. **Normalisation des retours ligne** avant import non identique (`CRLF` vs `LF`) si la dépendance n'harmonise pas strictement.
|
| 49 |
+
3. **Docnames invalides/dupliqués**: le pipeline renomme avec `make.unique`, ce qui peut modifier les correspondances ultérieures.
|
| 50 |
+
|
| 51 |
+
## 2.2 Niveau filtrage post-segmentation
|
| 52 |
+
Même si la segmentation est identique au départ, le nombre final peut diverger car:
|
| 53 |
+
1. `dfm_trim(min_docfreq)` peut supprimer des termes et rendre certains documents vides.
|
| 54 |
+
2. `supprimer_docs_vides_dfm(...)` retire explicitement les segments à somme nulle.
|
| 55 |
+
3. En CHD, les segments non assignés (`classe 0/NA`) sont exclus des stats et de l'AFC.
|
| 56 |
+
|
| 57 |
+
## 2.3 Niveau tokenisation
|
| 58 |
+
1. **Différence quanteda vs spaCy**: la définition d'un token n'est pas identique.
|
| 59 |
+
2. **Double tokenisation en chemin spaCy** (spaCy puis quanteda) -> pertes supplémentaires possibles.
|
| 60 |
+
3. **Option élisions FR** activée à un endroit et pas l'autre (R nettoyage vs Python spaCy).
|
| 61 |
+
4. **Suppression des chiffres** appliquée à deux niveaux possibles.
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 3) Fonctions IRaMuTeQ-like auditées et risques par fonction
|
| 66 |
+
|
| 67 |
+
### 3.1 `preparer_entrees_chd_iramuteq(...)`
|
| 68 |
+
- Fait: nettoyage + tokenisation quanteda + stopwords + DFM.
|
| 69 |
+
- Risques:
|
| 70 |
+
- incohérence si ce prétraitement n'est pas exactement celui utilisé en pipeline principal,
|
| 71 |
+
- variation stopwords selon langue/source.
|
| 72 |
+
|
| 73 |
+
### 3.2 `calculer_chd_iramuteq(...)`
|
| 74 |
+
- Fait: conversion DFM -> matrice, binarisation optionnelle, appel `CHD(...)` historique.
|
| 75 |
+
- Risques:
|
| 76 |
+
- DFM trop pauvre (<2 lignes/colonnes),
|
| 77 |
+
- effet de la binarisation sur la stabilité des classes.
|
| 78 |
+
|
| 79 |
+
### 3.3 `reconstruire_classes_terminales_iramuteq(...)`
|
| 80 |
+
- Fait: reconstruit classes terminales avec `find.terminales` + propagation descendants.
|
| 81 |
+
- Risques:
|
| 82 |
+
- classes 0 (non assignées) nombreuses si `mincl` trop strict,
|
| 83 |
+
- conflit entre `nb_classes_cible` et terminales reconstruites.
|
| 84 |
+
|
| 85 |
+
### 3.4 `construire_stats_classes_iramuteq(...)`
|
| 86 |
+
- Fait: chi² signé doc-terme, exclut explicitement classes 0.
|
| 87 |
+
- Risques:
|
| 88 |
+
- impression de "perte" de segments côté utilisateur (en réalité exclus post-classement).
|
| 89 |
+
|
| 90 |
+
---
|
| 91 |
+
|
| 92 |
+
## 4) Réponse précise à "comment est tokenisé ?"
|
| 93 |
+
|
| 94 |
+
### En mode Lexique FR
|
| 95 |
+
- `quanteda::tokens(textes_lexique, remove_punct=..., remove_numbers=...)`.
|
| 96 |
+
- Les formes sont ensuite potentiellement abaissées en minuscules, stopwords retirés, puis DFM.
|
| 97 |
+
|
| 98 |
+
### En mode spaCy
|
| 99 |
+
1. spaCy segmente selon son tokenizer modèle langue.
|
| 100 |
+
2. Filtrage optionnel POS + nombres + élisions.
|
| 101 |
+
3. Lemme (ou surface) en minuscules.
|
| 102 |
+
4. Reconstruction du texte par espaces.
|
| 103 |
+
5. Nouvelle tokenisation quanteda pour DFM.
|
| 104 |
+
|
| 105 |
+
**Conséquence**: le mode spaCy n'est pas équivalent byte-à-byte au mode lexique, même avec "mêmes paramètres UI".
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## 5) Réponse précise à "comment est découpé le texte ?"
|
| 110 |
+
|
| 111 |
+
1. Découpage en segments d'abord par `split_segments(corpus, segment_size=...)` (fonction externe).
|
| 112 |
+
2. Ensuite, il existe un **découpage analytique secondaire**: les segments peuvent être retirés du jeu analysé si vidés par les filtres (`dfm_trim`, suppression docs vides, classes 0).
|
| 113 |
+
|
| 114 |
+
Donc il faut distinguer:
|
| 115 |
+
- **segments bruts après split**,
|
| 116 |
+
- **segments conservés pour CHD**,
|
| 117 |
+
- **segments classés (hors 0/NA)**.
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## 6) Erreurs possibles (checklist de diagnostic)
|
| 122 |
+
|
| 123 |
+
1. Version package `rainette` différente -> comportement `split_segments` différent.
|
| 124 |
+
2. `segment_size` identique mais options de nettoyage non alignées.
|
| 125 |
+
3. `supprimer_apostrophes` active d'un côté, inactive de l'autre.
|
| 126 |
+
4. `supprimer_chiffres` active à la fois en nettoyage R et en spaCy.
|
| 127 |
+
5. `remove_punct`/`remove_numbers` quanteda différents.
|
| 128 |
+
6. `min_docfreq` trop élevé -> segments vidés.
|
| 129 |
+
7. POS trop restrictif en spaCy -> segments vidés.
|
| 130 |
+
8. Stopwords retirés puis fallback partiel.
|
| 131 |
+
9. Docnames renommés (`make.unique`) perturbant certains alignements aval.
|
| 132 |
+
10. Exclusion classes 0 interprétée comme erreur de découpage.
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
## 7) Recommandations immédiates (très concrètes)
|
| 137 |
+
|
| 138 |
+
1. Logger séparément 3 compteurs à chaque run:
|
| 139 |
+
- `N_split = ndoc(corpus)` après `split_segments`,
|
| 140 |
+
- `N_non_vide = ndoc(dfm_obj)` après `supprimer_docs_vides_dfm`,
|
| 141 |
+
- `N_classes = ndoc(filtered_corpus_ok)` après exclusion classes 0/NA.
|
| 142 |
+
2. Afficher ces 3 nombres dans l'UI, pas un seul "nombre de segments".
|
| 143 |
+
3. Figer version `rainette` dans l'environnement pour reproductibilité.
|
| 144 |
+
4. Pour audit comparatif, désactiver temporairement: POS filter, stopwords, `min_docfreq > 1`, suppression apostrophes/chiffres.
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## 8) Conclusion opérationnelle
|
| 149 |
+
|
| 150 |
+
Le script ne fait pas qu'un découpage unique. Il y a:
|
| 151 |
+
1. une segmentation primaire (`split_segments`),
|
| 152 |
+
2. un prétraitement/tokenisation,
|
| 153 |
+
3. des filtrages qui peuvent éliminer des segments,
|
| 154 |
+
4. une exclusion finale des segments non classés.
|
| 155 |
+
|
| 156 |
+
Si vous observez un "nombre de segments" différent malgré corpus + segment_size identiques, l'écart provient très probablement de (a) version de `split_segments` ou (b) filtrages post-segmentation, et non du seul paramètre de segmentation UI.
|
help/rapport_dendrogramme_iramuteq.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rapport — construction du dendrogramme IRaMuTeQ-like
|
| 2 |
+
|
| 3 |
+
## Constat
|
| 4 |
+
Le dendrogramme pouvait afficher plus de classes que les classes finales réellement exploitées (ex: 16 affichées vs 6 classes finales).
|
| 5 |
+
|
| 6 |
+
## Vérification de `iramuteq_clone_v3`
|
| 7 |
+
Dans `iramuteq_clone_v3/tabchddist.py`, la découpe de l'arbre est pilotée explicitement par le nombre de classes cible (`clnb`) via :
|
| 8 |
+
|
| 9 |
+
- `classes<-as.data.frame(cutree(as.hclust(chd), k=clnb))[,1]`
|
| 10 |
+
|
| 11 |
+
Cette logique borne donc l'affichage/les statistiques à un nombre de classes final choisi.
|
| 12 |
+
|
| 13 |
+
## Correctif appliqué
|
| 14 |
+
Dans `tracer_dendrogramme_chd_iramuteq` :
|
| 15 |
+
|
| 16 |
+
1. Source de vérité prioritaire = classes présentes dans `res_stats_df$Classe` (résultat final affiché côté UI).
|
| 17 |
+
2. Sinon repli sur `classes` documentaires.
|
| 18 |
+
3. Projection sur `terminales` par index de classe (classe `i` -> `terminales[i]`) avec filtrage des indices invalides.
|
| 19 |
+
4. Pas de repli "toutes les feuilles" tant qu'une liste de classes finales utiles existe.
|
| 20 |
+
|
| 21 |
+
## Effet attendu
|
| 22 |
+
Le dendrogramme affiche uniquement les classes finales réellement présentes dans les résultats CHD (et donc alignées avec l'analyse affichée).
|
help/rapport_mapping_chd_iramuteq_like.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rapport de mapping IRaMuTeQ-like (CHD)
|
| 2 |
+
|
| 3 |
+
## 1) État de la branche demandée
|
| 4 |
+
|
| 5 |
+
- La branche Git locale `iramuteq-like` n'existe pas dans ce dépôt au moment de l'audit (`git branch -a` ne retourne que `work`).
|
| 6 |
+
- L'analyse a donc été réalisée sur le **module fonctionnel `iramuteq-like/`** présent dans la branche courante.
|
| 7 |
+
|
| 8 |
+
## 2) Mapping bout-en-bout: du concordancier au dendrogramme, à l'AFC (nuage de points) et à l'UI
|
| 9 |
+
|
| 10 |
+
### 2.1 Concordancier
|
| 11 |
+
|
| 12 |
+
- **Calcul des termes par classe**: `iramuteq-like/concordancier-iramuteq.R`
|
| 13 |
+
- `.generer_concordancier_iramuteq_termes()`:
|
| 14 |
+
- filtre par classe,
|
| 15 |
+
- filtre p-value (`p` / `p_value`) selon `max_p`,
|
| 16 |
+
- conserve chi2 positifs,
|
| 17 |
+
- trie par chi2 décroissant.
|
| 18 |
+
- **Génération HTML**: `generer_concordancier_iramuteq_html()`:
|
| 19 |
+
- prend `segments_by_class`, `res_stats_df`, `textes_indexation`,
|
| 20 |
+
- applique fallback top chi2 si aucun terme filtré,
|
| 21 |
+
- détecte segments contenant les termes,
|
| 22 |
+
- surligne et écrit le HTML final.
|
| 23 |
+
|
| 24 |
+
### 2.2 Calcul CHD IRaMuTeQ-like
|
| 25 |
+
|
| 26 |
+
- **Moteur CHD**: `iramuteq-like/chd_iramuteq.R`
|
| 27 |
+
- `calculer_chd_iramuteq()`:
|
| 28 |
+
- charge scripts historiques (`anacor.R`, `CHD.R`, `chdtxt.R`),
|
| 29 |
+
- binarise la matrice documentaire,
|
| 30 |
+
- lance `CHD(...)`.
|
| 31 |
+
- `reconstruire_classes_terminales_iramuteq()`:
|
| 32 |
+
- reconstruit classes documentaires finales depuis `n1`, `list_mere`, `list_fille`,
|
| 33 |
+
- applique logique `mincl` auto/manuel,
|
| 34 |
+
- mappe docs vers classes terminales.
|
| 35 |
+
- `construire_stats_classes_iramuteq()`:
|
| 36 |
+
- calcule stats par classe/terme,
|
| 37 |
+
- chi2 signé et p-value (présence/absence doc),
|
| 38 |
+
- structure compatible avec le concordancier et les sorties UI.
|
| 39 |
+
|
| 40 |
+
### 2.3 Dendrogramme CHD
|
| 41 |
+
|
| 42 |
+
- **Entrée UI du dendrogramme**: `iramuteq-like/dendogramme_iramuteq.R`
|
| 43 |
+
- `tracer_dendogramme_iramuteq_ui()` récupère l'objet CHD (`rv$res$chd`, `rv$res_chd`, fallback) et appelle le traceur principal.
|
| 44 |
+
- **Traceur principal**: `iramuteq-like/chd_iramuteq.R`
|
| 45 |
+
- `tracer_dendrogramme_chd_iramuteq()`:
|
| 46 |
+
- reconstruit la topologie depuis `list_fille`,
|
| 47 |
+
- identifie racine/feuilles terminales,
|
| 48 |
+
- calcule layout arborescent,
|
| 49 |
+
- annote classes + pourcentages + termes top chi2,
|
| 50 |
+
- gère orientation `vertical` / `horizontal`.
|
| 51 |
+
|
| 52 |
+
### 2.4 Nuage de points (AFC classes × termes / variables)
|
| 53 |
+
|
| 54 |
+
- **Calcul AFC**: `iramuteq-like/afc_iramuteq.R`
|
| 55 |
+
- `calculer_afc_classes_termes()` construit la table classes × termes,
|
| 56 |
+
- `executer_afc_classes_termes()` exécute l'AFC,
|
| 57 |
+
- `tracer_afc_classes_seules()` affiche classes,
|
| 58 |
+
- `tracer_afc_classes_termes()` affiche classes + termes.
|
| 59 |
+
- **AFC variables étoilées**: même fichier
|
| 60 |
+
- `calculer_afc_classes_variables()`, `executer_afc_classes_variables()`, `tracer_afc_classes_variables()`.
|
| 61 |
+
|
| 62 |
+
### 2.5 Nuage de mots par classe
|
| 63 |
+
|
| 64 |
+
- **Génération assets PNG**: `iramuteq-like/wordcloud_iramuteq.R`
|
| 65 |
+
- **Exposition UI (iframe/img)**: `iramuteq-like/server_events_lancer_iramuteq.R` via `output$ui_wordcloud_iramuteq`.
|
| 66 |
+
|
| 67 |
+
### 2.6 Orchestration serveur (pipeline)
|
| 68 |
+
|
| 69 |
+
- **Pipeline principal au clic Lancer**: `iramuteq-like/server_events_lancer_iramuteq.R`
|
| 70 |
+
- préparation corpus,
|
| 71 |
+
- exécution CHD,
|
| 72 |
+
- calcul stats,
|
| 73 |
+
- calcul AFC,
|
| 74 |
+
- génération wordclouds,
|
| 75 |
+
- génération concordancier HTML,
|
| 76 |
+
- alimentation de `rv$*` pour l'UI.
|
| 77 |
+
|
| 78 |
+
### 2.7 Couches UI
|
| 79 |
+
|
| 80 |
+
- **Panneau résultats IRaMuTeQ-like**: `iramuteq-like/affichage_iramuteq-like.R`
|
| 81 |
+
- sous-onglets Dendrogramme / Stats CHD / Concordancier / Nuage de mots.
|
| 82 |
+
- **Rendu serveur principal Shiny**: `app.R`
|
| 83 |
+
- `output$plot_chd_iramuteq_dendro`,
|
| 84 |
+
- `output$ui_tables_stats_chd_iramuteq`,
|
| 85 |
+
- `output$plot_afc_classes`, `output$plot_afc`, `output$plot_afc_vars`,
|
| 86 |
+
- intègre `register_events_lancer(...)`.
|
| 87 |
+
|
| 88 |
+
## 3) Changement réalisé: méthode d'affichage du dendrogramme
|
| 89 |
+
|
| 90 |
+
### 3.1 Objectif
|
| 91 |
+
|
| 92 |
+
Réduire la surcharge visuelle quand les annotations de termes par classe se chevauchent.
|
| 93 |
+
|
| 94 |
+
### 3.2 Implémentation
|
| 95 |
+
|
| 96 |
+
- **Nouvelle option UI** dans l'onglet Dendrogramme:
|
| 97 |
+
- `Méthode d'affichage`
|
| 98 |
+
- `standard` (comportement historique: termes près des classes)
|
| 99 |
+
- `compact` (nouveau défaut: termes regroupés en légende).
|
| 100 |
+
- **Propagation des paramètres**:
|
| 101 |
+
- `app.R` transmet `input$iramuteq_dendro_display_method` au traceur.
|
| 102 |
+
- `tracer_dendogramme_iramuteq_ui()` accepte `display_method` et le passe à `tracer_dendrogramme_chd_iramuteq()`.
|
| 103 |
+
- **Rendu compact dans le traceur**:
|
| 104 |
+
- en `vertical`: légende textuelle regroupée en bas (`mtext`).
|
| 105 |
+
- en `horizontal`: légende compacte en bas à droite (`legend`).
|
| 106 |
+
- titres ajustés pour expliciter le mode `compact`.
|
| 107 |
+
|
| 108 |
+
### 3.3 Impact
|
| 109 |
+
|
| 110 |
+
- Pas de changement du calcul CHD ni des classes.
|
| 111 |
+
- Changement purement visuel du dendrogramme côté affichage.
|
| 112 |
+
- Le mode `standard` reste disponible.
|
help/rapport_tokenisation_iramuteq_clone_v3.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rapport ciblé — chaîne de tokenisation `iramuteq_clone_v3` et pipeline `iramuteq-like`
|
| 2 |
+
|
| 3 |
+
## Contexte
|
| 4 |
+
|
| 5 |
+
Ce rapport répond à deux points :
|
| 6 |
+
1. Auditer **la chaîne de tokenisation** du dossier `iramuteq_clone_v3`.
|
| 7 |
+
2. Vérifier, côté application actuelle, le mode **`iramuteq-like`** (qui force `lexique_fr`) sans analyse spaCy.
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 1) Résultat principal sur `iramuteq_clone_v3`
|
| 12 |
+
|
| 13 |
+
### 1.1 Limite structurelle du dépôt cloné
|
| 14 |
+
|
| 15 |
+
Dans ce dépôt, le cœur de préparation du corpus texte (module `corpus`) est référencé mais **n'est pas présent** dans l'arborescence locale fournie.
|
| 16 |
+
|
| 17 |
+
- `iramuteq.py` importe `from corpus import Builder, SubBuilder, MergeClusters`. Cela indique que la chaîne de tokenisation principale est attendue dans `corpus.py`/module `corpus`.【F:iramuteq_clone_v3/iramuteq.py†L43-L44】
|
| 18 |
+
- `textcheckcorpus.py` importe aussi `Corpus` depuis `corpus`, confirmant la dépendance centrale à ce module absent localement.【F:iramuteq_clone_v3/textcheckcorpus.py†L16-L17】
|
| 19 |
+
|
| 20 |
+
👉 Conséquence : on peut auditer les options visibles en amont/aval, mais **pas reconstruire à 100% la tokenisation interne historique** sans ce module.
|
| 21 |
+
|
| 22 |
+
### 1.2 Ce qui est explicitement vérifiable dans `iramuteq_clone_v3`
|
| 23 |
+
|
| 24 |
+
#### a) Contrôle du format corpus (pas de tokenisation lexicale ici)
|
| 25 |
+
`textcheckcorpus.py` vérifie surtout la syntaxe des lignes/étoiles (`****`, variables `*`, thématiques `-*`, espaces interdits, etc.), mais pas un découpage lexical type tokenizer moderne.【F:iramuteq_clone_v3/textcheckcorpus.py†L19-L39】
|
| 26 |
+
|
| 27 |
+
#### b) Options de CHD (aval de la tokenisation)
|
| 28 |
+
`textreinert.py` expose des paramètres de classification (pas la tokenisation elle-même), notamment :
|
| 29 |
+
- `classif_mode` (simple/double/uci),
|
| 30 |
+
- `tailleuc1`, `tailleuc2` (taille des unités de contexte),
|
| 31 |
+
- `mincl`, `minforme`,
|
| 32 |
+
- `nbcl_p1`,
|
| 33 |
+
- `max_actives`,
|
| 34 |
+
- `svdmethod`,
|
| 35 |
+
- `mode.patate`.【F:iramuteq_clone_v3/textreinert.py†L70-L81】
|
| 36 |
+
|
| 37 |
+
Il génère ensuite des matrices clairsemées à partir des UC/UCE/UCI via des méthodes de `corpus` (`make_and_write_sparse_matrix_from_uc`, `..._from_uces`, `..._from_uci`).【F:iramuteq_clone_v3/textreinert.py†L33-L41】
|
| 38 |
+
|
| 39 |
+
#### c) Règle mincl auto côté scripts R historiques
|
| 40 |
+
Dans `Rscripts/chdtxt.R`, si `mincl == 0`, la formule automatique est :
|
| 41 |
+
- `ind = nbcl * 2` en mode double, sinon `ind = nbcl` ;
|
| 42 |
+
- `mincl = round(nrow(classeuce1)/ind)`.【F:iramuteq_clone_v3/Rscripts/chdtxt.R†L278-L281】
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## 2) Pipeline **réel** utilisé par ton application `iramuteq-like` (sans spaCy)
|
| 47 |
+
|
| 48 |
+
Tu as raison : en mode `iramuteq-like`, la source est forcée sur `lexique_fr`.
|
| 49 |
+
|
| 50 |
+
### 2.1 Forçage `lexique_fr`
|
| 51 |
+
Dans `server_events_lancer.R`, si `modele_chd == "iramuteq"`, la source dictionnaire est forcée à `lexique_fr` avec log explicite.【F:R/server_events_lancer.R†L314-L317】
|
| 52 |
+
|
| 53 |
+
### 2.2 Chaîne de prétraitement effectivement appliquée
|
| 54 |
+
Avant tokenisation, la pipeline applique :
|
| 55 |
+
- nettoyage caractères,
|
| 56 |
+
- minuscules (option),
|
| 57 |
+
- suppression chiffres (option),
|
| 58 |
+
- suppression apostrophes/élisions (option).【F:R/server_events_lancer.R†L295-L301】
|
| 59 |
+
|
| 60 |
+
Ensuite, en branche `lexique_fr`, la pipeline passe par `executer_pipeline_lexique(...)`.【F:R/server_events_lancer.R†L342-L347】
|
| 61 |
+
|
| 62 |
+
### 2.3 Tokenisation et options actives en branche `lexique_fr`
|
| 63 |
+
Dans `executer_pipeline_lexique` :
|
| 64 |
+
- tokenisation `quanteda::tokens(...)` sur `textes_lexique`,
|
| 65 |
+
- options actives : `remove_punct = input$supprimer_ponctuation`, `remove_numbers = input$supprimer_chiffres`.
|
| 66 |
+
- puis construction DFM avec logique stopwords/min_docfreq via `construire_dfm_avec_fallback_stopwords(...)`.【F:R/pipeline_lexique_analysis.R†L67-L82】
|
| 67 |
+
|
| 68 |
+
### 2.4 Option de normalisation en minuscules au niveau DFM
|
| 69 |
+
La fonction `construire_dfm_avec_fallback_stopwords(...)` applique `tokens_tolower(...)` (avec ou sans retrait stopwords), donc la représentation finale est normalisée en minuscules côté DFM.【F:R/chd_afc_pipeline.R†L63-L70】
|
| 70 |
+
|
| 71 |
+
### 2.5 Option de préparation spécifique `iramuteq-like`
|
| 72 |
+
Le module `iramuteq-like/chd_iramuteq.R` normalise explicitement les options de nettoyage suivantes :
|
| 73 |
+
- `nettoyage_caracteres`,
|
| 74 |
+
- `forcer_minuscules_avant`,
|
| 75 |
+
- `supprimer_chiffres`,
|
| 76 |
+
- `supprimer_apostrophes`,
|
| 77 |
+
- `supprimer_ponctuation`,
|
| 78 |
+
- `retirer_stopwords`.
|
| 79 |
+
Puis tokenise avec `quanteda::tokens(remove_punct, remove_numbers)` et option stopwords quanteda selon langue.【F:iramuteq-like/chd_iramuteq.R†L26-L33】【F:iramuteq-like/chd_iramuteq.R†L80-L89】
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## 3) Rapport des options actives (mode `iramuteq-like`)
|
| 84 |
+
|
| 85 |
+
### 3.1 Options effectivement actives dans ta chaîne (et où)
|
| 86 |
+
|
| 87 |
+
- `nettoyage_caracteres` : actif avant tokenisation via `appliquer_nettoyage_et_minuscules(...)`.【F:R/server_events_lancer.R†L295-L299】
|
| 88 |
+
- `forcer_minuscules_avant` : actif avant tokenisation via `appliquer_nettoyage_et_minuscules(...)`.【F:R/server_events_lancer.R†L298-L299】
|
| 89 |
+
- `supprimer_chiffres` : actif avant tokenisation + actif dans `tokens(remove_numbers=...)`.【F:R/server_events_lancer.R†L299-L300】【F:R/pipeline_lexique_analysis.R†L69-L71】
|
| 90 |
+
- `supprimer_apostrophes` : actif avant tokenisation via nettoyage texte.【F:R/server_events_lancer.R†L300-L301】
|
| 91 |
+
- `supprimer_ponctuation` : actif dans `tokens(remove_punct=...)` en branche `lexique_fr`.【F:R/pipeline_lexique_analysis.R†L69-L70】
|
| 92 |
+
- `retirer_stopwords` : actif dans `construire_dfm_avec_fallback_stopwords(...)` (avec fallback si DFM trop pauvre).【F:R/chd_afc_pipeline.R†L55-L66】【F:R/chd_afc_pipeline.R†L87-L93】
|
| 93 |
+
- `min_docfreq` : actif au `dfm_trim(min_docfreq=...)`.【F:R/chd_afc_pipeline.R†L77-L77】
|
| 94 |
+
- `filtrage_morpho` + `lexique_utiliser_lemmes` : actifs en branche lexique via filtrage cgram/lemmatisation forme→lemme si demandé.【F:R/pipeline_lexique_analysis.R†L31-L36】【F:R/pipeline_lexique_analysis.R†L48-L61】
|
| 95 |
+
|
| 96 |
+
### 3.2 Options non pertinentes pour ce mode
|
| 97 |
+
- Les options spaCy ne sont pas utilisées quand `modele_chd="iramuteq"` puisque la source est forcée `lexique_fr`.【F:R/server_events_lancer.R†L314-L317】
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
|
| 101 |
+
## 4) Pourquoi l'écart de formes peut persister malgré “même paramétrage”
|
| 102 |
+
|
| 103 |
+
Même en excluant spaCy, un écart peut persister entre IRaMuTeQ desktop et ton app si la tokenisation historique interne d'IRaMuTeQ (module `corpus` absent ici) diffère de ta chaîne `quanteda + lexique_fr + trim`.
|
| 104 |
+
|
| 105 |
+
En pratique, les points sensibles sont :
|
| 106 |
+
- règles de segmentation des unités de contexte (UC/UCE/UCI) et calcul d'actives/supplémentaires dans `corpus` historique (non auditable dans ce clone incomplet),
|
| 107 |
+
- ordre exact nettoyage → lemmatisation → tokenisation,
|
| 108 |
+
- traitement des élisions/apostrophes,
|
| 109 |
+
- seuil `min_docfreq`, suppression stopwords, fallback, et suppression segments vides avant CHD.
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## 5) Recommandation opérationnelle
|
| 114 |
+
|
| 115 |
+
Pour une comparaison “iso-IRaMuTeQ” stricte, je recommande un mode audit en 2 passes :
|
| 116 |
+
|
| 117 |
+
1. **Pass A (référence max proche IRaMuTeQ)**
|
| 118 |
+
- `retirer_stopwords = FALSE`
|
| 119 |
+
- `min_docfreq = 1`
|
| 120 |
+
- pas de filtrage morpho
|
| 121 |
+
- pas de suppression apostrophes/chiffres supplémentaire
|
| 122 |
+
|
| 123 |
+
2. **Pass B (tes réglages actuels)**
|
| 124 |
+
- comparer : nb tokens bruts, nb formes DFM, nb hapax, nb segments non vides.
|
| 125 |
+
|
| 126 |
+
Et ajouter un export intermédiaire (tokens par segment) pour expliquer où apparaissent les +194 formes.
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 6) Corrections concrètes recommandées pour rapprocher les scores
|
| 132 |
+
|
| 133 |
+
1. **Figer une prépa corpus unique avant `quanteda::tokens`**
|
| 134 |
+
- Utiliser `iramuteq-like/textprepa_iramuteq.py` pour générer un texte préparé stable et auditable (mêmes règles à chaque run), puis tokeniser ce texte côté R.
|
| 135 |
+
|
| 136 |
+
2. **Éviter les doubles effets de nettoyage**
|
| 137 |
+
- Si `iramuteq-like/textprepa_iramuteq.py` est activé, neutraliser le nettoyage redondant en amont pour ne pas supprimer deux fois chiffres/élisions.
|
| 138 |
+
|
| 139 |
+
3. **Comparer les formes à 3 niveaux**
|
| 140 |
+
- Niveau A: formes post-prépa (sortie `output_tokens` de `iramuteq-like/textprepa_iramuteq.py`),
|
| 141 |
+
- Niveau B: formes post-`quanteda::tokens`,
|
| 142 |
+
- Niveau C: formes post-`dfm_trim`.
|
| 143 |
+
|
| 144 |
+
4. **Conserver un mode “audit IRaMuTeQ”**
|
| 145 |
+
- `min_docfreq=1`, `retirer_stopwords=FALSE`, pas de filtrage morpho, pour isoler l'écart de tokenisation.
|
| 146 |
+
|
| 147 |
+
5. **Comparer sur un sous-corpus fixe**
|
| 148 |
+
- Exporter 50–100 segments identiques entre IRaMuTeQ desktop et app, comparer les listes de formes exactes segment par segment.
|
iramuteq-like/CHD.R
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Author: Pierre Ratinaud
|
| 2 |
+
#Copyright (c) 2008-2020 Pierre Ratinaud
|
| 3 |
+
#License: GNU/GPL
|
| 4 |
+
|
| 5 |
+
pp<-function(txt,val) {
|
| 6 |
+
d<-paste(txt,' : ')
|
| 7 |
+
print(paste(d,val))
|
| 8 |
+
}
|
| 9 |
+
MyChiSq<-function(x,sc,n){
|
| 10 |
+
sr<-rowSums(x)
|
| 11 |
+
E <- outer(sr, sc, "*")/n
|
| 12 |
+
STAT<-sum(((x - E)^2)/E)
|
| 13 |
+
STAT
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
MySpeedChi <- function(x,sc) {
|
| 17 |
+
sr <-rowSums(x)
|
| 18 |
+
E <- outer(sr, sc, "*")
|
| 19 |
+
STAT<-sum((x - E)^2/E)
|
| 20 |
+
STAT
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
find.max <- function(dtable, chitable, compte, rmax, maxinter, sc, TT) {
|
| 24 |
+
ln <- which(dtable==1, arr.ind=TRUE)
|
| 25 |
+
lo <- list()
|
| 26 |
+
lo[1:nrow(dtable)] <- 0
|
| 27 |
+
for (k in 1:nrow(ln)) {lo[[ln[k,1]]]<-append(lo[[ln[k,1]]],ln[k,2])}
|
| 28 |
+
for (k in 1:nrow(dtable)) {lo[[k]] <- lo[[k]][-1]}
|
| 29 |
+
## lo<-lo[-c(1,length(lo))]
|
| 30 |
+
## for (l in lo) {
|
| 31 |
+
## compte <- compte + 1
|
| 32 |
+
## chitable[1,l]<-chitable[1,l]+1
|
| 33 |
+
## chitable[2,l]<-chitable[2,l]-1
|
| 34 |
+
## chi<-MyChiSq(chitable,sc,TT)
|
| 35 |
+
## if (chi>maxinter) {
|
| 36 |
+
## maxinter<-chi
|
| 37 |
+
## rmax<-compte
|
| 38 |
+
## }
|
| 39 |
+
#}
|
| 40 |
+
lo<-lo[-c(1)]
|
| 41 |
+
for (l in lo) {
|
| 42 |
+
chi<-MyChiSq(chitable,sc,TT)
|
| 43 |
+
if (chi>maxinter) {
|
| 44 |
+
maxinter<-chi
|
| 45 |
+
rmax<-compte
|
| 46 |
+
}
|
| 47 |
+
compte <- compte + 1
|
| 48 |
+
chitable[1,l]<-chitable[1,l]+1
|
| 49 |
+
chitable[2,l]<-chitable[2,l]-1
|
| 50 |
+
}
|
| 51 |
+
res <- list(maxinter=maxinter, rmax=rmax)
|
| 52 |
+
res
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
CHD<-function(data.in, x=9, mode.patate = FALSE, svd.method, libsvdc.path=NULL){
|
| 60 |
+
# sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt')
|
| 61 |
+
dataori <- data.in
|
| 62 |
+
row.names(dataori) <- rownames(data.in)
|
| 63 |
+
dtable <- data.in
|
| 64 |
+
colnames(dtable) <- 1:ncol(dtable)
|
| 65 |
+
dout <- NULL
|
| 66 |
+
rowelim<-NULL
|
| 67 |
+
pp('ncol entree : ',ncol(dtable))
|
| 68 |
+
pp('nrow entree',nrow(dtable))
|
| 69 |
+
listcol <- list()
|
| 70 |
+
listmere <- list()
|
| 71 |
+
list_fille <- list()
|
| 72 |
+
print('vire colonnes vides en entree')#FIXME : il ne doit pas y avoir de colonnes vides en entree !!
|
| 73 |
+
sdt<-colSums(dtable)
|
| 74 |
+
if (min(sdt)==0)
|
| 75 |
+
dtable<-dtable[,-which(sdt==0)]
|
| 76 |
+
print('vire lignes vides en entree')
|
| 77 |
+
sdt<-rowSums(dtable)
|
| 78 |
+
if (min(sdt)==0) {
|
| 79 |
+
rowelim<-as.integer(rownames(dtable)[which(sdt==0)])
|
| 80 |
+
print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
|
| 81 |
+
print(rowelim)
|
| 82 |
+
print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
|
| 83 |
+
dtable<-dtable[-which(sdt==0),]
|
| 84 |
+
}
|
| 85 |
+
mere<-1
|
| 86 |
+
for (i in 1:x) {
|
| 87 |
+
clnb<-(i*2)
|
| 88 |
+
listmere[[clnb]]<-mere
|
| 89 |
+
listmere[[clnb+1]]<-mere
|
| 90 |
+
list_fille[[mere]] <- c(clnb,clnb+1)
|
| 91 |
+
listcol[[clnb]]<-vector()
|
| 92 |
+
listcol[[clnb+1]]<-vector()
|
| 93 |
+
#extraction du premier facteur de l'afc
|
| 94 |
+
print('afc')
|
| 95 |
+
pp('taille dtable dans boucle (col/row)',c(ncol(dtable),nrow(dtable)))
|
| 96 |
+
afc<-boostana(dtable, nd=1, svd.method = svd.method, libsvdc.path=libsvdc.path)
|
| 97 |
+
pp('SV',afc$singular.values)
|
| 98 |
+
pp('V.P.', afc$eigen.values)
|
| 99 |
+
coordrow <- as.matrix(afc$row.scores[,1])
|
| 100 |
+
coordrowori<-coordrow
|
| 101 |
+
row.names(coordrow)<-rownames(dtable)
|
| 102 |
+
coordrow <- cbind(coordrow,1:nrow(dtable))
|
| 103 |
+
print('deb recherche meilleur partition')
|
| 104 |
+
ordert <- as.matrix(coordrow[order(coordrow[,1]),])
|
| 105 |
+
ordert <- cbind(ordert, 1:nrow(ordert))
|
| 106 |
+
ordert <- ordert[order(ordert[,2]),]
|
| 107 |
+
|
| 108 |
+
listinter<-vector()
|
| 109 |
+
listlim<-vector()
|
| 110 |
+
dtable <- dtable[order(ordert[,3]),]
|
| 111 |
+
sc <- colSums(dtable)
|
| 112 |
+
TT <- sum(sc)
|
| 113 |
+
sc1 <- dtable[1,]
|
| 114 |
+
sc2 <- colSums(dtable) - sc1
|
| 115 |
+
chitable <- rbind(sc1, sc2)
|
| 116 |
+
compte <- 1
|
| 117 |
+
maxinter <- 0
|
| 118 |
+
rmax <- NULL
|
| 119 |
+
|
| 120 |
+
inert <- find.max(dtable, chitable, compte, rmax, maxinter, sc, TT)
|
| 121 |
+
print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@')
|
| 122 |
+
pp('max inter phase 1', inert$maxinter/TT)#max(listinter))
|
| 123 |
+
print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@')
|
| 124 |
+
ordert <- ordert[order(ordert[,3]),]
|
| 125 |
+
listclasse<-ifelse(coordrowori<=ordert[(inert$rmax),1],clnb,clnb+1)
|
| 126 |
+
dtable <- dtable[order(ordert[,2]),]
|
| 127 |
+
cl<-listclasse
|
| 128 |
+
pp('TT',TT)
|
| 129 |
+
#dtable<-cbind(dtable,'cl'= as.vector(cl))
|
| 130 |
+
|
| 131 |
+
N1<-length(listclasse[listclasse==clnb])
|
| 132 |
+
N2<-length(listclasse[listclasse==clnb+1])
|
| 133 |
+
pp('N1',N1)
|
| 134 |
+
pp('N2',N2)
|
| 135 |
+
###################################################################
|
| 136 |
+
# reclassement des individus #
|
| 137 |
+
###################################################################
|
| 138 |
+
if (!mode.patate) {
|
| 139 |
+
malcl<-1000000000000
|
| 140 |
+
it<-0
|
| 141 |
+
listsub<-list()
|
| 142 |
+
#in boucle
|
| 143 |
+
ln <- which(dtable==1, arr.ind=TRUE)
|
| 144 |
+
lnz <- list()
|
| 145 |
+
lnz[1:nrow(dtable)] <- 0
|
| 146 |
+
|
| 147 |
+
for (k in 1:nrow(ln)) {lnz[[ln[k,1]]]<-append(lnz[[ln[k,1]]],ln[k,2])}
|
| 148 |
+
for (k in 1:nrow(dtable)) {lnz[[k]] <- lnz[[k]][-1]}
|
| 149 |
+
TT<-sum(dtable)
|
| 150 |
+
|
| 151 |
+
while (malcl!=0 & N1>=5 & N2>=5) {
|
| 152 |
+
it<-it+1
|
| 153 |
+
listsub[[it]]<-vector()
|
| 154 |
+
txt <- paste('nombre iteration', it)
|
| 155 |
+
#pp('nombre iteration',it)
|
| 156 |
+
vdelta<-vector()
|
| 157 |
+
#dtable[,'cl']<-cl
|
| 158 |
+
t1<-dtable[which(cl[,1]==clnb),]#[,-ncol(dtable)]
|
| 159 |
+
t2<-dtable[which(cl[,1]==clnb+1),]#[,-ncol(dtable)]
|
| 160 |
+
ncolt<-ncol(t1)
|
| 161 |
+
#pp('ncolt',ncolt)
|
| 162 |
+
|
| 163 |
+
if (N1 != 1) {
|
| 164 |
+
sc1<-colSums(t1)
|
| 165 |
+
} else {
|
| 166 |
+
sc1 <- t1
|
| 167 |
+
}
|
| 168 |
+
if (N2 != 1) {
|
| 169 |
+
sc2<-colSums(t2)
|
| 170 |
+
} else {
|
| 171 |
+
sc2 <- t2
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
sc<-sc1+sc2
|
| 175 |
+
chtableori<-rbind(sc1,sc2)
|
| 176 |
+
chtable<-chtableori
|
| 177 |
+
interori<-MyChiSq(chtableori,sc,TT)/TT#chisq.test(chtableori)$statistic#/TT
|
| 178 |
+
txt <- paste(txt, ' - interori : ',interori)
|
| 179 |
+
#pp('interori',interori)
|
| 180 |
+
|
| 181 |
+
N1<-nrow(t1)
|
| 182 |
+
N2<-nrow(t2)
|
| 183 |
+
|
| 184 |
+
#pp('N1',N1)
|
| 185 |
+
#pp('N2',N2)
|
| 186 |
+
txt <- paste(txt, 'N1:', N1,'-N2:',N2)
|
| 187 |
+
print(txt)
|
| 188 |
+
compte <- 0
|
| 189 |
+
for (l in lnz){
|
| 190 |
+
chi.in<-chtable
|
| 191 |
+
compte <- compte + 1
|
| 192 |
+
if(cl[compte]==clnb){
|
| 193 |
+
chtable[1,l]<-chtable[1,l]-1
|
| 194 |
+
chtable[2,l]<-chtable[2,l]+1
|
| 195 |
+
}else{
|
| 196 |
+
chtable[1,l]<-chtable[1,l]+1
|
| 197 |
+
chtable[2,l]<-chtable[2,l]-1
|
| 198 |
+
}
|
| 199 |
+
interswitch<-MyChiSq(chtable,sc,TT)/TT#chisq.test(chtable)$statistic/TT
|
| 200 |
+
ws<-interori-interswitch
|
| 201 |
+
|
| 202 |
+
if (ws<0){
|
| 203 |
+
interori<-interswitch
|
| 204 |
+
if(cl[compte]==clnb){
|
| 205 |
+
#sc1<-chtable[1,]
|
| 206 |
+
#sc2<-chtable[2,]
|
| 207 |
+
cl[compte]<-clnb+1
|
| 208 |
+
listsub[[it]]<-append(listsub[[it]],compte)
|
| 209 |
+
} else {
|
| 210 |
+
#sc1<-chtable[1,]
|
| 211 |
+
#sc2<-chtable[2,]
|
| 212 |
+
cl[compte]<-clnb
|
| 213 |
+
listsub[[it]]<-append(listsub[[it]],compte)
|
| 214 |
+
}
|
| 215 |
+
vdelta<-append(vdelta,compte)
|
| 216 |
+
} else {
|
| 217 |
+
chtable<-chi.in
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
# for (val in vdelta) {
|
| 221 |
+
# if (cl[val]==clnb) {
|
| 222 |
+
# cl[val]<-clnb+1
|
| 223 |
+
# listsub[[it]]<-append(listsub[[it]],val)
|
| 224 |
+
# }else {
|
| 225 |
+
# cl[val]<-clnb
|
| 226 |
+
# listsub[[it]]<-append(listsub[[it]],val)
|
| 227 |
+
# }
|
| 228 |
+
# }
|
| 229 |
+
print('###################################')
|
| 230 |
+
print('longueur < 0')
|
| 231 |
+
malcl<-length(vdelta)
|
| 232 |
+
|
| 233 |
+
if ((it>1)&&(!is.logical(listsub[[it]]))&&(!is.logical(listsub[[it-1]]))){
|
| 234 |
+
if (all(listsub[[it]]==listsub[[(it-1)]])){
|
| 235 |
+
malcl<-0
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
print(malcl)
|
| 239 |
+
print('###################################')
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
#dtable<-cbind(dtable,'cl'=as.vector(cl))
|
| 243 |
+
#dtable[,'cl'] <-as.vector(cl)
|
| 244 |
+
#######################################################################
|
| 245 |
+
# Fin reclassement des individus #
|
| 246 |
+
#######################################################################
|
| 247 |
+
# if (!(length(cl[cl==clnb])==1 || length(cl[cl==clnb+1])==1)) {
|
| 248 |
+
#t1<-dtable[dtable[,'cl']==clnb,][,-ncol(dtable)]
|
| 249 |
+
#t2<-dtable[dtable[,'cl']==clnb+1,][,-ncol(dtable)]
|
| 250 |
+
t1<-dtable[which(cl[,1]==clnb),]#[,-ncol(dtable)]
|
| 251 |
+
t2<-dtable[which(cl[,1]==clnb+1),]#[,-ncol(dtable)]
|
| 252 |
+
if (inherits(t1, "numeric")) {
|
| 253 |
+
sc1 <- as.vector(t1)
|
| 254 |
+
nrowt1 <- 1
|
| 255 |
+
} else {
|
| 256 |
+
sc1 <- colSums(t1)
|
| 257 |
+
nrowt1 <- nrow(t1)
|
| 258 |
+
}
|
| 259 |
+
if (inherits(t2, "numeric")) {
|
| 260 |
+
sc2 <- as.vector(t2)
|
| 261 |
+
nrowt2 <- 1
|
| 262 |
+
} else {
|
| 263 |
+
sc2 <- colSums(t2)
|
| 264 |
+
nrowt2 <- nrow(t2)
|
| 265 |
+
}
|
| 266 |
+
chtable<-rbind(sc1,sc2)
|
| 267 |
+
inter<-chisq.test(chtable)$statistic/TT
|
| 268 |
+
pp('last inter',inter)
|
| 269 |
+
print('=====================')
|
| 270 |
+
#calcul de la specificite des colonnes
|
| 271 |
+
mint<-min(nrowt1,nrowt2)
|
| 272 |
+
maxt<-max(nrowt1,nrowt2)
|
| 273 |
+
seuil<-round((1.9*(maxt/mint))+1.9,digit=6)
|
| 274 |
+
#sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt')
|
| 275 |
+
# print('ATTENTION SEUIL 3,84')
|
| 276 |
+
# seuil<-3.84
|
| 277 |
+
pp('seuil',seuil)
|
| 278 |
+
sominf<-0
|
| 279 |
+
nv<-0
|
| 280 |
+
nz<-0
|
| 281 |
+
ncclnb<-0
|
| 282 |
+
ncclnbp<-0
|
| 283 |
+
NN1<-0
|
| 284 |
+
NN2<-0
|
| 285 |
+
maxchip<-0
|
| 286 |
+
nbzeroun<-0
|
| 287 |
+
res1<-0
|
| 288 |
+
res2<-0
|
| 289 |
+
nbseuil<-0
|
| 290 |
+
nbexe<-0
|
| 291 |
+
nbcontrib<-0
|
| 292 |
+
cn<-colnames(dtable)
|
| 293 |
+
#another try#########################################
|
| 294 |
+
one <- cbind(sc1,sc2)
|
| 295 |
+
cols <- c(length(which(cl==clnb)), length(which(cl==clnb+1)))
|
| 296 |
+
print(cols)
|
| 297 |
+
colss <- matrix(rep(cols,ncol(dtable)), ncol=2, byrow=TRUE)
|
| 298 |
+
zero <- colss - one
|
| 299 |
+
rows <- cbind(rowSums(zero), rowSums(one))
|
| 300 |
+
n <- sum(cols)
|
| 301 |
+
for (m in 1:nrow(rows)) {
|
| 302 |
+
obs <- t(matrix(c(zero[m,],one[m,]),2,2))
|
| 303 |
+
E <- outer(rows[m,],cols,'*')/n
|
| 304 |
+
if ((min(obs[2,])==0) & (min(obs[1,])!=0)) {
|
| 305 |
+
chi <- seuil + 1
|
| 306 |
+
} else if ((min(obs[1,])==0) & (min(obs[2,])!=0)) {
|
| 307 |
+
chi <- seuil - 1
|
| 308 |
+
} else if (any(obs < 10)) {
|
| 309 |
+
chi <- sum((abs(obs - E) - 0.5)^2 / E)
|
| 310 |
+
} else {
|
| 311 |
+
chi <- sum(((obs - E)^2)/E)
|
| 312 |
+
}
|
| 313 |
+
if (is.na(chi)) {
|
| 314 |
+
chi <- 0
|
| 315 |
+
}
|
| 316 |
+
if (chi > seuil) {
|
| 317 |
+
if (obs[2,1] < E[2,1]) {
|
| 318 |
+
listcol[[clnb]]<-append(listcol[[clnb]],cn[m])
|
| 319 |
+
ncclnb<-ncclnb+1
|
| 320 |
+
} else if (obs[2,2] < E[2,2]) {
|
| 321 |
+
listcol[[clnb+1]]<-append(listcol[[clnb+1]],cn[m])
|
| 322 |
+
ncclnbp<-ncclnbp+1
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
######################################################
|
| 327 |
+
print('resultats elim item')
|
| 328 |
+
pp(clnb+1,length(listcol[[clnb+1]]))
|
| 329 |
+
pp(clnb,length(listcol[[clnb]]))
|
| 330 |
+
pp('ncclnb',ncclnb)
|
| 331 |
+
pp('ncclnbp',ncclnbp)
|
| 332 |
+
listrownamedtable<-rownames(dtable)
|
| 333 |
+
listrownamedtable<-as.integer(listrownamedtable)
|
| 334 |
+
newcol<-vector(length=nrow(dataori))
|
| 335 |
+
#remplissage de la nouvelle colonne avec les nouvelles classes
|
| 336 |
+
print('remplissage')
|
| 337 |
+
# num<-0
|
| 338 |
+
newcol[listrownamedtable] <- cl[,1]
|
| 339 |
+
#recuperation de la classe precedante pour les cases vides
|
| 340 |
+
print('recuperation classes precedentes')
|
| 341 |
+
if (i!=1) {
|
| 342 |
+
newcol[which(newcol==0)] <- dout[,ncol(dout)][which(newcol==0)]
|
| 343 |
+
}
|
| 344 |
+
if(!is.null(rowelim)) {
|
| 345 |
+
newcol[rowelim] <- 0
|
| 346 |
+
}
|
| 347 |
+
tailleclasse<-as.matrix(summary(as.factor(as.character(newcol))))
|
| 348 |
+
print('tailleclasse')
|
| 349 |
+
print(tailleclasse)
|
| 350 |
+
tailleclasse<-as.matrix(tailleclasse[!(rownames(tailleclasse)==0),])
|
| 351 |
+
plusgrand<-which.max(tailleclasse)
|
| 352 |
+
#???????????????????????????????????
|
| 353 |
+
#Si 2 classes ont des effectifs egaux, on prend la premiere de la liste...
|
| 354 |
+
if (length(plusgrand)>1) {
|
| 355 |
+
plusgrand<-plusgrand[1]
|
| 356 |
+
}
|
| 357 |
+
#????????????????????????????????????
|
| 358 |
+
|
| 359 |
+
#constuction du prochain tableau a analyser
|
| 360 |
+
print('construction tableau suivant')
|
| 361 |
+
dout<-cbind(dout,newcol)
|
| 362 |
+
classe<-as.integer(rownames(tailleclasse)[plusgrand])
|
| 363 |
+
dtable<-dataori[which(newcol==classe),]
|
| 364 |
+
row.names(dtable)<-rownames(dataori)[which(newcol==classe)]
|
| 365 |
+
colnames(dtable) <- 1:ncol(dtable)
|
| 366 |
+
mere<-classe
|
| 367 |
+
listcolelim<-listcol[[as.integer(classe)]]
|
| 368 |
+
mother<-listmere[[as.integer(classe)]]
|
| 369 |
+
while (mother!=1) {
|
| 370 |
+
listcolelim<-append(listcolelim,listcol[[mother]])
|
| 371 |
+
mother<-listmere[[mother]]
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
listcolelim<-sort(unique(listcolelim))
|
| 375 |
+
pp('avant',ncol(dtable))
|
| 376 |
+
if (!is.logical(listcolelim)){
|
| 377 |
+
print('elimination colonne')
|
| 378 |
+
#dtable<-dtable[,-listcolelim]
|
| 379 |
+
dtable<-dtable[,!(colnames(dtable) %in% listcolelim)]
|
| 380 |
+
}
|
| 381 |
+
pp('apres',ncol(dtable))
|
| 382 |
+
#elimination des colonnes ne contenant que des 0
|
| 383 |
+
print('vire colonne inf 3 dans boucle')
|
| 384 |
+
sdt<-colSums(dtable)
|
| 385 |
+
if (min(sdt)<=3)
|
| 386 |
+
dtable<-dtable[,-which(sdt<=3)]
|
| 387 |
+
|
| 388 |
+
#elimination des lignes ne contenant que des 0
|
| 389 |
+
print('vire ligne vide dans boucle')
|
| 390 |
+
if (ncol(dtable)==1) {
|
| 391 |
+
sdt<-dtable[,1]
|
| 392 |
+
} else {
|
| 393 |
+
sdt<-rowSums(dtable)
|
| 394 |
+
}
|
| 395 |
+
if (min(sdt)==0) {
|
| 396 |
+
rowelim<-as.integer(rownames(dtable)[which(sdt==0)])
|
| 397 |
+
print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
|
| 398 |
+
print(rowelim)
|
| 399 |
+
print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
|
| 400 |
+
dtable<-dtable[-which(sdt==0),]
|
| 401 |
+
}
|
| 402 |
+
# }
|
| 403 |
+
}
|
| 404 |
+
# sink()
|
| 405 |
+
res <- list(n1 = dout, list_mere = listmere, list_fille = list_fille)
|
| 406 |
+
res
|
| 407 |
+
}
|
iramuteq-like/afc_helpers_iramuteq.R
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: afc_helpers.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
# Il facilite la maintenance en explicitant le périmètre et les points d'intégration.
|
| 4 |
+
# Module AFC - helpers de contextualisation des termes
|
| 5 |
+
# Ce fichier regroupe des utilitaires AFC, notamment la construction de segments
|
| 6 |
+
# exemples associés aux termes caractéristiques de classes.
|
| 7 |
+
|
| 8 |
+
construire_segments_exemples_afc <- function(termes_stats, dfm_obj, corpus_obj, max_chars = 220) {
|
| 9 |
+
if (is.null(termes_stats) || nrow(termes_stats) == 0 || is.null(dfm_obj) || is.null(corpus_obj)) return(termes_stats)
|
| 10 |
+
if (!all(c("Terme", "Classe_max") %in% names(termes_stats))) return(termes_stats)
|
| 11 |
+
|
| 12 |
+
classes_docs <- normaliser_classes(docvars(corpus_obj)$Classes)
|
| 13 |
+
textes <- as.character(corpus_obj)
|
| 14 |
+
|
| 15 |
+
if (length(classes_docs) != ndoc(dfm_obj) || length(textes) != ndoc(dfm_obj)) return(termes_stats)
|
| 16 |
+
|
| 17 |
+
mat <- as.matrix(dfm_obj)
|
| 18 |
+
termes_stats$Segment_texte <- NA_character_
|
| 19 |
+
|
| 20 |
+
for (i in seq_len(nrow(termes_stats))) {
|
| 21 |
+
terme <- as.character(termes_stats$Terme[i])
|
| 22 |
+
classe_num <- suppressWarnings(as.numeric(gsub("^Classe\\s+", "", as.character(termes_stats$Classe_max[i]))))
|
| 23 |
+
|
| 24 |
+
if (is.na(classe_num) || !nzchar(terme) || !(terme %in% colnames(mat))) next
|
| 25 |
+
|
| 26 |
+
idx <- which(classes_docs == as.character(classe_num) & mat[, terme] > 0)
|
| 27 |
+
if (length(idx) == 0) next
|
| 28 |
+
|
| 29 |
+
# Segment le plus représentatif pour le terme dans la classe (fréquence max du terme)
|
| 30 |
+
i_best <- idx[which.max(mat[idx, terme])]
|
| 31 |
+
seg <- gsub("\\s+", " ", trimws(textes[i_best]), perl = TRUE)
|
| 32 |
+
if (!nzchar(seg)) next
|
| 33 |
+
if (nchar(seg) > max_chars) seg <- paste0(substr(seg, 1, max_chars - 1), "…")
|
| 34 |
+
|
| 35 |
+
termes_stats$Segment_texte[i] <- seg
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
termes_stats
|
| 39 |
+
}
|
iramuteq-like/afc_iramuteq.R
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: afc.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
# Il facilite la maintenance en explicitant le périmètre et les points d'intégration.
|
| 4 |
+
# afc.R
|
| 5 |
+
# AFC (Analyse Factorielle des Correspondances) pour classes × termes et classes × variables étoilées
|
| 6 |
+
# Dépendance principale : FactoMineR (CA)
|
| 7 |
+
# Le fichier fournit les fonctions attendues par app.R, sans changer la logique globale :
|
| 8 |
+
# - calcul des tables de contingence
|
| 9 |
+
# - exécution de l'AFC
|
| 10 |
+
# - statistiques (fréquence, chi2, p-value)
|
| 11 |
+
# - tracés avec axes centrés (0 au centre) et limites symétriques
|
| 12 |
+
# - option anti-chevauchement des labels (placement en spirale + tests de collision)
|
| 13 |
+
|
| 14 |
+
verifier_factominer <- function() {
|
| 15 |
+
if (!requireNamespace("FactoMineR", quietly = TRUE)) {
|
| 16 |
+
stop("AFC : le package 'FactoMineR' n'est pas installé ou indisponible dans l'environnement.")
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
# Limites symétriques autour de 0 pour centrer le graphe (0,0) au centre
|
| 21 |
+
calculer_lim_sym <- function(x, y, marge = 0.08) {
|
| 22 |
+
x <- x[is.finite(x)]
|
| 23 |
+
y <- y[is.finite(y)]
|
| 24 |
+
if (length(x) == 0 || length(y) == 0) return(c(-1, 1))
|
| 25 |
+
m <- max(abs(c(x, y)))
|
| 26 |
+
if (!is.finite(m) || m == 0) m <- 1
|
| 27 |
+
m <- m * (1 + marge)
|
| 28 |
+
c(-m, m)
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
# Test de chevauchement rectangles
|
| 32 |
+
.rectangles_chevauchent <- function(r1, r2) {
|
| 33 |
+
!(r1$xmax < r2$xmin || r2$xmax < r1$xmin || r1$ymax < r2$ymin || r2$ymax < r1$ymin)
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# Placement anti-chevauchement des labels
|
| 37 |
+
# Technique : on place les mots un par un. Si collision avec un label déjà placé, on déplace le label le long d'une spirale
|
| 38 |
+
# (rayon croissant + angle) jusqu'à trouver une position non collisionnante, ou jusqu'à max_iter.
|
| 39 |
+
placer_labels_sans_chevauchement_spirale <- function(x, y, labels, cex_vec, max_iter = 220) {
|
| 40 |
+
x <- as.numeric(x)
|
| 41 |
+
y <- as.numeric(y)
|
| 42 |
+
n <- length(labels)
|
| 43 |
+
if (n == 0) return(list(x = x, y = y))
|
| 44 |
+
|
| 45 |
+
if (length(cex_vec) == 1) cex_vec <- rep(cex_vec, n)
|
| 46 |
+
cex_vec <- as.numeric(cex_vec)
|
| 47 |
+
cex_vec[!is.finite(cex_vec)] <- 1
|
| 48 |
+
|
| 49 |
+
usr <- par("usr")
|
| 50 |
+
rx <- usr[2] - usr[1]
|
| 51 |
+
ry <- usr[4] - usr[3]
|
| 52 |
+
if (!is.finite(rx) || rx == 0) rx <- 1
|
| 53 |
+
if (!is.finite(ry) || ry == 0) ry <- 1
|
| 54 |
+
|
| 55 |
+
# Pas de déplacement relatif à l'étendue du graphe
|
| 56 |
+
pas <- 0.012 * max(rx, ry)
|
| 57 |
+
|
| 58 |
+
x2 <- x
|
| 59 |
+
y2 <- y
|
| 60 |
+
|
| 61 |
+
rects <- vector("list", n)
|
| 62 |
+
|
| 63 |
+
for (i in seq_len(n)) {
|
| 64 |
+
lab <- labels[i]
|
| 65 |
+
cexi <- cex_vec[i]
|
| 66 |
+
|
| 67 |
+
wi <- strwidth(lab, units = "user", cex = cexi)
|
| 68 |
+
hi <- strheight(lab, units = "user", cex = cexi)
|
| 69 |
+
|
| 70 |
+
if (!is.finite(wi) || wi == 0) wi <- 0.02 * rx
|
| 71 |
+
if (!is.finite(hi) || hi == 0) hi <- 0.02 * ry
|
| 72 |
+
|
| 73 |
+
xi <- x2[i]
|
| 74 |
+
yi <- y2[i]
|
| 75 |
+
|
| 76 |
+
ri <- list(
|
| 77 |
+
xmin = xi - wi / 2, xmax = xi + wi / 2,
|
| 78 |
+
ymin = yi - hi / 2, ymax = yi + hi / 2
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
collision <- FALSE
|
| 82 |
+
if (i > 1) {
|
| 83 |
+
for (j in seq_len(i - 1)) {
|
| 84 |
+
if (!is.null(rects[[j]]) && .rectangles_chevauchent(ri, rects[[j]])) {
|
| 85 |
+
collision <- TRUE
|
| 86 |
+
break
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if (!collision) {
|
| 92 |
+
rects[[i]] <- ri
|
| 93 |
+
next
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
# Recherche en spirale
|
| 97 |
+
angle <- 0
|
| 98 |
+
rayon <- pas
|
| 99 |
+
|
| 100 |
+
trouve <- FALSE
|
| 101 |
+
for (k in seq_len(max_iter)) {
|
| 102 |
+
angle <- angle + 0.65
|
| 103 |
+
rayon <- rayon + pas * 0.15
|
| 104 |
+
|
| 105 |
+
xi_try <- x[i] + rayon * cos(angle)
|
| 106 |
+
yi_try <- y[i] + rayon * sin(angle)
|
| 107 |
+
|
| 108 |
+
ri_try <- list(
|
| 109 |
+
xmin = xi_try - wi / 2, xmax = xi_try + wi / 2,
|
| 110 |
+
ymin = yi_try - hi / 2, ymax = yi_try + hi / 2
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
collision2 <- FALSE
|
| 114 |
+
if (i > 1) {
|
| 115 |
+
for (j in seq_len(i - 1)) {
|
| 116 |
+
if (!is.null(rects[[j]]) && .rectangles_chevauchent(ri_try, rects[[j]])) {
|
| 117 |
+
collision2 <- TRUE
|
| 118 |
+
break
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
if (!collision2) {
|
| 124 |
+
x2[i] <- xi_try
|
| 125 |
+
y2[i] <- yi_try
|
| 126 |
+
rects[[i]] <- ri_try
|
| 127 |
+
trouve <- TRUE
|
| 128 |
+
break
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
if (!trouve) {
|
| 133 |
+
rects[[i]] <- ri
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
list(x = x2, y = y2)
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
# Calcul des résidus standardisés par colonne (terme/modalité) et par classe
|
| 141 |
+
.calculer_residus_standardises <- function(tab) {
|
| 142 |
+
tab <- as.matrix(tab)
|
| 143 |
+
if (any(tab < 0, na.rm = TRUE)) stop("AFC : table de contingence invalide (valeurs négatives).")
|
| 144 |
+
n <- sum(tab)
|
| 145 |
+
if (!is.finite(n) || n <= 0) stop("AFC : table de contingence vide (somme nulle).")
|
| 146 |
+
|
| 147 |
+
rs <- rowSums(tab)
|
| 148 |
+
cs <- colSums(tab)
|
| 149 |
+
attendu <- outer(rs, cs) / n
|
| 150 |
+
|
| 151 |
+
# Résidus standardisés : (O - E) / sqrt(E)
|
| 152 |
+
res <- (tab - attendu) / sqrt(attendu)
|
| 153 |
+
res[!is.finite(res)] <- 0
|
| 154 |
+
list(attendu = attendu, residus = res)
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
# Statistiques globales d'association d'une colonne (terme/modalité) avec les classes
|
| 158 |
+
# chi2 et p_value : test sur la distribution observée vs attendue sur les classes
|
| 159 |
+
.calculer_stats_colonnes <- function(tab, seuil_p = 0.05) {
|
| 160 |
+
tab <- as.matrix(tab)
|
| 161 |
+
n <- sum(tab)
|
| 162 |
+
rs <- rowSums(tab)
|
| 163 |
+
cs <- colSums(tab)
|
| 164 |
+
k <- nrow(tab)
|
| 165 |
+
|
| 166 |
+
exp_mat <- outer(rs, cs) / n
|
| 167 |
+
exp_mat[!is.finite(exp_mat)] <- 0
|
| 168 |
+
|
| 169 |
+
out <- data.frame(
|
| 170 |
+
feature = colnames(tab),
|
| 171 |
+
frequency = as.numeric(cs),
|
| 172 |
+
chi2 = NA_real_,
|
| 173 |
+
p_value = NA_real_,
|
| 174 |
+
Classe_max = NA_character_,
|
| 175 |
+
resid_max = NA_real_,
|
| 176 |
+
stringsAsFactors = FALSE
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
rr <- .calculer_residus_standardises(tab)$residus
|
| 180 |
+
|
| 181 |
+
for (j in seq_len(ncol(tab))) {
|
| 182 |
+
obs <- tab[, j]
|
| 183 |
+
expv <- exp_mat[, j]
|
| 184 |
+
|
| 185 |
+
# chi2 global sur classes
|
| 186 |
+
good <- expv > 0
|
| 187 |
+
if (sum(good) >= 2) {
|
| 188 |
+
chi2j <- sum((obs[good] - expv[good])^2 / expv[good])
|
| 189 |
+
df <- max(1, sum(good) - 1)
|
| 190 |
+
pv <- suppressWarnings(stats::pchisq(chi2j, df = df, lower.tail = FALSE))
|
| 191 |
+
} else {
|
| 192 |
+
chi2j <- NA_real_
|
| 193 |
+
pv <- NA_real_
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
out$chi2[j] <- as.numeric(chi2j)
|
| 197 |
+
out$p_value[j] <- as.numeric(pv)
|
| 198 |
+
|
| 199 |
+
# classe de surreprésentation : max résidu standardisé
|
| 200 |
+
rj <- rr[, j]
|
| 201 |
+
imax <- which.max(rj)
|
| 202 |
+
out$Classe_max[j] <- rownames(tab)[imax]
|
| 203 |
+
out$resid_max[j] <- rj[imax]
|
| 204 |
+
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
out
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
# Construit la table Classes × Termes à partir d'un dfm quanteda
|
| 211 |
+
calculer_table_classes_termes <- function(dfm_obj, groupes, termes_cibles = NULL, max_termes = 400) {
|
| 212 |
+
if (!inherits(dfm_obj, "dfm")) stop("AFC : dfm_obj doit être un objet quanteda::dfm.")
|
| 213 |
+
if (is.null(groupes) || length(groupes) != quanteda::ndoc(dfm_obj)) stop("AFC : 'groupes' doit avoir la même longueur que ndoc(dfm_obj).")
|
| 214 |
+
|
| 215 |
+
g <- suppressWarnings(as.integer(groupes))
|
| 216 |
+
g[!is.finite(g) | is.na(g) | g <= 0] <- NA_integer_
|
| 217 |
+
|
| 218 |
+
keep <- !is.na(g)
|
| 219 |
+
dfm2 <- dfm_obj[keep, ]
|
| 220 |
+
g2 <- as.character(g[keep])
|
| 221 |
+
|
| 222 |
+
if (quanteda::ndoc(dfm2) < 2) stop("AFC : pas assez de segments classés (hors NA).")
|
| 223 |
+
|
| 224 |
+
# Sélection des termes
|
| 225 |
+
if (!is.null(termes_cibles)) {
|
| 226 |
+
termes_cibles <- unique(as.character(termes_cibles))
|
| 227 |
+
termes_cibles <- termes_cibles[!is.na(termes_cibles) & nzchar(termes_cibles)]
|
| 228 |
+
termes_cibles <- intersect(termes_cibles, quanteda::featnames(dfm2))
|
| 229 |
+
if (length(termes_cibles) >= 2) {
|
| 230 |
+
dfm2 <- dfm2[, termes_cibles]
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
if (quanteda::nfeat(dfm2) > max_termes) {
|
| 235 |
+
top <- quanteda::topfeatures(dfm2, n = max_termes)
|
| 236 |
+
dfm2 <- dfm2[, names(top)]
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
if (quanteda::nfeat(dfm2) < 2) stop("AFC : moins de 2 termes disponibles pour l'AFC.")
|
| 240 |
+
|
| 241 |
+
dfm_g <- quanteda::dfm_group(dfm2, groups = g2)
|
| 242 |
+
mat <- as.matrix(dfm_g)
|
| 243 |
+
if (nrow(mat) < 2 || ncol(mat) < 2) stop("AFC : table Classes × Termes trop petite.")
|
| 244 |
+
|
| 245 |
+
# Noms des classes pour affichage
|
| 246 |
+
rn <- rownames(mat)
|
| 247 |
+
rn2 <- paste0("Classe ", rn)
|
| 248 |
+
rownames(mat) <- rn2
|
| 249 |
+
|
| 250 |
+
mat
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
# Exécution AFC classes × termes
|
| 254 |
+
executer_afc_classes <- function(dfm_obj, groupes, termes_cibles = NULL, max_termes = 400, seuil_p = 0.05, rv = NULL) {
|
| 255 |
+
verifier_factominer()
|
| 256 |
+
|
| 257 |
+
tab <- calculer_table_classes_termes(
|
| 258 |
+
dfm_obj = dfm_obj,
|
| 259 |
+
groupes = groupes,
|
| 260 |
+
termes_cibles = termes_cibles,
|
| 261 |
+
max_termes = max_termes
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
ca <- FactoMineR::CA(tab, graph = FALSE)
|
| 265 |
+
|
| 266 |
+
rowcoord <- ca$row$coord
|
| 267 |
+
colcoord <- ca$col$coord
|
| 268 |
+
|
| 269 |
+
# Stats des termes (globales sur la table AFC)
|
| 270 |
+
st <- .calculer_stats_colonnes(tab, seuil_p = seuil_p)
|
| 271 |
+
names(st)[names(st) == "feature"] <- "Terme"
|
| 272 |
+
|
| 273 |
+
# Harmonisation : noms de classes déjà "Classe X"
|
| 274 |
+
st$Classe_max <- as.character(st$Classe_max)
|
| 275 |
+
# Dans .calculer_stats_colonnes, les classes sont rownames(tab), donc déjà "Classe X"
|
| 276 |
+
|
| 277 |
+
list(
|
| 278 |
+
table = tab,
|
| 279 |
+
ca = ca,
|
| 280 |
+
rowcoord = rowcoord,
|
| 281 |
+
colcoord = colcoord,
|
| 282 |
+
termes_stats = st,
|
| 283 |
+
seuil_p = seuil_p
|
| 284 |
+
)
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
# Tracé AFC des classes uniquement
|
| 288 |
+
tracer_afc_classes_seules <- function(obj, axes = c(1, 2), cex_labels = 1.0) {
|
| 289 |
+
if (is.null(obj$ca) || is.null(obj$rowcoord)) stop("AFC classes : objet incomplet.")
|
| 290 |
+
ax1 <- axes[1]
|
| 291 |
+
ax2 <- axes[2]
|
| 292 |
+
|
| 293 |
+
rc <- obj$rowcoord
|
| 294 |
+
x_c <- rc[, ax1]
|
| 295 |
+
y_c <- rc[, ax2]
|
| 296 |
+
|
| 297 |
+
lim <- calculer_lim_sym(x_c, y_c)
|
| 298 |
+
plot(
|
| 299 |
+
0, 0,
|
| 300 |
+
type = "n",
|
| 301 |
+
xlab = paste0("Axe ", ax1),
|
| 302 |
+
ylab = paste0("Axe ", ax2),
|
| 303 |
+
xlim = lim, ylim = lim
|
| 304 |
+
)
|
| 305 |
+
abline(h = 0, v = 0, col = "gray80")
|
| 306 |
+
|
| 307 |
+
points(x_c, y_c, pch = 19, cex = 1.3)
|
| 308 |
+
text(x_c, y_c, labels = rownames(rc), pos = 3, cex = cex_labels)
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
# Tracé AFC classes + termes
|
| 312 |
+
# - couleurs des mots : selon la classe où ils sont le plus surreprésentés (Classe_max)
|
| 313 |
+
# - taille des mots : au choix (frequency ou chi2)
|
| 314 |
+
# - anti-chevauchement : option activer_repel
|
| 315 |
+
tracer_afc_classes_termes <- function(
|
| 316 |
+
obj,
|
| 317 |
+
axes = c(1, 2),
|
| 318 |
+
top_termes = 120,
|
| 319 |
+
taille_sel = c("frequency", "chi2"),
|
| 320 |
+
activer_repel = TRUE,
|
| 321 |
+
cex_min = 0.8,
|
| 322 |
+
cex_max = 2.0,
|
| 323 |
+
repel_max_iter = 220
|
| 324 |
+
) {
|
| 325 |
+
if (is.null(obj$ca) || is.null(obj$rowcoord) || is.null(obj$colcoord) || is.null(obj$termes_stats)) {
|
| 326 |
+
stop("AFC : objet incomplet (coordonnées / stats manquantes).")
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
taille_sel <- match.arg(taille_sel)
|
| 330 |
+
ax1 <- axes[1]
|
| 331 |
+
ax2 <- axes[2]
|
| 332 |
+
|
| 333 |
+
rc <- obj$rowcoord
|
| 334 |
+
cc <- obj$colcoord
|
| 335 |
+
st <- obj$termes_stats
|
| 336 |
+
|
| 337 |
+
st <- st[!is.na(st$Terme) & nzchar(st$Terme), , drop = FALSE]
|
| 338 |
+
st <- st[order(-st$frequency), , drop = FALSE]
|
| 339 |
+
|
| 340 |
+
if (!is.null(top_termes) && is.finite(top_termes) && nrow(st) > top_termes) {
|
| 341 |
+
st <- st[seq_len(top_termes), , drop = FALSE]
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
st <- st[st$Terme %in% rownames(cc), , drop = FALSE]
|
| 345 |
+
if (nrow(st) < 2) {
|
| 346 |
+
plot.new()
|
| 347 |
+
text(0.5, 0.5, "AFC : pas assez de termes à tracer.", cex = 1.1)
|
| 348 |
+
return(invisible(NULL))
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
mots <- st$Terme
|
| 352 |
+
xy_m <- cc[mots, , drop = FALSE]
|
| 353 |
+
x_m <- xy_m[, ax1]
|
| 354 |
+
y_m <- xy_m[, ax2]
|
| 355 |
+
|
| 356 |
+
x_c <- rc[, ax1]
|
| 357 |
+
y_c <- rc[, ax2]
|
| 358 |
+
|
| 359 |
+
lim <- calculer_lim_sym(c(x_m, x_c), c(y_m, y_c))
|
| 360 |
+
plot(
|
| 361 |
+
0, 0,
|
| 362 |
+
type = "n",
|
| 363 |
+
xlab = paste0("Axe ", ax1),
|
| 364 |
+
ylab = paste0("Axe ", ax2),
|
| 365 |
+
xlim = lim, ylim = lim
|
| 366 |
+
)
|
| 367 |
+
abline(h = 0, v = 0, col = "gray80")
|
| 368 |
+
|
| 369 |
+
# Note sur la technique : si activer_repel=TRUE, on applique un placement itératif en spirale
|
| 370 |
+
# avec tests de collision rectangles afin de réduire le chevauchement des étiquettes.
|
| 371 |
+
# Cela ne garantit pas 0 collision dans tous les cas, mais réduit fortement les superpositions.
|
| 372 |
+
points(x_c, y_c, pch = 19, cex = 1.25)
|
| 373 |
+
text(x_c, y_c, labels = rownames(rc), pos = 3, cex = 1.0)
|
| 374 |
+
|
| 375 |
+
# Couleurs par classe
|
| 376 |
+
classes <- sort(unique(rownames(rc)))
|
| 377 |
+
ncl <- length(classes)
|
| 378 |
+
pal <- if (requireNamespace("RColorBrewer", quietly = TRUE) && ncl <= 8) {
|
| 379 |
+
RColorBrewer::brewer.pal(max(3, ncl), "Set2")[seq_len(ncl)]
|
| 380 |
+
} else {
|
| 381 |
+
grDevices::rainbow(ncl)
|
| 382 |
+
}
|
| 383 |
+
col_map <- setNames(pal, classes)
|
| 384 |
+
col_m <- col_map[st$Classe_max]
|
| 385 |
+
col_m[is.na(col_m)] <- "gray40"
|
| 386 |
+
|
| 387 |
+
# Tailles
|
| 388 |
+
poids <- if (taille_sel == "chi2") st$chi2 else st$frequency
|
| 389 |
+
poids <- suppressWarnings(as.numeric(poids))
|
| 390 |
+
poids[!is.finite(poids)] <- 0
|
| 391 |
+
poids <- pmax(0, poids)
|
| 392 |
+
|
| 393 |
+
if (max(poids) == min(poids)) {
|
| 394 |
+
cex_vec <- rep((cex_min + cex_max) / 2, length(poids))
|
| 395 |
+
} else {
|
| 396 |
+
v <- sqrt(poids)
|
| 397 |
+
v <- (v - min(v)) / (max(v) - min(v))
|
| 398 |
+
cex_vec <- cex_min + v * (cex_max - cex_min)
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
if (isTRUE(activer_repel)) {
|
| 402 |
+
coords <- placer_labels_sans_chevauchement_spirale(
|
| 403 |
+
x = x_m, y = y_m, labels = mots, cex_vec = cex_vec, max_iter = repel_max_iter
|
| 404 |
+
)
|
| 405 |
+
x_lab <- coords$x
|
| 406 |
+
y_lab <- coords$y
|
| 407 |
+
} else {
|
| 408 |
+
x_lab <- x_m
|
| 409 |
+
y_lab <- y_m
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
points(x_lab, y_lab, pch = 16, cex = 0.5, col = col_m)
|
| 413 |
+
text(x_lab, y_lab, labels = mots, cex = cex_vec, col = col_m)
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
# Fallback : extraction de modalités depuis une ligne IRaMuTeQ (si docvars vides)
|
| 417 |
+
.extraire_modalites_depuis_ligne_iramuteq <- function(textes) {
|
| 418 |
+
if (length(textes) == 0) return(vector("list", 0))
|
| 419 |
+
res <- vector("list", length(textes))
|
| 420 |
+
for (i in seq_along(textes)) {
|
| 421 |
+
tx <- textes[i]
|
| 422 |
+
if (is.na(tx) || !nzchar(tx)) {
|
| 423 |
+
res[[i]] <- character(0)
|
| 424 |
+
next
|
| 425 |
+
}
|
| 426 |
+
# On cherche des tokens commençant par * dans les 400 premiers caractères
|
| 427 |
+
head <- substr(tx, 1, 400)
|
| 428 |
+
mods <- unlist(regmatches(head, gregexpr("\\*[A-Za-z0-9_\\-]+", head, perl = TRUE)), use.names = FALSE)
|
| 429 |
+
mods <- unique(mods)
|
| 430 |
+
mods <- mods[!is.na(mods) & nzchar(mods)]
|
| 431 |
+
res[[i]] <- mods
|
| 432 |
+
}
|
| 433 |
+
res
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
# Construction table Classes × Modalités à partir des docvars (ou fallback texte)
|
| 437 |
+
calculer_table_classes_modalites <- function(corpus_aligne, groupes, max_modalites = 400) {
|
| 438 |
+
if (is.null(corpus_aligne)) stop("AFC variables étoilées : corpus_aligne requis.")
|
| 439 |
+
dv <- quanteda::docvars(corpus_aligne)
|
| 440 |
+
|
| 441 |
+
g <- suppressWarnings(as.integer(groupes))
|
| 442 |
+
g[!is.finite(g) | is.na(g) | g <= 0] <- NA_integer_
|
| 443 |
+
keep <- !is.na(g)
|
| 444 |
+
if (sum(keep) < 2) stop("AFC variables étoilées : pas assez de segments classés (hors NA).")
|
| 445 |
+
|
| 446 |
+
dv2 <- dv[keep, , drop = FALSE]
|
| 447 |
+
g2 <- as.character(g[keep])
|
| 448 |
+
|
| 449 |
+
# Colonnes candidates : on exclut les colonnes techniques
|
| 450 |
+
exclure <- c("segment_source", "Classes")
|
| 451 |
+
cols <- setdiff(colnames(dv2), exclure)
|
| 452 |
+
cols <- cols[!is.na(cols) & nzchar(cols)]
|
| 453 |
+
|
| 454 |
+
# Priorité aux variables étoilées importées depuis l'entête IRaMuTeQ.
|
| 455 |
+
# Certains imports exposent les variables avec un nom préfixé "*".
|
| 456 |
+
cols_etoilees <- cols[grepl("^\\*", cols)]
|
| 457 |
+
if (length(cols_etoilees) > 0) {
|
| 458 |
+
cols <- cols_etoilees
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
modalites_par_seg <- NULL
|
| 462 |
+
|
| 463 |
+
if (length(cols) > 0) {
|
| 464 |
+
modalites_par_seg <- vector("list", nrow(dv2))
|
| 465 |
+
for (i in seq_len(nrow(dv2))) {
|
| 466 |
+
mods <- character(0)
|
| 467 |
+
for (cn in cols) {
|
| 468 |
+
if (is.na(cn) || !nzchar(cn)) next
|
| 469 |
+
v <- dv2[i, cn]
|
| 470 |
+
v <- as.character(v)
|
| 471 |
+
v <- v[!is.na(v) & nzchar(v)]
|
| 472 |
+
if (length(v) == 0) next
|
| 473 |
+
v <- gsub("\\s+", " ", trimws(v), perl = TRUE)
|
| 474 |
+
if (!nzchar(v)) next
|
| 475 |
+
# Modalité de type "var=valeur"
|
| 476 |
+
# Si la colonne est déjà une variable étoilée, on garde une modalité
|
| 477 |
+
# compacte de type "*var_valeur" pour rester proche de la syntaxe IRaMuTeQ.
|
| 478 |
+
if (isTRUE(grepl("^\\*", cn))) {
|
| 479 |
+
val_norm <- gsub("\\s+", "_", v, perl = TRUE)
|
| 480 |
+
val_norm <- gsub("[^[:alnum:]_\\-]", "_", val_norm, perl = TRUE)
|
| 481 |
+
val_norm <- gsub("_+", "_", val_norm, perl = TRUE)
|
| 482 |
+
val_norm <- gsub("^_+|_+$", "", val_norm, perl = TRUE)
|
| 483 |
+
if (nzchar(val_norm)) {
|
| 484 |
+
mods <- c(mods, paste0(cn, "_", val_norm))
|
| 485 |
+
}
|
| 486 |
+
} else {
|
| 487 |
+
mods <- c(mods, paste0(cn, "=", v))
|
| 488 |
+
}
|
| 489 |
+
}
|
| 490 |
+
modalites_par_seg[[i]] <- unique(mods)
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
# Fallback si aucune modalité trouvée via docvars
|
| 495 |
+
if (is.null(modalites_par_seg) || all(vapply(modalites_par_seg, length, integer(1)) == 0)) {
|
| 496 |
+
textes <- as.character(corpus_aligne)[keep]
|
| 497 |
+
modalites_par_seg <- .extraire_modalites_depuis_ligne_iramuteq(textes)
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
all_mods <- unique(unlist(modalites_par_seg, use.names = FALSE))
|
| 501 |
+
all_mods <- all_mods[!is.na(all_mods) & nzchar(all_mods)]
|
| 502 |
+
if (length(all_mods) < 2) stop("AFC variables étoilées : aucune modalité détectée.")
|
| 503 |
+
|
| 504 |
+
# Comptages classes × modalités (présence/absence par segment)
|
| 505 |
+
classes <- unique(g2)
|
| 506 |
+
classes <- classes[!is.na(classes)]
|
| 507 |
+
classes <- sort(classes)
|
| 508 |
+
|
| 509 |
+
tab <- matrix(0, nrow = length(classes), ncol = length(all_mods))
|
| 510 |
+
rownames(tab) <- paste0("Classe ", classes)
|
| 511 |
+
colnames(tab) <- all_mods
|
| 512 |
+
|
| 513 |
+
# Remplissage
|
| 514 |
+
for (i in seq_along(g2)) {
|
| 515 |
+
cl <- g2[i]
|
| 516 |
+
if (is.na(cl)) next
|
| 517 |
+
mods <- modalites_par_seg[[i]]
|
| 518 |
+
if (length(mods) == 0) next
|
| 519 |
+
mods <- intersect(mods, all_mods)
|
| 520 |
+
if (length(mods) == 0) next
|
| 521 |
+
tab[paste0("Classe ", cl), mods] <- tab[paste0("Classe ", cl), mods] + 1L
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
# Filtrage éventuel top modalités par fréquence
|
| 525 |
+
freq <- colSums(tab)
|
| 526 |
+
keepm <- freq > 0
|
| 527 |
+
tab <- tab[, keepm, drop = FALSE]
|
| 528 |
+
if (ncol(tab) < 2) stop("AFC variables étoilées : table trop pauvre après filtrage.")
|
| 529 |
+
|
| 530 |
+
if (ncol(tab) > max_modalites) {
|
| 531 |
+
ord <- order(colSums(tab), decreasing = TRUE)
|
| 532 |
+
tab <- tab[, ord[seq_len(max_modalites)], drop = FALSE]
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
tab
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
# Exécution AFC classes × variables étoilées
|
| 539 |
+
executer_afc_variables_etoilees <- function(corpus_aligne, groupes, max_modalites = 400, seuil_p = 0.05, rv = NULL) {
|
| 540 |
+
verifier_factominer()
|
| 541 |
+
|
| 542 |
+
tab <- calculer_table_classes_modalites(
|
| 543 |
+
corpus_aligne = corpus_aligne,
|
| 544 |
+
groupes = groupes,
|
| 545 |
+
max_modalites = max_modalites
|
| 546 |
+
)
|
| 547 |
+
|
| 548 |
+
if (!is.null(rv)) {
|
| 549 |
+
ajouter_log(
|
| 550 |
+
rv,
|
| 551 |
+
paste0(
|
| 552 |
+
"AFC variables étoilées : table construite (",
|
| 553 |
+
nrow(tab),
|
| 554 |
+
" classes × ",
|
| 555 |
+
ncol(tab),
|
| 556 |
+
" modalités)."
|
| 557 |
+
)
|
| 558 |
+
)
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
ca <- FactoMineR::CA(tab, graph = FALSE)
|
| 562 |
+
rowcoord <- ca$row$coord
|
| 563 |
+
colcoord <- ca$col$coord
|
| 564 |
+
|
| 565 |
+
st <- .calculer_stats_colonnes(tab, seuil_p = seuil_p)
|
| 566 |
+
names(st)[names(st) == "feature"] <- "Modalite"
|
| 567 |
+
|
| 568 |
+
list(
|
| 569 |
+
table = tab,
|
| 570 |
+
ca = ca,
|
| 571 |
+
rowcoord = rowcoord,
|
| 572 |
+
colcoord = colcoord,
|
| 573 |
+
modalites_stats = st,
|
| 574 |
+
seuil_p = seuil_p
|
| 575 |
+
)
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
# Tracé AFC classes + variables étoilées
|
| 579 |
+
tracer_afc_variables_etoilees <- function(
|
| 580 |
+
obj,
|
| 581 |
+
axes = c(1, 2),
|
| 582 |
+
top_modalites = 120,
|
| 583 |
+
activer_repel = TRUE,
|
| 584 |
+
cex_min = 0.8,
|
| 585 |
+
cex_max = 2.0,
|
| 586 |
+
repel_max_iter = 220
|
| 587 |
+
) {
|
| 588 |
+
if (is.null(obj$ca) || is.null(obj$rowcoord) || is.null(obj$colcoord) || is.null(obj$modalites_stats)) {
|
| 589 |
+
stop("AFC variables étoilées : objet incomplet (coordonnées / stats manquantes).")
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
ax1 <- axes[1]
|
| 593 |
+
ax2 <- axes[2]
|
| 594 |
+
|
| 595 |
+
rc <- obj$rowcoord
|
| 596 |
+
cc <- obj$colcoord
|
| 597 |
+
st <- obj$modalites_stats
|
| 598 |
+
|
| 599 |
+
st <- st[!is.na(st$Modalite) & nzchar(st$Modalite), , drop = FALSE]
|
| 600 |
+
st <- st[order(-st$frequency), , drop = FALSE]
|
| 601 |
+
|
| 602 |
+
if (!is.null(top_modalites) && is.finite(top_modalites) && nrow(st) > top_modalites) {
|
| 603 |
+
st <- st[seq_len(top_modalites), , drop = FALSE]
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
st <- st[st$Modalite %in% rownames(cc), , drop = FALSE]
|
| 607 |
+
if (nrow(st) < 2) {
|
| 608 |
+
plot.new()
|
| 609 |
+
text(0.5, 0.5, "AFC variables étoilées : pas assez de modalités à tracer.", cex = 1.1)
|
| 610 |
+
return(invisible(NULL))
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
mods <- st$Modalite
|
| 614 |
+
xy_m <- cc[mods, , drop = FALSE]
|
| 615 |
+
x_m <- xy_m[, ax1]
|
| 616 |
+
y_m <- xy_m[, ax2]
|
| 617 |
+
|
| 618 |
+
x_c <- rc[, ax1]
|
| 619 |
+
y_c <- rc[, ax2]
|
| 620 |
+
|
| 621 |
+
lim <- calculer_lim_sym(c(x_m, x_c), c(y_m, y_c))
|
| 622 |
+
plot(
|
| 623 |
+
0, 0,
|
| 624 |
+
type = "n",
|
| 625 |
+
xlab = paste0("Axe ", ax1),
|
| 626 |
+
ylab = paste0("Axe ", ax2),
|
| 627 |
+
xlim = lim, ylim = lim
|
| 628 |
+
)
|
| 629 |
+
abline(h = 0, v = 0, col = "gray80")
|
| 630 |
+
|
| 631 |
+
points(x_c, y_c, pch = 19, cex = 1.25)
|
| 632 |
+
text(x_c, y_c, labels = rownames(rc), pos = 3, cex = 1.0)
|
| 633 |
+
|
| 634 |
+
# Couleurs par classe (classe max)
|
| 635 |
+
classes <- sort(unique(rownames(rc)))
|
| 636 |
+
ncl <- length(classes)
|
| 637 |
+
pal <- if (requireNamespace("RColorBrewer", quietly = TRUE) && ncl <= 8) {
|
| 638 |
+
RColorBrewer::brewer.pal(max(3, ncl), "Set2")[seq_len(ncl)]
|
| 639 |
+
} else {
|
| 640 |
+
grDevices::rainbow(ncl)
|
| 641 |
+
}
|
| 642 |
+
col_map <- setNames(pal, classes)
|
| 643 |
+
col_m <- col_map[st$Classe_max]
|
| 644 |
+
col_m[is.na(col_m)] <- "gray40"
|
| 645 |
+
|
| 646 |
+
# Tailles : fréquence globale des modalités
|
| 647 |
+
poids <- suppressWarnings(as.numeric(st$frequency))
|
| 648 |
+
poids[!is.finite(poids)] <- 0
|
| 649 |
+
poids <- pmax(0, poids)
|
| 650 |
+
|
| 651 |
+
if (max(poids) == min(poids)) {
|
| 652 |
+
cex_vec <- rep((cex_min + cex_max) / 2, length(poids))
|
| 653 |
+
} else {
|
| 654 |
+
v <- sqrt(poids)
|
| 655 |
+
v <- (v - min(v)) / (max(v) - min(v))
|
| 656 |
+
cex_vec <- cex_min + v * (cex_max - cex_min)
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
if (isTRUE(activer_repel)) {
|
| 660 |
+
coords <- placer_labels_sans_chevauchement_spirale(
|
| 661 |
+
x = x_m, y = y_m, labels = mods, cex_vec = cex_vec, max_iter = repel_max_iter
|
| 662 |
+
)
|
| 663 |
+
x_lab <- coords$x
|
| 664 |
+
y_lab <- coords$y
|
| 665 |
+
} else {
|
| 666 |
+
x_lab <- x_m
|
| 667 |
+
y_lab <- y_m
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
points(x_lab, y_lab, pch = 16, cex = 0.5, col = col_m)
|
| 671 |
+
text(x_lab, y_lab, labels = mods, cex = cex_vec, col = col_m)
|
| 672 |
+
}
|
iramuteq-like/affichage_iramuteq-like.R
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: centraliser l'affichage des résultats IRaMuTeQ-like avec des sous-onglets dédiés.
|
| 2 |
+
|
| 3 |
+
ui_resultats_chd_iramuteq <- function() {
|
| 4 |
+
tabPanel(
|
| 5 |
+
"Résultats CHD Iramuteq",
|
| 6 |
+
tabsetPanel(
|
| 7 |
+
id = "tabs_resultats_chd_iramuteq",
|
| 8 |
+
tabPanel(
|
| 9 |
+
"Dendrogramme",
|
| 10 |
+
tags$h3("Dendrogramme CHD (IRaMuTeQ-like)"),
|
| 11 |
+
radioButtons(
|
| 12 |
+
"iramuteq_dendro_display_method",
|
| 13 |
+
"Méthode d'affichage",
|
| 14 |
+
choices = c(
|
| 15 |
+
"Style IRaMuTeQ (barres + mots par classe)" = "iramuteq_blocks",
|
| 16 |
+
"Standard (labels près des classes)" = "standard",
|
| 17 |
+
"Compact (légende des termes en bas)" = "compact"
|
| 18 |
+
),
|
| 19 |
+
selected = "iramuteq_blocks",
|
| 20 |
+
inline = FALSE
|
| 21 |
+
),
|
| 22 |
+
plotOutput("plot_chd_iramuteq_dendro", height = "420px")
|
| 23 |
+
),
|
| 24 |
+
tabPanel(
|
| 25 |
+
"Stats CHD",
|
| 26 |
+
tags$h3("Tableaux statistiques CHD par classe"),
|
| 27 |
+
uiOutput("ui_tables_stats_chd_iramuteq")
|
| 28 |
+
),
|
| 29 |
+
tabPanel(
|
| 30 |
+
"Concordancier IRaMuTeQ-like",
|
| 31 |
+
tags$h3("Concordancier"),
|
| 32 |
+
uiOutput("ui_concordancier_iramuteq")
|
| 33 |
+
),
|
| 34 |
+
tabPanel(
|
| 35 |
+
"Nuage de mots",
|
| 36 |
+
tags$h3("Nuage de mots par classe"),
|
| 37 |
+
selectInput("classe_viz_iramuteq", "Classe", choices = c("1"), selected = "1"),
|
| 38 |
+
uiOutput("ui_wordcloud_iramuteq")
|
| 39 |
+
)
|
| 40 |
+
)
|
| 41 |
+
)
|
| 42 |
+
}
|
iramuteq-like/anacor.R
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#################################################################################
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
write.sparse <- function (m, to) {
|
| 5 |
+
## Writes in a format that SVDLIBC can read
|
| 6 |
+
stopifnot(inherits(m, "dgCMatrix"))
|
| 7 |
+
fh <- file(to, open="w")
|
| 8 |
+
|
| 9 |
+
wl <- function(...) cat(..., "\n", file=fh)
|
| 10 |
+
|
| 11 |
+
## header
|
| 12 |
+
wl(dim(m), length(m@x))
|
| 13 |
+
|
| 14 |
+
globalCount <- 1
|
| 15 |
+
nper <- diff(m@p)
|
| 16 |
+
for(i in 1:ncol(m)) {
|
| 17 |
+
wl(nper[i]) ## Number of entries in this column
|
| 18 |
+
if (nper[i]==0) next
|
| 19 |
+
for(j in 1:nper[i]) {
|
| 20 |
+
wl(m@i[globalCount], m@x[m@p[i]+j])
|
| 21 |
+
globalCount <- globalCount+1
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
my.svd <- function(x, nu, nv, libsvdc.path=NULL, sparse.path=NULL) {
|
| 27 |
+
print('my.svd')
|
| 28 |
+
stopifnot(nu==nv)
|
| 29 |
+
outfile <- file.path(tempdir(),'sout')
|
| 30 |
+
cmd <- paste(libsvdc.path, '-o', outfile, '-d')
|
| 31 |
+
#rc <- system(paste("/usr/bin/svd -o /tmp/sout -d", nu, "/tmp/sparse.m"))
|
| 32 |
+
rc <- system(paste(cmd, nu, sparse.path))
|
| 33 |
+
if (rc != 0)
|
| 34 |
+
stop("Couldn't run external svd code")
|
| 35 |
+
res1 <- paste(outfile,'-S', sep='')
|
| 36 |
+
d <- scan(res1, skip=1)
|
| 37 |
+
#FIXME : sometimes, libsvdc doesn't find solution with 2 dimensions, but does with 3
|
| 38 |
+
if (length(d)==1) {
|
| 39 |
+
nu <- nu + 1
|
| 40 |
+
#rc <- system(paste("/usr/bin/svd -o /tmp/sout -d", nu, "/tmp/sparse.m"))
|
| 41 |
+
rc <- system(paste(cmd, nu, sparse.path))
|
| 42 |
+
d <- scan(res1, skip=1)
|
| 43 |
+
}
|
| 44 |
+
utfile <- paste(outfile, '-Ut', sep='')
|
| 45 |
+
ut <- matrix(scan(utfile,skip=1),nrow=nu,byrow=TRUE)
|
| 46 |
+
if (nrow(ut)==3) {
|
| 47 |
+
ut <- ut[-3,]
|
| 48 |
+
}
|
| 49 |
+
vt <- NULL
|
| 50 |
+
list(d=d, u=-t(ut), v=vt)
|
| 51 |
+
}
|
| 52 |
+
###################################################################################
|
| 53 |
+
|
| 54 |
+
#from anacor package
|
| 55 |
+
boostana<-function (tab, ndim = 2, svd.method = 'svdR', libsvdc.path=NULL)
|
| 56 |
+
{
|
| 57 |
+
#tab <- as.matrix(tab)
|
| 58 |
+
if (ndim > min(dim(tab)) - 1)
|
| 59 |
+
stop("Too many dimensions!")
|
| 60 |
+
name <- deparse(substitute(tab))
|
| 61 |
+
if (any(is.na(tab)))
|
| 62 |
+
print('YA NA')
|
| 63 |
+
#tab <- reconstitute(tab, eps = eps)
|
| 64 |
+
n <- dim(tab)[1]
|
| 65 |
+
m <- dim(tab)[2]
|
| 66 |
+
N <- sum(tab)
|
| 67 |
+
#tab <- as.matrix(tab)
|
| 68 |
+
#prop <- as.vector(t(tab))/N
|
| 69 |
+
r <- rowSums(tab)
|
| 70 |
+
c <- colSums(tab)
|
| 71 |
+
qdim <- ndim + 1
|
| 72 |
+
r <- ifelse(r == 0, 1, r)
|
| 73 |
+
c <- ifelse(c == 0, 1, c)
|
| 74 |
+
print('make z')
|
| 75 |
+
z1 <- t(tab)/sqrt(c)
|
| 76 |
+
z2 <- tab/sqrt(r)
|
| 77 |
+
z <- t(z1) * z2
|
| 78 |
+
if (svd.method == 'svdlibc') {
|
| 79 |
+
#START NEW SVD
|
| 80 |
+
z <- as(z, "dgCMatrix")
|
| 81 |
+
tmpmat <- tempfile(pattern='sparse')
|
| 82 |
+
print('write sparse matrix')
|
| 83 |
+
write.sparse(z, tmpmat)
|
| 84 |
+
print('do svd')
|
| 85 |
+
sv <- my.svd(z, qdim, qdim, libsvdc.path=libsvdc.path, sparse.path=tmpmat)
|
| 86 |
+
#END NEW SVD
|
| 87 |
+
} else if (svd.method == 'svdR') {
|
| 88 |
+
print('start R svd')
|
| 89 |
+
sv <- svd(z, nu = qdim, nv = qdim)
|
| 90 |
+
print('end svd')
|
| 91 |
+
} else if (svd.method == 'irlba') {
|
| 92 |
+
if (!requireNamespace("irlba", quietly = TRUE)) {
|
| 93 |
+
warning("Package 'irlba' introuvable, bascule automatique vers 'svdR'.")
|
| 94 |
+
print('start R svd (fallback)')
|
| 95 |
+
sv <- svd(z, nu = qdim, nv = qdim)
|
| 96 |
+
print('end svd (fallback)')
|
| 97 |
+
} else {
|
| 98 |
+
print('irlba')
|
| 99 |
+
sv <- irlba::irlba(z, nv = qdim, nu = qdim)
|
| 100 |
+
print('end irlba')
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
sigmavec <- (sv$d)[2:qdim]
|
| 104 |
+
x <- ((sv$u)/sqrt(r))[, -1]
|
| 105 |
+
x <- x * sqrt(N)
|
| 106 |
+
x <- x * outer(rep(1, n), sigmavec)
|
| 107 |
+
dimlab <- paste("D", 1:ndim, sep = "")
|
| 108 |
+
colnames(x) <- dimlab# <- colnames(y) <- dimlab
|
| 109 |
+
rownames(x) <- rownames(tab)
|
| 110 |
+
result <- list(ndim = ndim, row.scores = x,
|
| 111 |
+
singular.values = sigmavec, eigen.values = sigmavec^2)
|
| 112 |
+
class(result) <- "boostanacor"
|
| 113 |
+
result
|
| 114 |
+
}
|
iramuteq-like/chd_afc_pipeline_iramuteq.R
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: chd_afc_pipeline.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Module CHD/AFC - préparation des données et génération des artefacts CHD
|
| 3 |
+
# Ce fichier centralise les fonctions de préparation DFM/docvars, l'ajustement de `k`,
|
| 4 |
+
# les utilitaires de graphes d'adjacence/cooccurrence, et la génération des exports
|
| 5 |
+
# CHD (PNG + HTML) afin d'alléger `app.R` sans modifier le comportement existant.
|
| 6 |
+
|
| 7 |
+
extraire_classes_alignees <- function(corpus_obj, doc_ids, nom_colonne = "Classes") {
|
| 8 |
+
dv <- docvars(corpus_obj)
|
| 9 |
+
if (!(nom_colonne %in% names(dv))) return(rep(NA_character_, length(doc_ids)))
|
| 10 |
+
|
| 11 |
+
doc_ids <- as.character(doc_ids)
|
| 12 |
+
dn_corpus <- as.character(docnames(corpus_obj))
|
| 13 |
+
idx <- match(doc_ids, dn_corpus)
|
| 14 |
+
|
| 15 |
+
out <- rep(NA_character_, length(doc_ids))
|
| 16 |
+
ok <- !is.na(idx)
|
| 17 |
+
if (any(ok)) {
|
| 18 |
+
out[ok] <- as.character(dv[[nom_colonne]][idx[ok]])
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
normaliser_classes(out)
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
construire_segment_source <- function(corpus_segmente) {
|
| 25 |
+
dv <- docvars(corpus_segmente)
|
| 26 |
+
|
| 27 |
+
if ("segment_source" %in% names(dv)) {
|
| 28 |
+
v <- as.character(dv$segment_source)
|
| 29 |
+
if (length(v) == ndoc(corpus_segmente)) return(v)
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
dn <- docnames(corpus_segmente)
|
| 33 |
+
|
| 34 |
+
if (any(grepl("_seg[0-9]+$", dn))) return(gsub("_seg[0-9]+$", "", dn))
|
| 35 |
+
if (any(grepl("_[0-9]+$", dn))) return(gsub("_[0-9]+$", "", dn))
|
| 36 |
+
if (any(grepl("-[0-9]+$", dn))) return(gsub("-[0-9]+$", "", dn))
|
| 37 |
+
|
| 38 |
+
dn
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
assurer_docvars_dfm_minimal <- function(dfm_obj, corpus_aligne) {
|
| 42 |
+
seg_source <- construire_segment_source(corpus_aligne)
|
| 43 |
+
dv <- data.frame(segment_source = seg_source, stringsAsFactors = FALSE)
|
| 44 |
+
rownames(dv) <- docnames(corpus_aligne)
|
| 45 |
+
docvars(dfm_obj) <- dv
|
| 46 |
+
dfm_obj
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
construire_dfm_avec_fallback_stopwords <- function(tok_base, min_docfreq, retirer_stopwords, langue_spacy, rv, libelle, source_dictionnaire = "spacy", lexique_source_stopwords = "quanteda") {
|
| 50 |
+
n_base <- compter_tokens(tok_base)
|
| 51 |
+
ajouter_log(rv, paste0(libelle, " : tokens (avant stopwords) = ", n_base))
|
| 52 |
+
|
| 53 |
+
if (isTRUE(retirer_stopwords)) {
|
| 54 |
+
stopwords_a_retirer <- obtenir_stopwords_analyse(
|
| 55 |
+
langue_spacy = langue_spacy,
|
| 56 |
+
source_dictionnaire = source_dictionnaire,
|
| 57 |
+
lexique_source_stopwords = lexique_source_stopwords,
|
| 58 |
+
rv = rv
|
| 59 |
+
)
|
| 60 |
+
tok_sw <- tokens_remove(tok_base, stopwords_a_retirer)
|
| 61 |
+
tok_sw <- tokens_tolower(tok_sw)
|
| 62 |
+
n_sw <- compter_tokens(tok_sw)
|
| 63 |
+
ajouter_log(rv, paste0(libelle, " : tokens (après stopwords) = ", n_sw))
|
| 64 |
+
tok_final <- tok_sw
|
| 65 |
+
} else {
|
| 66 |
+
ajouter_log(rv, paste0(libelle, " : suppression des stopwords désactivée."))
|
| 67 |
+
tok_final <- tokens_tolower(tok_base)
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
if (!isTRUE(retirer_stopwords) && isTRUE(min_docfreq > 1)) {
|
| 71 |
+
ajouter_log(rv, paste0(libelle, " : min_docfreq=", min_docfreq, " peut éliminer des nombres rares (ex: numéros de téléphone)."))
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
dfm_obj <- dfm(tok_final)
|
| 75 |
+
dfm_obj <- dfm_trim(dfm_obj, min_docfreq = min_docfreq)
|
| 76 |
+
ajouter_log(
|
| 77 |
+
rv,
|
| 78 |
+
paste0(
|
| 79 |
+
libelle,
|
| 80 |
+
" : DFM après trim = ", ndoc(dfm_obj), " docs ; ", nfeat(dfm_obj),
|
| 81 |
+
ifelse(isTRUE(retirer_stopwords), " termes (avec stopwords retirés)", " termes (sans suppression des stopwords)")
|
| 82 |
+
)
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
if (isTRUE(retirer_stopwords) && nfeat(dfm_obj) < 2) {
|
| 86 |
+
ajouter_log(rv, paste0(libelle, " : DFM trop pauvre avec stopwords retirés. Relance automatique sans suppression des stopwords."))
|
| 87 |
+
tok_final <- tokens_tolower(tok_base)
|
| 88 |
+
dfm_obj <- dfm(tok_final)
|
| 89 |
+
dfm_obj <- dfm_trim(dfm_obj, min_docfreq = min_docfreq)
|
| 90 |
+
ajouter_log(rv, paste0(libelle, " : DFM après trim = ", ndoc(dfm_obj), " docs ; ", nfeat(dfm_obj), " termes (sans stopwords)"))
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
list(tok = tok_final, dfm = dfm_obj)
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
supprimer_docs_vides_dfm <- function(dfm_obj, corpus_aligne, tok_aligne, rv) {
|
| 97 |
+
rs <- tryCatch(Matrix::rowSums(dfm_obj), error = function(e) NULL)
|
| 98 |
+
|
| 99 |
+
if (is.null(rs)) {
|
| 100 |
+
ajouter_log(rv, "Impossible de calculer rowSums(dfm). Aucune suppression de segments vides.")
|
| 101 |
+
return(list(dfm = dfm_obj, corpus = corpus_aligne, tok = tok_aligne))
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
n_vides <- sum(rs == 0)
|
| 105 |
+
if (n_vides > 0) {
|
| 106 |
+
ajouter_log(rv, paste0("Segments vides (aucun terme) détectés : ", n_vides, ". Suppression avant CHD."))
|
| 107 |
+
garder <- rs > 0
|
| 108 |
+
dfm_obj <- dfm_obj[garder, ]
|
| 109 |
+
noms <- docnames(dfm_obj)
|
| 110 |
+
corpus_aligne <- corpus_aligne[noms]
|
| 111 |
+
tok_aligne <- tok_aligne[noms]
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
list(dfm = dfm_obj, corpus = corpus_aligne, tok = tok_aligne)
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
calculer_k_effectif <- function(dfm_obj, k_demande, min_split_members, rv = NULL) {
|
| 118 |
+
n_docs <- ndoc(dfm_obj)
|
| 119 |
+
if (!is.finite(min_split_members) || is.na(min_split_members) || min_split_members < 1) {
|
| 120 |
+
min_split_members <- 1
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
k_max_theorique <- floor(n_docs / min_split_members)
|
| 124 |
+
if (!is.finite(k_max_theorique) || is.na(k_max_theorique)) k_max_theorique <- n_docs
|
| 125 |
+
k_max_theorique <- max(1, min(k_max_theorique, n_docs - 1))
|
| 126 |
+
|
| 127 |
+
k_effectif <- min(k_demande, k_max_theorique)
|
| 128 |
+
|
| 129 |
+
if (k_effectif < 2) {
|
| 130 |
+
stop(
|
| 131 |
+
"Paramètres incompatibles : min_split_members=", min_split_members,
|
| 132 |
+
" est trop élevé pour ", n_docs,
|
| 133 |
+
" segments. Réduis min_split_members ou augmente la taille du corpus segmenté."
|
| 134 |
+
)
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
if (!is.null(rv) && k_effectif < k_demande) {
|
| 138 |
+
ajouter_log(
|
| 139 |
+
rv,
|
| 140 |
+
paste0(
|
| 141 |
+
"k ajusté automatiquement de ", k_demande, " à ", k_effectif,
|
| 142 |
+
" pour respecter min_split_members=", min_split_members,
|
| 143 |
+
" (", n_docs, " segments disponibles)."
|
| 144 |
+
)
|
| 145 |
+
)
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
k_effectif
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
verifier_dfm_avant_rainette <- function(dfm_obj, input) {
|
| 152 |
+
if (ndoc(dfm_obj) < 2) {
|
| 153 |
+
stop("Après filtrages, il reste moins de 2 segments utilisables. Réduis les filtrages ou augmente segment_size.")
|
| 154 |
+
}
|
| 155 |
+
if (nfeat(dfm_obj) < 2) {
|
| 156 |
+
stop(
|
| 157 |
+
"Après filtrages, il reste moins de 2 termes dans le DFM. ",
|
| 158 |
+
"Même avec min_docfreq=1, cela arrive si le filtrage morphosyntaxique est trop strict et/ou si les stopwords retirent la majorité des formes. ",
|
| 159 |
+
"Élargis les catégories morphosyntaxiques ou augmente segment_size."
|
| 160 |
+
)
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
obtenir_objet_dendrogramme <- function(res) {
|
| 165 |
+
if (is.null(res)) return(NULL)
|
| 166 |
+
|
| 167 |
+
if (inherits(res, "hclust") || inherits(res, "dendrogram")) return(res)
|
| 168 |
+
|
| 169 |
+
if (is.list(res)) {
|
| 170 |
+
candidats <- c("tree", "hc", "hclust", "dendro", "dendrogram")
|
| 171 |
+
for (nm in candidats) {
|
| 172 |
+
if (!is.null(res[[nm]]) && (inherits(res[[nm]], "hclust") || inherits(res[[nm]], "dendrogram"))) {
|
| 173 |
+
return(res[[nm]])
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
NULL
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
construire_graphe_adjacence <- function(mat) {
|
| 182 |
+
if ("graph_from_adjacency_matrix" %in% getNamespaceExports("igraph")) {
|
| 183 |
+
igraph::graph_from_adjacency_matrix(mat, mode = "undirected", weighted = TRUE, diag = FALSE)
|
| 184 |
+
} else {
|
| 185 |
+
igraph::graph.adjacency(mat, mode = "undirected", weighted = TRUE, diag = FALSE)
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
generer_chd_explor_si_absente <- function(rv) {
|
| 190 |
+
if (is.null(rv$export_dir) || !nzchar(rv$export_dir)) return(FALSE)
|
| 191 |
+
|
| 192 |
+
explor_dir <- file.path(rv$export_dir, "explor")
|
| 193 |
+
dir.create(explor_dir, showWarnings = FALSE, recursive = TRUE)
|
| 194 |
+
|
| 195 |
+
chd_png <- file.path(explor_dir, "chd.png")
|
| 196 |
+
if (file.exists(chd_png)) return(TRUE)
|
| 197 |
+
|
| 198 |
+
chd_obj <- rv$res_chd
|
| 199 |
+
if (is.null(chd_obj)) chd_obj <- rv$res
|
| 200 |
+
if (is.null(chd_obj)) return(FALSE)
|
| 201 |
+
|
| 202 |
+
dfm_obj <- rv$dfm_chd
|
| 203 |
+
err_msg <- NULL
|
| 204 |
+
|
| 205 |
+
ecrire_png_secours <- function(message = NULL) {
|
| 206 |
+
grDevices::png(chd_png, width = 2000, height = 1500, res = 180)
|
| 207 |
+
tryCatch({
|
| 208 |
+
plot.new()
|
| 209 |
+
title("CHD (export)")
|
| 210 |
+
txt <- "CHD indisponible pour cet export."
|
| 211 |
+
if (!is.null(message) && nzchar(message)) {
|
| 212 |
+
txt <- paste0(txt, "\n", message)
|
| 213 |
+
}
|
| 214 |
+
text(0.5, 0.5, txt, cex = 1.1)
|
| 215 |
+
}, finally = {
|
| 216 |
+
try(grDevices::dev.off(), silent = TRUE)
|
| 217 |
+
})
|
| 218 |
+
file.exists(chd_png) && is.finite(file.info(chd_png)$size) && file.info(chd_png)$size > 0
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
dessiner_chd <- function(avec_dfm = FALSE) {
|
| 222 |
+
grDevices::png(chd_png, width = 2000, height = 1500, res = 180)
|
| 223 |
+
ok_plot <- FALSE
|
| 224 |
+
|
| 225 |
+
tryCatch({
|
| 226 |
+
if (isTRUE(avec_dfm) && !is.null(dfm_obj)) {
|
| 227 |
+
k_plot <- suppressWarnings(as.integer(rv$max_n_groups_chd))
|
| 228 |
+
if (!is.finite(k_plot) || is.na(k_plot) || k_plot < 2) {
|
| 229 |
+
if (!is.null(chd_obj$group)) {
|
| 230 |
+
k_plot <- suppressWarnings(max(as.integer(chd_obj$group), na.rm = TRUE))
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
if (!is.finite(k_plot) || is.na(k_plot) || k_plot < 2) k_plot <- 2L
|
| 234 |
+
|
| 235 |
+
args_plot <- list(
|
| 236 |
+
chd_obj,
|
| 237 |
+
dfm_obj,
|
| 238 |
+
k = k_plot,
|
| 239 |
+
measure = "chi2",
|
| 240 |
+
type = "bar",
|
| 241 |
+
n_terms = 20,
|
| 242 |
+
show_negative = FALSE,
|
| 243 |
+
text_size = 12
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
params_plot <- tryCatch(names(formals(rainette_plot)), error = function(e) character(0))
|
| 247 |
+
if ("same_scales" %in% params_plot) args_plot$same_scales <- TRUE
|
| 248 |
+
if ("free_scales" %in% params_plot) args_plot$free_scales <- FALSE
|
| 249 |
+
|
| 250 |
+
do.call(rainette_plot, args_plot)
|
| 251 |
+
} else {
|
| 252 |
+
rainette_plot(chd_obj)
|
| 253 |
+
}
|
| 254 |
+
ok_plot <- TRUE
|
| 255 |
+
}, error = function(e) {
|
| 256 |
+
err_msg <<- conditionMessage(e)
|
| 257 |
+
}, finally = {
|
| 258 |
+
try(grDevices::dev.off(), silent = TRUE)
|
| 259 |
+
})
|
| 260 |
+
|
| 261 |
+
isTRUE(ok_plot) && file.exists(chd_png) && is.finite(file.info(chd_png)$size) && file.info(chd_png)$size > 0
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
ok <- dessiner_chd(avec_dfm = FALSE)
|
| 265 |
+
if (!ok && !is.null(dfm_obj)) {
|
| 266 |
+
ok <- dessiner_chd(avec_dfm = TRUE)
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
if (!ok) {
|
| 270 |
+
if (!is.null(rv)) {
|
| 271 |
+
msg <- if (!is.null(err_msg) && nzchar(err_msg)) err_msg else "raison inconnue"
|
| 272 |
+
ajouter_log(rv, paste0("CHD PNG non généré (", msg, ")."))
|
| 273 |
+
}
|
| 274 |
+
if (file.exists(chd_png)) unlink(chd_png)
|
| 275 |
+
|
| 276 |
+
ok_fallback <- ecrire_png_secours(err_msg)
|
| 277 |
+
if (ok_fallback) {
|
| 278 |
+
if (!is.null(rv)) ajouter_log(rv, paste0("CHD PNG de secours généré : ", chd_png))
|
| 279 |
+
ok <- TRUE
|
| 280 |
+
}
|
| 281 |
+
} else if (!is.null(rv)) {
|
| 282 |
+
ajouter_log(rv, paste0("CHD PNG généré : ", chd_png))
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
ok
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
generer_chd_html_explor <- function(rv, chd_png_rel = NULL) {
|
| 289 |
+
if (is.null(rv$export_dir) || !nzchar(rv$export_dir)) return(NULL)
|
| 290 |
+
|
| 291 |
+
explor_dir <- file.path(rv$export_dir, "explor")
|
| 292 |
+
dir.create(explor_dir, showWarnings = FALSE, recursive = TRUE)
|
| 293 |
+
|
| 294 |
+
chd_html <- file.path(explor_dir, "chd.html")
|
| 295 |
+
img_part <- "<p><em>CHD non disponible dans l'export.</em></p>"
|
| 296 |
+
if (!is.null(chd_png_rel) && nzchar(chd_png_rel)) {
|
| 297 |
+
img_src <- basename(chd_png_rel)
|
| 298 |
+
img_part <- paste0("<p><img src='", img_src, "' style='max-width:100%;height:auto;border:1px solid #ddd;'/></p>")
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
con <- file(chd_html, open = "wt", encoding = "UTF-8")
|
| 302 |
+
on.exit(try(close(con), silent = TRUE), add = TRUE)
|
| 303 |
+
|
| 304 |
+
writeLines("<html><head><meta charset='utf-8'/><title>CHD Rainette</title>", con)
|
| 305 |
+
writeLines("<style>body{font-family:Arial,sans-serif;margin:20px;} h1{margin-top:0;}</style>", con)
|
| 306 |
+
writeLines("</head><body>", con)
|
| 307 |
+
writeLines("<h1>CHD (Rainette)</h1>", con)
|
| 308 |
+
writeLines(img_part, con)
|
| 309 |
+
writeLines("</body></html>", con)
|
| 310 |
+
|
| 311 |
+
if (!file.exists(chd_html)) return(NULL)
|
| 312 |
+
if (!is.finite(file.info(chd_html)$size) || file.info(chd_html)$size <= 0) return(NULL)
|
| 313 |
+
|
| 314 |
+
if (!is.null(rv)) ajouter_log(rv, paste0("CHD HTML généré : ", chd_html))
|
| 315 |
+
file.path("explor", "chd.html")
|
| 316 |
+
}
|
iramuteq-like/chd_engine_iramuteq.R
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: chd_engine_iramuteq.R encapsule le lancement du moteur CHD IRaMuTeQ-like.
|
| 2 |
+
# Ce module sert de point d'entrée dédié pour exécuter la CHD historique et reconstruire
|
| 3 |
+
# les classes terminales avec mincl (auto ou manuel).
|
| 4 |
+
|
| 5 |
+
.obtenir_fonction_iramuteq <- function(nom_fonction,
|
| 6 |
+
chemin_module = "R/chd_iramuteq.R",
|
| 7 |
+
env = parent.frame()) {
|
| 8 |
+
fn <- get0(nom_fonction, mode = "function", inherits = TRUE)
|
| 9 |
+
if (!is.null(fn)) return(fn)
|
| 10 |
+
|
| 11 |
+
# Répertoire du projet (quand l'app est lancée hors du dossier racine).
|
| 12 |
+
racine_projet <- tryCatch({
|
| 13 |
+
if (requireNamespace("shiny", quietly = TRUE)) {
|
| 14 |
+
shiny::getShinyOption("appDir")
|
| 15 |
+
} else {
|
| 16 |
+
NULL
|
| 17 |
+
}
|
| 18 |
+
}, error = function(...) NULL)
|
| 19 |
+
|
| 20 |
+
# Répertoire courant du fichier R/chd_engine_iramuteq.R, si disponible.
|
| 21 |
+
fichier_courant <- tryCatch({
|
| 22 |
+
frames <- rev(sys.frames())
|
| 23 |
+
ofiles <- vapply(
|
| 24 |
+
frames,
|
| 25 |
+
function(fr) {
|
| 26 |
+
of <- get0("ofile", envir = fr, inherits = FALSE)
|
| 27 |
+
if (is.null(of)) "" else as.character(of)
|
| 28 |
+
},
|
| 29 |
+
FUN.VALUE = character(1)
|
| 30 |
+
)
|
| 31 |
+
ofiles <- ofiles[nzchar(ofiles)]
|
| 32 |
+
if (length(ofiles)) ofiles[[1]] else ""
|
| 33 |
+
}, error = function(...) "")
|
| 34 |
+
dir_fichier_courant <- if (nzchar(fichier_courant)) dirname(normalizePath(fichier_courant, mustWork = FALSE)) else ""
|
| 35 |
+
racine_depuis_fichier <- if (nzchar(dir_fichier_courant)) normalizePath(file.path(dir_fichier_courant, ".."), mustWork = FALSE) else ""
|
| 36 |
+
|
| 37 |
+
candidats <- unique(c(
|
| 38 |
+
chemin_module,
|
| 39 |
+
file.path("R", "chd_iramuteq.R"),
|
| 40 |
+
file.path("R", "chd_iramuteq_like.R"),
|
| 41 |
+
if (nzchar(racine_depuis_fichier)) file.path(racine_depuis_fichier, "iramuteq-like", "chd_iramuteq_compat.R") else "",
|
| 42 |
+
if (nzchar(racine_depuis_fichier)) file.path(racine_depuis_fichier, "iramuteq-like", "chd_iramuteq.R") else "",
|
| 43 |
+
if (nzchar(racine_depuis_fichier)) file.path(racine_depuis_fichier, "R", "chd_iramuteq.R") else "",
|
| 44 |
+
if (!is.null(racine_projet) && nzchar(racine_projet)) file.path(racine_projet, "iramuteq-like", "chd_iramuteq_compat.R") else "",
|
| 45 |
+
if (!is.null(racine_projet) && nzchar(racine_projet)) file.path(racine_projet, "iramuteq-like", "chd_iramuteq.R") else "",
|
| 46 |
+
if (!is.null(racine_projet) && nzchar(racine_projet)) file.path(racine_projet, "R", "chd_iramuteq.R") else "",
|
| 47 |
+
file.path(".", "iramuteq-like", "chd_iramuteq_compat.R"),
|
| 48 |
+
file.path(".", "iramuteq-like", "chd_iramuteq.R"),
|
| 49 |
+
file.path(".", "R", "chd_iramuteq.R"),
|
| 50 |
+
file.path(getwd(), "iramuteq-like", "chd_iramuteq_compat.R"),
|
| 51 |
+
file.path(getwd(), "iramuteq-like", "chd_iramuteq.R"),
|
| 52 |
+
file.path(getwd(), "R", "chd_iramuteq.R")
|
| 53 |
+
))
|
| 54 |
+
candidats <- candidats[!is.na(candidats) & nzchar(candidats)]
|
| 55 |
+
|
| 56 |
+
for (cand in candidats) {
|
| 57 |
+
if (file.exists(cand)) {
|
| 58 |
+
source(cand, encoding = "UTF-8", local = env)
|
| 59 |
+
fn <- get0(nom_fonction, mode = "function", inherits = TRUE)
|
| 60 |
+
if (!is.null(fn)) return(fn)
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
stop(
|
| 65 |
+
"Moteur CHD IRaMuTeQ-like indisponible: ", nom_fonction,
|
| 66 |
+
"() introuvable. Module recherché dans: ",
|
| 67 |
+
paste(candidats, collapse = ", "),
|
| 68 |
+
"."
|
| 69 |
+
)
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
lancer_moteur_chd_iramuteq <- function(
|
| 73 |
+
dfm_obj,
|
| 74 |
+
k,
|
| 75 |
+
mincl_mode = c("auto", "manuel"),
|
| 76 |
+
mincl = 0,
|
| 77 |
+
classif_mode = c("simple", "double"),
|
| 78 |
+
svd_method = c("irlba", "svdR"),
|
| 79 |
+
mode_patate = FALSE,
|
| 80 |
+
libsvdc_path = NULL,
|
| 81 |
+
binariser = TRUE,
|
| 82 |
+
rscripts_dir = NULL
|
| 83 |
+
) {
|
| 84 |
+
mincl_mode <- match.arg(mincl_mode)
|
| 85 |
+
classif_mode <- match.arg(classif_mode)
|
| 86 |
+
svd_method <- match.arg(svd_method)
|
| 87 |
+
|
| 88 |
+
calculer_chd_iramuteq_fn <- .obtenir_fonction_iramuteq("calculer_chd_iramuteq", env = environment())
|
| 89 |
+
reconstruire_classes_terminales_iramuteq_fn <- .obtenir_fonction_iramuteq("reconstruire_classes_terminales_iramuteq", env = environment())
|
| 90 |
+
|
| 91 |
+
chd_obj <- calculer_chd_iramuteq_fn(
|
| 92 |
+
dfm_obj = dfm_obj,
|
| 93 |
+
k = k,
|
| 94 |
+
mode_patate = mode_patate,
|
| 95 |
+
svd_method = svd_method,
|
| 96 |
+
libsvdc_path = libsvdc_path,
|
| 97 |
+
binariser = binariser,
|
| 98 |
+
rscripts_dir = rscripts_dir
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
classes_obj <- reconstruire_classes_terminales_iramuteq_fn(
|
| 102 |
+
chd_obj = chd_obj,
|
| 103 |
+
mincl = mincl,
|
| 104 |
+
mincl_mode = mincl_mode,
|
| 105 |
+
classif_mode = classif_mode,
|
| 106 |
+
nb_classes_cible = NULL,
|
| 107 |
+
respecter_nb_classes = FALSE
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
list(
|
| 111 |
+
engine = "iramuteq-like",
|
| 112 |
+
chd = chd_obj,
|
| 113 |
+
classes = classes_obj$classes,
|
| 114 |
+
terminales = classes_obj$terminales,
|
| 115 |
+
mincl = classes_obj$mincl
|
| 116 |
+
)
|
| 117 |
+
}
|
iramuteq-like/chd_iramuteq.R
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: chd_iramuteq.R introduit une base "IRaMuTeQ-like" pour la CHD.
|
| 2 |
+
# Ce module prépare les entrées de CHD en respectant les options de nettoyage de l'application,
|
| 3 |
+
# expose des utilitaires pour le calcul de mincl (convention IRaMuTeQ texte),
|
| 4 |
+
# et fournit un calcul CHD réel en s'appuyant sur les scripts R historiques d'IRaMuTeQ.
|
| 5 |
+
|
| 6 |
+
# Valeur mincl automatique (mode texte IRaMuTeQ):
|
| 7 |
+
# mincl = round(n_uce / ind), avec ind = nbcl * 2 (double) sinon nbcl.
|
| 8 |
+
calculer_mincl_auto_iramuteq <- function(n_uce, nbcl, classif_mode = c("double", "simple")) {
|
| 9 |
+
classif_mode <- match.arg(classif_mode)
|
| 10 |
+
n_uce <- as.integer(n_uce)
|
| 11 |
+
nbcl <- as.integer(nbcl)
|
| 12 |
+
|
| 13 |
+
if (!is.finite(n_uce) || is.na(n_uce) || n_uce < 1) {
|
| 14 |
+
stop("mincl auto IRaMuTeQ: n_uce invalide.")
|
| 15 |
+
}
|
| 16 |
+
if (!is.finite(nbcl) || is.na(nbcl) || nbcl < 1) {
|
| 17 |
+
stop("mincl auto IRaMuTeQ: nbcl invalide.")
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
ind <- if (identical(classif_mode, "double")) nbcl * 2L else nbcl
|
| 21 |
+
mincl <- round(n_uce / ind)
|
| 22 |
+
as.integer(max(1L, mincl))
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
# Normalise une liste d'options de nettoyage selon les clés utilisées dans l'UI.
|
| 26 |
+
normaliser_options_nettoyage_iramuteq <- function(options_nettoyage = list()) {
|
| 27 |
+
opts <- list(
|
| 28 |
+
nettoyage_caracteres = isTRUE(options_nettoyage$nettoyage_caracteres),
|
| 29 |
+
forcer_minuscules_avant = isTRUE(options_nettoyage$forcer_minuscules_avant),
|
| 30 |
+
supprimer_chiffres = isTRUE(options_nettoyage$supprimer_chiffres),
|
| 31 |
+
supprimer_apostrophes = isTRUE(options_nettoyage$supprimer_apostrophes),
|
| 32 |
+
supprimer_ponctuation = isTRUE(options_nettoyage$supprimer_ponctuation),
|
| 33 |
+
retirer_stopwords = isTRUE(options_nettoyage$retirer_stopwords)
|
| 34 |
+
)
|
| 35 |
+
opts
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# Prépare textes/tokens/dfm en tenant compte des options de nettoyage existantes de l'application.
|
| 39 |
+
preparer_entrees_chd_iramuteq <- function(
|
| 40 |
+
textes,
|
| 41 |
+
langue = "fr",
|
| 42 |
+
options_nettoyage = list(),
|
| 43 |
+
appliquer_nettoyage_fun = NULL
|
| 44 |
+
) {
|
| 45 |
+
if (!is.character(textes)) {
|
| 46 |
+
textes <- as.character(textes)
|
| 47 |
+
}
|
| 48 |
+
textes[is.na(textes)] <- ""
|
| 49 |
+
|
| 50 |
+
opts <- normaliser_options_nettoyage_iramuteq(options_nettoyage)
|
| 51 |
+
|
| 52 |
+
if (is.null(appliquer_nettoyage_fun)) {
|
| 53 |
+
if (exists("appliquer_nettoyage_et_minuscules", mode = "function", inherits = TRUE)) {
|
| 54 |
+
appliquer_nettoyage_fun <- get("appliquer_nettoyage_et_minuscules", mode = "function", inherits = TRUE)
|
| 55 |
+
} else {
|
| 56 |
+
op <- par(no.readonly = TRUE)
|
| 57 |
+
on.exit(par(op), add = TRUE)
|
| 58 |
+
par(mar = c(3.0, 2.6, 3.4, 8.5))
|
| 59 |
+
|
| 60 |
+
appliquer_nettoyage_fun <- function(textes,
|
| 61 |
+
activer_nettoyage = FALSE,
|
| 62 |
+
forcer_minuscules = FALSE,
|
| 63 |
+
supprimer_chiffres = FALSE,
|
| 64 |
+
supprimer_apostrophes = FALSE) {
|
| 65 |
+
x <- as.character(textes)
|
| 66 |
+
if (isTRUE(forcer_minuscules)) x <- tolower(x)
|
| 67 |
+
x
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
textes_prep <- appliquer_nettoyage_fun(
|
| 73 |
+
textes = textes,
|
| 74 |
+
activer_nettoyage = opts$nettoyage_caracteres,
|
| 75 |
+
forcer_minuscules = opts$forcer_minuscules_avant,
|
| 76 |
+
supprimer_chiffres = opts$supprimer_chiffres,
|
| 77 |
+
supprimer_apostrophes = opts$supprimer_apostrophes
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
if (!requireNamespace("quanteda", quietly = TRUE)) {
|
| 81 |
+
stop("CHD IRaMuTeQ-like: package quanteda requis pour préparer les entrées.")
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
tok <- quanteda::tokens(
|
| 85 |
+
textes_prep,
|
| 86 |
+
remove_punct = opts$supprimer_ponctuation,
|
| 87 |
+
remove_numbers = opts$supprimer_chiffres
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
if (opts$retirer_stopwords) {
|
| 91 |
+
sw <- quanteda::stopwords(language = langue)
|
| 92 |
+
tok <- quanteda::tokens_remove(tok, pattern = sw)
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
dfm_obj <- quanteda::dfm(tok)
|
| 96 |
+
list(textes = textes_prep, tok = tok, dfm = dfm_obj, options = opts)
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.trouver_fichier_insensible_casse <- function(dir_path, filename) {
|
| 100 |
+
if (!dir.exists(dir_path)) return(NA_character_)
|
| 101 |
+
files <- list.files(dir_path, full.names = TRUE)
|
| 102 |
+
if (length(files) == 0) return(NA_character_)
|
| 103 |
+
bn <- basename(files)
|
| 104 |
+
idx <- which(tolower(bn) == tolower(filename))
|
| 105 |
+
if (length(idx) == 0) return(NA_character_)
|
| 106 |
+
files[idx[1]]
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.trouver_rscripts_iramuteq <- function(base_dir = NULL) {
|
| 110 |
+
scripts <- c("anacor.R", "CHD.R", "chdtxt.R")
|
| 111 |
+
candidats <- unique(c(
|
| 112 |
+
base_dir,
|
| 113 |
+
"iramuteq-like",
|
| 114 |
+
"iramuteq-like/Rscripts",
|
| 115 |
+
"iramuteq_clone_v3/Rscripts"
|
| 116 |
+
))
|
| 117 |
+
candidats <- candidats[!is.na(candidats) & nzchar(candidats)]
|
| 118 |
+
|
| 119 |
+
for (cand in candidats) {
|
| 120 |
+
paths <- vapply(scripts, function(sc) .trouver_fichier_insensible_casse(cand, sc), FUN.VALUE = character(1))
|
| 121 |
+
if (all(!is.na(paths))) {
|
| 122 |
+
return(unname(paths))
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
stop(
|
| 127 |
+
"CHD IRaMuTeQ-like: scripts R introuvables. Répertoires testés: ",
|
| 128 |
+
paste(candidats, collapse = ", "),
|
| 129 |
+
". Fichiers attendus: ",
|
| 130 |
+
paste(scripts, collapse = ", "),
|
| 131 |
+
"."
|
| 132 |
+
)
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.charger_scripts_iramuteq_chd <- function(base_dir = NULL) {
|
| 136 |
+
paths <- .trouver_rscripts_iramuteq(base_dir)
|
| 137 |
+
for (p in paths) {
|
| 138 |
+
source(p, encoding = "UTF-8", local = .GlobalEnv)
|
| 139 |
+
}
|
| 140 |
+
invisible(paths)
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.normaliser_n1_chd <- function(n1) {
|
| 144 |
+
if (is.null(n1)) return(NULL)
|
| 145 |
+
if (is.data.frame(n1)) n1 <- as.matrix(n1)
|
| 146 |
+
if (is.vector(n1)) {
|
| 147 |
+
n1 <- matrix(as.integer(n1), ncol = 1)
|
| 148 |
+
}
|
| 149 |
+
if (!is.matrix(n1)) return(NULL)
|
| 150 |
+
if (nrow(n1) < 1 || ncol(n1) < 1) return(NULL)
|
| 151 |
+
n1
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
# Calcul CHD IRaMuTeQ-like (algorithme historique via scripts R IRaMuTeQ).
|
| 155 |
+
calculer_chd_iramuteq <- function(
|
| 156 |
+
dfm_obj,
|
| 157 |
+
k = 3,
|
| 158 |
+
mode_patate = FALSE,
|
| 159 |
+
svd_method = c("irlba", "svdR"),
|
| 160 |
+
libsvdc_path = NULL,
|
| 161 |
+
binariser = TRUE,
|
| 162 |
+
rscripts_dir = NULL
|
| 163 |
+
) {
|
| 164 |
+
svd_method <- match.arg(svd_method)
|
| 165 |
+
|
| 166 |
+
if (is.null(dfm_obj)) stop("CHD IRaMuTeQ-like: dfm_obj manquant.")
|
| 167 |
+
if (!is.finite(k) || is.na(k) || as.integer(k) < 2) stop("CHD IRaMuTeQ-like: k doit être >= 2.")
|
| 168 |
+
|
| 169 |
+
.charger_scripts_iramuteq_chd(rscripts_dir)
|
| 170 |
+
|
| 171 |
+
mat <- as.matrix(dfm_obj)
|
| 172 |
+
if (nrow(mat) < 2 || ncol(mat) < 2) {
|
| 173 |
+
stop("CHD IRaMuTeQ-like: matrice trop pauvre (>=2 lignes et >=2 colonnes requises).")
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
if (isTRUE(binariser)) {
|
| 177 |
+
mat <- ifelse(mat > 0, 1, 0)
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
rownames(mat) <- as.character(seq_len(nrow(mat)))
|
| 181 |
+
|
| 182 |
+
nb_tours <- as.integer(k) - 1L
|
| 183 |
+
if (nb_tours < 1) nb_tours <- 1L
|
| 184 |
+
|
| 185 |
+
chd <- CHD(
|
| 186 |
+
data.in = mat,
|
| 187 |
+
x = nb_tours,
|
| 188 |
+
mode.patate = isTRUE(mode_patate),
|
| 189 |
+
svd.method = svd_method,
|
| 190 |
+
libsvdc.path = libsvdc_path
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
n1 <- .normaliser_n1_chd(chd$n1)
|
| 194 |
+
if (is.null(n1) || nrow(n1) != nrow(mat)) {
|
| 195 |
+
stop("CHD IRaMuTeQ-like: sortie CHD invalide.")
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
chd$n1 <- n1
|
| 199 |
+
|
| 200 |
+
chd
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
# Reconstitue des classes finales depuis la sortie CHD et le principe find.terminales.
|
| 204 |
+
reconstruire_classes_terminales_iramuteq <- function(
|
| 205 |
+
chd_obj,
|
| 206 |
+
mincl = 0,
|
| 207 |
+
mincl_mode = c("auto", "manuel"),
|
| 208 |
+
classif_mode = c("simple", "double"),
|
| 209 |
+
nb_classes_cible = NULL,
|
| 210 |
+
respecter_nb_classes = TRUE
|
| 211 |
+
) {
|
| 212 |
+
mincl_mode <- match.arg(mincl_mode)
|
| 213 |
+
classif_mode <- match.arg(classif_mode)
|
| 214 |
+
|
| 215 |
+
n1 <- .normaliser_n1_chd(chd_obj$n1)
|
| 216 |
+
list_mere <- chd_obj$list_mere
|
| 217 |
+
list_fille <- chd_obj$list_fille
|
| 218 |
+
|
| 219 |
+
if (is.null(n1) || is.null(list_mere) || is.null(list_fille)) {
|
| 220 |
+
stop("CHD IRaMuTeQ-like: objet chd incomplet.")
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
nbcl <- length(unique(n1[, ncol(n1)]))
|
| 224 |
+
nbcl <- max(2L, as.integer(nbcl))
|
| 225 |
+
|
| 226 |
+
if (mincl_mode == "auto") {
|
| 227 |
+
mincl_use <- calculer_mincl_auto_iramuteq(
|
| 228 |
+
n_uce = nrow(n1),
|
| 229 |
+
nbcl = nbcl,
|
| 230 |
+
classif_mode = classif_mode
|
| 231 |
+
)
|
| 232 |
+
} else {
|
| 233 |
+
op <- par(no.readonly = TRUE)
|
| 234 |
+
on.exit(par(op), add = TRUE)
|
| 235 |
+
par(mar = c(3.0, 2.6, 3.4, 8.5))
|
| 236 |
+
|
| 237 |
+
mincl_use <- as.integer(mincl)
|
| 238 |
+
if (!is.finite(mincl_use) || is.na(mincl_use) || mincl_use < 1) mincl_use <- 1L
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
terminales <- find.terminales(n1, list_mere, list_fille, mincl = mincl_use)
|
| 242 |
+
if (is.character(terminales) && length(terminales) == 1 && terminales == "no clusters") {
|
| 243 |
+
stop("CHD IRaMuTeQ-like: aucune classe terminale retenue.")
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
feuilles <- unique(as.integer(n1[, ncol(n1)]))
|
| 247 |
+
|
| 248 |
+
if (isTRUE(respecter_nb_classes) && !is.null(nb_classes_cible) && is.finite(nb_classes_cible)) {
|
| 249 |
+
nb_classes_cible <- as.integer(nb_classes_cible)
|
| 250 |
+
if (nb_classes_cible >= 2 && length(feuilles) == nb_classes_cible && length(unique(terminales)) != nb_classes_cible) {
|
| 251 |
+
terminales <- sort(feuilles)
|
| 252 |
+
mincl_use <- 1L
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
classes_finales <- rep(0L, nrow(n1))
|
| 257 |
+
feuilles_docs <- suppressWarnings(as.integer(n1[, ncol(n1)]))
|
| 258 |
+
|
| 259 |
+
# Reproduction fidèle de iramuteq_clone_v3/Rscripts/chdtxt.R::make.classes
|
| 260 |
+
# (sans la partie manipulation du tree, non nécessaire au calcul des classes docs).
|
| 261 |
+
cl_names <- seq_along(terminales)
|
| 262 |
+
for (i in seq_along(terminales)) {
|
| 263 |
+
cl <- suppressWarnings(as.integer(terminales[[i]]))
|
| 264 |
+
if (!is.finite(cl)) next
|
| 265 |
+
|
| 266 |
+
if (cl %in% feuilles) {
|
| 267 |
+
classes_finales[which(feuilles_docs == cl)] <- cl_names[[i]]
|
| 268 |
+
} else {
|
| 269 |
+
op <- par(no.readonly = TRUE)
|
| 270 |
+
on.exit(par(op), add = TRUE)
|
| 271 |
+
par(mar = c(3.0, 2.6, 3.4, 8.5))
|
| 272 |
+
|
| 273 |
+
filles <- suppressWarnings(as.integer(getfille(list_fille, cl, NULL)))
|
| 274 |
+
tochange <- intersect(filles, feuilles)
|
| 275 |
+
for (cl_fille in tochange) {
|
| 276 |
+
classes_finales[which(feuilles_docs == cl_fille)] <- cl_names[[i]]
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
classes_finales[which(is.na(classes_finales))] <- 0L
|
| 282 |
+
list(
|
| 283 |
+
classes = classes_finales,
|
| 284 |
+
terminales = as.integer(terminales),
|
| 285 |
+
mincl = mincl_use
|
| 286 |
+
)
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
# Calcule une table de statistiques par classe dans l'esprit des sorties IRaMuTeQ.
|
| 290 |
+
construire_stats_classes_iramuteq <- function(dfm_obj, classes, max_p = 1) {
|
| 291 |
+
if (is.null(dfm_obj)) stop("Stats IRaMuTeQ-like: dfm_obj manquant.")
|
| 292 |
+
if (is.null(classes)) stop("Stats IRaMuTeQ-like: classes manquantes.")
|
| 293 |
+
|
| 294 |
+
mat <- as.matrix(dfm_obj)
|
| 295 |
+
if (nrow(mat) != length(classes)) {
|
| 296 |
+
stop("Stats IRaMuTeQ-like: longueur de classes incohérente avec le DFM.")
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
classes <- as.integer(classes)
|
| 300 |
+
# Alignement IRaMuTeQ clone (BuildProf/chdfunct.R):
|
| 301 |
+
# les UCE non classées (classe 0) sont exclues du comptage
|
| 302 |
+
# des effectifs et des tableaux de contingence.
|
| 303 |
+
ok_docs <- !is.na(classes) & classes > 0L
|
| 304 |
+
mat <- mat[ok_docs, , drop = FALSE]
|
| 305 |
+
classes <- classes[ok_docs]
|
| 306 |
+
|
| 307 |
+
if (nrow(mat) < 2 || ncol(mat) < 1) return(data.frame())
|
| 308 |
+
|
| 309 |
+
# Alignement avec l'approche IRaMuTeQ historique (BuildProf):
|
| 310 |
+
# - contingence documentaire (présence/absence terme)
|
| 311 |
+
# - chi2 signé (sur/sous-représentation)
|
| 312 |
+
# - p-value issue de chisq.test(..., correct = FALSE)
|
| 313 |
+
mat_bin <- ifelse(mat > 0, 1L, 0L)
|
| 314 |
+
total_docs <- nrow(mat_bin)
|
| 315 |
+
docs_par_terme <- colSums(mat_bin)
|
| 316 |
+
occ_par_terme <- colSums(mat)
|
| 317 |
+
|
| 318 |
+
calc_chi_sign <- function(a, b, c, d) {
|
| 319 |
+
tb <- matrix(c(a, b, c, d), nrow = 2, byrow = TRUE)
|
| 320 |
+
chi <- suppressWarnings(stats::chisq.test(tb, correct = FALSE))
|
| 321 |
+
stat <- suppressWarnings(as.numeric(chi$statistic))
|
| 322 |
+
pval <- suppressWarnings(as.numeric(chi$p.value))
|
| 323 |
+
exp11 <- suppressWarnings(as.numeric(chi$expected[1, 1]))
|
| 324 |
+
|
| 325 |
+
if (!is.finite(stat) || is.na(stat)) stat <- 0
|
| 326 |
+
if (!is.finite(pval) || is.na(pval)) pval <- 1
|
| 327 |
+
if (!is.finite(exp11) || is.na(exp11)) exp11 <- a
|
| 328 |
+
|
| 329 |
+
signe <- if (a >= exp11) 1 else -1
|
| 330 |
+
c(chi2 = stat * signe, p = pval)
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
classes_uniques <- sort(unique(classes))
|
| 334 |
+
sorties <- vector("list", length(classes_uniques))
|
| 335 |
+
|
| 336 |
+
for (i in seq_along(classes_uniques)) {
|
| 337 |
+
cl <- classes_uniques[[i]]
|
| 338 |
+
in_cl <- classes == cl
|
| 339 |
+
|
| 340 |
+
docs_cl <- sum(in_cl)
|
| 341 |
+
if (docs_cl < 1) next
|
| 342 |
+
|
| 343 |
+
docs_terme_cl <- colSums(mat_bin[in_cl, , drop = FALSE])
|
| 344 |
+
docs_terme_hors <- pmax(0, docs_par_terme - docs_terme_cl)
|
| 345 |
+
|
| 346 |
+
n11 <- as.numeric(docs_terme_cl)
|
| 347 |
+
n12 <- as.numeric(docs_terme_hors)
|
| 348 |
+
n21 <- as.numeric(pmax(0, docs_cl - docs_terme_cl))
|
| 349 |
+
n22 <- as.numeric(pmax(0, (total_docs - docs_cl) - docs_terme_hors))
|
| 350 |
+
|
| 351 |
+
chi_p <- t(mapply(calc_chi_sign, n11, n12, n21, n22))
|
| 352 |
+
|
| 353 |
+
freq_cl <- colSums(mat[in_cl, , drop = FALSE])
|
| 354 |
+
docprop_cl <- if (docs_cl > 0) docs_terme_cl / docs_cl else rep(0, ncol(mat))
|
| 355 |
+
lr <- mapply(function(a, b, c, d) {
|
| 356 |
+
n <- a + b + c + d
|
| 357 |
+
r1 <- a + b
|
| 358 |
+
r2 <- c + d
|
| 359 |
+
c1 <- a + c
|
| 360 |
+
c2 <- b + d
|
| 361 |
+
expected <- c(r1 * c1 / n, r1 * c2 / n, r2 * c1 / n, r2 * c2 / n)
|
| 362 |
+
observed <- c(a, b, c, d)
|
| 363 |
+
idx <- observed > 0 & expected > 0
|
| 364 |
+
if (!any(idx)) return(0)
|
| 365 |
+
2 * sum(observed[idx] * log(observed[idx] / expected[idx]))
|
| 366 |
+
}, n11, n12, n21, n22)
|
| 367 |
+
|
| 368 |
+
df <- data.frame(
|
| 369 |
+
Terme = colnames(mat),
|
| 370 |
+
chi2 = as.numeric(chi_p[, "chi2"]),
|
| 371 |
+
lr = as.numeric(lr),
|
| 372 |
+
frequency = as.numeric(freq_cl),
|
| 373 |
+
docprop = as.numeric(docprop_cl),
|
| 374 |
+
# Alignement IRaMuTeQ: les colonnes d'effectifs affichées en table
|
| 375 |
+
# correspondent aux occurrences (et non au nombre de segments contenant le terme).
|
| 376 |
+
# Ex.: "23/46" signifie 23 occurrences dans la classe sur 46 occurrences au total.
|
| 377 |
+
eff_st = as.numeric(freq_cl),
|
| 378 |
+
eff_total = as.numeric(occ_par_terme),
|
| 379 |
+
pourcentage = as.numeric(ifelse(occ_par_terme > 0, 100 * freq_cl / occ_par_terme, 0)),
|
| 380 |
+
# Colonnes documentaires conservées pour diagnostic (chi2 calculé sur présence/absence doc).
|
| 381 |
+
eff_docs_st = as.numeric(docs_terme_cl),
|
| 382 |
+
eff_docs_total = as.numeric(docs_par_terme),
|
| 383 |
+
p = as.numeric(chi_p[, "p"]),
|
| 384 |
+
Classe = as.integer(cl),
|
| 385 |
+
stringsAsFactors = FALSE
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
df <- df[is.finite(df$chi2) & !is.na(df$chi2), , drop = FALSE]
|
| 389 |
+
if (is.finite(max_p) && !is.na(max_p) && max_p < 1) {
|
| 390 |
+
df <- df[df$p <= max_p, , drop = FALSE]
|
| 391 |
+
}
|
| 392 |
+
df <- df[order(-df$chi2, -df$frequency, -occ_par_terme[df$Terme]), , drop = FALSE]
|
| 393 |
+
sorties[[i]] <- df
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
out <- dplyr::bind_rows(sorties)
|
| 397 |
+
if (!nrow(out)) return(out)
|
| 398 |
+
|
| 399 |
+
out$Classe_brut <- as.character(out$Classe)
|
| 400 |
+
out$p_value <- out$p
|
| 401 |
+
out$p_value_filter <- ifelse(out$p <= max_p, paste0("≤ ", max_p), paste0("> ", max_p))
|
| 402 |
+
out
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
# Dendrogramme CHD basé sur la structure hiérarchique IRaMuTeQ (list_mere/list_fille).
|
| 406 |
+
tracer_dendrogramme_chd_iramuteq <- function(chd_obj,
|
| 407 |
+
terminales = NULL,
|
| 408 |
+
classes = NULL,
|
| 409 |
+
res_stats_df = NULL,
|
| 410 |
+
top_n_terms = 4,
|
| 411 |
+
orientation = c("vertical", "horizontal"),
|
| 412 |
+
display_method = c("standard", "compact", "iramuteq_blocks")) {
|
| 413 |
+
orientation <- match.arg(orientation)
|
| 414 |
+
display_method <- match.arg(display_method)
|
| 415 |
+
|
| 416 |
+
if (is.null(chd_obj)) {
|
| 417 |
+
plot.new()
|
| 418 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible.", cex = 1.1)
|
| 419 |
+
return(invisible(NULL))
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
n1 <- .normaliser_n1_chd(chd_obj$n1)
|
| 423 |
+
if (is.null(chd_obj$list_fille) || is.null(n1)) {
|
| 424 |
+
plot.new()
|
| 425 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible.", cex = 1.1)
|
| 426 |
+
return(invisible(NULL))
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
list_fille <- chd_obj$list_fille
|
| 430 |
+
if (!is.list(list_fille) || length(list_fille) == 0) {
|
| 431 |
+
plot.new()
|
| 432 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible (list_fille vide).", cex = 1.1)
|
| 433 |
+
return(invisible(NULL))
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
noms <- names(list_fille)
|
| 437 |
+
if (is.null(noms) || any(!nzchar(noms))) noms <- as.character(seq_along(list_fille))
|
| 438 |
+
map_filles <- stats::setNames(lapply(list_fille, function(x) as.integer(x)), noms)
|
| 439 |
+
|
| 440 |
+
meres <- suppressWarnings(as.integer(names(map_filles)))
|
| 441 |
+
meres <- meres[is.finite(meres)]
|
| 442 |
+
enfants <- unique(as.integer(unlist(map_filles, use.names = FALSE)))
|
| 443 |
+
enfants <- enfants[is.finite(enfants)]
|
| 444 |
+
|
| 445 |
+
racines <- setdiff(meres, enfants)
|
| 446 |
+
racine <- if (length(racines)) racines[[1]] else if (length(meres)) meres[[1]] else NA_integer_
|
| 447 |
+
if (!is.finite(racine)) {
|
| 448 |
+
feuilles_n1 <- suppressWarnings(as.integer(n1[, ncol(n1)]))
|
| 449 |
+
feuilles_n1 <- feuilles_n1[is.finite(feuilles_n1)]
|
| 450 |
+
if (length(feuilles_n1)) racine <- min(feuilles_n1, na.rm = TRUE)
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
if (!is.finite(racine)) {
|
| 454 |
+
plot.new()
|
| 455 |
+
text(0.5, 0.5, "Structure CHD invalide.", cex = 1.1)
|
| 456 |
+
return(invisible(NULL))
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
get_filles <- function(node) {
|
| 460 |
+
key <- as.character(node)
|
| 461 |
+
x <- map_filles[[key]]
|
| 462 |
+
x <- x[is.finite(x)]
|
| 463 |
+
if (is.null(x)) integer(0) else as.integer(x)
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
terminales <- suppressWarnings(as.integer(terminales))
|
| 467 |
+
terminales <- terminales[is.finite(terminales)]
|
| 468 |
+
terminales <- unique(terminales)
|
| 469 |
+
|
| 470 |
+
# Source de vérité pour le nombre de classes à afficher :
|
| 471 |
+
# 1) résultats statistiques CHD (si disponibles), sinon
|
| 472 |
+
# 2) vecteur des classes finales documentaires.
|
| 473 |
+
# Comme dans iramuteq_clone_v3 (cutree(..., k = clnb)), on borne l'affichage
|
| 474 |
+
# aux classes finales réellement exploitées.
|
| 475 |
+
classes_utiles <- integer(0)
|
| 476 |
+
if (!is.null(res_stats_df) && is.data.frame(res_stats_df) && "Classe" %in% names(res_stats_df)) {
|
| 477 |
+
classes_stats <- suppressWarnings(as.integer(res_stats_df$Classe))
|
| 478 |
+
classes_stats <- classes_stats[is.finite(classes_stats) & classes_stats > 0]
|
| 479 |
+
classes_utiles <- sort(unique(classes_stats))
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
if (!length(classes_utiles) && !is.null(classes)) {
|
| 483 |
+
classes_int <- suppressWarnings(as.integer(classes))
|
| 484 |
+
classes_int <- classes_int[is.finite(classes_int) & classes_int > 0]
|
| 485 |
+
classes_utiles <- sort(unique(classes_int))
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
if (length(classes_utiles) && length(terminales)) {
|
| 489 |
+
idx_valides <- classes_utiles[classes_utiles >= 1L & classes_utiles <= length(terminales)]
|
| 490 |
+
terminales <- terminales[idx_valides]
|
| 491 |
+
classes_utiles <- idx_valides
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
utiliser_terminales <- length(terminales) > 0
|
| 495 |
+
|
| 496 |
+
leaves <- integer(0)
|
| 497 |
+
visited <- integer(0)
|
| 498 |
+
walk_leaves <- function(node) {
|
| 499 |
+
if (node %in% visited) return(invisible(NULL))
|
| 500 |
+
visited <<- c(visited, node)
|
| 501 |
+
|
| 502 |
+
if (isTRUE(utiliser_terminales) && node %in% terminales) {
|
| 503 |
+
leaves <<- c(leaves, node)
|
| 504 |
+
return(invisible(NULL))
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
filles <- get_filles(node)
|
| 508 |
+
if (!length(filles)) {
|
| 509 |
+
if (!isTRUE(utiliser_terminales)) {
|
| 510 |
+
leaves <<- c(leaves, node)
|
| 511 |
+
}
|
| 512 |
+
return(invisible(NULL))
|
| 513 |
+
}
|
| 514 |
+
for (f in filles) walk_leaves(f)
|
| 515 |
+
}
|
| 516 |
+
walk_leaves(racine)
|
| 517 |
+
|
| 518 |
+
if (isTRUE(utiliser_terminales)) {
|
| 519 |
+
terminales_atteintes <- intersect(unique(terminales), unique(visited))
|
| 520 |
+
leaves <- unique(c(leaves, terminales_atteintes))
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
if (!length(leaves) && !length(classes_utiles)) {
|
| 524 |
+
leaves <- sort(unique(suppressWarnings(as.integer(n1[, ncol(n1)]))))
|
| 525 |
+
leaves <- leaves[is.finite(leaves)]
|
| 526 |
+
}
|
| 527 |
+
if (!length(leaves)) {
|
| 528 |
+
plot.new()
|
| 529 |
+
text(0.5, 0.5, "Aucune feuille exploitable pour le dendrogramme.", cex = 1.1)
|
| 530 |
+
return(invisible(NULL))
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
leaves <- unique(leaves)
|
| 534 |
+
y_map <- stats::setNames(seq_along(leaves), as.character(leaves))
|
| 535 |
+
pos <- list()
|
| 536 |
+
seen <- integer(0)
|
| 537 |
+
|
| 538 |
+
layout_phylo <- function(node, depth = 0L) {
|
| 539 |
+
if (node %in% seen) return(pos[[as.character(node)]])
|
| 540 |
+
seen <<- c(seen, node)
|
| 541 |
+
|
| 542 |
+
if (isTRUE(utiliser_terminales) && node %in% leaves) {
|
| 543 |
+
y <- unname(y_map[[as.character(node)]])
|
| 544 |
+
if (is.null(y) || !is.finite(y)) y <- max(unname(y_map)) + 1
|
| 545 |
+
pos[[as.character(node)]] <<- c(x = depth, y = y)
|
| 546 |
+
return(pos[[as.character(node)]])
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
filles <- get_filles(node)
|
| 550 |
+
if (!length(filles)) {
|
| 551 |
+
y <- unname(y_map[[as.character(node)]])
|
| 552 |
+
if (is.null(y) || !is.finite(y)) y <- max(unname(y_map)) + 1
|
| 553 |
+
pos[[as.character(node)]] <<- c(x = depth, y = y)
|
| 554 |
+
return(pos[[as.character(node)]])
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
child_pos <- lapply(filles, function(f) layout_phylo(f, depth + 1L))
|
| 558 |
+
ys <- vapply(child_pos, function(v) as.numeric(v[["y"]]), numeric(1))
|
| 559 |
+
pos[[as.character(node)]] <<- c(x = depth, y = mean(ys))
|
| 560 |
+
return(pos[[as.character(node)]])
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
layout_phylo(racine, 0L)
|
| 564 |
+
|
| 565 |
+
if (!length(pos)) {
|
| 566 |
+
plot.new()
|
| 567 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible (positions vides).", cex = 1.1)
|
| 568 |
+
return(invisible(NULL))
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
all_pos <- do.call(rbind, pos)
|
| 572 |
+
if (is.null(dim(all_pos))) {
|
| 573 |
+
all_pos <- matrix(all_pos, nrow = 1L, dimnames = list(names(pos)[1], names(all_pos)))
|
| 574 |
+
}
|
| 575 |
+
all_pos <- as.matrix(all_pos)
|
| 576 |
+
if (is.null(colnames(all_pos)) || !all(c("x", "y") %in% colnames(all_pos))) {
|
| 577 |
+
plot.new()
|
| 578 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible (positions invalides).", cex = 1.1)
|
| 579 |
+
return(invisible(NULL))
|
| 580 |
+
}
|
| 581 |
+
depth_max <- max(all_pos[, "x"], na.rm = TRUE)
|
| 582 |
+
order_max <- max(all_pos[, "y"], na.rm = TRUE)
|
| 583 |
+
|
| 584 |
+
node_ids <- suppressWarnings(as.integer(rownames(all_pos)))
|
| 585 |
+
node_ids[!is.finite(node_ids)] <- NA_integer_
|
| 586 |
+
tip_idx <- which(node_ids %in% leaves)
|
| 587 |
+
|
| 588 |
+
tip_cols <- rep("#5B8FF9", nrow(all_pos))
|
| 589 |
+
if (length(terminales)) tip_cols[which(node_ids %in% terminales)] <- "#d62728"
|
| 590 |
+
|
| 591 |
+
tip_labels <- paste0("Classe ", rownames(all_pos)[tip_idx])
|
| 592 |
+
tip_nodes_chr <- rownames(all_pos)[tip_idx]
|
| 593 |
+
classe_par_noeud <- stats::setNames(rep(NA_integer_, length(tip_nodes_chr)), tip_nodes_chr)
|
| 594 |
+
|
| 595 |
+
if (length(terminales)) {
|
| 596 |
+
for (i in seq_along(terminales)) {
|
| 597 |
+
node <- terminales[[i]]
|
| 598 |
+
idx_node <- which(tip_nodes_chr == as.character(node))
|
| 599 |
+
if (!length(idx_node)) next
|
| 600 |
+
tip_labels[idx_node] <- paste0("Classe ", i)
|
| 601 |
+
classe_par_noeud[as.character(node)] <- i
|
| 602 |
+
}
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
if (!is.null(classes)) {
|
| 606 |
+
classes <- suppressWarnings(as.integer(classes))
|
| 607 |
+
classes <- classes[is.finite(classes) & classes > 0]
|
| 608 |
+
if (length(classes) && length(terminales)) {
|
| 609 |
+
pct_par_classe <- prop.table(table(classes)) * 100
|
| 610 |
+
for (i in seq_along(terminales)) {
|
| 611 |
+
node <- terminales[[i]]
|
| 612 |
+
idx_node <- which(tip_nodes_chr == as.character(node))
|
| 613 |
+
if (!length(idx_node)) next
|
| 614 |
+
pct <- unname(pct_par_classe[as.character(i)])
|
| 615 |
+
if (!is.finite(pct) || is.na(pct)) pct <- 0
|
| 616 |
+
tip_labels[idx_node] <- paste0("Classe ", i, " (", format(round(pct, 1), nsmall = 1), " %)")
|
| 617 |
+
}
|
| 618 |
+
}
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
top_n_terms <- suppressWarnings(as.integer(top_n_terms))
|
| 622 |
+
if (!is.finite(top_n_terms) || is.na(top_n_terms) || top_n_terms < 1L) top_n_terms <- 1L
|
| 623 |
+
termes_par_classe <- list()
|
| 624 |
+
if (!is.null(res_stats_df) && is.data.frame(res_stats_df) && nrow(res_stats_df) > 0 && all(c("Classe", "Terme") %in% names(res_stats_df))) {
|
| 625 |
+
df_terms <- res_stats_df
|
| 626 |
+
classes_num <- suppressWarnings(as.integer(df_terms$Classe))
|
| 627 |
+
df_terms <- df_terms[is.finite(classes_num) & !is.na(df_terms$Terme) & nzchar(as.character(df_terms$Terme)), , drop = FALSE]
|
| 628 |
+
df_terms$Classe <- suppressWarnings(as.integer(df_terms$Classe))
|
| 629 |
+
|
| 630 |
+
for (i in seq_along(terminales)) {
|
| 631 |
+
sous <- df_terms[df_terms$Classe == i, , drop = FALSE]
|
| 632 |
+
if (!nrow(sous)) next
|
| 633 |
+
if ("chi2" %in% names(sous)) {
|
| 634 |
+
chi <- suppressWarnings(as.numeric(sous$chi2))
|
| 635 |
+
chi[!is.finite(chi)] <- -Inf
|
| 636 |
+
sous <- sous[order(chi, decreasing = TRUE), , drop = FALSE]
|
| 637 |
+
}
|
| 638 |
+
termes <- unique(as.character(sous$Terme))
|
| 639 |
+
termes <- termes[nzchar(termes)]
|
| 640 |
+
if (length(termes)) {
|
| 641 |
+
termes_par_classe[[as.character(i)]] <- paste(utils::head(termes, top_n_terms), collapse = ", ")
|
| 642 |
+
}
|
| 643 |
+
}
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
if (identical(orientation, "vertical")) {
|
| 647 |
+
if (identical(display_method, "iramuteq_blocks")) {
|
| 648 |
+
op <- par(no.readonly = TRUE)
|
| 649 |
+
on.exit(par(op), add = TRUE)
|
| 650 |
+
par(mar = c(1.4, 1.4, 2.6, 1.4), xpd = NA)
|
| 651 |
+
|
| 652 |
+
class_ids <- sort(unique(na.omit(as.integer(classe_par_noeud))))
|
| 653 |
+
if (!length(class_ids) && length(terminales)) class_ids <- seq_along(terminales)
|
| 654 |
+
if (!length(class_ids)) {
|
| 655 |
+
plot.new()
|
| 656 |
+
text(0.5, 0.5, "Aucune classe terminale disponible.", cex = 1.1)
|
| 657 |
+
return(invisible(NULL))
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
cols_palette <- c("#ff3300", "#00ff00", "#1f3bff", "#ff00a8", "#00d4ff", "#ffaa00")
|
| 661 |
+
class_cols <- stats::setNames(cols_palette[(seq_along(class_ids) - 1L) %% length(cols_palette) + 1L], as.character(class_ids))
|
| 662 |
+
|
| 663 |
+
pct_par_classe <- NULL
|
| 664 |
+
if (!is.null(classes)) {
|
| 665 |
+
classes <- suppressWarnings(as.integer(classes))
|
| 666 |
+
classes <- classes[is.finite(classes) & classes > 0]
|
| 667 |
+
if (length(classes)) pct_par_classe <- prop.table(table(classes)) * 100
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
order_nodes <- tip_nodes_chr
|
| 671 |
+
if (!length(order_nodes) && length(terminales)) order_nodes <- as.character(terminales)
|
| 672 |
+
classes_ord <- suppressWarnings(as.integer(classe_par_noeud[order_nodes]))
|
| 673 |
+
keep <- is.finite(classes_ord)
|
| 674 |
+
order_nodes <- order_nodes[keep]
|
| 675 |
+
classes_ord <- classes_ord[keep]
|
| 676 |
+
if (!length(classes_ord)) classes_ord <- class_ids
|
| 677 |
+
|
| 678 |
+
n <- length(classes_ord)
|
| 679 |
+
x_pos <- seq(0.12, 0.88, length.out = n)
|
| 680 |
+
|
| 681 |
+
plot.new()
|
| 682 |
+
plot.window(xlim = c(0, 1), ylim = c(0, 1))
|
| 683 |
+
|
| 684 |
+
y_top <- 0.90
|
| 685 |
+
y_box_top <- 0.78
|
| 686 |
+
y_box_bot <- 0.64
|
| 687 |
+
box_w <- min(0.28, if (n > 1) 0.72 / (n - 1) else 0.3)
|
| 688 |
+
|
| 689 |
+
if (n >= 2) {
|
| 690 |
+
mid <- mean(x_pos)
|
| 691 |
+
segments(mid, y_top, min(x_pos), y_box_top + 0.02, lwd = 2.5, col = "#111111")
|
| 692 |
+
segments(mid, y_top, max(x_pos), y_box_top + 0.02, lwd = 2.5, col = "#111111")
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
for (i in seq_len(n)) {
|
| 696 |
+
cl <- classes_ord[i]
|
| 697 |
+
col_cl <- unname(class_cols[as.character(cl)])
|
| 698 |
+
if (is.na(col_cl) || !nzchar(col_cl)) col_cl <- "#1f3bff"
|
| 699 |
+
x <- x_pos[i]
|
| 700 |
+
|
| 701 |
+
rect(x - box_w / 2, y_box_bot, x + box_w / 2, y_box_top, col = col_cl, border = NA)
|
| 702 |
+
segments(x, y_box_top + 0.02, x, y_box_top, lwd = 2.5, col = "#111111")
|
| 703 |
+
|
| 704 |
+
text(x, y_box_top + 0.05, labels = paste0("classe ", cl), col = col_cl, cex = 1.2, font = 2)
|
| 705 |
+
pct <- if (!is.null(pct_par_classe)) unname(pct_par_classe[as.character(cl)]) else NA_real_
|
| 706 |
+
pct_lbl <- if (is.finite(pct)) paste0(format(round(pct, 1), trim = TRUE), " %") else ""
|
| 707 |
+
if (nzchar(pct_lbl)) text(x, y_box_bot + 0.015, labels = pct_lbl, cex = 0.9, col = "#111111")
|
| 708 |
+
|
| 709 |
+
termes <- character(0)
|
| 710 |
+
if (!is.null(termes_par_classe[[as.character(cl)]])) {
|
| 711 |
+
termes <- strsplit(termes_par_classe[[as.character(cl)]], "\\s*,\\s*")[[1]]
|
| 712 |
+
termes <- termes[nzchar(termes)]
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
if (length(termes)) {
|
| 716 |
+
y0 <- 0.58
|
| 717 |
+
step <- 0.055
|
| 718 |
+
for (j in seq_len(min(length(termes), 9L))) {
|
| 719 |
+
text(x - box_w / 2, y0 - (j - 1) * step, labels = termes[j], adj = c(0, 1), col = col_cl, cex = 1.6, font = 2)
|
| 720 |
+
}
|
| 721 |
+
}
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
title(main = "Dendrogramme CHD IRaMuTeQ-like (style IRaMuTeQ)")
|
| 725 |
+
return(invisible(NULL))
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
op <- par(no.readonly = TRUE)
|
| 729 |
+
on.exit(par(op), add = TRUE)
|
| 730 |
+
par(mar = c(3.2, 2.8, 3.6, 1.5))
|
| 731 |
+
|
| 732 |
+
all_pos_plot <- cbind(x = all_pos[, "y", drop = TRUE], y = depth_max - all_pos[, "x", drop = TRUE])
|
| 733 |
+
all_pos_plot <- as.matrix(all_pos_plot)
|
| 734 |
+
x_max <- max(all_pos_plot[, "x"], na.rm = TRUE)
|
| 735 |
+
y_max <- max(all_pos_plot[, "y"], na.rm = TRUE)
|
| 736 |
+
|
| 737 |
+
plot(
|
| 738 |
+
NA,
|
| 739 |
+
xlim = c(0.5, x_max + 0.5),
|
| 740 |
+
ylim = c(if (length(termes_par_classe) && identical(display_method, "standard")) -1.6 else -0.8, y_max + 0.8),
|
| 741 |
+
axes = FALSE,
|
| 742 |
+
xlab = "",
|
| 743 |
+
ylab = "",
|
| 744 |
+
main = if (identical(display_method, "compact")) "Dendrogramme CHD IRaMuTeQ-like (compact)" else "Dendrogramme CHD IRaMuTeQ-like"
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
for (mere_name in names(map_filles)) {
|
| 748 |
+
mere <- suppressWarnings(as.integer(mere_name))
|
| 749 |
+
if (!is.finite(mere)) next
|
| 750 |
+
p_m <- pos[[as.character(mere)]]
|
| 751 |
+
if (is.null(p_m)) next
|
| 752 |
+
p_m_plot <- c(x = p_m[["y"]], y = depth_max - p_m[["x"]])
|
| 753 |
+
|
| 754 |
+
filles <- as.integer(map_filles[[mere_name]])
|
| 755 |
+
filles <- filles[is.finite(filles)]
|
| 756 |
+
if (!length(filles)) next
|
| 757 |
+
|
| 758 |
+
x_child <- numeric(0)
|
| 759 |
+
for (f in filles) {
|
| 760 |
+
p_f <- pos[[as.character(f)]]
|
| 761 |
+
if (!is.null(p_f)) x_child <- c(x_child, p_f[["y"]])
|
| 762 |
+
}
|
| 763 |
+
if (length(x_child) >= 2) {
|
| 764 |
+
segments(min(x_child), p_m_plot[["y"]], max(x_child), p_m_plot[["y"]], col = "#2f4f4f", lwd = 1.6)
|
| 765 |
+
}
|
| 766 |
+
|
| 767 |
+
for (f in filles) {
|
| 768 |
+
p_f <- pos[[as.character(f)]]
|
| 769 |
+
if (is.null(p_f)) next
|
| 770 |
+
p_f_plot <- c(x = p_f[["y"]], y = depth_max - p_f[["x"]])
|
| 771 |
+
segments(p_f_plot[["x"]], p_m_plot[["y"]], p_f_plot[["x"]], p_f_plot[["y"]], col = "#2f4f4f", lwd = 1.6)
|
| 772 |
+
}
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
if (length(tip_idx)) {
|
| 776 |
+
points(all_pos_plot[tip_idx, "x", drop = TRUE], all_pos_plot[tip_idx, "y", drop = TRUE], pch = 19, col = tip_cols[tip_idx], cex = 0.95)
|
| 777 |
+
text(x = all_pos_plot[tip_idx, "x", drop = TRUE], y = all_pos_plot[tip_idx, "y", drop = TRUE] - 0.18, labels = tip_labels, adj = c(0.5, 1), cex = 0.78)
|
| 778 |
+
|
| 779 |
+
if (length(termes_par_classe) && identical(display_method, "standard")) {
|
| 780 |
+
for (j in seq_along(tip_idx)) {
|
| 781 |
+
node_id <- tip_nodes_chr[j]
|
| 782 |
+
class_id <- suppressWarnings(as.integer(classe_par_noeud[[node_id]]))
|
| 783 |
+
if (!is.finite(class_id)) next
|
| 784 |
+
termes_lbl <- termes_par_classe[[as.character(class_id)]]
|
| 785 |
+
if (is.null(termes_lbl) || !nzchar(termes_lbl)) next
|
| 786 |
+
text(
|
| 787 |
+
x = all_pos_plot[tip_idx[j], "x", drop = TRUE],
|
| 788 |
+
y = all_pos_plot[tip_idx[j], "y", drop = TRUE] - 0.56,
|
| 789 |
+
labels = termes_lbl,
|
| 790 |
+
adj = c(0.5, 1),
|
| 791 |
+
cex = 0.66,
|
| 792 |
+
col = "#333333"
|
| 793 |
+
)
|
| 794 |
+
}
|
| 795 |
+
}
|
| 796 |
+
|
| 797 |
+
if (length(termes_par_classe) && identical(display_method, "compact")) {
|
| 798 |
+
legend_lines <- unlist(lapply(sort(names(termes_par_classe)), function(cl_id) {
|
| 799 |
+
paste0("Classe ", cl_id, " : ", termes_par_classe[[cl_id]])
|
| 800 |
+
}), use.names = FALSE)
|
| 801 |
+
|
| 802 |
+
if (length(legend_lines)) {
|
| 803 |
+
texte_legend <- paste(legend_lines, collapse = "\n")
|
| 804 |
+
mtext(texte_legend, side = 1, line = 0.4, adj = 0, cex = 0.62, col = "#333333")
|
| 805 |
+
}
|
| 806 |
+
}
|
| 807 |
+
}
|
| 808 |
+
} else {
|
| 809 |
+
op <- par(no.readonly = TRUE)
|
| 810 |
+
on.exit(par(op), add = TRUE)
|
| 811 |
+
par(mar = c(3.0, 2.6, 3.4, 8.5))
|
| 812 |
+
|
| 813 |
+
plot(
|
| 814 |
+
NA,
|
| 815 |
+
xlim = c(-0.2, depth_max + 1.4),
|
| 816 |
+
ylim = c(order_max + 0.6, 0.4),
|
| 817 |
+
axes = FALSE,
|
| 818 |
+
xlab = "",
|
| 819 |
+
ylab = "",
|
| 820 |
+
main = if (identical(display_method, "compact")) "Dendrogramme CHD IRaMuTeQ-like (phylogramme compact)" else "Dendrogramme CHD IRaMuTeQ-like (phylogramme)"
|
| 821 |
+
)
|
| 822 |
+
|
| 823 |
+
for (mere_name in names(map_filles)) {
|
| 824 |
+
mere <- suppressWarnings(as.integer(mere_name))
|
| 825 |
+
if (!is.finite(mere)) next
|
| 826 |
+
p_m <- pos[[as.character(mere)]]
|
| 827 |
+
if (is.null(p_m)) next
|
| 828 |
+
|
| 829 |
+
filles <- as.integer(map_filles[[mere_name]])
|
| 830 |
+
filles <- filles[is.finite(filles)]
|
| 831 |
+
if (!length(filles)) next
|
| 832 |
+
|
| 833 |
+
y_child <- numeric(0)
|
| 834 |
+
for (f in filles) {
|
| 835 |
+
p_f <- pos[[as.character(f)]]
|
| 836 |
+
if (!is.null(p_f)) y_child <- c(y_child, p_f[["y"]])
|
| 837 |
+
}
|
| 838 |
+
if (length(y_child) >= 2) {
|
| 839 |
+
segments(p_m[["x"]], min(y_child), p_m[["x"]], max(y_child), col = "#2f4f4f", lwd = 1.6)
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
for (f in filles) {
|
| 843 |
+
p_f <- pos[[as.character(f)]]
|
| 844 |
+
if (is.null(p_f)) next
|
| 845 |
+
segments(p_m[["x"]], p_f[["y"]], p_f[["x"]], p_f[["y"]], col = "#2f4f4f", lwd = 1.6)
|
| 846 |
+
}
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
if (length(tip_idx)) {
|
| 850 |
+
points(all_pos[tip_idx, "x", drop = TRUE], all_pos[tip_idx, "y", drop = TRUE], pch = 19, col = tip_cols[tip_idx], cex = 0.95)
|
| 851 |
+
text(x = all_pos[tip_idx, "x", drop = TRUE] + 0.12, y = all_pos[tip_idx, "y", drop = TRUE], labels = tip_labels, adj = c(0, 0.5), cex = 0.78)
|
| 852 |
+
|
| 853 |
+
if (length(termes_par_classe) && identical(display_method, "standard")) {
|
| 854 |
+
for (j in seq_along(tip_idx)) {
|
| 855 |
+
node_id <- tip_nodes_chr[j]
|
| 856 |
+
class_id <- suppressWarnings(as.integer(classe_par_noeud[[node_id]]))
|
| 857 |
+
if (!is.finite(class_id)) next
|
| 858 |
+
termes_lbl <- termes_par_classe[[as.character(class_id)]]
|
| 859 |
+
if (is.null(termes_lbl) || !nzchar(termes_lbl)) next
|
| 860 |
+
text(
|
| 861 |
+
x = all_pos[tip_idx[j], "x", drop = TRUE] + 0.12,
|
| 862 |
+
y = all_pos[tip_idx[j], "y", drop = TRUE] + 0.28,
|
| 863 |
+
labels = termes_lbl,
|
| 864 |
+
adj = c(0, 0.5),
|
| 865 |
+
cex = 0.66,
|
| 866 |
+
col = "#333333"
|
| 867 |
+
)
|
| 868 |
+
}
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
if (length(termes_par_classe) && identical(display_method, "compact")) {
|
| 872 |
+
legend_lines <- unlist(lapply(sort(names(termes_par_classe)), function(cl_id) {
|
| 873 |
+
paste0("Classe ", cl_id, " : ", termes_par_classe[[cl_id]])
|
| 874 |
+
}), use.names = FALSE)
|
| 875 |
+
|
| 876 |
+
if (length(legend_lines)) {
|
| 877 |
+
legend(
|
| 878 |
+
"bottomright",
|
| 879 |
+
legend = legend_lines,
|
| 880 |
+
bty = "n",
|
| 881 |
+
cex = 0.64,
|
| 882 |
+
text.col = "#333333",
|
| 883 |
+
inset = c(-0.02, -0.02),
|
| 884 |
+
xpd = NA
|
| 885 |
+
)
|
| 886 |
+
}
|
| 887 |
+
}
|
| 888 |
+
}
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
invisible(NULL)
|
| 892 |
+
}
|
iramuteq-like/chdtxt.R
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Author: Pierre Ratinaud
|
| 2 |
+
#Copyright (c) 2008-2020 Pierre Ratinaud
|
| 3 |
+
#License: GNU/GPL
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
#fonction pour la double classification
|
| 7 |
+
#cette fonction doit etre splitter en 4 ou 5 fonctions
|
| 8 |
+
|
| 9 |
+
AssignClasseToUce <- function(listuce, chd) {
|
| 10 |
+
print('assigne classe -> uce')
|
| 11 |
+
chd[listuce[,2]+1,]
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
fille<-function(classe,classeuce) {
|
| 15 |
+
listfm<-unique(unlist(classeuce[classeuce[,classe%/%2]==classe,]))
|
| 16 |
+
listf<-listfm[listfm>=classe]
|
| 17 |
+
listf<-unique(listf)
|
| 18 |
+
listf
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
croiseeff <- function(croise, classeuce1, classeuce2) {
|
| 23 |
+
cl1 <- 0
|
| 24 |
+
cl2 <- 1
|
| 25 |
+
for (i in 1:ncol(classeuce1)) {
|
| 26 |
+
cl1 <- cl1 + 2
|
| 27 |
+
cl2 <- cl2 + 2
|
| 28 |
+
clj1 <- 0
|
| 29 |
+
clj2 <- 1
|
| 30 |
+
for (j in 1:ncol(classeuce2)) {
|
| 31 |
+
clj1 <- clj1 + 2
|
| 32 |
+
clj2 <- clj2 + 2
|
| 33 |
+
croise[cl1 - 1, clj1 -1] <- length(which(classeuce1[,i] == cl1 & classeuce2[,j] == clj1))
|
| 34 |
+
croise[cl1 - 1, clj2 -1] <- length(which(classeuce1[,i] == cl1 & classeuce2[,j] == clj2))
|
| 35 |
+
croise[cl2 - 1, clj1 -1] <- length(which(classeuce1[,i] == cl2 & classeuce2[,j] == clj1))
|
| 36 |
+
croise[cl2 - 1, clj2 -1] <- length(which(classeuce1[,i] == cl2 & classeuce2[,j] == clj2))
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
croise
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
addallfille <- function(lf) {
|
| 43 |
+
nlf <- list()
|
| 44 |
+
for (i in 1:length(lf)) {
|
| 45 |
+
if (! is.null(lf[[i]])) {
|
| 46 |
+
nlf[[i]] <- lf[[i]]
|
| 47 |
+
filles <- lf[[i]]
|
| 48 |
+
f1 <- filles[1]
|
| 49 |
+
f2 <- filles[2]
|
| 50 |
+
if (f1 > length(lf)) {
|
| 51 |
+
for (j in (length(lf) + 1):f2) {
|
| 52 |
+
nlf[[j]] <- 0
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
} else {
|
| 56 |
+
nlf[[i]] <- 0
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
nlf
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
getfille <- function(nlf, classe, pf) {
|
| 63 |
+
if (!length(nlf[[classe]])) {
|
| 64 |
+
return(pf)
|
| 65 |
+
} else {
|
| 66 |
+
for (cl in nlf[[classe]]) {
|
| 67 |
+
pf <- c(pf, cl)
|
| 68 |
+
if (cl <= length(nlf)) {
|
| 69 |
+
pf <- getfille(nlf, cl, pf)
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
return(pf)
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
getmere <- function(list_mere, classe) {
|
| 77 |
+
i <- as.numeric(classe)
|
| 78 |
+
pf <- NULL
|
| 79 |
+
while (i != 1 ) {
|
| 80 |
+
pf <- c(pf, list_mere[[i]])
|
| 81 |
+
i <- list_mere[[i]]
|
| 82 |
+
}
|
| 83 |
+
pf
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
getfillemere <- function(list_fille, list_mere, classe) {
|
| 87 |
+
return(c(getfille(list_fille, classe, NULL), getmere(list_mere, classe)))
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
getlength <- function(n1, clnb) {
|
| 91 |
+
colnb <- (clnb %/%2)
|
| 92 |
+
tab <- table(n1[,colnb])
|
| 93 |
+
eff <- tab[which(names(tab) == as.character(clnb))]
|
| 94 |
+
return(eff)
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
find.terminales <- function(n1, list_mere, list_fille, mincl) {
|
| 99 |
+
tab <- table(n1[,ncol(n1)])
|
| 100 |
+
clnames <- rownames(tab)
|
| 101 |
+
terminales <- clnames[which(tab >= mincl)]
|
| 102 |
+
tocheck <- setdiff(clnames, terminales)
|
| 103 |
+
if ("0" %in% terminales) {
|
| 104 |
+
terminales <- terminales[which(terminales != 0)]
|
| 105 |
+
}
|
| 106 |
+
if (length(terminales) == 0) {
|
| 107 |
+
return('no clusters')
|
| 108 |
+
}
|
| 109 |
+
if ("0" %in% tocheck) {
|
| 110 |
+
tocheck <- tocheck[which(tocheck != "0")]
|
| 111 |
+
}
|
| 112 |
+
print(terminales)
|
| 113 |
+
print(tocheck)
|
| 114 |
+
while (length(tocheck)!=0) {
|
| 115 |
+
for (val in tocheck) {
|
| 116 |
+
print(val)
|
| 117 |
+
mere <- list_mere[[as.numeric(val)]]
|
| 118 |
+
print('mere')
|
| 119 |
+
print(mere)
|
| 120 |
+
if (mere != 1) {
|
| 121 |
+
ln.mere <- getlength(n1, mere)
|
| 122 |
+
print('ln.mere')
|
| 123 |
+
print(ln.mere)
|
| 124 |
+
filles.mere <- getfille(list_fille, mere, NULL)
|
| 125 |
+
print('fille mere')
|
| 126 |
+
print(filles.mere)
|
| 127 |
+
filles.mere <- filles.mere[which(filles.mere != val)]
|
| 128 |
+
print(filles.mere)
|
| 129 |
+
if ((ln.mere >= mincl) & (length(intersect(filles.mere, tocheck)) == 0) & (length(intersect(filles.mere, terminales)) == 0 )) {
|
| 130 |
+
print('mere ok')
|
| 131 |
+
terminales <- c(terminales, mere)
|
| 132 |
+
for (f in c(filles.mere, val, mere)) {
|
| 133 |
+
tocheck <- tocheck[which(tocheck != f)]
|
| 134 |
+
}
|
| 135 |
+
} else if ((ln.mere >= mincl) & (length(intersect(filles.mere, terminales)) == 0) & (length(intersect(filles.mere, tocheck))!=0)){
|
| 136 |
+
print('mere a checke cause fille ds tocheck')
|
| 137 |
+
tocheck <- tocheck[which(tocheck != val)]
|
| 138 |
+
tocheck <- c(mere, tocheck)
|
| 139 |
+
|
| 140 |
+
} else {
|
| 141 |
+
print('pas ok on vire du check')
|
| 142 |
+
tocheck <- tocheck[which(tocheck != val)]
|
| 143 |
+
}
|
| 144 |
+
} else {
|
| 145 |
+
print('mere == 1')
|
| 146 |
+
tocheck <- tocheck[which(tocheck != val)]
|
| 147 |
+
}
|
| 148 |
+
print('tocheck')
|
| 149 |
+
print(tocheck)
|
| 150 |
+
}
|
| 151 |
+
print(tocheck)
|
| 152 |
+
}
|
| 153 |
+
terminales
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
make.classes <- function(terminales, n1, tree, lf) {
|
| 157 |
+
term.n1 <- unique(n1[,ncol(n1)])
|
| 158 |
+
tree.tip <- tree$tip.label
|
| 159 |
+
cl.n1 <- n1[,ncol(n1)]
|
| 160 |
+
classes <- rep(NA, nrow(n1))
|
| 161 |
+
cl.names <- 1:length(terminales)
|
| 162 |
+
new.cl <- list()
|
| 163 |
+
for (i in 1:length(terminales)) {
|
| 164 |
+
if (terminales[i] %in% term.n1) {
|
| 165 |
+
classes[which(cl.n1==terminales[i])] <- cl.names[i]
|
| 166 |
+
new.cl[[terminales[i]]] <- cl.names[i]
|
| 167 |
+
tree.tip[which(tree.tip==terminales[i])] <- paste('a', cl.names[i], sep='')
|
| 168 |
+
} else {
|
| 169 |
+
filles <- getfille(lf, as.numeric(terminales[i]), NULL)
|
| 170 |
+
tochange <- intersect(filles, term.n1)
|
| 171 |
+
for (cl in tochange) {
|
| 172 |
+
classes[which(cl.n1==cl)] <- cl.names[i]
|
| 173 |
+
new.cl[[cl]] <- cl.names[i]
|
| 174 |
+
tree.tip[which(tree.tip==cl)] <- paste('a', cl.names[i], sep='')
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
make.tip <- function(x) {
|
| 179 |
+
if (substring(x,1,1)=='a') {
|
| 180 |
+
return(substring(x,2))
|
| 181 |
+
} else {
|
| 182 |
+
return(0)
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
tree$tip.label <- tree.tip
|
| 186 |
+
ntree <- tree
|
| 187 |
+
tree$tip.label <- sapply(tree.tip, make.tip)
|
| 188 |
+
tovire <- sapply(tree.tip, function(x) {substring(x,1,1)!='a'})
|
| 189 |
+
tovire <- which(tovire)
|
| 190 |
+
ntree <- drop.tip(ntree, tip=tovire)
|
| 191 |
+
en.double <- ntree$tip.label[duplicated(ntree$tip.label)]
|
| 192 |
+
en.double <- unique(en.double)
|
| 193 |
+
tovire <- sapply(en.double, function(x) {which(ntree$tip.label == x)[1]})
|
| 194 |
+
ntree <- drop.tip(ntree, tip=tovire)
|
| 195 |
+
ntree$tip.label <- sapply(ntree$tip.label, function(x) {substring(x,2)})
|
| 196 |
+
classes[which(is.na(classes))] <- 0
|
| 197 |
+
res <- list(dendro_tot_cl = tree, tree.cl = ntree, n1=as.matrix(classes))
|
| 198 |
+
res
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
#nbt nbcl = nbt+1 tcl=((nbt+1) *2) - 2 n1[,ncol(n1)], nchd1[,ncol(nchd1)]
|
| 202 |
+
Rchdtxt<-function(uceout, chd1, chd2 = NULL, mincl=0, classif_mode=0, nbt = 9) {
|
| 203 |
+
#FIXME: le nombre de classe peut etre inferieur
|
| 204 |
+
nbcl <- nbt + 1
|
| 205 |
+
tcl <- ((nbt+1) * 2) - 2
|
| 206 |
+
#Assignation des classes
|
| 207 |
+
classeuce1<-AssignClasseToUce(listuce1,chd1$n1)
|
| 208 |
+
if (classif_mode==0) {
|
| 209 |
+
classeuce2<-AssignClasseToUce(listuce2,chd2$n1)
|
| 210 |
+
}
|
| 211 |
+
#} else {
|
| 212 |
+
# classeuce2<-classeuce1
|
| 213 |
+
#}
|
| 214 |
+
|
| 215 |
+
#calcul des poids (effectifs)
|
| 216 |
+
|
| 217 |
+
makepoids <- function(classeuce, poids) {
|
| 218 |
+
cl1 <- 0
|
| 219 |
+
cl2 <- 1
|
| 220 |
+
for (i in 1:nbt) {
|
| 221 |
+
cl1 <- cl1 + 2
|
| 222 |
+
cl2 <- cl2 + 2
|
| 223 |
+
poids[cl1 - 1] <- length(which(classeuce[,i] == cl1))
|
| 224 |
+
poids[cl2 - 1] <- length(which(classeuce[,i] == cl2))
|
| 225 |
+
}
|
| 226 |
+
poids
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
# makepoids<-function(classeuce,poids) {
|
| 230 |
+
# for (classes in 2:(tcl + 1)){
|
| 231 |
+
# for (i in 1:ncol(classeuce)) {
|
| 232 |
+
# if (poids[(classes-1)]<length(classeuce[,i][classeuce[,i]==classes])) {
|
| 233 |
+
# poids[(classes-1)]<-length(classeuce[,i][classeuce[,i]==classes])
|
| 234 |
+
# }
|
| 235 |
+
# }
|
| 236 |
+
# }
|
| 237 |
+
# poids
|
| 238 |
+
# }
|
| 239 |
+
print('make poids')
|
| 240 |
+
poids1<-vector(mode='integer',length = tcl)
|
| 241 |
+
poids1<-makepoids(classeuce1,poids1)
|
| 242 |
+
if (classif_mode==0) {
|
| 243 |
+
poids2<-vector(mode='integer',length = tcl)
|
| 244 |
+
poids2<-makepoids(classeuce2,poids2)
|
| 245 |
+
}# else {
|
| 246 |
+
# poids2<-poids1
|
| 247 |
+
#}
|
| 248 |
+
|
| 249 |
+
print('croisement classif')
|
| 250 |
+
|
| 251 |
+
# croise=matrix(ncol=tcl,nrow=tcl)
|
| 252 |
+
#
|
| 253 |
+
# docroise <- function(croise, classeuce1, classeuce2) {
|
| 254 |
+
# #production du tableau de contingence
|
| 255 |
+
# for (i in 1:ncol(classeuce1)) {
|
| 256 |
+
# #poids[i]<-length(classeuce1[,i][x==classes])
|
| 257 |
+
# for (j in 1:ncol(classeuce2)) {
|
| 258 |
+
# tablecroise<-table(classeuce1[,i],classeuce2[,j])
|
| 259 |
+
# tabcolnames<-as.numeric(colnames(tablecroise))
|
| 260 |
+
# #tabcolnames<-c(tabcolnames[(length(tabcolnames)-1)],tabcolnames[length(tabcolnames)])
|
| 261 |
+
# tabrownames<-as.numeric(rownames(tablecroise))
|
| 262 |
+
# #tabrownames<-c(tabrownames[(length(tabrownames)-1)],tabrownames[length(tabrownames)])
|
| 263 |
+
# for (k in (ncol(tablecroise)-1):ncol(tablecroise)) {
|
| 264 |
+
# for (l in (nrow(tablecroise)-1):nrow(tablecroise)) {
|
| 265 |
+
# croise[(tabrownames[l]-1),(tabcolnames[k]-1)]<-tablecroise[l,k]
|
| 266 |
+
# }
|
| 267 |
+
# }
|
| 268 |
+
# }
|
| 269 |
+
# }
|
| 270 |
+
# croise
|
| 271 |
+
# }
|
| 272 |
+
if (classif_mode==0) {
|
| 273 |
+
croise <- croiseeff( matrix(ncol=tcl,nrow=tcl), classeuce1, classeuce2)
|
| 274 |
+
} else {
|
| 275 |
+
croise <- croiseeff( matrix(ncol=tcl,nrow=tcl), classeuce1, classeuce1)
|
| 276 |
+
}
|
| 277 |
+
#print(croise)
|
| 278 |
+
if (classif_mode == 0) {ind <- (nbcl * 2)} else {ind <- nbcl}
|
| 279 |
+
if (mincl==0){
|
| 280 |
+
mincl<-round(nrow(classeuce1)/ind)
|
| 281 |
+
}
|
| 282 |
+
#if (mincl<3){
|
| 283 |
+
# mincl<-3
|
| 284 |
+
#}
|
| 285 |
+
print(mincl)
|
| 286 |
+
#print('table1')
|
| 287 |
+
#print(croise)
|
| 288 |
+
#tableau des chi2 signes
|
| 289 |
+
print('croise chi2')
|
| 290 |
+
#chicroise<-croise
|
| 291 |
+
|
| 292 |
+
# nr <- nrow(classeuce1)
|
| 293 |
+
# newchicroise <- function(croise, mincl, nr, poids1, poids2) {
|
| 294 |
+
# chicroise <- croise
|
| 295 |
+
# chicroise[which(croise < mincl)] <- 0
|
| 296 |
+
# tocompute <- which(chicroise > 0, arr.ind = TRUE)
|
| 297 |
+
# for (i in 1:nrow(tocompute)) {
|
| 298 |
+
# chitable <- matrix(ncol=2,nrow=2)
|
| 299 |
+
# chitable[1,1] <- croise[tocompute[i,1], tocompute[i,2]]
|
| 300 |
+
# chitable[1,2] <- poids1[tocompute[i,1]] - chitable[1,1]
|
| 301 |
+
# chitable[2,1] <- poids2[tocompute[i,2]] - chitable[1,1]
|
| 302 |
+
# chitable[2,2] <- nr - poids2[tocompute[i,2]] - chitable[1,2]
|
| 303 |
+
# chitest<-chisq.test(chitable,correct=FALSE)
|
| 304 |
+
# chicroise[tocompute[i,1], tocompute[i,2]] <- ifelse(chitable[1,1] > chitest$expected[1,1], round(chitest$statistic,digits=7), -round(chitest$statistic,digits=7))
|
| 305 |
+
# }
|
| 306 |
+
# chicroise
|
| 307 |
+
# }
|
| 308 |
+
#
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
dochicroise <- function(croise, mincl) {
|
| 312 |
+
chicroise <- croise
|
| 313 |
+
for (i in 1:nrow(croise)) {
|
| 314 |
+
for (j in 1:ncol(croise)) {
|
| 315 |
+
if (croise[i,j]==0) {
|
| 316 |
+
chicroise[i,j]<-0
|
| 317 |
+
} else if (croise[i,j]<mincl) {
|
| 318 |
+
chicroise[i,j]<-0
|
| 319 |
+
} else {
|
| 320 |
+
chitable<-matrix(ncol=2,nrow=2)
|
| 321 |
+
chitable[1,1]<-croise[i,j]
|
| 322 |
+
chitable[1,2]<-poids1[i]-chitable[1,1]
|
| 323 |
+
chitable[2,1]<-poids2[j]-chitable[1,1]
|
| 324 |
+
chitable[2,2]<-nrow(classeuce1)-poids2[j]-chitable[1,2]
|
| 325 |
+
chitest<-chisq.test(chitable,correct=FALSE)
|
| 326 |
+
if ((chitable[1,1]-chitest$expected[1,1])<0) {
|
| 327 |
+
chicroise[i,j]<--round(chitest$statistic,digits=7)
|
| 328 |
+
} else {
|
| 329 |
+
chicroise[i,j]<-round(chitest$statistic,digits=7)
|
| 330 |
+
#print(chitest)
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
chicroise
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
dochicroisesimple <- function(croise, mincl) {
|
| 339 |
+
chicroise <- croise
|
| 340 |
+
for (i in 1:nrow(croise)) {
|
| 341 |
+
for (j in 1:ncol(croise)) {
|
| 342 |
+
if (croise[i,j]==0) {
|
| 343 |
+
chicroise[i,j]<-0
|
| 344 |
+
} else if (croise[i,j]<mincl) {
|
| 345 |
+
chicroise[i,j]<-0
|
| 346 |
+
} else {
|
| 347 |
+
chitable<-matrix(ncol=2,nrow=2)
|
| 348 |
+
chitable[1,1]<-croise[i,j]
|
| 349 |
+
chitable[1,2]<-poids1[i]-chitable[1,1]
|
| 350 |
+
chitable[2,1]<-poids1[j]-chitable[1,1]
|
| 351 |
+
chitable[2,2]<-nrow(classeuce1)-poids1[j]-chitable[1,2]
|
| 352 |
+
chitest<-chisq.test(chitable,correct=FALSE)
|
| 353 |
+
if ((chitable[1,1]-chitest$expected[1,1])<0) {
|
| 354 |
+
chicroise[i,j]<--round(chitest$statistic,digits=7)
|
| 355 |
+
} else {
|
| 356 |
+
chicroise[i,j]<-round(chitest$statistic,digits=7)
|
| 357 |
+
#print(chitest)
|
| 358 |
+
}
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
chicroise
|
| 363 |
+
}
|
| 364 |
+
if (classif_mode == 0) {
|
| 365 |
+
chicroise <- dochicroise(croise, mincl)
|
| 366 |
+
} else {
|
| 367 |
+
chicroise <- dochicroisesimple(croise, mincl)
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
print('fin croise')
|
| 371 |
+
#print(chicroise)
|
| 372 |
+
#determination des chi2 les plus fort
|
| 373 |
+
chicroiseori<-chicroise
|
| 374 |
+
|
| 375 |
+
doxy <- function(chicroise) {
|
| 376 |
+
listx <- NULL
|
| 377 |
+
listy <- NULL
|
| 378 |
+
listxy <- which(chicroise > 3.84, arr.ind = TRUE)
|
| 379 |
+
#print(listxy)
|
| 380 |
+
val <- chicroise[which(chicroise > 3.84)]
|
| 381 |
+
ord <- order(val, decreasing = TRUE)
|
| 382 |
+
listxy <- listxy[ord,]
|
| 383 |
+
#for (i in 1:nrow(listxy)) {
|
| 384 |
+
# if ((!listxy[,2][i] %in% listx) & (!listxy[,1][i] %in% listy)) {
|
| 385 |
+
# listx <- c(listx, listxy[,2][i])
|
| 386 |
+
# listy <- c(listy, listxy[,1][i])
|
| 387 |
+
# }
|
| 388 |
+
#}
|
| 389 |
+
xy <- list(x = listxy[,2], y = listxy[,1])
|
| 390 |
+
xy
|
| 391 |
+
}
|
| 392 |
+
xy <- doxy(chicroise)
|
| 393 |
+
listx <- xy$x
|
| 394 |
+
listy <- xy$y
|
| 395 |
+
|
| 396 |
+
# maxi<-vector()
|
| 397 |
+
# chimax<-vector()
|
| 398 |
+
# for (i in 1:tcl) {
|
| 399 |
+
# maxi[i]<-which.max(chicroise)
|
| 400 |
+
# chimax[i]<-chicroise[maxi[i]]
|
| 401 |
+
# chicroise[maxi[i]]<-0
|
| 402 |
+
# }
|
| 403 |
+
# testpres<-function(x,listcoord) {
|
| 404 |
+
# for (i in 1:length(listcoord)) {
|
| 405 |
+
# if (x==listcoord[i]) {
|
| 406 |
+
# return(-1)
|
| 407 |
+
# } else {
|
| 408 |
+
# a<-1
|
| 409 |
+
# }
|
| 410 |
+
# }
|
| 411 |
+
# a
|
| 412 |
+
# }
|
| 413 |
+
# c.len=nrow(chicroise)
|
| 414 |
+
# r.len=ncol(chicroise)
|
| 415 |
+
# listx<-c(0)
|
| 416 |
+
# listy<-c(0)
|
| 417 |
+
# rang<-0
|
| 418 |
+
# cons<-list()
|
| 419 |
+
# #on garde une valeur par ligne / colonne
|
| 420 |
+
# for (i in 1:length(maxi)) {
|
| 421 |
+
# #coordonnées de chi2 max
|
| 422 |
+
# #coord <- arrayInd(maxi[i], dim(chicroise))
|
| 423 |
+
# #x.co <- coord[1,2]
|
| 424 |
+
# #y.co <- coord[1,1]
|
| 425 |
+
# x.co<-ceiling(maxi[i]/c.len)
|
| 426 |
+
# y.co<-maxi[i]-(x.co-1)*c.len
|
| 427 |
+
# #print(x.co)
|
| 428 |
+
# #print(y.co)
|
| 429 |
+
# #print(arrayInd(maxi[i], dim(chicroise)))
|
| 430 |
+
# a<-testpres(x.co,listx)
|
| 431 |
+
# b<-testpres(y.co,listy)
|
| 432 |
+
#
|
| 433 |
+
# if (a==1) {
|
| 434 |
+
# if (b==1) {
|
| 435 |
+
# rang<-rang+1
|
| 436 |
+
# listx[rang]<-x.co
|
| 437 |
+
# listy[rang]<-y.co
|
| 438 |
+
# }
|
| 439 |
+
# }
|
| 440 |
+
# cons[[1]]<-listx
|
| 441 |
+
# cons[[2]]<-listy
|
| 442 |
+
# }
|
| 443 |
+
#pour ecrire les resultats
|
| 444 |
+
for (i in 1:length(listx)) {
|
| 445 |
+
txt<-paste(listx[i]+1,listy[i]+1,sep=' ')
|
| 446 |
+
txt<-paste(txt,croise[listy[i],listx[i]],sep=' ')
|
| 447 |
+
txt<-paste(txt,chicroiseori[listy[i],listx[i]],sep=' ')
|
| 448 |
+
#print(txt)
|
| 449 |
+
}
|
| 450 |
+
#colonne de la classe
|
| 451 |
+
#trouver les filles et les meres
|
| 452 |
+
trouvefillemere<-function(classe,chd) {
|
| 453 |
+
unique(unlist(chd[chd[,classe%/%2]==classe,]))
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
#----------------------------------------------------------------------
|
| 458 |
+
findbestcoord <- function(classeuce1, classeuce2, classif_mode, nbt) {
|
| 459 |
+
#fillemere1 <- NULL
|
| 460 |
+
#fillemere2 <- NULL
|
| 461 |
+
|
| 462 |
+
#fillemere1 <- unique(classeuce1)
|
| 463 |
+
#if (classif_mode == 0) {
|
| 464 |
+
# fillemere2 <- unique(classeuce2)
|
| 465 |
+
#} else {
|
| 466 |
+
# fillemere2 <- fillemere1
|
| 467 |
+
#}
|
| 468 |
+
|
| 469 |
+
#
|
| 470 |
+
listcoordok <- list()
|
| 471 |
+
maxcl <- 0
|
| 472 |
+
nb <- 0
|
| 473 |
+
lf1 <- addallfille(chd1$list_fille)
|
| 474 |
+
if (classif_mode == 0) {
|
| 475 |
+
lf2 <- addallfille(chd2$list_fille)
|
| 476 |
+
} else {
|
| 477 |
+
lf2 <- lf1
|
| 478 |
+
listx<-listx[1:((nbt+1)*2)]
|
| 479 |
+
listy<-listy[1:((nbt+1)*2)]
|
| 480 |
+
}
|
| 481 |
+
lme1 <- chd1$list_mere
|
| 482 |
+
if (classif_mode == 0) {
|
| 483 |
+
lme2 <- chd2$list_mere
|
| 484 |
+
} else {
|
| 485 |
+
lme2 <- lme1
|
| 486 |
+
}
|
| 487 |
+
print('length listx')
|
| 488 |
+
print(length(listx))
|
| 489 |
+
#if (classif_mode == 0) {
|
| 490 |
+
for (first in 1:length(listx)) {
|
| 491 |
+
coordok <- NULL
|
| 492 |
+
f1 <- NULL
|
| 493 |
+
f2 <- NULL
|
| 494 |
+
listxp<-listx
|
| 495 |
+
listyp<-listy
|
| 496 |
+
|
| 497 |
+
#listxp<-listx[first:length(listx)]
|
| 498 |
+
#listxp<-c(listxp,listx[1:(first-1)])
|
| 499 |
+
#listyp<-listy[first:length(listy)]
|
| 500 |
+
#listyp<-c(listyp,listy[1:(first-1)])
|
| 501 |
+
listxp <- listxp[order(listx, decreasing = TRUE)]
|
| 502 |
+
listyp <- listyp[order(listx, decreasing = TRUE)]
|
| 503 |
+
#listxp<-c(listxp[first:length(listx)], listx[1:(first-1)])
|
| 504 |
+
#listyp<-c(listyp[first:length(listy)], listy[1:(first-1)])
|
| 505 |
+
for (i in 1:length(listx)) {
|
| 506 |
+
if( (!(listxp[i]+1) %in% f1) & (!(listyp[i]+1) %in% f2) ) {
|
| 507 |
+
#print(listyp[i]+1)
|
| 508 |
+
#print('not in')
|
| 509 |
+
#print(f2)
|
| 510 |
+
coordok <- rbind(coordok, c(listyp[i] + 1,listxp[i] + 1))
|
| 511 |
+
#print(c(listyp[i] + 1,listxp[i] + 1))
|
| 512 |
+
un1 <- getfillemere(lf2, chd2$list_mere, listxp[i] + 1)
|
| 513 |
+
f1 <- c(f1, un1)
|
| 514 |
+
f1 <- c(f1, listxp[i] + 1)
|
| 515 |
+
un2 <- getfillemere(lf1, chd1$list_mere, listyp[i] + 1)
|
| 516 |
+
f2 <- c(f2, un2)
|
| 517 |
+
f2 <- c(f2, listyp[i] + 1)
|
| 518 |
+
}
|
| 519 |
+
#print(coordok)
|
| 520 |
+
}
|
| 521 |
+
#if (nrow(coordok) > maxcl) {
|
| 522 |
+
nb <- 1
|
| 523 |
+
# listcoordok <- list()
|
| 524 |
+
listcoordok[[nb]] <- coordok
|
| 525 |
+
# maxcl <- nrow(coordok)
|
| 526 |
+
#} else if (nrow(coordok) == maxcl) {
|
| 527 |
+
nb <- nb + 1
|
| 528 |
+
# listcoordok[[nb]] <- coordok
|
| 529 |
+
#}
|
| 530 |
+
}
|
| 531 |
+
#} else {
|
| 532 |
+
# stopid <- ((nbt+1) * 2) - 2
|
| 533 |
+
# for (first in 1:stopid) {
|
| 534 |
+
# coordok <- NULL
|
| 535 |
+
# f1 <- NULL
|
| 536 |
+
# f2 <- NULL
|
| 537 |
+
# listxp<-listx
|
| 538 |
+
# listyp<-listy
|
| 539 |
+
#
|
| 540 |
+
# #listxp<-listx[first:length(listx)]
|
| 541 |
+
# #listxp<-c(listxp,listx[1:(first-1)])
|
| 542 |
+
# #listyp<-listy[first:length(listy)]
|
| 543 |
+
# #listyp<-c(listyp,listy[1:(first-1)])
|
| 544 |
+
# listxp <- listxp[order(listx, decreasing = TRUE)]
|
| 545 |
+
# listyp <- listyp[order(listx, decreasing = TRUE)]
|
| 546 |
+
# #listxp<-c(listxp[first:length(listx)], listx[1:(first-1)])
|
| 547 |
+
# #listyp<-c(listyp[first:length(listy)], listy[1:(first-1)])
|
| 548 |
+
# for (i in 1:stopid) {
|
| 549 |
+
# if( (!(listxp[i]+1) %in% f1) & (!(listyp[i]+1) %in% f2) ) {
|
| 550 |
+
# #print(listyp[i]+1)
|
| 551 |
+
# #print('not in')
|
| 552 |
+
# #print(f2)
|
| 553 |
+
# coordok <- rbind(coordok, c(listyp[i] + 1,listxp[i] + 1))
|
| 554 |
+
# #print(c(listyp[i] + 1,listxp[i] + 1))
|
| 555 |
+
# un1 <- getfillemere(lf2, chd2$list_mere, listxp[i] + 1)
|
| 556 |
+
# f1 <- c(f1, un1)
|
| 557 |
+
# f1 <- c(f1, listxp[i] + 1)
|
| 558 |
+
# un2 <- getfillemere(lf1, chd1$list_mere, listyp[i] + 1)
|
| 559 |
+
# f2 <- c(f2, un2)
|
| 560 |
+
# f2 <- c(f2, listyp[i] + 1)
|
| 561 |
+
# }
|
| 562 |
+
# #print(coordok)
|
| 563 |
+
# }
|
| 564 |
+
# #if (nrow(coordok) > maxcl) {
|
| 565 |
+
# nb <- 1
|
| 566 |
+
# # listcoordok <- list()
|
| 567 |
+
# listcoordok[[nb]] <- coordok
|
| 568 |
+
# # maxcl <- nrow(coordok)
|
| 569 |
+
# #} else if (nrow(coordok) == maxcl) {
|
| 570 |
+
# nb <- nb + 1
|
| 571 |
+
# # listcoordok[[nb]] <- coordok
|
| 572 |
+
# #}
|
| 573 |
+
# }
|
| 574 |
+
# }
|
| 575 |
+
#print(listcoordok)
|
| 576 |
+
listcoordok <- unique(listcoordok)
|
| 577 |
+
print(listcoordok)
|
| 578 |
+
best <- 1
|
| 579 |
+
if (length(listcoordok) > 1) {
|
| 580 |
+
maxchi <- 0
|
| 581 |
+
for (i in 1:length(listcoordok)) {
|
| 582 |
+
chi <- NULL
|
| 583 |
+
uce <- NULL
|
| 584 |
+
for (j in 1:nrow(listcoordok[[i]])) {
|
| 585 |
+
chi<-c(chi,chicroiseori[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)])
|
| 586 |
+
uce<-c(uce,croise[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)])
|
| 587 |
+
}
|
| 588 |
+
if (maxchi < sum(chi)) {
|
| 589 |
+
maxchi <- sum(chi)
|
| 590 |
+
suce <- sum(uce)
|
| 591 |
+
best <- i
|
| 592 |
+
}
|
| 593 |
+
}
|
| 594 |
+
print(suce/nrow(classeuce1))
|
| 595 |
+
}
|
| 596 |
+
listcoordok[[best]]
|
| 597 |
+
}
|
| 598 |
+
#---------------------------------------------------------------------------------
|
| 599 |
+
#pour trouver une valeur dans une liste
|
| 600 |
+
#is.element(elem, list)
|
| 601 |
+
#== elem%in%list
|
| 602 |
+
oldfindbestcoord <- function(listx, listy) {
|
| 603 |
+
coordok<-NULL
|
| 604 |
+
trouvecoordok<-function(first) {
|
| 605 |
+
fillemere1<-NULL
|
| 606 |
+
fillemere2<-NULL
|
| 607 |
+
listxp<-listx
|
| 608 |
+
listyp<-listy
|
| 609 |
+
listxp<-listx[first:length(listx)]
|
| 610 |
+
listxp<-c(listxp,listx[1:(first-1)])
|
| 611 |
+
listyp<-listy[first:length(listy)]
|
| 612 |
+
listyp<-c(listyp,listy[1:(first-1)])
|
| 613 |
+
for (i in 1:length(listxp)) {
|
| 614 |
+
if (!(listxp[i]+1)%in%fillemere1) {
|
| 615 |
+
if (!(listyp[i]+1)%in%fillemere2) {
|
| 616 |
+
coordok<-rbind(coordok,c(listyp[i]+1,listxp[i]+1))
|
| 617 |
+
fillemere1<-c(fillemere1,trouvefillemere(listxp[i]+1,chd2$n1))
|
| 618 |
+
fillemere2<-c(fillemere2,trouvefillemere(listyp[i]+1,chd1$n1))
|
| 619 |
+
}
|
| 620 |
+
}
|
| 621 |
+
}
|
| 622 |
+
coordok
|
| 623 |
+
}
|
| 624 |
+
#fonction pour trouver le nombre maximum de classes
|
| 625 |
+
findmaxclasse<-function(listx,listy) {
|
| 626 |
+
listcoordok<-list()
|
| 627 |
+
maxcl<-0
|
| 628 |
+
nb<-1
|
| 629 |
+
for (i in 1:length(listy)) {
|
| 630 |
+
coordok<-trouvecoordok(i)
|
| 631 |
+
if (maxcl <= nrow(coordok)) {
|
| 632 |
+
maxcl<-nrow(coordok)
|
| 633 |
+
listcoordok[[nb]]<-coordok
|
| 634 |
+
nb<-nb+1
|
| 635 |
+
}
|
| 636 |
+
}
|
| 637 |
+
listcoordok<-unique(listcoordok)
|
| 638 |
+
#print(listcoordok)
|
| 639 |
+
#si plusieurs ensemble avec le meme nombre de classe, on conserve
|
| 640 |
+
#la liste avec le plus fort chi2
|
| 641 |
+
if (length(listcoordok)>1) {
|
| 642 |
+
maxchi<-0
|
| 643 |
+
best<-NULL
|
| 644 |
+
for (i in 1:length(listcoordok)) {
|
| 645 |
+
chi<-NULL
|
| 646 |
+
uce<-NULL
|
| 647 |
+
if (nrow(listcoordok[[i]])==maxcl) {
|
| 648 |
+
for (j in 1:nrow(listcoordok[[i]])) {
|
| 649 |
+
chi<-c(chi,croise[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)])
|
| 650 |
+
uce<-c(uce,chicroiseori[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)])
|
| 651 |
+
}
|
| 652 |
+
if (maxchi < sum(chi)) {
|
| 653 |
+
maxchi <- sum(chi)
|
| 654 |
+
suce <- sum(uce)
|
| 655 |
+
best <- i
|
| 656 |
+
}
|
| 657 |
+
}
|
| 658 |
+
}
|
| 659 |
+
}
|
| 660 |
+
print((maxchi/nrow(classeuce1)*100))
|
| 661 |
+
listcoordok[[best]]
|
| 662 |
+
}
|
| 663 |
+
print('cherche max')
|
| 664 |
+
coordok<-findmaxclasse(listx,listy)
|
| 665 |
+
coordok
|
| 666 |
+
}
|
| 667 |
+
#findmaxclasse(listx,listy)
|
| 668 |
+
#coordok<-trouvecoordok(1)
|
| 669 |
+
#coordok <- oldfindbestcoord(listx, listy)
|
| 670 |
+
print('begin bestcoord')
|
| 671 |
+
coordok <- findbestcoord(listx, listy, classif_mode, nbt)
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
lfilletot<-function(classeuce,x) {
|
| 675 |
+
listfille<-NULL
|
| 676 |
+
for (classe in 1:nrow(coordok)) {
|
| 677 |
+
listfille<-unique(c(listfille,fille(coordok[classe,x],classeuce)))
|
| 678 |
+
listfille
|
| 679 |
+
}
|
| 680 |
+
}
|
| 681 |
+
print('listfille')
|
| 682 |
+
listfille1<-lfilletot(classeuce1,1)
|
| 683 |
+
if (classif_mode == 0) {
|
| 684 |
+
listfille2<-lfilletot(classeuce2,2)
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
#utiliser rownames comme coordonnees dans un tableau de 0
|
| 688 |
+
Assignclasse<-function(classeuce,x) {
|
| 689 |
+
nchd<-matrix(0,ncol=ncol(classeuce),nrow=nrow(classeuce))
|
| 690 |
+
for (classe in 1:nrow(coordok)) {
|
| 691 |
+
clnb<-coordok[classe,x]
|
| 692 |
+
colnb<-clnb%/%2
|
| 693 |
+
nchd[which(classeuce[,colnb]==clnb), colnb:ncol(nchd)] <- classe
|
| 694 |
+
}
|
| 695 |
+
nchd
|
| 696 |
+
}
|
| 697 |
+
print('commence assigne new classe')
|
| 698 |
+
nchd1<-Assignclasse(classeuce1,1)
|
| 699 |
+
if (classif_mode==0) {
|
| 700 |
+
nchd2<-Assignclasse(classeuce2,2)
|
| 701 |
+
}
|
| 702 |
+
print('fini assign new classe')
|
| 703 |
+
#croisep<-matrix(ncol=nrow(coordok),nrow=nrow(coordok))
|
| 704 |
+
if (classif_mode==0) {
|
| 705 |
+
nchd2[which(nchd1[,ncol(nchd1)]==0),] <- 0
|
| 706 |
+
nchd2[which(nchd1[,ncol(nchd1)]!=nchd2[,ncol(nchd2)]),] <- 0
|
| 707 |
+
nchd1[which(nchd2[,ncol(nchd2)]==0),] <- 0
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
print('fini croise')
|
| 711 |
+
elim<-which(nchd1[,ncol(nchd1)]==0)
|
| 712 |
+
keep<-which(nchd1[,ncol(nchd1)]!=0)
|
| 713 |
+
n1<-nchd1[nchd1[,ncol(nchd1)]!=0,]
|
| 714 |
+
if (classif_mode==0) {
|
| 715 |
+
n2<-nchd2[nchd2[,ncol(nchd2)]!=0,]
|
| 716 |
+
} else {
|
| 717 |
+
classeuce2 <- NULL
|
| 718 |
+
}
|
| 719 |
+
#clnb<-nrow(coordok)
|
| 720 |
+
print('fini')
|
| 721 |
+
write.csv2(nchd1[,ncol(nchd1)],uceout)
|
| 722 |
+
res <- list(n1 = nchd1, coord_ok = coordok, cuce1 = classeuce1, cuce2 = classeuce2)
|
| 723 |
+
res
|
| 724 |
+
}
|
iramuteq-like/concordancier-iramuteq.R
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: concordancier-iramuteq.R génère un concordancier HTML dédié au mode IRaMuTeQ-like.
|
| 2 |
+
# Le rendu suit le style Rainette (segments par classe + surlignage),
|
| 3 |
+
# avec une sélection des termes alignée sur les filtres statistiques IRaMuTeQ-like.
|
| 4 |
+
|
| 5 |
+
.generer_concordancier_iramuteq_termes <- function(res_stats_df, classe, max_p = 1, filtrer_pvalue = TRUE) {
|
| 6 |
+
if (is.null(res_stats_df) || nrow(res_stats_df) == 0) return(character(0))
|
| 7 |
+
if (!all(c("Classe", "Terme") %in% names(res_stats_df))) return(character(0))
|
| 8 |
+
|
| 9 |
+
df <- res_stats_df
|
| 10 |
+
cl <- suppressWarnings(as.numeric(classe))
|
| 11 |
+
if (!is.na(cl)) {
|
| 12 |
+
classes_num <- suppressWarnings(as.numeric(df$Classe))
|
| 13 |
+
df <- df[!is.na(classes_num) & classes_num == cl, , drop = FALSE]
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
if (nrow(df) == 0) return(character(0))
|
| 17 |
+
|
| 18 |
+
# Filtres IRaMuTeQ-like: p <= max_p et, par défaut, uniquement les chi2 positifs.
|
| 19 |
+
if (isTRUE(filtrer_pvalue)) {
|
| 20 |
+
if ("p" %in% names(df) && is.finite(max_p) && !is.na(max_p)) {
|
| 21 |
+
p_vals <- suppressWarnings(as.numeric(df$p))
|
| 22 |
+
df <- df[!is.na(p_vals) & p_vals <= max_p, , drop = FALSE]
|
| 23 |
+
} else if ("p_value" %in% names(df) && is.finite(max_p) && !is.na(max_p)) {
|
| 24 |
+
p_vals <- suppressWarnings(as.numeric(df$p_value))
|
| 25 |
+
df <- df[!is.na(p_vals) & p_vals <= max_p, , drop = FALSE]
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
if ("chi2" %in% names(df)) {
|
| 30 |
+
chi2_vals <- suppressWarnings(as.numeric(df$chi2))
|
| 31 |
+
df <- df[!is.na(chi2_vals) & chi2_vals > 0, , drop = FALSE]
|
| 32 |
+
chi2_vals <- suppressWarnings(as.numeric(df$chi2))
|
| 33 |
+
df <- df[order(-chi2_vals), , drop = FALSE]
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
termes <- unique(as.character(df$Terme))
|
| 37 |
+
termes <- termes[!is.na(termes) & nzchar(trimws(termes))]
|
| 38 |
+
termes
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
generer_concordancier_iramuteq_html <- function(
|
| 42 |
+
chemin_sortie,
|
| 43 |
+
segments_by_class,
|
| 44 |
+
res_stats_df,
|
| 45 |
+
max_p,
|
| 46 |
+
filtrer_pvalue = TRUE,
|
| 47 |
+
textes_indexation,
|
| 48 |
+
avancer = NULL,
|
| 49 |
+
rv = NULL,
|
| 50 |
+
...
|
| 51 |
+
) {
|
| 52 |
+
if (!is.null(rv)) ajouter_log(rv, "Concordancier IRaMuTeQ-like : génération HTML (filtres IRaMuTeQ + surlignage Unicode).")
|
| 53 |
+
|
| 54 |
+
con <- file(chemin_sortie, open = "wt", encoding = "UTF-8")
|
| 55 |
+
on.exit(try(close(con), silent = TRUE), add = TRUE)
|
| 56 |
+
|
| 57 |
+
writeLines("<html><head><meta charset='utf-8'/>", con)
|
| 58 |
+
writeLines("<style>body{font-family:Arial,sans-serif;line-height:1.45;} span.highlight{background-color:yellow;} p.segment{margin:0 0 .45rem 0;} .classe-bloc{margin-bottom:1.25rem;padding-bottom:.8rem;border-bottom:1px solid #eee;}</style>", con)
|
| 59 |
+
writeLines("</head><body>", con)
|
| 60 |
+
writeLines("<h1>Concordancier IRaMuTeQ-like</h1>", con)
|
| 61 |
+
writeLines("<h2>Segments par classe</h2>", con)
|
| 62 |
+
writeLines(if (isTRUE(filtrer_pvalue)) "<h3>Filtrage: p ≤ seuil + χ² positif (puis fallback top χ²)</h3>" else "<h3>Filtrage: χ² positif (sans filtre p-value)</h3>", con)
|
| 63 |
+
|
| 64 |
+
noms_classes <- names(segments_by_class)
|
| 65 |
+
n_classes <- length(noms_classes)
|
| 66 |
+
if (n_classes == 0) n_classes <- 1
|
| 67 |
+
|
| 68 |
+
for (i in seq_along(noms_classes)) {
|
| 69 |
+
cl <- noms_classes[[i]]
|
| 70 |
+
if (!is.null(avancer)) avancer(0.75 + (i / n_classes) * 0.08, paste0("HTML IRaMuTeQ : classe ", cl))
|
| 71 |
+
|
| 72 |
+
writeLines("<div class='classe-bloc'>", con)
|
| 73 |
+
writeLines(paste0("<h2>Classe ", cl, "</h2>"), con)
|
| 74 |
+
|
| 75 |
+
segments <- segments_by_class[[cl]]
|
| 76 |
+
ids_cl <- names(segments)
|
| 77 |
+
if (length(ids_cl) == 0) {
|
| 78 |
+
writeLines("<p><em>Aucun segment.</em></p>", con)
|
| 79 |
+
writeLines("</div>", con)
|
| 80 |
+
next
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
textes_filtrage <- unname(segments)
|
| 84 |
+
if (!is.null(textes_indexation) && length(textes_indexation) > 0) {
|
| 85 |
+
tx <- textes_indexation[ids_cl]
|
| 86 |
+
ok_tx <- !is.na(tx) & nzchar(tx)
|
| 87 |
+
if (any(ok_tx)) textes_filtrage[ok_tx] <- tx[ok_tx]
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
termes_cl <- .generer_concordancier_iramuteq_termes(res_stats_df, cl, max_p = max_p, filtrer_pvalue = filtrer_pvalue)
|
| 91 |
+
|
| 92 |
+
if (length(termes_cl) == 0 && !is.null(res_stats_df) && nrow(res_stats_df) > 0 && "Classe" %in% names(res_stats_df)) {
|
| 93 |
+
df_cl <- res_stats_df[suppressWarnings(as.numeric(res_stats_df$Classe)) == suppressWarnings(as.numeric(cl)), , drop = FALSE]
|
| 94 |
+
if (nrow(df_cl) > 0 && "chi2" %in% names(df_cl) && "Terme" %in% names(df_cl)) {
|
| 95 |
+
chi2_vals <- suppressWarnings(as.numeric(df_cl$chi2))
|
| 96 |
+
idx <- !is.na(chi2_vals) & !is.na(df_cl$Terme) & nzchar(as.character(df_cl$Terme))
|
| 97 |
+
if (any(idx)) {
|
| 98 |
+
df_cl <- df_cl[idx, , drop = FALSE]
|
| 99 |
+
df_cl <- df_cl[order(-suppressWarnings(as.numeric(df_cl$chi2))), , drop = FALSE]
|
| 100 |
+
termes_cl <- unique(head(as.character(df_cl$Terme), 20))
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
termes_cl <- expandir_variantes_termes(termes_cl)
|
| 106 |
+
keep <- detecter_segments_contenant_termes_unicode(textes_filtrage, termes_cl)
|
| 107 |
+
keep[is.na(keep)] <- FALSE
|
| 108 |
+
|
| 109 |
+
segments_keep <- segments[keep]
|
| 110 |
+
if (length(segments_keep) == 0 && length(segments) > 0) {
|
| 111 |
+
segments_keep <- segments
|
| 112 |
+
if (!is.null(rv)) {
|
| 113 |
+
ajouter_log(rv, paste0(
|
| 114 |
+
"Concordancier IRaMuTeQ-like : classe ", cl,
|
| 115 |
+
" sans segment après filtrage, fallback sur tous les segments."
|
| 116 |
+
))
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
writeLines(paste0("<p><em>Segments conservés : ", length(segments_keep), " / ", length(segments), "</em></p>"), con)
|
| 121 |
+
|
| 122 |
+
if (length(segments_keep) == 0) {
|
| 123 |
+
writeLines("<p><em>Aucun segment.</em></p>", con)
|
| 124 |
+
writeLines("</div>", con)
|
| 125 |
+
next
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if (length(termes_cl) == 0) {
|
| 129 |
+
for (seg in echapper_segments_en_preservant_surlignage(unname(segments_keep), "<span class='highlight'>", "</span>")) {
|
| 130 |
+
writeLines(paste0("<p class='segment'>", seg, "</p>"), con)
|
| 131 |
+
}
|
| 132 |
+
writeLines("</div>", con)
|
| 133 |
+
next
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
motifs <- preparer_motifs_surlignage_nfd(termes_cl, taille_lot = 80)
|
| 137 |
+
segments_hl <- surligner_vecteur_html_unicode(
|
| 138 |
+
unname(segments_keep),
|
| 139 |
+
motifs,
|
| 140 |
+
"<span class='highlight'>",
|
| 141 |
+
"</span>",
|
| 142 |
+
on_error = function(e, pat) {
|
| 143 |
+
if (!is.null(rv)) {
|
| 144 |
+
ajouter_log(rv, paste0("Concordancier IRaMuTeQ-like : erreur regex [", pat, "] - ", conditionMessage(e)))
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
has_hl <- any(grepl("<span class='highlight'>", segments_hl, fixed = TRUE))
|
| 150 |
+
if (!has_hl) {
|
| 151 |
+
textes_keep_idx <- textes_filtrage[keep]
|
| 152 |
+
segments_hl_idx <- surligner_vecteur_html_unicode(
|
| 153 |
+
unname(textes_keep_idx),
|
| 154 |
+
motifs,
|
| 155 |
+
"<span class='highlight'>",
|
| 156 |
+
"</span>",
|
| 157 |
+
on_error = function(e, pat) {
|
| 158 |
+
if (!is.null(rv)) {
|
| 159 |
+
ajouter_log(rv, paste0("Concordancier IRaMuTeQ-like : erreur regex index [", pat, "] - ", conditionMessage(e)))
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
)
|
| 163 |
+
if (any(grepl("<span class='highlight'>", segments_hl_idx, fixed = TRUE))) {
|
| 164 |
+
segments_hl <- segments_hl_idx
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
if (length(segments_hl) == 0 && length(segments_keep) > 0) {
|
| 169 |
+
segments_hl <- unname(segments_keep)
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
for (seg in echapper_segments_en_preservant_surlignage(segments_hl, "<span class='highlight'>", "</span>")) {
|
| 173 |
+
writeLines(paste0("<p class='segment'>", seg, "</p>"), con)
|
| 174 |
+
}
|
| 175 |
+
writeLines("</div>", con)
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
writeLines("</body></html>", con)
|
| 179 |
+
close(con)
|
| 180 |
+
if (!is.null(rv)) ajouter_log(rv, paste0("Concordancier IRaMuTeQ-like : HTML écrit dans : ", chemin_sortie))
|
| 181 |
+
chemin_sortie
|
| 182 |
+
}
|
iramuteq-like/cooccurrences_iramuteq.R
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: cooccurrences.R regroupe l'analyse des cooccurrences par classe.
|
| 2 |
+
|
| 3 |
+
generer_cooccurrences_par_classe <- function(tok_ok,
|
| 4 |
+
filtered_corpus_ok,
|
| 5 |
+
classes_uniques,
|
| 6 |
+
cooc_dir,
|
| 7 |
+
top_n,
|
| 8 |
+
top_feat,
|
| 9 |
+
window_cooc) {
|
| 10 |
+
top_n_demande <- suppressWarnings(as.integer(top_n))
|
| 11 |
+
if (!is.finite(top_n_demande) || is.na(top_n_demande)) top_n_demande <- 20L
|
| 12 |
+
top_n_demande <- max(5L, top_n_demande)
|
| 13 |
+
|
| 14 |
+
top_feat_demande <- suppressWarnings(as.integer(top_feat))
|
| 15 |
+
if (!is.finite(top_feat_demande) || is.na(top_feat_demande)) top_feat_demande <- 20L
|
| 16 |
+
top_feat_demande <- max(5L, top_feat_demande)
|
| 17 |
+
|
| 18 |
+
window_effectif <- suppressWarnings(as.integer(window_cooc))
|
| 19 |
+
if (!is.finite(window_effectif) || is.na(window_effectif)) window_effectif <- 5L
|
| 20 |
+
window_effectif <- max(1L, window_effectif)
|
| 21 |
+
|
| 22 |
+
for (cl in classes_uniques) {
|
| 23 |
+
tok_cl <- tok_ok[docvars(filtered_corpus_ok)$Classes == cl]
|
| 24 |
+
cooc_png <- file.path(cooc_dir, paste0("cluster_", cl, "_fcm_network.png"))
|
| 25 |
+
|
| 26 |
+
try({
|
| 27 |
+
if (length(tok_cl) > 0) {
|
| 28 |
+
fcm_cl <- fcm(tok_cl, context = "window", window = window_effectif, tri = FALSE)
|
| 29 |
+
term_freq <- sort(colSums(fcm_cl), decreasing = TRUE)
|
| 30 |
+
|
| 31 |
+
# On borne aussi par top_n pour garder une cohérence entre nuage de mots et graphe de cooccurrences.
|
| 32 |
+
top_feat_effectif <- min(top_feat_demande, top_n_demande)
|
| 33 |
+
feat_sel <- names(term_freq)[seq_len(min(top_feat_effectif, length(term_freq)))]
|
| 34 |
+
fcm_cl <- fcm_select(fcm_cl, feat_sel, selection = "keep")
|
| 35 |
+
|
| 36 |
+
adj <- as.matrix(fcm_cl)
|
| 37 |
+
g <- graph_from_adjacency_matrix(adj, mode = "undirected", weighted = TRUE, diag = FALSE)
|
| 38 |
+
|
| 39 |
+
num_nodes <- length(V(g))
|
| 40 |
+
palette_colors <- brewer.pal(min(8, num_nodes), "Set3")
|
| 41 |
+
V(g)$color <- palette_colors[seq_along(V(g))]
|
| 42 |
+
|
| 43 |
+
png(cooc_png, width = 1600, height = 1200)
|
| 44 |
+
plot(
|
| 45 |
+
g,
|
| 46 |
+
layout = layout_with_fr(g),
|
| 47 |
+
main = paste("Cooccurrences - Classe", cl),
|
| 48 |
+
vertex.size = 16,
|
| 49 |
+
vertex.color = V(g)$color,
|
| 50 |
+
vertex.label = V(g)$name,
|
| 51 |
+
vertex.label.cex = 1,
|
| 52 |
+
edge.width = E(g)$weight / 2,
|
| 53 |
+
edge.color = "gray80"
|
| 54 |
+
)
|
| 55 |
+
dev.off()
|
| 56 |
+
}
|
| 57 |
+
}, silent = TRUE)
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
construire_table_cooccurrences <- function(cooc_dir) {
|
| 62 |
+
cooc_files <- list.files(cooc_dir, pattern = "\\.png$", full.names = FALSE)
|
| 63 |
+
if (length(cooc_files) > 0) {
|
| 64 |
+
cooc_classes <- gsub("^cluster_([0-9]+)_fcm_network\\.png$", "\\1", cooc_files)
|
| 65 |
+
coocs_df <- data.frame(
|
| 66 |
+
classe = cooc_classes,
|
| 67 |
+
src = file.path("cooccurrences", cooc_files),
|
| 68 |
+
stringsAsFactors = FALSE
|
| 69 |
+
)
|
| 70 |
+
coocs_df <- coocs_df[order(suppressWarnings(as.integer(coocs_df$classe))), , drop = FALSE]
|
| 71 |
+
} else {
|
| 72 |
+
coocs_df <- data.frame(classe = character(0), src = character(0), stringsAsFactors = FALSE)
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
coocs_df
|
| 76 |
+
}
|
iramuteq-like/dendogramme_iramuteq.R
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: point d'entrée UI pour le tracé du dendrogramme IRaMuTeQ-like.
|
| 2 |
+
|
| 3 |
+
tracer_dendogramme_iramuteq_ui <- function(rv,
|
| 4 |
+
top_n_terms = 4,
|
| 5 |
+
orientation = "vertical",
|
| 6 |
+
display_method = "compact") {
|
| 7 |
+
if (is.null(rv$res) && is.null(rv$res_chd)) {
|
| 8 |
+
plot.new()
|
| 9 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible.", cex = 1.1)
|
| 10 |
+
return(invisible(NULL))
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
chd_obj <- NULL
|
| 14 |
+
if (!is.null(rv$res$chd)) {
|
| 15 |
+
chd_obj <- rv$res$chd
|
| 16 |
+
} else if (!is.null(rv$res_chd)) {
|
| 17 |
+
chd_obj <- rv$res_chd
|
| 18 |
+
} else if (exists("obtenir_objet_dendrogramme", mode = "function", inherits = TRUE)) {
|
| 19 |
+
chd_obj <- obtenir_objet_dendrogramme(rv$res)
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
terminales <- if (!is.null(rv$res$terminales)) rv$res$terminales else NULL
|
| 23 |
+
classes <- if (!is.null(rv$res$classes)) rv$res$classes else NULL
|
| 24 |
+
if (is.null(classes) && !is.null(rv$filtered_corpus) && "Classes" %in% names(docvars(rv$filtered_corpus))) {
|
| 25 |
+
classes <- docvars(rv$filtered_corpus)$Classes
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
if (is.null(chd_obj)) {
|
| 29 |
+
plot.new()
|
| 30 |
+
text(0.5, 0.5, "Dendrogramme CHD indisponible (objet CHD introuvable).", cex = 1.1)
|
| 31 |
+
return(invisible(NULL))
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
tracer_dendrogramme_chd_iramuteq(
|
| 35 |
+
chd_obj = chd_obj,
|
| 36 |
+
terminales = terminales,
|
| 37 |
+
classes = classes,
|
| 38 |
+
res_stats_df = rv$res_stats_df,
|
| 39 |
+
top_n_terms = top_n_terms,
|
| 40 |
+
orientation = orientation,
|
| 41 |
+
display_method = display_method
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
invisible(NULL)
|
| 45 |
+
}
|
iramuteq-like/nettoyage_iramuteq.R
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: nettoyage_iramuteq.R isole la préparation texte du mode IRaMuTeQ-like.
|
| 2 |
+
# Cette logique est volontairement séparée de Rainette car les conventions de préparation
|
| 3 |
+
# ne sont pas identiques (script textprepa Python et dictionnaire lexique_fr imposé).
|
| 4 |
+
|
| 5 |
+
appliquer_nettoyage_iramuteq <- function(textes,
|
| 6 |
+
activer_nettoyage = FALSE,
|
| 7 |
+
forcer_minuscules = FALSE,
|
| 8 |
+
supprimer_chiffres = FALSE,
|
| 9 |
+
supprimer_apostrophes = FALSE) {
|
| 10 |
+
x <- as.character(textes)
|
| 11 |
+
if (length(x) == 0) return(character(0))
|
| 12 |
+
|
| 13 |
+
x <- gsub("\u00A0", " ", x, fixed = TRUE)
|
| 14 |
+
|
| 15 |
+
if (isTRUE(supprimer_chiffres)) {
|
| 16 |
+
x <- gsub("[0-9]+", " ", x, perl = TRUE)
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
if (isTRUE(supprimer_apostrophes)) {
|
| 20 |
+
x <- gsub("(?i)\\b(?:[cdjlmnst]|qu)['’`´ʼʹ](?=\\p{L})", "", x, perl = TRUE)
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
if (isTRUE(activer_nettoyage)) {
|
| 24 |
+
regex_autorises <- "a-zA-Z0-9àÀâÂäÄáÁåÅãéÉèÈêÊëËìÌîÎïÏíÍóÓòÒôÔöÖõÕøØùÙûÛüÜúÚçÇßœŒ’ñÑ\\.:,;!\\?'"
|
| 25 |
+
regex_a_supprimer <- paste0("[^", regex_autorises, "]")
|
| 26 |
+
x <- gsub(regex_a_supprimer, " ", x, perl = TRUE)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
x <- gsub("\\s+", " ", x, perl = TRUE)
|
| 30 |
+
x <- trimws(x)
|
| 31 |
+
|
| 32 |
+
if (isTRUE(forcer_minuscules)) {
|
| 33 |
+
x <- tolower(x)
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
x
|
| 37 |
+
}
|
iramuteq-like/nlp_lexique_iramuteq.R
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: nlp_lexique.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
# Il facilite la maintenance en explicitant le périmètre et les points d'intégration.
|
| 4 |
+
# Module NLP - lemmatisation via lexique externe (dictionnaires/lexique_fr.csv)
|
| 5 |
+
# Ce module charge un lexique 3 colonnes au format canonique
|
| 6 |
+
# (c_mot, c_lemme, c_morpho).
|
| 7 |
+
# et applique une lemmatisation explicite sans fallback silencieux vers spaCy.
|
| 8 |
+
|
| 9 |
+
charger_lexique_fr <- function(chemin = "dictionnaires/lexique_fr.csv") {
|
| 10 |
+
fichier <- tryCatch(normalizePath(chemin, mustWork = TRUE), error = function(e) NA_character_)
|
| 11 |
+
if (is.na(fichier) || !file.exists(fichier)) {
|
| 12 |
+
stop(
|
| 13 |
+
paste0(
|
| 14 |
+
"Lexique (fr) introuvable : fichier '", chemin,
|
| 15 |
+
"' absent. Ajoute dictionnaires/lexique_fr.csv (format lexique_fr ou IRaMuTeQ)."
|
| 16 |
+
)
|
| 17 |
+
)
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
lire_tsv <- function() {
|
| 21 |
+
read.delim(
|
| 22 |
+
fichier,
|
| 23 |
+
sep = "\t",
|
| 24 |
+
header = TRUE,
|
| 25 |
+
stringsAsFactors = FALSE,
|
| 26 |
+
fileEncoding = "UTF-8",
|
| 27 |
+
check.names = FALSE
|
| 28 |
+
)
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
lire_csv <- function() {
|
| 32 |
+
read.csv(
|
| 33 |
+
fichier,
|
| 34 |
+
sep = ",",
|
| 35 |
+
header = TRUE,
|
| 36 |
+
stringsAsFactors = FALSE,
|
| 37 |
+
fileEncoding = "UTF-8",
|
| 38 |
+
check.names = FALSE
|
| 39 |
+
)
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
lire_csv_point_virgule <- function() {
|
| 43 |
+
read.csv(
|
| 44 |
+
fichier,
|
| 45 |
+
sep = ";",
|
| 46 |
+
header = TRUE,
|
| 47 |
+
stringsAsFactors = FALSE,
|
| 48 |
+
fileEncoding = "UTF-8",
|
| 49 |
+
check.names = FALSE
|
| 50 |
+
)
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
lexique <- tryCatch(lire_tsv(), error = function(e) NULL)
|
| 54 |
+
if (is.null(lexique) || ncol(lexique) <= 1) {
|
| 55 |
+
lexique <- tryCatch(lire_csv(), error = function(e) NULL)
|
| 56 |
+
}
|
| 57 |
+
if (is.null(lexique) || ncol(lexique) <= 1) {
|
| 58 |
+
lexique <- tryCatch(lire_csv_point_virgule(), error = function(e) NULL)
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
if (is.null(lexique) || nrow(lexique) == 0) {
|
| 62 |
+
stop("Lexique (fr) invalide : fichier vide ou illisible.")
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
names(lexique) <- trimws(sub("^\ufeff", "", names(lexique)))
|
| 66 |
+
|
| 67 |
+
colonnes_attendues <- c("c_mot", "c_lemme", "c_morpho")
|
| 68 |
+
manquantes <- setdiff(colonnes_attendues, names(lexique))
|
| 69 |
+
if (length(manquantes) > 0) {
|
| 70 |
+
stop(
|
| 71 |
+
paste0(
|
| 72 |
+
"Lexique (fr) mal configuré : colonnes manquantes [",
|
| 73 |
+
paste(manquantes, collapse = ", "),
|
| 74 |
+
"]. Format attendu : c_mot, c_lemme, c_morpho."
|
| 75 |
+
)
|
| 76 |
+
)
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
lexique$c_mot <- tolower(trimws(as.character(lexique$c_mot)))
|
| 80 |
+
lexique$c_lemme <- tolower(trimws(as.character(lexique$c_lemme)))
|
| 81 |
+
lexique$c_morpho <- toupper(trimws(as.character(lexique$c_morpho)))
|
| 82 |
+
|
| 83 |
+
lexique <- lexique[
|
| 84 |
+
nzchar(lexique$c_mot) &
|
| 85 |
+
nzchar(lexique$c_lemme) &
|
| 86 |
+
nzchar(lexique$c_morpho),
|
| 87 |
+
,
|
| 88 |
+
drop = FALSE
|
| 89 |
+
]
|
| 90 |
+
|
| 91 |
+
if (nrow(lexique) == 0) {
|
| 92 |
+
stop("Lexique (fr) mal configuré : aucune entrée exploitable après nettoyage.")
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
lexique
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
construire_type_lexique_fr <- function(termes, lexique) {
|
| 99 |
+
if (is.null(termes)) return(character(0))
|
| 100 |
+
x <- tolower(trimws(as.character(termes)))
|
| 101 |
+
x[is.na(x)] <- ""
|
| 102 |
+
|
| 103 |
+
if (is.null(lexique) || !is.data.frame(lexique) || nrow(lexique) < 1) {
|
| 104 |
+
return(rep("", length(x)))
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
cols_ok <- all(c("c_mot", "c_lemme", "c_morpho") %in% names(lexique))
|
| 108 |
+
if (!cols_ok) {
|
| 109 |
+
return(rep("", length(x)))
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
key_mot <- tolower(trimws(as.character(lexique$c_mot)))
|
| 113 |
+
key_lemme <- tolower(trimws(as.character(lexique$c_lemme)))
|
| 114 |
+
val_type <- tolower(trimws(as.character(lexique$c_morpho)))
|
| 115 |
+
|
| 116 |
+
map_lemme <- tapply(val_type, key_lemme, function(v) unique(v)[1])
|
| 117 |
+
map_mot <- tapply(val_type, key_mot, function(v) unique(v)[1])
|
| 118 |
+
|
| 119 |
+
out <- unname(map_lemme[x])
|
| 120 |
+
idx_na <- is.na(out) | !nzchar(out)
|
| 121 |
+
if (any(idx_na)) {
|
| 122 |
+
out[idx_na] <- unname(map_mot[x[idx_na]])
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
out[is.na(out)] <- ""
|
| 126 |
+
out
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
lemmatiser_textes_lexique <- function(textes, lexique, rv = NULL) {
|
| 131 |
+
tok <- quanteda::tokens(
|
| 132 |
+
textes,
|
| 133 |
+
remove_punct = FALSE,
|
| 134 |
+
remove_numbers = FALSE
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
map_forme_lemme <- tapply(
|
| 138 |
+
lexique$c_lemme,
|
| 139 |
+
lexique$c_mot,
|
| 140 |
+
function(x) unique(x)[1]
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
liste_tok <- as.list(tok)
|
| 144 |
+
textes_lem <- vapply(liste_tok, function(v) {
|
| 145 |
+
if (length(v) == 0) return("")
|
| 146 |
+
v_low <- tolower(as.character(v))
|
| 147 |
+
lem <- unname(map_forme_lemme[v_low])
|
| 148 |
+
lem[is.na(lem) | !nzchar(lem)] <- v_low[is.na(lem) | !nzchar(lem)]
|
| 149 |
+
paste(lem, collapse = " ")
|
| 150 |
+
}, FUN.VALUE = character(1))
|
| 151 |
+
|
| 152 |
+
if (!is.null(rv)) {
|
| 153 |
+
ajouter_log(rv, "Lexique (fr) : lemmatisation forme->lemme appliquée sans spaCy.")
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
textes_lem
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
filtrer_textes_lexique_par_cgram <- function(textes, lexique, cgram_a_conserver, rv = NULL) {
|
| 162 |
+
cgram_keep <- unique(toupper(trimws(as.character(cgram_a_conserver))))
|
| 163 |
+
cgram_keep <- cgram_keep[nzchar(cgram_keep)]
|
| 164 |
+
if (length(cgram_keep) == 0) return(textes)
|
| 165 |
+
|
| 166 |
+
formes_keep <- unique(lexique$c_mot[lexique$c_morpho %in% cgram_keep])
|
| 167 |
+
formes_keep <- formes_keep[nzchar(formes_keep)]
|
| 168 |
+
|
| 169 |
+
tok <- quanteda::tokens(
|
| 170 |
+
textes,
|
| 171 |
+
remove_punct = FALSE,
|
| 172 |
+
remove_numbers = FALSE
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
liste_tok <- as.list(tok)
|
| 176 |
+
total_tokens <- 0L
|
| 177 |
+
total_conserves <- 0L
|
| 178 |
+
|
| 179 |
+
textes_filtres <- vapply(liste_tok, function(v) {
|
| 180 |
+
if (length(v) == 0) return("")
|
| 181 |
+
v_low <- tolower(trimws(as.character(v)))
|
| 182 |
+
total_tokens <<- total_tokens + length(v_low)
|
| 183 |
+
garder <- v_low %in% formes_keep
|
| 184 |
+
total_conserves <<- total_conserves + sum(garder)
|
| 185 |
+
paste(v_low[garder], collapse = " ")
|
| 186 |
+
}, FUN.VALUE = character(1))
|
| 187 |
+
|
| 188 |
+
if (!is.null(rv)) {
|
| 189 |
+
ajouter_log(
|
| 190 |
+
rv,
|
| 191 |
+
paste0(
|
| 192 |
+
"Lexique (fr) : filtrage c_morpho [", paste(cgram_keep, collapse = ", "),
|
| 193 |
+
"] => ", total_conserves, "/", total_tokens, " token(s) conservé(s)."
|
| 194 |
+
)
|
| 195 |
+
)
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
names(textes_filtres) <- names(textes)
|
| 199 |
+
textes_filtres
|
| 200 |
+
}
|
iramuteq-like/pipeline_iramuteq_analysis_iramuteq.R
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: pipeline_iramuteq_analysis.R porte le pipeline dédié au mode IRaMuTeQ-like.
|
| 2 |
+
# Ce flux est volontairement séparé de Rainette/spaCy pour éviter tout mélange de logique.
|
| 3 |
+
|
| 4 |
+
executer_pipeline_iramuteq <- function(input, rv, textes_chd) {
|
| 5 |
+
ajouter_log(rv, "IRaMuTeQ-like : pipeline lexical dédié (lexique_fr uniquement).")
|
| 6 |
+
|
| 7 |
+
tok_base <- tokens(
|
| 8 |
+
textes_chd,
|
| 9 |
+
remove_punct = isTRUE(input$supprimer_ponctuation),
|
| 10 |
+
remove_numbers = isTRUE(input$supprimer_chiffres)
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
res_dfm <- construire_dfm_avec_fallback_stopwords(
|
| 14 |
+
tok_base = tok_base,
|
| 15 |
+
min_docfreq = input$min_docfreq,
|
| 16 |
+
retirer_stopwords = isTRUE(input$retirer_stopwords),
|
| 17 |
+
langue_spacy = "fr",
|
| 18 |
+
rv = rv,
|
| 19 |
+
libelle = "IRaMuTeQ-like",
|
| 20 |
+
source_dictionnaire = "lexique_fr",
|
| 21 |
+
lexique_source_stopwords = "quanteda"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
rv$spacy_tokens_df <- NULL
|
| 25 |
+
rv$lexique_fr_df <- NULL
|
| 26 |
+
|
| 27 |
+
list(
|
| 28 |
+
tok = res_dfm$tok,
|
| 29 |
+
dfm_obj = res_dfm$dfm,
|
| 30 |
+
langue_reference = "fr",
|
| 31 |
+
source_dictionnaire = "lexique_fr"
|
| 32 |
+
)
|
| 33 |
+
}
|
iramuteq-like/pipeline_lexique_analysis_iramuteq.R
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: pipeline_lexique_analysis.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
# Il facilite la maintenance en explicitant le périmètre et les points d'intégration.
|
| 4 |
+
executer_pipeline_lexique <- function(input, rv, textes_chd) {
|
| 5 |
+
filtrage_morpho <- isTRUE(input$filtrage_morpho)
|
| 6 |
+
utiliser_lemmes_lexique <- isTRUE(input$lexique_utiliser_lemmes)
|
| 7 |
+
langue_reference <- "fr"
|
| 8 |
+
lexique_source_stopwords <- "quanteda"
|
| 9 |
+
|
| 10 |
+
if (isTRUE(utiliser_lemmes_lexique) || isTRUE(filtrage_morpho)) {
|
| 11 |
+
lexique_fr <- charger_lexique_fr("dictionnaires/lexique_fr.csv")
|
| 12 |
+
ajouter_log(rv, paste0("Lexique (fr) chargé : ", nrow(lexique_fr), " entrées."))
|
| 13 |
+
rv$lexique_fr_df <- lexique_fr
|
| 14 |
+
} else {
|
| 15 |
+
lexique_fr <- NULL
|
| 16 |
+
rv$lexique_fr_df <- NULL
|
| 17 |
+
ajouter_log(rv, "Lexique (fr) sans lemmatisation/filtrage : conservation des formes d'origine.")
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
rv$spacy_tokens_df <- NULL
|
| 21 |
+
|
| 22 |
+
if (isTRUE(utiliser_lemmes_lexique) && !isTRUE(filtrage_morpho)) {
|
| 23 |
+
ajouter_log(rv, "Lexique (fr) sans filtrage morphosyntaxique : lemmatisation directe forme->lemme.")
|
| 24 |
+
|
| 25 |
+
textes_lexique <- lemmatiser_textes_lexique(
|
| 26 |
+
textes = textes_chd,
|
| 27 |
+
lexique = lexique_fr,
|
| 28 |
+
rv = rv
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
} else if (isTRUE(filtrage_morpho)) {
|
| 32 |
+
cgram_lexique_a_conserver <- toupper(trimws(as.character(input$pos_lexique_a_conserver)))
|
| 33 |
+
cgram_lexique_a_conserver <- unique(cgram_lexique_a_conserver[nzchar(cgram_lexique_a_conserver)])
|
| 34 |
+
if (is.null(cgram_lexique_a_conserver) || length(cgram_lexique_a_conserver) == 0) {
|
| 35 |
+
cgram_lexique_a_conserver <- c("NOM", "ADJ", "VER")
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
ajouter_log(
|
| 39 |
+
rv,
|
| 40 |
+
paste0(
|
| 41 |
+
"lexique_fr | filtrage morpho=1 (c_morpho: ",
|
| 42 |
+
paste(cgram_lexique_a_conserver, collapse = ", "),
|
| 43 |
+
") | lemmes=", ifelse(utiliser_lemmes_lexique, "1", "0"),
|
| 44 |
+
" | stopwords: quanteda (Lexique fr)"
|
| 45 |
+
)
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
textes_lexique <- filtrer_textes_lexique_par_cgram(
|
| 49 |
+
textes = textes_chd,
|
| 50 |
+
lexique = lexique_fr,
|
| 51 |
+
cgram_a_conserver = cgram_lexique_a_conserver,
|
| 52 |
+
rv = rv
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
if (isTRUE(utiliser_lemmes_lexique)) {
|
| 56 |
+
textes_lexique <- lemmatiser_textes_lexique(
|
| 57 |
+
textes = textes_lexique,
|
| 58 |
+
lexique = lexique_fr,
|
| 59 |
+
rv = rv
|
| 60 |
+
)
|
| 61 |
+
}
|
| 62 |
+
} else {
|
| 63 |
+
ajouter_log(rv, "Lexique (fr) sans filtrage morphosyntaxique ni lemmatisation : pipeline standard.")
|
| 64 |
+
textes_lexique <- textes_chd
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
tok_base <- tokens(
|
| 68 |
+
textes_lexique,
|
| 69 |
+
remove_punct = isTRUE(input$supprimer_ponctuation),
|
| 70 |
+
remove_numbers = isTRUE(input$supprimer_chiffres)
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
res_dfm <- construire_dfm_avec_fallback_stopwords(
|
| 74 |
+
tok_base = tok_base,
|
| 75 |
+
min_docfreq = input$min_docfreq,
|
| 76 |
+
retirer_stopwords = isTRUE(input$retirer_stopwords),
|
| 77 |
+
langue_spacy = langue_reference,
|
| 78 |
+
rv = rv,
|
| 79 |
+
libelle = ifelse(utiliser_lemmes_lexique, "Lexique (fr)", "lexique_fr"),
|
| 80 |
+
source_dictionnaire = "lexique_fr",
|
| 81 |
+
lexique_source_stopwords = lexique_source_stopwords
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
list(
|
| 85 |
+
tok = res_dfm$tok,
|
| 86 |
+
dfm_obj = res_dfm$dfm,
|
| 87 |
+
langue_reference = langue_reference,
|
| 88 |
+
source_dictionnaire = "lexique_fr",
|
| 89 |
+
lexique_source_stopwords = lexique_source_stopwords
|
| 90 |
+
)
|
| 91 |
+
}
|
iramuteq-like/server_events_lancer_iramuteq.R
ADDED
|
@@ -0,0 +1,1133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: server_events_lancer_iramuteq.R porte le pipeline d'analyse IRaMuTeQ-like.
|
| 2 |
+
# Ce script centralise une responsabilité métier/technique utilisée par l'application.
|
| 3 |
+
# Module server - événement principal `input$lancer`
|
| 4 |
+
# Ce fichier encapsule le pipeline principal lancé au clic sur "Lancer l'analyse"
|
| 5 |
+
# (préparation, CHD/AFC/NER, exports) pour alléger `app.R` à comportement constant.
|
| 6 |
+
|
| 7 |
+
register_events_lancer <- function(input, output, session, rv) {
|
| 8 |
+
app_dir <- tryCatch(shiny::getShinyOption("appDir"), error = function(e) NULL)
|
| 9 |
+
if (is.null(app_dir) || !nzchar(app_dir)) app_dir <- getwd()
|
| 10 |
+
env_modules <- environment()
|
| 11 |
+
|
| 12 |
+
charger_module_langue <- function() {
|
| 13 |
+
candidats_langue <- unique(c(
|
| 14 |
+
file.path(app_dir, "iramuteq-like", "nlp_lexique_iramuteq.R"),
|
| 15 |
+
file.path(getwd(), "iramuteq-like", "nlp_lexique_iramuteq.R"),
|
| 16 |
+
file.path("iramuteq-like", "nlp_lexique_iramuteq.R"),
|
| 17 |
+
file.path(app_dir, "iramuteq-like", "nlp_lexique_iramuteq.R"),
|
| 18 |
+
file.path(getwd(), "iramuteq-like", "nlp_lexique_iramuteq.R"),
|
| 19 |
+
file.path("iramuteq-like", "nlp_lexique_iramuteq.R")
|
| 20 |
+
))
|
| 21 |
+
|
| 22 |
+
dernier_chemin <- candidats_langue[[1]]
|
| 23 |
+
derniere_raison <- "fonction verifier_coherence_dictionnaire_langue absente après source"
|
| 24 |
+
|
| 25 |
+
for (chemin_langue in candidats_langue) {
|
| 26 |
+
dernier_chemin <- chemin_langue
|
| 27 |
+
if (!file.exists(chemin_langue)) next
|
| 28 |
+
|
| 29 |
+
source_res <- tryCatch({
|
| 30 |
+
source(chemin_langue, encoding = "UTF-8", local = env_modules)
|
| 31 |
+
NULL
|
| 32 |
+
}, error = function(e) e)
|
| 33 |
+
|
| 34 |
+
if (inherits(source_res, "error")) {
|
| 35 |
+
derniere_raison <- paste0("échec source: ", conditionMessage(source_res))
|
| 36 |
+
next
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
if (exists("verifier_coherence_dictionnaire_langue", mode = "function", envir = env_modules, inherits = TRUE)) {
|
| 40 |
+
return(list(ok = TRUE, chemin = chemin_langue, raison = ""))
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
derniere_raison <- "fonction verifier_coherence_dictionnaire_langue absente après source"
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
list(ok = FALSE, chemin = dernier_chemin, raison = derniere_raison)
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
if (!exists("appliquer_nettoyage_iramuteq", mode = "function", inherits = TRUE)) {
|
| 50 |
+
chemin_nettoyage_iramuteq <- file.path(app_dir, "iramuteq-like", "nettoyage_iramuteq.R")
|
| 51 |
+
if (file.exists(chemin_nettoyage_iramuteq)) {
|
| 52 |
+
source(chemin_nettoyage_iramuteq, encoding = "UTF-8", local = TRUE)
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
if (!exists("appliquer_nettoyage_rainette", mode = "function", inherits = TRUE)) {
|
| 57 |
+
appliquer_nettoyage_rainette <- function(textes,
|
| 58 |
+
activer_nettoyage = FALSE,
|
| 59 |
+
forcer_minuscules = FALSE,
|
| 60 |
+
supprimer_chiffres = FALSE,
|
| 61 |
+
supprimer_apostrophes = FALSE) {
|
| 62 |
+
ajouter_log(rv, "Avertissement: appliquer_nettoyage_rainette indisponible; nettoyage contourné pour préserver l'exécution.")
|
| 63 |
+
if (is.null(textes)) return(character(0))
|
| 64 |
+
x <- as.character(textes)
|
| 65 |
+
if (isTRUE(forcer_minuscules)) x <- tolower(x)
|
| 66 |
+
x
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
if (!exists("appliquer_nettoyage_iramuteq", mode = "function", inherits = TRUE)) {
|
| 71 |
+
appliquer_nettoyage_iramuteq <- appliquer_nettoyage_rainette
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
executer_textprepa_iramuteq <- function(ids, textes, input, rv) {
|
| 76 |
+
candidats_script <- unique(c(
|
| 77 |
+
file.path(app_dir, "iramuteq-like", "textprepa_iramuteq.py"),
|
| 78 |
+
file.path(getwd(), "iramuteq-like", "textprepa_iramuteq.py"),
|
| 79 |
+
file.path("iramuteq-like", "textprepa_iramuteq.py")
|
| 80 |
+
))
|
| 81 |
+
script_path <- candidats_script[file.exists(candidats_script)][1]
|
| 82 |
+
if (is.na(script_path) || !nzchar(script_path)) {
|
| 83 |
+
stop("IRaMuTeQ-like: script textprepa_iramuteq.py introuvable.")
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
py_bin <- Sys.which("python3")
|
| 87 |
+
if (!nzchar(py_bin)) py_bin <- Sys.which("python")
|
| 88 |
+
if (!nzchar(py_bin)) {
|
| 89 |
+
stop("IRaMuTeQ-like: Python introuvable (python3/python).")
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
in_tsv <- tempfile(pattern = "iramuteq_prepa_in_", fileext = ".tsv")
|
| 93 |
+
out_tsv <- tempfile(pattern = "iramuteq_prepa_out_", fileext = ".tsv")
|
| 94 |
+
|
| 95 |
+
df_in <- data.frame(
|
| 96 |
+
doc_id = as.character(ids),
|
| 97 |
+
text = as.character(textes),
|
| 98 |
+
stringsAsFactors = FALSE
|
| 99 |
+
)
|
| 100 |
+
write.table(df_in, file = in_tsv, sep = " ", row.names = FALSE, col.names = TRUE, quote = TRUE, fileEncoding = "UTF-8")
|
| 101 |
+
|
| 102 |
+
args <- c(
|
| 103 |
+
script_path,
|
| 104 |
+
"--input", in_tsv,
|
| 105 |
+
"--output", out_tsv,
|
| 106 |
+
"--nettoyage_caracteres", ifelse(isTRUE(input$nettoyage_caracteres), "1", "0"),
|
| 107 |
+
"--forcer_minuscules_avant", ifelse(isTRUE(input$forcer_minuscules_avant), "1", "0"),
|
| 108 |
+
"--supprimer_chiffres", ifelse(isTRUE(input$supprimer_chiffres), "1", "0"),
|
| 109 |
+
"--supprimer_apostrophes", ifelse(isTRUE(input$supprimer_apostrophes), "1", "0")
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
res <- tryCatch(
|
| 113 |
+
system2(py_bin, args = args, stdout = TRUE, stderr = TRUE),
|
| 114 |
+
error = function(e) structure(conditionMessage(e), status = 1L)
|
| 115 |
+
)
|
| 116 |
+
status <- attr(res, "status")
|
| 117 |
+
if (is.null(status)) status <- 0L
|
| 118 |
+
if (!identical(as.integer(status), 0L) || !file.exists(out_tsv)) {
|
| 119 |
+
out_msg <- if (length(res)) paste(res, collapse = " | ") else "(aucun message)"
|
| 120 |
+
stop(paste0("IRaMuTeQ-like: échec textprepa_iramuteq.py (code ", status, ") : ", out_msg))
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
df_out <- read.delim(out_tsv, sep = " ", header = TRUE, stringsAsFactors = FALSE, quote = '"', encoding = "UTF-8")
|
| 124 |
+
if (!all(c("doc_id", "text") %in% names(df_out))) {
|
| 125 |
+
stop("IRaMuTeQ-like: sortie textprepa invalide (colonnes doc_id/text manquantes).")
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
ids_chr <- as.character(ids)
|
| 129 |
+
idx <- match(ids_chr, as.character(df_out$doc_id))
|
| 130 |
+
if (any(is.na(idx))) {
|
| 131 |
+
stop("IRaMuTeQ-like: alignement doc_id invalide après textprepa.")
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
textes_prep <- as.character(df_out$text[idx])
|
| 135 |
+
names(textes_prep) <- ids_chr
|
| 136 |
+
ajouter_log(rv, "IRaMuTeQ-like: préparation texte exécutée via iramuteq-like/textprepa_iramuteq.py")
|
| 137 |
+
textes_prep
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
formater_df_csv_6_decimales <- function(df) {
|
| 141 |
+
if (is.null(df)) return(df)
|
| 142 |
+
df_out <- df
|
| 143 |
+
for (nm in names(df_out)) {
|
| 144 |
+
col <- df_out[[nm]]
|
| 145 |
+
if (is.numeric(col)) {
|
| 146 |
+
df_out[[nm]] <- ifelse(
|
| 147 |
+
is.na(col),
|
| 148 |
+
NA_character_,
|
| 149 |
+
formatC(col, format = "f", digits = 6)
|
| 150 |
+
)
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
df_out
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
ecrire_csv_6_decimales <- function(df, chemin, row.names = FALSE) {
|
| 157 |
+
write.csv(formater_df_csv_6_decimales(df), chemin, row.names = row.names)
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
observeEvent(input$modele_chd, {
|
| 161 |
+
if (identical(as.character(input$modele_chd), "iramuteq")) {
|
| 162 |
+
updateRadioButtons(
|
| 163 |
+
session,
|
| 164 |
+
"source_dictionnaire",
|
| 165 |
+
choices = c("Lexique (fr)" = "lexique_fr"),
|
| 166 |
+
selected = "lexique_fr"
|
| 167 |
+
)
|
| 168 |
+
if (isTRUE(input$activer_ner)) {
|
| 169 |
+
updateCheckboxInput(session, "activer_ner", value = FALSE)
|
| 170 |
+
ajouter_log(rv, "Mode IRaMuTeQ-like : NER spaCy automatiquement désactivé (mode uniquement Lexique fr).")
|
| 171 |
+
}
|
| 172 |
+
} else {
|
| 173 |
+
updateRadioButtons(
|
| 174 |
+
session,
|
| 175 |
+
"source_dictionnaire",
|
| 176 |
+
choices = c("spaCy" = "spacy", "Lexique (fr)" = "lexique_fr"),
|
| 177 |
+
selected = if (identical(as.character(input$source_dictionnaire), "lexique_fr")) "lexique_fr" else "spacy"
|
| 178 |
+
)
|
| 179 |
+
}
|
| 180 |
+
}, ignoreInit = FALSE)
|
| 181 |
+
|
| 182 |
+
output$ui_concordancier_iramuteq <- renderUI({
|
| 183 |
+
req(rv$export_dir)
|
| 184 |
+
|
| 185 |
+
if (!identical(rv$res_type, "iramuteq")) {
|
| 186 |
+
return(tags$p("Concordancier IRaMuTeQ-like indisponible (mode Rainette actif)."))
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
if (is.null(rv$exports_prefix) || !nzchar(rv$exports_prefix)) {
|
| 190 |
+
return(tags$div(
|
| 191 |
+
style = "padding: 12px;",
|
| 192 |
+
tags$p("Préfixe de ressources invalide."),
|
| 193 |
+
tags$p("Relance l'analyse pour régénérer les exports.")
|
| 194 |
+
))
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
if (!(rv$exports_prefix %in% names(shiny::resourcePaths()))) {
|
| 198 |
+
shiny::addResourcePath(rv$exports_prefix, rv$export_dir)
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
candidats_html <- c(
|
| 202 |
+
rv$html_file,
|
| 203 |
+
file.path(rv$export_dir, "segments_par_classe.html"),
|
| 204 |
+
file.path(rv$export_dir, "concordancier.html")
|
| 205 |
+
)
|
| 206 |
+
candidats_dyn <- list.files(
|
| 207 |
+
rv$export_dir,
|
| 208 |
+
pattern = "(segments.*classe|concord).*\\.html$",
|
| 209 |
+
ignore.case = TRUE,
|
| 210 |
+
full.names = TRUE
|
| 211 |
+
)
|
| 212 |
+
candidats_html <- c(candidats_html, candidats_dyn)
|
| 213 |
+
candidats_html <- unique(candidats_html[!is.na(candidats_html) & nzchar(candidats_html)])
|
| 214 |
+
html_existant <- candidats_html[file.exists(candidats_html)]
|
| 215 |
+
|
| 216 |
+
if (length(html_existant) == 0) {
|
| 217 |
+
return(tags$div(
|
| 218 |
+
style = "padding: 12px;",
|
| 219 |
+
tags$p("Le fichier du concordancier HTML n'est pas disponible pour cette analyse."),
|
| 220 |
+
tags$p("Relance l'analyse puis vérifie les logs si le problème persiste.")
|
| 221 |
+
))
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
src_html <- html_existant[[1]]
|
| 225 |
+
nom_html <- basename(src_html)
|
| 226 |
+
src_dans_exports <- file.path(rv$export_dir, nom_html)
|
| 227 |
+
|
| 228 |
+
if (!isTRUE(file.exists(src_dans_exports))) {
|
| 229 |
+
ok_copy <- tryCatch(file.copy(src_html, src_dans_exports, overwrite = TRUE), error = function(e) FALSE)
|
| 230 |
+
if (isTRUE(ok_copy)) src_html <- src_dans_exports
|
| 231 |
+
} else {
|
| 232 |
+
src_html <- src_dans_exports
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
tags$iframe(
|
| 236 |
+
src = paste0("/", rv$exports_prefix, "/", basename(src_html)),
|
| 237 |
+
style = "width: 100%; height: 70vh; border: 1px solid #999;"
|
| 238 |
+
)
|
| 239 |
+
})
|
| 240 |
+
|
| 241 |
+
output$ui_wordcloud_iramuteq <- renderUI({
|
| 242 |
+
req(rv$export_dir, rv$exports_prefix)
|
| 243 |
+
|
| 244 |
+
if (!identical(rv$res_type, "iramuteq")) {
|
| 245 |
+
return(tags$p("Nuage de mots IRaMuTeQ-like indisponible (mode Rainette actif)."))
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
classe_sel <- as.character(input$classe_viz_iramuteq)
|
| 249 |
+
if (length(classe_sel) != 1 || is.na(classe_sel) || !nzchar(classe_sel)) {
|
| 250 |
+
return(tags$p("Sélectionne une classe pour afficher le nuage de mots."))
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
src_rel <- file.path("wordclouds", paste0("cluster_", classe_sel, "_wordcloud.png"))
|
| 254 |
+
if (!file.exists(file.path(rv$export_dir, src_rel))) {
|
| 255 |
+
return(tags$p("Aucun nuage de mots disponible pour cette classe."))
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
tags$div(
|
| 259 |
+
style = "text-align: center;",
|
| 260 |
+
tags$img(
|
| 261 |
+
src = paste0("/", rv$exports_prefix, "/", src_rel),
|
| 262 |
+
style = "max-width: 100%; height: auto; border: 1px solid #999; display: inline-block;"
|
| 263 |
+
)
|
| 264 |
+
)
|
| 265 |
+
})
|
| 266 |
+
|
| 267 |
+
normaliser_id_classe_local <- function(x) {
|
| 268 |
+
x_chr <- as.character(x)
|
| 269 |
+
x_chr <- trimws(x_chr)
|
| 270 |
+
|
| 271 |
+
x_num <- suppressWarnings(as.numeric(x_chr))
|
| 272 |
+
need_extract <- is.na(x_num) & !is.na(x_chr) & nzchar(x_chr)
|
| 273 |
+
|
| 274 |
+
if (any(need_extract)) {
|
| 275 |
+
extrait <- sub("^.*?(\\d+).*$", "\\1", x_chr[need_extract])
|
| 276 |
+
extrait[!grepl("\\d", x_chr[need_extract])] <- NA_character_
|
| 277 |
+
x_num[need_extract] <- suppressWarnings(as.numeric(extrait))
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
x_num
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
observeEvent(input$lancer, {
|
| 284 |
+
rv$logs <- ""
|
| 285 |
+
rv$statut <- "Vérification du fichier..."
|
| 286 |
+
rv$progression <- 0
|
| 287 |
+
|
| 288 |
+
rv$spacy_tokens_df <- NULL
|
| 289 |
+
rv$lexique_fr_df <- NULL
|
| 290 |
+
rv$textes_indexation <- NULL
|
| 291 |
+
rv$ner_df <- NULL
|
| 292 |
+
rv$ner_nb_segments <- NA_integer_
|
| 293 |
+
rv$afc_obj <- NULL
|
| 294 |
+
rv$afc_erreur <- NULL
|
| 295 |
+
rv$afc_vars_obj <- NULL
|
| 296 |
+
rv$afc_vars_erreur <- NULL
|
| 297 |
+
|
| 298 |
+
rv$afc_dir <- NULL
|
| 299 |
+
rv$afc_table_mots <- NULL
|
| 300 |
+
rv$afc_table_vars <- NULL
|
| 301 |
+
rv$afc_plot_classes <- NULL
|
| 302 |
+
rv$afc_plot_termes <- NULL
|
| 303 |
+
rv$afc_plot_vars <- NULL
|
| 304 |
+
|
| 305 |
+
rv$segments_file <- NULL
|
| 306 |
+
rv$stats_file <- NULL
|
| 307 |
+
rv$html_file <- NULL
|
| 308 |
+
rv$ner_file <- NULL
|
| 309 |
+
rv$zip_file <- NULL
|
| 310 |
+
|
| 311 |
+
rv$res <- NULL
|
| 312 |
+
rv$res_chd <- NULL
|
| 313 |
+
rv$dfm_chd <- NULL
|
| 314 |
+
rv$res_type <- "simple"
|
| 315 |
+
rv$max_n_groups <- NULL
|
| 316 |
+
rv$max_n_groups_chd <- NULL
|
| 317 |
+
rv$explor_assets <- NULL
|
| 318 |
+
rv$stats_corpus_df <- NULL
|
| 319 |
+
rv$stats_zipf_df <- NULL
|
| 320 |
+
|
| 321 |
+
ajouter_log(rv, "Clic sur 'Lancer l'analyse' reçu.")
|
| 322 |
+
|
| 323 |
+
modele_chd <- "iramuteq"
|
| 324 |
+
mode_iramuteq <- TRUE
|
| 325 |
+
source_dictionnaire <- "lexique_fr"
|
| 326 |
+
updateRadioButtons(
|
| 327 |
+
session,
|
| 328 |
+
"source_dictionnaire",
|
| 329 |
+
choices = c("Lexique (fr)" = "lexique_fr"),
|
| 330 |
+
selected = "lexique_fr"
|
| 331 |
+
)
|
| 332 |
+
updateRadioButtons(
|
| 333 |
+
session,
|
| 334 |
+
"modele_chd",
|
| 335 |
+
selected = "iramuteq"
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
if (is.null(input$fichier_corpus) || is.null(input$fichier_corpus$datapath) || !file.exists(input$fichier_corpus$datapath)) {
|
| 339 |
+
rv$statut <- "Aucun fichier uploadé."
|
| 340 |
+
rv$progression <- 0
|
| 341 |
+
ajouter_log(rv, "Aucun fichier uploadé côté serveur. Sélectionne un .txt puis relance.")
|
| 342 |
+
showNotification("Aucun fichier uploadé. Choisis un .txt.", type = "error", duration = 6)
|
| 343 |
+
return(invisible(NULL))
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
if (isTRUE(input$activer_ner) && mode_iramuteq) {
|
| 347 |
+
rv$statut <- "Configuration invalide : NER indisponible en mode IRaMuTeQ-like."
|
| 348 |
+
rv$progression <- 0
|
| 349 |
+
ajouter_log(rv, "Blocage de l'analyse : NER activé en mode IRaMuTeQ-like (mode uniquement Lexique fr).")
|
| 350 |
+
showNotification(
|
| 351 |
+
"Analyse bloquée : le mode IRaMuTeQ-like fonctionne uniquement avec Lexique (fr) et sans NER spaCy.",
|
| 352 |
+
type = "error",
|
| 353 |
+
duration = 8
|
| 354 |
+
)
|
| 355 |
+
return(invisible(NULL))
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
if (isTRUE(input$activer_ner) && !is.null(input$fichier_ner_json) && !is.null(input$fichier_ner_json$datapath) && file.exists(input$fichier_ner_json$datapath)) {
|
| 359 |
+
rv$ner_file <- input$fichier_ner_json$datapath
|
| 360 |
+
ajouter_log(rv, paste0("NER : dictionnaire JSON importé via l'UI : ", input$fichier_ner_json$name))
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
p <- Progress$new(session, min = 0, max = 1)
|
| 364 |
+
on.exit(try(p$close(), silent = TRUE), add = TRUE)
|
| 365 |
+
|
| 366 |
+
avancer <- function(valeur, detail) {
|
| 367 |
+
valeur <- max(0, min(1, valeur))
|
| 368 |
+
p$set(value = valeur, message = "Calculs CHD en cours", detail = detail)
|
| 369 |
+
rv$progression <- round(valeur * 100)
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
tryCatch({
|
| 373 |
+
|
| 374 |
+
avancer(0.02, "Préparation des répertoires")
|
| 375 |
+
rv$statut <- "Préparation des répertoires..."
|
| 376 |
+
|
| 377 |
+
rv$base_dir <- file.path(tempdir(), paste0("rainette_", session$token))
|
| 378 |
+
rv$export_dir <- file.path(rv$base_dir, "exports")
|
| 379 |
+
dir.create(rv$export_dir, showWarnings = FALSE, recursive = TRUE)
|
| 380 |
+
ajouter_log(rv, paste0("export_dir = ", rv$export_dir))
|
| 381 |
+
|
| 382 |
+
avancer(0.08, "Import du corpus")
|
| 383 |
+
rv$statut <- "Import du corpus..."
|
| 384 |
+
chemin_fichier <- input$fichier_corpus$datapath
|
| 385 |
+
md5 <- md5_fichier(chemin_fichier)
|
| 386 |
+
ajouter_log(rv, paste0("MD5 fichier = ", md5))
|
| 387 |
+
|
| 388 |
+
corpus <- import_corpus_iramuteq(chemin_fichier)
|
| 389 |
+
ajouter_log(rv, paste0("Nombre de documents importés : ", ndoc(corpus)))
|
| 390 |
+
|
| 391 |
+
avancer(0.14, "Segmentation")
|
| 392 |
+
rv$statut <- "Segmentation..."
|
| 393 |
+
segment_size <- input$segment_size
|
| 394 |
+
corpus <- split_segments(corpus, segment_size = segment_size)
|
| 395 |
+
ajouter_log(rv, paste0("Nombre de segments après découpage : ", ndoc(corpus)))
|
| 396 |
+
|
| 397 |
+
stats_corpus <- calculer_stats_corpus(
|
| 398 |
+
chemin_fichier = chemin_fichier,
|
| 399 |
+
corpus_segments = corpus,
|
| 400 |
+
nom_corpus = input$fichier_corpus$name
|
| 401 |
+
)
|
| 402 |
+
if (is.null(stats_corpus)) {
|
| 403 |
+
rv$stats_corpus_df <- NULL
|
| 404 |
+
rv$stats_zipf_df <- NULL
|
| 405 |
+
} else {
|
| 406 |
+
rv$stats_corpus_df <- stats_corpus$table
|
| 407 |
+
rv$stats_zipf_df <- stats_corpus$zipf
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
ids_orig <- as.character(docnames(corpus))
|
| 411 |
+
ids_corpus <- ids_orig
|
| 412 |
+
invalides <- is.na(ids_corpus) | !nzchar(trimws(ids_corpus))
|
| 413 |
+
if (any(invalides)) {
|
| 414 |
+
ids_corpus[invalides] <- paste0("doc_", which(invalides))
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
ids_uniques <- make.unique(ids_corpus, sep = "_dup")
|
| 418 |
+
modif_ids <- any(ids_uniques != ids_orig)
|
| 419 |
+
if (isTRUE(modif_ids)) {
|
| 420 |
+
n_problemes <- sum(invalides) + sum(duplicated(ids_corpus))
|
| 421 |
+
ajouter_log(rv, paste0("Docnames invalides/dupliqués détectés après segmentation : ", n_problemes, ". Renommage automatique via make.unique()."))
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
docnames(corpus) <- ids_uniques
|
| 425 |
+
ids_corpus <- as.character(docnames(corpus))
|
| 426 |
+
|
| 427 |
+
textes_orig <- as.character(corpus)
|
| 428 |
+
|
| 429 |
+
avancer(0.18, "Préparation texte (nettoyage / minuscules)")
|
| 430 |
+
rv$statut <- "Préparation texte..."
|
| 431 |
+
|
| 432 |
+
if (mode_iramuteq) {
|
| 433 |
+
textes_nettoyes <- appliquer_nettoyage_iramuteq(
|
| 434 |
+
textes = textes_orig,
|
| 435 |
+
activer_nettoyage = isTRUE(input$nettoyage_caracteres),
|
| 436 |
+
forcer_minuscules = isTRUE(input$forcer_minuscules_avant),
|
| 437 |
+
supprimer_chiffres = isTRUE(input$supprimer_chiffres),
|
| 438 |
+
supprimer_apostrophes = isTRUE(input$supprimer_apostrophes)
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
textes_chd <- executer_textprepa_iramuteq(
|
| 442 |
+
ids = ids_corpus,
|
| 443 |
+
textes = textes_nettoyes,
|
| 444 |
+
input = input,
|
| 445 |
+
rv = rv
|
| 446 |
+
)
|
| 447 |
+
} else {
|
| 448 |
+
textes_chd <- appliquer_nettoyage_rainette(
|
| 449 |
+
textes = textes_orig,
|
| 450 |
+
activer_nettoyage = isTRUE(input$nettoyage_caracteres),
|
| 451 |
+
forcer_minuscules = isTRUE(input$forcer_minuscules_avant),
|
| 452 |
+
supprimer_chiffres = isTRUE(input$supprimer_chiffres),
|
| 453 |
+
supprimer_apostrophes = isTRUE(input$supprimer_apostrophes)
|
| 454 |
+
)
|
| 455 |
+
names(textes_chd) <- ids_corpus
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
if (!exists("verifier_coherence_dictionnaire_langue", mode = "function", inherits = TRUE)) {
|
| 459 |
+
charge_langue <- charger_module_langue()
|
| 460 |
+
if (!isTRUE(charge_langue$ok)) {
|
| 461 |
+
stop(paste0("Module langue indisponible (", charge_langue$raison, ") : ", charge_langue$chemin))
|
| 462 |
+
}
|
| 463 |
+
ajouter_log(rv, paste0("Diagnostic langue: module chargé depuis ", charge_langue$chemin, "."))
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
source_dictionnaire <- "lexique_fr"
|
| 467 |
+
|
| 468 |
+
verifier_coherence_dictionnaire_langue(
|
| 469 |
+
textes_chd,
|
| 470 |
+
if (identical(source_dictionnaire, "lexique_fr")) "fr" else as.character(input$spacy_langue),
|
| 471 |
+
rv = rv
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
avancer(0.22, "Prétraitement + DFM")
|
| 475 |
+
rv$statut <- "Prétraitement et DFM..."
|
| 476 |
+
|
| 477 |
+
ajouter_log(
|
| 478 |
+
rv,
|
| 479 |
+
paste0(
|
| 480 |
+
"Diagnostic pipeline: dictionnaire=", source_dictionnaire,
|
| 481 |
+
" | langue UI=", as.character(input$spacy_langue),
|
| 482 |
+
" | filtrage_morpho=", ifelse(isTRUE(input$filtrage_morpho), "1", "0"),
|
| 483 |
+
" | retirer_stopwords=", ifelse(isTRUE(input$retirer_stopwords), "1", "0"),
|
| 484 |
+
" | supprimer_ponctuation=", ifelse(isTRUE(input$supprimer_ponctuation), "1", "0"),
|
| 485 |
+
" | supprimer_chiffres=", ifelse(isTRUE(input$supprimer_chiffres), "1", "0"),
|
| 486 |
+
" | supprimer_apostrophes=", ifelse(isTRUE(input$supprimer_apostrophes), "1", "0"),
|
| 487 |
+
" | nettoyage_caracteres=", ifelse(isTRUE(input$nettoyage_caracteres), "1", "0")
|
| 488 |
+
)
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
sortie_pipeline <- executer_pipeline_iramuteq(
|
| 492 |
+
input = input,
|
| 493 |
+
rv = rv,
|
| 494 |
+
textes_chd = textes_chd
|
| 495 |
+
)
|
| 496 |
+
|
| 497 |
+
tok <- sortie_pipeline$tok
|
| 498 |
+
dfm_obj <- sortie_pipeline$dfm_obj
|
| 499 |
+
langue_reference <- sortie_pipeline$langue_reference
|
| 500 |
+
source_dictionnaire <- sortie_pipeline$source_dictionnaire
|
| 501 |
+
|
| 502 |
+
if (anyDuplicated(docnames(dfm_obj)) > 0) {
|
| 503 |
+
dups_dfm <- sum(duplicated(as.character(docnames(dfm_obj))))
|
| 504 |
+
docnames(dfm_obj) <- make.unique(as.character(docnames(dfm_obj)), sep = "_dup")
|
| 505 |
+
ajouter_log(rv, paste0("DFM : docnames dupliqués détectés (", dups_dfm, "). Renommage automatique."))
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
included_segments <- as.character(docnames(dfm_obj))
|
| 509 |
+
included_segments <- included_segments[!is.na(included_segments) & nzchar(included_segments)]
|
| 510 |
+
included_segments <- unique(included_segments)
|
| 511 |
+
|
| 512 |
+
filtered_corpus <- corpus[included_segments]
|
| 513 |
+
if (anyDuplicated(docnames(filtered_corpus)) > 0) {
|
| 514 |
+
dups_corpus <- sum(duplicated(as.character(docnames(filtered_corpus))))
|
| 515 |
+
docnames(filtered_corpus) <- make.unique(as.character(docnames(filtered_corpus)), sep = "_dup")
|
| 516 |
+
ajouter_log(rv, paste0("Corpus filtré : docnames dupliqués détectés (", dups_corpus, "). Renommage automatique."))
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
tok <- tok[included_segments]
|
| 520 |
+
if (anyDuplicated(docnames(tok)) > 0) {
|
| 521 |
+
dups_tok <- sum(duplicated(as.character(docnames(tok))))
|
| 522 |
+
docnames(tok) <- make.unique(as.character(docnames(tok)), sep = "_dup")
|
| 523 |
+
ajouter_log(rv, paste0("Tokens : docnames dupliqués détectés (", dups_tok, "). Renommage automatique."))
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
dfm_obj <- assurer_docvars_dfm_minimal(dfm_obj, filtered_corpus)
|
| 527 |
+
|
| 528 |
+
tmp <- supprimer_docs_vides_dfm(dfm_obj, filtered_corpus, tok, rv)
|
| 529 |
+
dfm_obj <- tmp$dfm
|
| 530 |
+
filtered_corpus <- tmp$corpus
|
| 531 |
+
tok <- tmp$tok
|
| 532 |
+
|
| 533 |
+
ajouter_log(rv, paste0("Après suppression segments vides : ", ndoc(dfm_obj), " docs ; ", nfeat(dfm_obj), " termes."))
|
| 534 |
+
verifier_dfm_avant_rainette(dfm_obj, input)
|
| 535 |
+
|
| 536 |
+
rv$textes_indexation <- vapply(as.list(tok), function(x) paste(x, collapse = " "), FUN.VALUE = character(1))
|
| 537 |
+
names(rv$textes_indexation) <- docnames(dfm_obj)
|
| 538 |
+
|
| 539 |
+
avancer(0.52, "Classification (rainette / rainette2)")
|
| 540 |
+
rv$statut <- "Classification en cours..."
|
| 541 |
+
|
| 542 |
+
modele_chd <- "iramuteq"
|
| 543 |
+
|
| 544 |
+
type_classif <- as.character(input$type_classification)
|
| 545 |
+
if (!type_classif %in% c("simple", "double")) type_classif <- "simple"
|
| 546 |
+
|
| 547 |
+
groupes <- NULL
|
| 548 |
+
res_final <- NULL
|
| 549 |
+
|
| 550 |
+
if (identical(modele_chd, "iramuteq")) {
|
| 551 |
+
|
| 552 |
+
rv$res_type <- "iramuteq"
|
| 553 |
+
ajouter_log(rv, "Mode : classification IRaMuTeQ-like.")
|
| 554 |
+
|
| 555 |
+
k_iramuteq <- suppressWarnings(as.integer(input$k_iramuteq))
|
| 556 |
+
if (is.na(k_iramuteq) || k_iramuteq < 2L) k_iramuteq <- 10L
|
| 557 |
+
|
| 558 |
+
mincl_mode_iramuteq <- as.character(input$iramuteq_mincl_mode)
|
| 559 |
+
if (!mincl_mode_iramuteq %in% c("auto", "manuel")) mincl_mode_iramuteq <- "auto"
|
| 560 |
+
|
| 561 |
+
mincl_iramuteq <- suppressWarnings(as.integer(input$iramuteq_mincl))
|
| 562 |
+
if (is.na(mincl_iramuteq) || mincl_iramuteq < 1L) mincl_iramuteq <- 1L
|
| 563 |
+
|
| 564 |
+
classif_mode_iramuteq <- as.character(input$iramuteq_classif_mode)
|
| 565 |
+
if (!classif_mode_iramuteq %in% c("simple", "double")) classif_mode_iramuteq <- "simple"
|
| 566 |
+
|
| 567 |
+
svd_method_iramuteq <- as.character(input$iramuteq_svd_method)
|
| 568 |
+
if (!svd_method_iramuteq %in% c("irlba", "svdR")) svd_method_iramuteq <- "irlba"
|
| 569 |
+
|
| 570 |
+
ajouter_log(
|
| 571 |
+
rv,
|
| 572 |
+
paste0(
|
| 573 |
+
"Paramètres IRaMuTeQ-like : k=", k_iramuteq,
|
| 574 |
+
" | mincl_mode=", mincl_mode_iramuteq,
|
| 575 |
+
if (identical(mincl_mode_iramuteq, "manuel")) paste0(" | mincl=", mincl_iramuteq) else "",
|
| 576 |
+
" | classif_mode=", classif_mode_iramuteq,
|
| 577 |
+
" | svd_method=", svd_method_iramuteq,
|
| 578 |
+
" | mode_patate=", ifelse(isTRUE(input$iramuteq_mode_patate), "1", "0")
|
| 579 |
+
)
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
res_ira <- lancer_moteur_chd_iramuteq(
|
| 583 |
+
dfm_obj = dfm_obj,
|
| 584 |
+
k = k_iramuteq,
|
| 585 |
+
mincl_mode = mincl_mode_iramuteq,
|
| 586 |
+
mincl = mincl_iramuteq,
|
| 587 |
+
classif_mode = classif_mode_iramuteq,
|
| 588 |
+
svd_method = svd_method_iramuteq,
|
| 589 |
+
mode_patate = isTRUE(input$iramuteq_mode_patate),
|
| 590 |
+
binariser = TRUE
|
| 591 |
+
)
|
| 592 |
+
|
| 593 |
+
groupes <- as.integer(res_ira$classes)
|
| 594 |
+
if (all(is.na(groupes)) || length(unique(groupes[groupes > 0])) < 2) {
|
| 595 |
+
stop("IRaMuTeQ-like n'a pas pu produire au moins 2 classes exploitables.")
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
res_final <- res_ira
|
| 599 |
+
rv$res_chd <- NULL
|
| 600 |
+
rv$dfm_chd <- NULL
|
| 601 |
+
rv$max_n_groups <- length(unique(groupes[groupes > 0]))
|
| 602 |
+
rv$max_n_groups_chd <- rv$max_n_groups
|
| 603 |
+
|
| 604 |
+
} else if (type_classif == "simple") {
|
| 605 |
+
|
| 606 |
+
rv$res_type <- "simple"
|
| 607 |
+
ajouter_log(rv, "Mode : classification simple (rainette).")
|
| 608 |
+
|
| 609 |
+
k_effectif <- calculer_k_effectif(dfm_obj, input$k, input$min_split_members, rv)
|
| 610 |
+
|
| 611 |
+
res <- rainette(
|
| 612 |
+
dfm_obj,
|
| 613 |
+
k = k_effectif,
|
| 614 |
+
min_segment_size = input$min_segment_size,
|
| 615 |
+
min_split_members = input$min_split_members,
|
| 616 |
+
doc_id = "segment_source"
|
| 617 |
+
)
|
| 618 |
+
|
| 619 |
+
if (is.null(res) || is.null(res$group) || length(res$group) == 0) stop("Rainette n'a pas pu calculer de clusters. Diminue les filtrages, augmente segment_size, ou réduis k.")
|
| 620 |
+
|
| 621 |
+
groupes <- res$group
|
| 622 |
+
res_final <- res
|
| 623 |
+
rv$res_chd <- res
|
| 624 |
+
rv$dfm_chd <- dfm_obj
|
| 625 |
+
rv$max_n_groups <- max(res$group, na.rm = TRUE)
|
| 626 |
+
rv$max_n_groups_chd <- rv$max_n_groups
|
| 627 |
+
|
| 628 |
+
} else {
|
| 629 |
+
|
| 630 |
+
rv$res_type <- "double"
|
| 631 |
+
ajouter_log(rv, "Mode : classification double (rainette2).")
|
| 632 |
+
|
| 633 |
+
k_effectif <- calculer_k_effectif(dfm_obj, input$k, input$min_split_members, rv)
|
| 634 |
+
|
| 635 |
+
res1 <- rainette(dfm_obj, k = k_effectif, min_segment_size = input$min_segment_size, min_split_members = input$min_split_members, doc_id = "segment_source")
|
| 636 |
+
if (is.null(res1) || is.null(res1$group) || length(res1$group) == 0) stop("Classification 1 (rainette) impossible.")
|
| 637 |
+
|
| 638 |
+
res2 <- rainette(dfm_obj, k = k_effectif, min_segment_size = input$min_segment_size2, min_split_members = input$min_split_members, doc_id = "segment_source")
|
| 639 |
+
if (is.null(res2) || is.null(res2$group) || length(res2$group) == 0) stop("Classification 2 (rainette) impossible.")
|
| 640 |
+
|
| 641 |
+
res_d <- rainette2(res1, res2, max_k = input$max_k_double)
|
| 642 |
+
groupes <- cutree(res_d, k = k_effectif)
|
| 643 |
+
|
| 644 |
+
res_final <- res_d
|
| 645 |
+
rv$res_chd <- res1
|
| 646 |
+
rv$dfm_chd <- dfm_obj
|
| 647 |
+
rv$max_n_groups <- input$max_k_double
|
| 648 |
+
rv$max_n_groups_chd <- max(res1$group, na.rm = TRUE)
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
docvars(filtered_corpus)$Classes <- groupes
|
| 652 |
+
|
| 653 |
+
classes_calculees <- suppressWarnings(as.integer(docvars(filtered_corpus)$Classes))
|
| 654 |
+
idx_ok <- !is.na(classes_calculees) & classes_calculees > 0
|
| 655 |
+
|
| 656 |
+
nb_non_assignes <- sum(!idx_ok)
|
| 657 |
+
if (nb_non_assignes > 0) {
|
| 658 |
+
ajouter_log(
|
| 659 |
+
rv,
|
| 660 |
+
paste0(
|
| 661 |
+
"Segments non assignés à une classe terminale (Classe 0 / NA) : ",
|
| 662 |
+
nb_non_assignes,
|
| 663 |
+
". Exclusion des calculs CHD/AFC."
|
| 664 |
+
)
|
| 665 |
+
)
|
| 666 |
+
}
|
| 667 |
+
filtered_corpus_ok <- filtered_corpus[idx_ok]
|
| 668 |
+
dfm_ok <- dfm_obj[idx_ok, ]
|
| 669 |
+
tok_ok <- tok[idx_ok]
|
| 670 |
+
|
| 671 |
+
if (ndoc(dfm_ok) < 2) stop("Après classification, il reste moins de 2 segments classés (hors NA).")
|
| 672 |
+
if (nfeat(dfm_ok) < 2) stop("Après classification, le DFM classé est trop pauvre (moins de 2 termes).")
|
| 673 |
+
|
| 674 |
+
rv$clusters <- sort(unique(docvars(filtered_corpus_ok)$Classes))
|
| 675 |
+
rv$res <- res_final
|
| 676 |
+
rv$dfm <- dfm_ok
|
| 677 |
+
rv$filtered_corpus <- filtered_corpus_ok
|
| 678 |
+
rv$res_stats_df <- NULL
|
| 679 |
+
|
| 680 |
+
avancer(0.58, "NER (si activé)")
|
| 681 |
+
rv$statut <- "NER (si activé)..."
|
| 682 |
+
|
| 683 |
+
if (isTRUE(input$activer_ner)) {
|
| 684 |
+
ajouter_log(rv, "NER ignoré : cette branche IRaMuTeQ-like est strictement sans spaCy.")
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
avancer(0.62, "Exports + stats")
|
| 688 |
+
rv$statut <- "Exports et statistiques..."
|
| 689 |
+
|
| 690 |
+
segments_vec <- as.character(filtered_corpus_ok)
|
| 691 |
+
names(segments_vec) <- docnames(filtered_corpus_ok)
|
| 692 |
+
segments_by_class <- split(segments_vec, docvars(filtered_corpus_ok)$Classes)
|
| 693 |
+
|
| 694 |
+
segments_file <- file.path(rv$export_dir, "segments_par_classe.txt")
|
| 695 |
+
writeLines(unlist(lapply(names(segments_by_class), function(cl) c(paste0("Classe ", cl, ":"), unname(segments_by_class[[cl]]), ""))), segments_file)
|
| 696 |
+
|
| 697 |
+
if (identical(rv$res_type, "iramuteq")) {
|
| 698 |
+
ajouter_log(rv, "Statistiques CHD : calcul IRaMuTeQ-like (contingence classe × terme).")
|
| 699 |
+
res_stats_df <- construire_stats_classes_iramuteq(
|
| 700 |
+
dfm_obj = dfm_ok,
|
| 701 |
+
classes = docvars(filtered_corpus_ok)$Classes,
|
| 702 |
+
max_p = 1
|
| 703 |
+
) %>%
|
| 704 |
+
mutate(Classe = normaliser_id_classe_local(Classe)) %>%
|
| 705 |
+
arrange(Classe, desc(chi2))
|
| 706 |
+
} else {
|
| 707 |
+
res_stats_list <- rainette_stats(
|
| 708 |
+
dtm = dfm_ok,
|
| 709 |
+
groups = docvars(filtered_corpus_ok)$Classes,
|
| 710 |
+
measure = c("chi2", "lr", "frequency", "docprop"),
|
| 711 |
+
n_terms = 9999,
|
| 712 |
+
# Harmonisation avec le graphe CHD :
|
| 713 |
+
# - pas de chi2 négatifs dans l'onglet Statistiques
|
| 714 |
+
# - pas de coupe préalable sur p-value pour conserver le même
|
| 715 |
+
# vivier de termes entre les vues (la colonne p_value_filter
|
| 716 |
+
# reste disponible pour distinguer les termes significatifs).
|
| 717 |
+
show_negative = FALSE,
|
| 718 |
+
max_p = 1
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
labels_stats <- names(res_stats_list)
|
| 722 |
+
labels_groupes <- as.character(sort(unique(docvars(filtered_corpus_ok)$Classes)))
|
| 723 |
+
|
| 724 |
+
if (is.null(labels_stats) || length(labels_stats) != length(res_stats_list) || any(!nzchar(labels_stats))) {
|
| 725 |
+
labels_stats <- labels_groupes
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
if (length(labels_stats) != length(res_stats_list)) {
|
| 729 |
+
labels_stats <- as.character(seq_along(res_stats_list))
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
tailles_stats <- vapply(res_stats_list, nrow, integer(1))
|
| 733 |
+
|
| 734 |
+
res_stats_df <- bind_rows(res_stats_list) %>%
|
| 735 |
+
mutate(ClusterID = rep(labels_stats, times = tailles_stats)) %>%
|
| 736 |
+
rename(Terme = feature, Classe = ClusterID) %>%
|
| 737 |
+
mutate(
|
| 738 |
+
p_value = p,
|
| 739 |
+
Classe_brut = as.character(Classe),
|
| 740 |
+
Classe = normaliser_id_classe_local(Classe),
|
| 741 |
+
p_value_filter = ifelse(p <= input$max_p, paste0("≤ ", input$max_p), paste0("> ", input$max_p))
|
| 742 |
+
) %>%
|
| 743 |
+
arrange(Classe, desc(chi2))
|
| 744 |
+
}
|
| 745 |
+
|
| 746 |
+
if (identical(source_dictionnaire, "lexique_fr") &&
|
| 747 |
+
!is.null(rv$lexique_fr_df) &&
|
| 748 |
+
is.data.frame(rv$lexique_fr_df) &&
|
| 749 |
+
nrow(rv$lexique_fr_df) > 0 &&
|
| 750 |
+
"Terme" %in% names(res_stats_df) &&
|
| 751 |
+
exists("construire_type_lexique_fr", mode = "function", inherits = TRUE)) {
|
| 752 |
+
res_stats_df$Type <- construire_type_lexique_fr(res_stats_df$Terme, rv$lexique_fr_df)
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
stats_file <- file.path(rv$export_dir, "stats_par_classe.csv")
|
| 756 |
+
ecrire_csv_6_decimales(res_stats_df, stats_file, row.names = FALSE)
|
| 757 |
+
|
| 758 |
+
rv$segments_file <- segments_file
|
| 759 |
+
rv$stats_file <- stats_file
|
| 760 |
+
rv$res_stats_df <- res_stats_df
|
| 761 |
+
|
| 762 |
+
avancer(0.72, "AFC (classes × termes)")
|
| 763 |
+
rv$statut <- "Calcul AFC classes × termes..."
|
| 764 |
+
|
| 765 |
+
rv$afc_obj <- NULL
|
| 766 |
+
rv$afc_erreur <- NULL
|
| 767 |
+
rv$afc_vars_obj <- NULL
|
| 768 |
+
rv$afc_vars_erreur <- NULL
|
| 769 |
+
rv$afc_dir <- file.path(rv$export_dir, "afc")
|
| 770 |
+
dir.create(rv$afc_dir, showWarnings = FALSE, recursive = TRUE)
|
| 771 |
+
|
| 772 |
+
filtrer_affichage_pvalue <- isTRUE(input$filtrer_affichage_pvalue)
|
| 773 |
+
|
| 774 |
+
termes_signif <- NULL
|
| 775 |
+
if (isTRUE(filtrer_affichage_pvalue)) {
|
| 776 |
+
termes_signif <- unique(subset(res_stats_df, p <= input$max_p)$Terme)
|
| 777 |
+
termes_signif <- termes_signif[!is.na(termes_signif) & nzchar(termes_signif)]
|
| 778 |
+
if (length(termes_signif) < 2) termes_signif <- NULL
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
tryCatch({
|
| 782 |
+
groupes_docs <- docvars(filtered_corpus_ok)$Classes
|
| 783 |
+
|
| 784 |
+
obj <- executer_afc_classes(
|
| 785 |
+
dfm_obj = dfm_ok,
|
| 786 |
+
groupes = groupes_docs,
|
| 787 |
+
termes_cibles = termes_signif,
|
| 788 |
+
max_termes = 400,
|
| 789 |
+
seuil_p = if (isTRUE(filtrer_affichage_pvalue)) input$max_p else 1,
|
| 790 |
+
rv = rv
|
| 791 |
+
)
|
| 792 |
+
|
| 793 |
+
if (!is.null(obj$termes_stats) && !is.null(rv$res_stats_df)) {
|
| 794 |
+
df_m <- obj$termes_stats
|
| 795 |
+
df_m$Classe_num <- suppressWarnings(as.numeric(gsub("^Classe\\s+", "", as.character(df_m$Classe_max))))
|
| 796 |
+
rs <- rv$res_stats_df
|
| 797 |
+
|
| 798 |
+
rs2 <- rs[, intersect(c("Terme", "Classe", "chi2", "p", "frequency", "docprop", "lr"), names(rs)), drop = FALSE]
|
| 799 |
+
rs2$Classe <- as.numeric(rs2$Classe)
|
| 800 |
+
|
| 801 |
+
m <- merge(
|
| 802 |
+
df_m,
|
| 803 |
+
rs2,
|
| 804 |
+
by.x = c("Terme", "Classe_num"),
|
| 805 |
+
by.y = c("Terme", "Classe"),
|
| 806 |
+
all.x = TRUE,
|
| 807 |
+
suffixes = c("_global", "_rainette")
|
| 808 |
+
)
|
| 809 |
+
|
| 810 |
+
if ("chi2" %in% names(m)) {
|
| 811 |
+
df_m$chi2 <- ifelse(is.na(m$chi2), df_m$chi2, m$chi2)
|
| 812 |
+
}
|
| 813 |
+
if ("p" %in% names(m)) {
|
| 814 |
+
df_m$p_value <- ifelse(is.na(m$p), df_m$p_value, m$p)
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
df_m$Classe_num <- NULL
|
| 818 |
+
obj$termes_stats <- df_m
|
| 819 |
+
}
|
| 820 |
+
|
| 821 |
+
obj$termes_stats <- construire_segments_exemples_afc(
|
| 822 |
+
termes_stats = obj$termes_stats,
|
| 823 |
+
dfm_obj = dfm_ok,
|
| 824 |
+
corpus_obj = filtered_corpus_ok
|
| 825 |
+
)
|
| 826 |
+
|
| 827 |
+
rv$afc_obj <- obj
|
| 828 |
+
ajouter_log(rv, "AFC classes × termes : calcul terminé.")
|
| 829 |
+
|
| 830 |
+
}, error = function(e) {
|
| 831 |
+
rv$afc_erreur <- paste0("AFC classes × termes : ", e$message)
|
| 832 |
+
ajouter_log(rv, rv$afc_erreur)
|
| 833 |
+
showNotification(rv$afc_erreur, type = "error", duration = 8)
|
| 834 |
+
})
|
| 835 |
+
|
| 836 |
+
avancer(0.74, "AFC (variables étoilées)")
|
| 837 |
+
rv$statut <- "Calcul AFC variables étoilées..."
|
| 838 |
+
|
| 839 |
+
tryCatch({
|
| 840 |
+
if (!is.null(docvars(filtered_corpus_ok)$Classes)) {
|
| 841 |
+
objv <- executer_afc_variables_etoilees(
|
| 842 |
+
corpus_aligne = filtered_corpus_ok,
|
| 843 |
+
groupes = docvars(filtered_corpus_ok)$Classes,
|
| 844 |
+
max_modalites = 400,
|
| 845 |
+
seuil_p = if (isTRUE(filtrer_affichage_pvalue)) input$max_p else 1,
|
| 846 |
+
rv = rv
|
| 847 |
+
)
|
| 848 |
+
rv$afc_vars_obj <- objv
|
| 849 |
+
ajouter_log(rv, "AFC variables étoilées : calcul terminé.")
|
| 850 |
+
}
|
| 851 |
+
}, error = function(e) {
|
| 852 |
+
rv$afc_vars_erreur <- paste0("AFC variables étoilées : ", e$message)
|
| 853 |
+
ajouter_log(rv, rv$afc_vars_erreur)
|
| 854 |
+
})
|
| 855 |
+
|
| 856 |
+
if (!is.null(rv$afc_obj) && !is.null(rv$afc_obj$ca)) {
|
| 857 |
+
|
| 858 |
+
afc_classes_png <- file.path(rv$afc_dir, "afc_classes.png")
|
| 859 |
+
afc_termes_png <- file.path(rv$afc_dir, "afc_termes.png")
|
| 860 |
+
|
| 861 |
+
activer_repel <- TRUE
|
| 862 |
+
if (!is.null(input$afc_reduire_chevauchement)) activer_repel <- isTRUE(input$afc_reduire_chevauchement)
|
| 863 |
+
|
| 864 |
+
taille_sel <- "frequency"
|
| 865 |
+
if (!is.null(input$afc_taille_mots) && nzchar(as.character(input$afc_taille_mots))) {
|
| 866 |
+
taille_sel <- as.character(input$afc_taille_mots)
|
| 867 |
+
}
|
| 868 |
+
if (!taille_sel %in% c("frequency", "chi2")) taille_sel <- "frequency"
|
| 869 |
+
|
| 870 |
+
top_termes <- 120
|
| 871 |
+
if (!is.null(input$afc_top_termes) && is.finite(input$afc_top_termes)) top_termes <- as.integer(input$afc_top_termes)
|
| 872 |
+
|
| 873 |
+
png(afc_classes_png, width = 1800, height = 1400, res = 180)
|
| 874 |
+
try(tracer_afc_classes_seules(rv$afc_obj, axes = c(1, 2), cex_labels = 1.05), silent = TRUE)
|
| 875 |
+
dev.off()
|
| 876 |
+
|
| 877 |
+
png(afc_termes_png, width = 2000, height = 1600, res = 180)
|
| 878 |
+
try(tracer_afc_classes_termes(rv$afc_obj, axes = c(1, 2), top_termes = top_termes, taille_sel = taille_sel, activer_repel = activer_repel), silent = TRUE)
|
| 879 |
+
dev.off()
|
| 880 |
+
|
| 881 |
+
rv$afc_plot_classes <- afc_classes_png
|
| 882 |
+
rv$afc_plot_termes <- afc_termes_png
|
| 883 |
+
|
| 884 |
+
ecrire_csv_6_decimales(rv$afc_obj$table, file.path(rv$afc_dir, "table_classes_termes.csv"), row.names = TRUE)
|
| 885 |
+
ecrire_csv_6_decimales(rv$afc_obj$rowcoord, file.path(rv$afc_dir, "coords_classes.csv"), row.names = TRUE)
|
| 886 |
+
ecrire_csv_6_decimales(rv$afc_obj$colcoord, file.path(rv$afc_dir, "coords_termes.csv"), row.names = TRUE)
|
| 887 |
+
ecrire_csv_6_decimales(rv$afc_obj$termes_stats, file.path(rv$afc_dir, "stats_termes.csv"), row.names = FALSE)
|
| 888 |
+
|
| 889 |
+
if (!is.null(rv$afc_obj$ca$eig)) {
|
| 890 |
+
ecrire_csv_6_decimales(as.data.frame(rv$afc_obj$ca$eig), file.path(rv$afc_dir, "valeurs_propres.csv"), row.names = TRUE)
|
| 891 |
+
}
|
| 892 |
+
|
| 893 |
+
rv$afc_table_mots <- rv$afc_obj$termes_stats
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
if (!is.null(rv$afc_vars_obj) && !is.null(rv$afc_vars_obj$ca)) {
|
| 897 |
+
|
| 898 |
+
afc_vars_png <- file.path(rv$afc_dir, "afc_variables_etoilees.png")
|
| 899 |
+
|
| 900 |
+
activer_repel2 <- TRUE
|
| 901 |
+
if (!is.null(input$afc_reduire_chevauchement)) activer_repel2 <- isTRUE(input$afc_reduire_chevauchement)
|
| 902 |
+
|
| 903 |
+
top_mod <- 120
|
| 904 |
+
if (!is.null(input$afc_top_modalites) && is.finite(input$afc_top_modalites)) top_mod <- as.integer(input$afc_top_modalites)
|
| 905 |
+
|
| 906 |
+
png(afc_vars_png, width = 2000, height = 1600, res = 180)
|
| 907 |
+
try(tracer_afc_variables_etoilees(rv$afc_vars_obj, axes = c(1, 2), top_modalites = top_mod, activer_repel = activer_repel2), silent = TRUE)
|
| 908 |
+
dev.off()
|
| 909 |
+
|
| 910 |
+
rv$afc_plot_vars <- afc_vars_png
|
| 911 |
+
|
| 912 |
+
ecrire_csv_6_decimales(rv$afc_vars_obj$table, file.path(rv$afc_dir, "table_classes_variables.csv"), row.names = TRUE)
|
| 913 |
+
ecrire_csv_6_decimales(rv$afc_vars_obj$rowcoord, file.path(rv$afc_dir, "coords_classes_vars.csv"), row.names = TRUE)
|
| 914 |
+
ecrire_csv_6_decimales(rv$afc_vars_obj$colcoord, file.path(rv$afc_dir, "coords_modalites.csv"), row.names = TRUE)
|
| 915 |
+
ecrire_csv_6_decimales(rv$afc_vars_obj$modalites_stats, file.path(rv$afc_dir, "stats_modalites.csv"), row.names = FALSE)
|
| 916 |
+
|
| 917 |
+
if (!is.null(rv$afc_vars_obj$ca$eig)) {
|
| 918 |
+
ecrire_csv_6_decimales(as.data.frame(rv$afc_vars_obj$ca$eig), file.path(rv$afc_dir, "valeurs_propres_vars.csv"), row.names = TRUE)
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
rv$afc_table_vars <- rv$afc_vars_obj$modalites_stats
|
| 922 |
+
}
|
| 923 |
+
|
| 924 |
+
avancer(0.76, "Concordancier HTML")
|
| 925 |
+
rv$statut <- "Concordancier..."
|
| 926 |
+
|
| 927 |
+
html_file <- file.path(rv$export_dir, "segments_par_classe.html")
|
| 928 |
+
textes_index_ok <- rv$textes_indexation[docnames(dfm_ok)]
|
| 929 |
+
names(textes_index_ok) <- docnames(dfm_ok)
|
| 930 |
+
|
| 931 |
+
wordcloud_dir <- file.path(rv$export_dir, "wordclouds")
|
| 932 |
+
dir.create(wordcloud_dir, showWarnings = FALSE, recursive = TRUE)
|
| 933 |
+
cooc_dir <- file.path(rv$export_dir, "cooccurrences")
|
| 934 |
+
dir.create(cooc_dir, showWarnings = FALSE, recursive = TRUE)
|
| 935 |
+
|
| 936 |
+
classes_uniques <- sort(unique(as.integer(docvars(filtered_corpus_ok)$Classes)))
|
| 937 |
+
classes_uniques <- classes_uniques[is.finite(classes_uniques)]
|
| 938 |
+
|
| 939 |
+
if (length(classes_uniques) > 0) {
|
| 940 |
+
classes_choices <- as.character(classes_uniques)
|
| 941 |
+
updateSelectInput(
|
| 942 |
+
session,
|
| 943 |
+
"classe_viz_iramuteq",
|
| 944 |
+
choices = classes_choices,
|
| 945 |
+
selected = classes_choices[[1]]
|
| 946 |
+
)
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
if (!identical(rv$res_type, "iramuteq")) {
|
| 950 |
+
for (cl in classes_uniques) {
|
| 951 |
+
top_n_demande <- suppressWarnings(as.integer(input$top_n))
|
| 952 |
+
if (!is.finite(top_n_demande) || is.na(top_n_demande)) top_n_demande <- 20L
|
| 953 |
+
top_n_demande <- max(5L, top_n_demande)
|
| 954 |
+
|
| 955 |
+
if (isTRUE(input$filtrer_affichage_pvalue)) {
|
| 956 |
+
df_stats_cl <- subset(res_stats_df, Classe == cl & p <= input$max_p)
|
| 957 |
+
} else {
|
| 958 |
+
df_stats_cl <- subset(res_stats_df, Classe == cl)
|
| 959 |
+
}
|
| 960 |
+
if (nrow(df_stats_cl) > 0) {
|
| 961 |
+
df_stats_cl <- df_stats_cl[order(-df_stats_cl$chi2), , drop = FALSE]
|
| 962 |
+
df_stats_cl <- head(df_stats_cl, top_n_demande)
|
| 963 |
+
|
| 964 |
+
wc_png <- file.path(wordcloud_dir, paste0("cluster_", cl, "_wordcloud.png"))
|
| 965 |
+
try({
|
| 966 |
+
png(wc_png, width = 800, height = 600)
|
| 967 |
+
suppressWarnings(wordcloud(
|
| 968 |
+
words = df_stats_cl$Terme,
|
| 969 |
+
freq = df_stats_cl$chi2,
|
| 970 |
+
scale = c(10, 0.5),
|
| 971 |
+
min.freq = 0,
|
| 972 |
+
max.words = nrow(df_stats_cl),
|
| 973 |
+
colors = brewer.pal(8, "Dark2")
|
| 974 |
+
))
|
| 975 |
+
dev.off()
|
| 976 |
+
}, silent = TRUE)
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
generer_cooccurrences_par_classe(
|
| 982 |
+
tok_ok = tok_ok,
|
| 983 |
+
filtered_corpus_ok = filtered_corpus_ok,
|
| 984 |
+
classes_uniques = classes_uniques,
|
| 985 |
+
cooc_dir = cooc_dir,
|
| 986 |
+
top_n = input$top_n,
|
| 987 |
+
top_feat = input$top_feat,
|
| 988 |
+
window_cooc = input$window_cooc
|
| 989 |
+
)
|
| 990 |
+
} else {
|
| 991 |
+
generer_wordclouds_iramuteq(
|
| 992 |
+
res_stats_df = res_stats_df,
|
| 993 |
+
classes_uniques = classes_uniques,
|
| 994 |
+
wordcloud_dir = wordcloud_dir,
|
| 995 |
+
top_n = input$top_n,
|
| 996 |
+
filtrer_pvalue = isTRUE(input$filtrer_affichage_pvalue),
|
| 997 |
+
max_p = input$max_p
|
| 998 |
+
)
|
| 999 |
+
ajouter_log(rv, "Mode IRaMuTeQ-like : nuages de mots générés via wordcloud_iramuteq.R (cooccurrences Explore rainette désactivées).")
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
explor_assets <- NULL
|
| 1003 |
+
ok_chd_png <- FALSE
|
| 1004 |
+
if (!identical(rv$res_type, "iramuteq")) {
|
| 1005 |
+
ok_chd_png <- generer_chd_explor_si_absente(rv)
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
chd_png_rel <- NULL
|
| 1009 |
+
if (isTRUE(ok_chd_png) && file.exists(file.path(rv$export_dir, "explor", "chd.png"))) {
|
| 1010 |
+
chd_png_rel <- file.path("explor", "chd.png")
|
| 1011 |
+
}
|
| 1012 |
+
chd_html_rel <- generer_chd_html_explor(rv, chd_png_rel)
|
| 1013 |
+
|
| 1014 |
+
wc_files <- list.files(wordcloud_dir, pattern = "\\.png$", full.names = FALSE)
|
| 1015 |
+
if (length(wc_files) > 0) {
|
| 1016 |
+
wc_classes <- gsub("^cluster_([0-9]+)_wordcloud\\.png$", "\\1", wc_files)
|
| 1017 |
+
wordclouds_df <- data.frame(
|
| 1018 |
+
classe = wc_classes,
|
| 1019 |
+
src = file.path("wordclouds", wc_files),
|
| 1020 |
+
stringsAsFactors = FALSE
|
| 1021 |
+
)
|
| 1022 |
+
wordclouds_df <- wordclouds_df[order(suppressWarnings(as.integer(wordclouds_df$classe))), , drop = FALSE]
|
| 1023 |
+
} else {
|
| 1024 |
+
wordclouds_df <- data.frame(classe = character(0), src = character(0), stringsAsFactors = FALSE)
|
| 1025 |
+
}
|
| 1026 |
+
|
| 1027 |
+
coocs_df <- construire_table_cooccurrences(cooc_dir)
|
| 1028 |
+
|
| 1029 |
+
explor_assets <- list(
|
| 1030 |
+
chd = chd_png_rel,
|
| 1031 |
+
chd_html = chd_html_rel,
|
| 1032 |
+
wordclouds = wordclouds_df,
|
| 1033 |
+
coocs = coocs_df
|
| 1034 |
+
)
|
| 1035 |
+
rv$explor_assets <- explor_assets
|
| 1036 |
+
|
| 1037 |
+
args_concordancier <- list(
|
| 1038 |
+
chemin_sortie = html_file,
|
| 1039 |
+
segments_by_class = segments_by_class,
|
| 1040 |
+
res_stats_df = res_stats_df,
|
| 1041 |
+
max_p = if (isTRUE(input$filtrer_affichage_pvalue)) input$max_p else 1,
|
| 1042 |
+
filtrer_pvalue = isTRUE(input$filtrer_affichage_pvalue),
|
| 1043 |
+
textes_indexation = textes_index_ok,
|
| 1044 |
+
spacy_tokens_df = rv$spacy_tokens_df,
|
| 1045 |
+
lexique_fr_df = rv$lexique_fr_df,
|
| 1046 |
+
source_dictionnaire = source_dictionnaire,
|
| 1047 |
+
avancer = avancer,
|
| 1048 |
+
rv = rv
|
| 1049 |
+
)
|
| 1050 |
+
|
| 1051 |
+
# Priorité explicite au concordancier IRaMuTeQ-like lorsque le mode
|
| 1052 |
+
# IRaMuTeQ est sélectionné dans l'UI (même si rv$res_type est désynchronisé).
|
| 1053 |
+
mode_iramuteq_actif <- identical(as.character(input$modele_chd), "iramuteq") ||
|
| 1054 |
+
identical(rv$res_type, "iramuteq")
|
| 1055 |
+
|
| 1056 |
+
fonction_concordancier <- if (isTRUE(mode_iramuteq_actif)) {
|
| 1057 |
+
generer_concordancier_iramuteq_html
|
| 1058 |
+
} else if (identical(source_dictionnaire, "lexique_fr")) {
|
| 1059 |
+
generer_concordancier_lexique_html
|
| 1060 |
+
} else {
|
| 1061 |
+
generer_concordancier_spacy_html
|
| 1062 |
+
}
|
| 1063 |
+
|
| 1064 |
+
html_genere <- do.call(fonction_concordancier, args_concordancier)
|
| 1065 |
+
|
| 1066 |
+
candidats_html <- unique(c(
|
| 1067 |
+
html_genere,
|
| 1068 |
+
html_file,
|
| 1069 |
+
file.path(rv$export_dir, "concordancier.html")
|
| 1070 |
+
))
|
| 1071 |
+
candidats_html <- candidats_html[is.character(candidats_html) & !is.na(candidats_html) & nzchar(candidats_html)]
|
| 1072 |
+
html_existants <- candidats_html[file.exists(candidats_html)]
|
| 1073 |
+
|
| 1074 |
+
if (length(html_existants) == 0) {
|
| 1075 |
+
html_fallback <- file.path(rv$export_dir, "concordancier.html")
|
| 1076 |
+
args_concordancier$chemin_sortie <- html_fallback
|
| 1077 |
+
ajouter_log(rv, "Concordancier HTML introuvable après la première génération. Nouvelle tentative vers exports/concordancier.html.")
|
| 1078 |
+
html_retry <- tryCatch(
|
| 1079 |
+
do.call(fonction_concordancier, args_concordancier),
|
| 1080 |
+
error = function(e) {
|
| 1081 |
+
ajouter_log(rv, paste0("Concordancier HTML : échec de la relance - ", e$message))
|
| 1082 |
+
NA_character_
|
| 1083 |
+
}
|
| 1084 |
+
)
|
| 1085 |
+
|
| 1086 |
+
candidats_retry <- unique(c(html_retry, html_fallback, html_genere, html_file))
|
| 1087 |
+
candidats_retry <- candidats_retry[is.character(candidats_retry) & !is.na(candidats_retry) & nzchar(candidats_retry)]
|
| 1088 |
+
html_existants <- candidats_retry[file.exists(candidats_retry)]
|
| 1089 |
+
}
|
| 1090 |
+
|
| 1091 |
+
if (length(html_existants) > 0) {
|
| 1092 |
+
rv$html_file <- html_existants[[1]]
|
| 1093 |
+
ajouter_log(rv, paste0("Concordancier HTML validé : ", rv$html_file))
|
| 1094 |
+
} else {
|
| 1095 |
+
rv$html_file <- html_file
|
| 1096 |
+
ajouter_log(rv, "Concordancier HTML introuvable après relance. Vérifier les logs de génération du concordancier.")
|
| 1097 |
+
}
|
| 1098 |
+
|
| 1099 |
+
avancer(0.96, "ZIP")
|
| 1100 |
+
rv$statut <- "Création ZIP..."
|
| 1101 |
+
rv$zip_file <- file.path(rv$base_dir, "exports_rainette.zip")
|
| 1102 |
+
if (file.exists(rv$zip_file)) unlink(rv$zip_file)
|
| 1103 |
+
|
| 1104 |
+
ancien_wd <- getwd()
|
| 1105 |
+
setwd(rv$base_dir)
|
| 1106 |
+
utils::zip(zipfile = rv$zip_file, files = "exports")
|
| 1107 |
+
setwd(ancien_wd)
|
| 1108 |
+
|
| 1109 |
+
exports_prefix <- as.character(rv$exports_prefix)
|
| 1110 |
+
if (length(exports_prefix) > 1) exports_prefix <- exports_prefix[[1]]
|
| 1111 |
+
if (!length(exports_prefix) || is.na(exports_prefix) || !nzchar(exports_prefix)) {
|
| 1112 |
+
# Fallback robuste: évite une erreur "missing value where TRUE/FALSE needed"
|
| 1113 |
+
# lorsque le token de session est indisponible.
|
| 1114 |
+
exports_prefix <- paste0("exports_", format(Sys.time(), "%Y%m%d%H%M%S"))
|
| 1115 |
+
rv$exports_prefix <- exports_prefix
|
| 1116 |
+
}
|
| 1117 |
+
|
| 1118 |
+
if (!(exports_prefix %in% names(shiny::resourcePaths()))) {
|
| 1119 |
+
shiny::addResourcePath(exports_prefix, rv$export_dir)
|
| 1120 |
+
}
|
| 1121 |
+
|
| 1122 |
+
rv$statut <- "Analyse terminée."
|
| 1123 |
+
rv$progression <- 100
|
| 1124 |
+
ajouter_log(rv, "Analyse terminée.")
|
| 1125 |
+
showNotification("Analyse terminée.", type = "message", duration = 5)
|
| 1126 |
+
|
| 1127 |
+
}, error = function(e) {
|
| 1128 |
+
rv$statut <- paste0("Erreur : ", e$message)
|
| 1129 |
+
ajouter_log(rv, paste0("ERREUR : ", e$message))
|
| 1130 |
+
showNotification(e$message, type = "error", duration = 8)
|
| 1131 |
+
})
|
| 1132 |
+
})
|
| 1133 |
+
}
|
iramuteq-like/stats_chd.R
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: stats_chd.R centralise la table des statistiques CHD pour le mode IRaMuTeQ-like.
|
| 2 |
+
|
| 3 |
+
formatter_6_decimales_chd <- function(x) {
|
| 4 |
+
ifelse(is.na(x), NA_character_, formatC(as.numeric(x), format = "f", digits = 6))
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
.inferer_type_terme_iramuteq <- function(termes) {
|
| 8 |
+
x <- tolower(as.character(termes))
|
| 9 |
+
out <- rep("", length(x))
|
| 10 |
+
|
| 11 |
+
extraire_tag <- function(pattern, value) {
|
| 12 |
+
idx <- grepl(pattern, x)
|
| 13 |
+
out[idx] <<- value
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
extraire_tag("(^|[_/])nom$", "nom")
|
| 17 |
+
extraire_tag("(^|[_/])adj$", "adj")
|
| 18 |
+
extraire_tag("(^|[_/])ver$", "ver")
|
| 19 |
+
extraire_tag("(^|[_/])adv$", "adv")
|
| 20 |
+
extraire_tag("(^|[_/])nr$", "nr")
|
| 21 |
+
|
| 22 |
+
out
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.normaliser_type_terme_iramuteq <- function(type_vals, termes) {
|
| 26 |
+
types <- tolower(trimws(as.character(type_vals)))
|
| 27 |
+
types[is.na(types)] <- ""
|
| 28 |
+
types[types %in% c("", "na", "nan", "null")] <- ""
|
| 29 |
+
|
| 30 |
+
types_inf <- .inferer_type_terme_iramuteq(termes)
|
| 31 |
+
idx_manquant <- !nzchar(types)
|
| 32 |
+
types[idx_manquant] <- types_inf[idx_manquant]
|
| 33 |
+
types
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
extraire_stats_chd_classe <- function(res_stats_df,
|
| 37 |
+
classe,
|
| 38 |
+
n_max = 50,
|
| 39 |
+
show_negative = FALSE,
|
| 40 |
+
max_p = 1,
|
| 41 |
+
seuil_p_significativite = 0.05,
|
| 42 |
+
style = c("iramuteq_clone", "legacy")) {
|
| 43 |
+
style <- match.arg(style)
|
| 44 |
+
|
| 45 |
+
if (is.null(res_stats_df) || nrow(res_stats_df) == 0) {
|
| 46 |
+
return(data.frame(Message = "Statistiques indisponibles.", stringsAsFactors = FALSE))
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
cl <- suppressWarnings(as.numeric(classe))
|
| 50 |
+
df <- res_stats_df
|
| 51 |
+
if (is.finite(cl) && !is.na(cl) && "Classe" %in% names(df)) {
|
| 52 |
+
df <- df[suppressWarnings(as.numeric(df$Classe)) == cl, , drop = FALSE]
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
colonnes_possibles <- intersect(
|
| 56 |
+
c("Terme", "chi2", "lr", "frequency", "docprop", "eff_st", "eff_total", "pourcentage", "p", "p_value", "p_value_filter", "Type", "type", "pos", "POS"),
|
| 57 |
+
names(df)
|
| 58 |
+
)
|
| 59 |
+
df <- df[, colonnes_possibles, drop = FALSE]
|
| 60 |
+
|
| 61 |
+
if ("p" %in% names(df) && is.finite(max_p) && !is.na(max_p) && max_p < 1) {
|
| 62 |
+
df <- df[suppressWarnings(as.numeric(df$p)) <= max_p, , drop = FALSE]
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
if ("chi2" %in% names(df)) {
|
| 66 |
+
chi2_vals <- suppressWarnings(as.numeric(df$chi2))
|
| 67 |
+
if (!isTRUE(show_negative)) {
|
| 68 |
+
df <- df[is.finite(chi2_vals) & chi2_vals > 0, , drop = FALSE]
|
| 69 |
+
chi2_vals <- suppressWarnings(as.numeric(df$chi2))
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
frequency_vals <- rep(-Inf, nrow(df))
|
| 73 |
+
if ("frequency" %in% names(df)) {
|
| 74 |
+
frequency_vals <- suppressWarnings(as.numeric(df$frequency))
|
| 75 |
+
frequency_vals[!is.finite(frequency_vals)] <- -Inf
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
chi2_sort <- chi2_vals
|
| 79 |
+
chi2_sort[!is.finite(chi2_sort)] <- -Inf
|
| 80 |
+
df <- df[order(-chi2_sort, -frequency_vals), , drop = FALSE]
|
| 81 |
+
}
|
| 82 |
+
df <- utils::head(df, n_max)
|
| 83 |
+
|
| 84 |
+
if (identical(style, "iramuteq_clone")) {
|
| 85 |
+
eff_st <- if ("eff_st" %in% names(df)) suppressWarnings(as.numeric(df$eff_st)) else round(suppressWarnings(as.numeric(df$docprop)) * suppressWarnings(as.numeric(df$eff_total)))
|
| 86 |
+
eff_total <- if ("eff_total" %in% names(df)) suppressWarnings(as.numeric(df$eff_total)) else suppressWarnings(as.numeric(df$frequency))
|
| 87 |
+
pourcentage <- if ("pourcentage" %in% names(df)) suppressWarnings(as.numeric(df$pourcentage)) else ifelse(eff_total > 0, 100 * eff_st / eff_total, NA_real_)
|
| 88 |
+
chi2_vals <- if ("chi2" %in% names(df)) suppressWarnings(as.numeric(df$chi2)) else NA_real_
|
| 89 |
+
p_vals <- if ("p" %in% names(df)) suppressWarnings(as.numeric(df$p)) else if ("p_value" %in% names(df)) suppressWarnings(as.numeric(df$p_value)) else NA_real_
|
| 90 |
+
formes <- if ("Terme" %in% names(df)) as.character(df$Terme) else rep("", nrow(df))
|
| 91 |
+
type_source <- if ("Type" %in% names(df)) df$Type else if ("type" %in% names(df)) df$type else if ("pos" %in% names(df)) df$pos else if ("POS" %in% names(df)) df$POS else rep("", nrow(df))
|
| 92 |
+
types <- .normaliser_type_terme_iramuteq(type_source, formes)
|
| 93 |
+
|
| 94 |
+
out <- data.frame(
|
| 95 |
+
num = seq_len(nrow(df)) - 1L,
|
| 96 |
+
forme = formes,
|
| 97 |
+
`eff. s.t.` = as.integer(round(eff_st)),
|
| 98 |
+
`eff. total` = as.integer(round(eff_total)),
|
| 99 |
+
pourcentage = ifelse(is.na(pourcentage), NA_character_, formatC(pourcentage, format = "f", digits = 2)),
|
| 100 |
+
chi2 = ifelse(is.na(chi2_vals), NA_character_, formatC(chi2_vals, format = "f", digits = 3)),
|
| 101 |
+
`p.value` = ifelse(is.na(p_vals), NA_character_, formatC(p_vals, format = "f", digits = 6)),
|
| 102 |
+
`p.value < 0.01` = ifelse(!is.na(p_vals) & p_vals < 0.01, "Oui", ""),
|
| 103 |
+
Type = types,
|
| 104 |
+
check.names = FALSE,
|
| 105 |
+
stringsAsFactors = FALSE
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
seuil_sig <- suppressWarnings(as.numeric(seuil_p_significativite))
|
| 109 |
+
if (is.finite(seuil_sig) && !is.na(seuil_sig) && nrow(out) > 0) {
|
| 110 |
+
idx_non_signif <- !is.na(p_vals) & p_vals > seuil_sig
|
| 111 |
+
if (any(idx_non_signif)) {
|
| 112 |
+
colonnes_colorables <- names(out)
|
| 113 |
+
out[idx_non_signif, colonnes_colorables] <- lapply(out[idx_non_signif, colonnes_colorables, drop = FALSE], function(col_vals) {
|
| 114 |
+
ifelse(
|
| 115 |
+
is.na(col_vals),
|
| 116 |
+
NA_character_,
|
| 117 |
+
sprintf("<span style='color:#842029;'>%s</span>", as.character(col_vals))
|
| 118 |
+
)
|
| 119 |
+
})
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
return(out)
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
colonnes_num <- intersect(c("chi2", "lr", "docprop", "p", "p_value"), names(df))
|
| 127 |
+
for (col in colonnes_num) {
|
| 128 |
+
df[[col]] <- formatter_6_decimales_chd(df[[col]])
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
if ("frequency" %in% names(df)) {
|
| 132 |
+
df$frequency <- ifelse(is.na(df$frequency), NA_character_, formatC(as.numeric(df$frequency), format = "f", digits = 6))
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
df
|
| 136 |
+
}
|
iramuteq-like/textprepa_iramuteq.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Préparation de corpus IRaMuTeQ-like (branche lexique_fr) pour audit de reproductibilité.
|
| 4 |
+
|
| 5 |
+
Entrée : TSV (doc_id, text)
|
| 6 |
+
Sortie : TSV (doc_id, text_prepared)
|
| 7 |
+
Optionnel: TSV de tokens (doc_id, token)
|
| 8 |
+
|
| 9 |
+
Objectif:
|
| 10 |
+
- Reproduire au plus près le pré-nettoyage de `nettoyage.R`
|
| 11 |
+
- Offrir un point d'audit unique pour comparer les formes avec IRaMuTeQ desktop
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import argparse
|
| 17 |
+
import csv
|
| 18 |
+
import re
|
| 19 |
+
from typing import Iterable, List, Tuple
|
| 20 |
+
|
| 21 |
+
ALLOWED_CHARS = "a-zA-Z0-9àÀâÂäÄáÁåÅãéÉèÈêÊëËìÌîÎïÏíÍóÓòÒôÔöÖõÕøØùÙûÛüÜúÚçÇßœŒ’ñÑ\\.:,;!\\?'"
|
| 22 |
+
RE_REMOVE_DISALLOWED = re.compile(rf"[^{ALLOWED_CHARS}]")
|
| 23 |
+
RE_MULTI_SPACES = re.compile(r"\s+")
|
| 24 |
+
RE_NUMBERS = re.compile(r"[0-9]+")
|
| 25 |
+
# Équivalent pratique de (?i)\b(?:[cdjlmnst]|qu)['’`´ʼʹ](?=\p{L})
|
| 26 |
+
RE_FR_ELISIONS = re.compile(r"(?i)\b(?:[cdjlmnst]|qu)['’`´ʼʹ](?=[A-Za-zÀ-ÖØ-öø-ÿŒœ])")
|
| 27 |
+
# Token "mot" avec apostrophe interne possible
|
| 28 |
+
RE_WORD = re.compile(r"[A-Za-zÀ-ÖØ-öø-ÿŒœ0-9]+(?:['’][A-Za-zÀ-ÖØ-öø-ÿŒœ0-9]+)*")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def read_tsv(path: str) -> Tuple[List[str], List[str]]:
|
| 32 |
+
ids: List[str] = []
|
| 33 |
+
texts: List[str] = []
|
| 34 |
+
with open(path, "r", encoding="utf-8", newline="") as f:
|
| 35 |
+
reader = csv.DictReader(f, delimiter="\t")
|
| 36 |
+
if reader.fieldnames is None or "doc_id" not in reader.fieldnames or "text" not in reader.fieldnames:
|
| 37 |
+
raise ValueError("TSV invalide: colonnes attendues 'doc_id' et 'text'.")
|
| 38 |
+
for row in reader:
|
| 39 |
+
ids.append((row.get("doc_id") or "").strip())
|
| 40 |
+
texts.append(row.get("text") or "")
|
| 41 |
+
return ids, texts
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def write_tsv(path: str, ids: Iterable[str], texts: Iterable[str]) -> None:
|
| 45 |
+
with open(path, "w", encoding="utf-8", newline="") as f:
|
| 46 |
+
writer = csv.DictWriter(f, fieldnames=["doc_id", "text"], delimiter="\t")
|
| 47 |
+
writer.writeheader()
|
| 48 |
+
for did, txt in zip(ids, texts):
|
| 49 |
+
writer.writerow({"doc_id": did, "text": txt})
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def write_tokens(path: str, rows: List[Tuple[str, str]]) -> None:
|
| 53 |
+
with open(path, "w", encoding="utf-8", newline="") as f:
|
| 54 |
+
writer = csv.DictWriter(f, fieldnames=["doc_id", "token"], delimiter="\t")
|
| 55 |
+
writer.writeheader()
|
| 56 |
+
for did, tok in rows:
|
| 57 |
+
writer.writerow({"doc_id": did, "token": tok})
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def prepare_text(
|
| 61 |
+
text: str,
|
| 62 |
+
nettoyer_caracteres: bool,
|
| 63 |
+
lower: bool,
|
| 64 |
+
remove_numbers: bool,
|
| 65 |
+
strip_fr_elisions: bool,
|
| 66 |
+
) -> str:
|
| 67 |
+
out = text.replace("\u00A0", " ")
|
| 68 |
+
|
| 69 |
+
if remove_numbers:
|
| 70 |
+
out = RE_NUMBERS.sub(" ", out)
|
| 71 |
+
|
| 72 |
+
if strip_fr_elisions:
|
| 73 |
+
out = RE_FR_ELISIONS.sub("", out)
|
| 74 |
+
|
| 75 |
+
if nettoyer_caracteres:
|
| 76 |
+
out = RE_REMOVE_DISALLOWED.sub(" ", out)
|
| 77 |
+
|
| 78 |
+
out = RE_MULTI_SPACES.sub(" ", out).strip()
|
| 79 |
+
|
| 80 |
+
if lower:
|
| 81 |
+
out = out.lower()
|
| 82 |
+
|
| 83 |
+
return out
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def tokenize(prepared: str, remove_numbers: bool, lower_tokens: bool) -> List[str]:
|
| 87 |
+
tokens = RE_WORD.findall(prepared)
|
| 88 |
+
if remove_numbers:
|
| 89 |
+
tokens = [t for t in tokens if not t.isdigit()]
|
| 90 |
+
if lower_tokens:
|
| 91 |
+
tokens = [t.lower() for t in tokens]
|
| 92 |
+
return tokens
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def main() -> int:
|
| 96 |
+
p = argparse.ArgumentParser()
|
| 97 |
+
p.add_argument("--input", required=True, help="TSV entrée (doc_id, text)")
|
| 98 |
+
p.add_argument("--output", required=True, help="TSV sortie (doc_id, text préparé)")
|
| 99 |
+
p.add_argument("--nettoyage_caracteres", default="0", help="1 pour activer le nettoyage des caractères")
|
| 100 |
+
p.add_argument("--forcer_minuscules_avant", default="0", help="1 pour forcer les minuscules")
|
| 101 |
+
p.add_argument("--supprimer_chiffres", default="0", help="1 pour supprimer les chiffres")
|
| 102 |
+
p.add_argument("--supprimer_apostrophes", default="0", help="1 pour retirer les élisions FR (c', d', l', qu', ...)")
|
| 103 |
+
p.add_argument("--output_tokens", default="", help="TSV optionnel de tokens (doc_id, token)")
|
| 104 |
+
args = p.parse_args()
|
| 105 |
+
|
| 106 |
+
nettoyer_caracteres = str(args.nettoyage_caracteres).strip() == "1"
|
| 107 |
+
lower = str(args.forcer_minuscules_avant).strip() == "1"
|
| 108 |
+
remove_numbers = str(args.supprimer_chiffres).strip() == "1"
|
| 109 |
+
strip_fr_elisions = str(args.supprimer_apostrophes).strip() == "1"
|
| 110 |
+
|
| 111 |
+
ids, texts = read_tsv(args.input)
|
| 112 |
+
|
| 113 |
+
prepared = [
|
| 114 |
+
prepare_text(
|
| 115 |
+
t,
|
| 116 |
+
nettoyer_caracteres=nettoyer_caracteres,
|
| 117 |
+
lower=lower,
|
| 118 |
+
remove_numbers=remove_numbers,
|
| 119 |
+
strip_fr_elisions=strip_fr_elisions,
|
| 120 |
+
)
|
| 121 |
+
for t in texts
|
| 122 |
+
]
|
| 123 |
+
|
| 124 |
+
write_tsv(args.output, ids, prepared)
|
| 125 |
+
|
| 126 |
+
if args.output_tokens:
|
| 127 |
+
rows: List[Tuple[str, str]] = []
|
| 128 |
+
for did, txt in zip(ids, prepared):
|
| 129 |
+
for tok in tokenize(txt, remove_numbers=remove_numbers, lower_tokens=True):
|
| 130 |
+
rows.append((did, tok))
|
| 131 |
+
write_tokens(args.output_tokens, rows)
|
| 132 |
+
|
| 133 |
+
return 0
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
if __name__ == "__main__":
|
| 137 |
+
raise SystemExit(main())
|
iramuteq-like/ui_options_iramuteq.R
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: iramuteq-like/ui_options_iramuteq.R définit les options UI
|
| 2 |
+
# spécifiques au mode IRaMuTeQ-like.
|
| 3 |
+
|
| 4 |
+
library(shiny)
|
| 5 |
+
|
| 6 |
+
ui_options_iramuteq <- function() {
|
| 7 |
+
tagList(
|
| 8 |
+
tags$div(class = "sidebar-section-title", "Paramètres CHD (IRaMuTeQ-like)"),
|
| 9 |
+
numericInput("k_iramuteq", "Nombre de classes terminales de la phase 1", value = 10, min = 2, step = 1),
|
| 10 |
+
radioButtons(
|
| 11 |
+
"iramuteq_mincl_mode",
|
| 12 |
+
"Nombre minimum d'UCE par classe terminale (mincl)",
|
| 13 |
+
choices = c("Automatique" = "auto", "Manuel" = "manuel"),
|
| 14 |
+
selected = "auto",
|
| 15 |
+
inline = FALSE
|
| 16 |
+
),
|
| 17 |
+
conditionalPanel(
|
| 18 |
+
condition = "input.iramuteq_mincl_mode == 'manuel'",
|
| 19 |
+
numericInput("iramuteq_mincl", "mincl (manuel)", value = 5, min = 1, step = 1)
|
| 20 |
+
),
|
| 21 |
+
radioButtons(
|
| 22 |
+
"iramuteq_classif_mode",
|
| 23 |
+
"Type de classification terminale",
|
| 24 |
+
choices = c("Simple" = "simple", "Double" = "double"),
|
| 25 |
+
selected = "simple",
|
| 26 |
+
inline = FALSE
|
| 27 |
+
),
|
| 28 |
+
selectInput(
|
| 29 |
+
"iramuteq_svd_method",
|
| 30 |
+
"Méthode SVD",
|
| 31 |
+
choices = c("irlba" = "irlba", "svdR" = "svdR"),
|
| 32 |
+
selected = "irlba"
|
| 33 |
+
),
|
| 34 |
+
checkboxInput("iramuteq_mode_patate", "Mode patate (moins précis, plus rapide)", value = FALSE)
|
| 35 |
+
)
|
| 36 |
+
}
|
iramuteq-like/wordcloud_iramuteq.R
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: générer des nuages de mots dédiés au mode IRaMuTeQ-like.
|
| 2 |
+
|
| 3 |
+
generer_wordclouds_iramuteq <- function(res_stats_df,
|
| 4 |
+
classes_uniques,
|
| 5 |
+
wordcloud_dir,
|
| 6 |
+
top_n = 20L,
|
| 7 |
+
filtrer_pvalue = FALSE,
|
| 8 |
+
max_p = 1) {
|
| 9 |
+
if (is.null(res_stats_df) || !is.data.frame(res_stats_df) || nrow(res_stats_df) == 0) {
|
| 10 |
+
return(invisible(NULL))
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
dir.create(wordcloud_dir, showWarnings = FALSE, recursive = TRUE)
|
| 14 |
+
|
| 15 |
+
top_n <- suppressWarnings(as.integer(top_n))
|
| 16 |
+
if (!is.finite(top_n) || is.na(top_n)) top_n <- 20L
|
| 17 |
+
top_n <- max(5L, top_n)
|
| 18 |
+
|
| 19 |
+
classe_col <- if ("Classe" %in% names(res_stats_df)) "Classe" else NULL
|
| 20 |
+
terme_col <- if ("Terme" %in% names(res_stats_df)) "Terme" else if ("forme" %in% names(res_stats_df)) "forme" else NULL
|
| 21 |
+
chi2_col <- if ("chi2" %in% names(res_stats_df)) "chi2" else NULL
|
| 22 |
+
p_col <- if ("p" %in% names(res_stats_df)) "p" else if ("p_value" %in% names(res_stats_df)) "p_value" else NULL
|
| 23 |
+
|
| 24 |
+
if (is.null(classe_col) || is.null(terme_col) || is.null(chi2_col)) {
|
| 25 |
+
return(invisible(NULL))
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
classes_num <- suppressWarnings(as.numeric(res_stats_df[[classe_col]]))
|
| 29 |
+
|
| 30 |
+
for (cl in classes_uniques) {
|
| 31 |
+
cl_num <- suppressWarnings(as.numeric(cl))
|
| 32 |
+
if (!is.finite(cl_num) || is.na(cl_num)) next
|
| 33 |
+
|
| 34 |
+
df_stats_cl <- res_stats_df[is.finite(classes_num) & !is.na(classes_num) & classes_num == cl_num, , drop = FALSE]
|
| 35 |
+
if (nrow(df_stats_cl) == 0) next
|
| 36 |
+
|
| 37 |
+
if (isTRUE(filtrer_pvalue) && !is.null(p_col) && is.finite(max_p) && !is.na(max_p)) {
|
| 38 |
+
p_vals <- suppressWarnings(as.numeric(df_stats_cl[[p_col]]))
|
| 39 |
+
df_stats_cl <- df_stats_cl[is.finite(p_vals) & !is.na(p_vals) & p_vals <= max_p, , drop = FALSE]
|
| 40 |
+
}
|
| 41 |
+
if (nrow(df_stats_cl) == 0) next
|
| 42 |
+
|
| 43 |
+
chi2_vals <- suppressWarnings(as.numeric(df_stats_cl[[chi2_col]]))
|
| 44 |
+
df_stats_cl <- df_stats_cl[is.finite(chi2_vals) & !is.na(chi2_vals), , drop = FALSE]
|
| 45 |
+
if (nrow(df_stats_cl) == 0) next
|
| 46 |
+
|
| 47 |
+
chi2_vals <- suppressWarnings(as.numeric(df_stats_cl[[chi2_col]]))
|
| 48 |
+
df_stats_cl <- df_stats_cl[order(-chi2_vals), , drop = FALSE]
|
| 49 |
+
df_stats_cl <- head(df_stats_cl, top_n)
|
| 50 |
+
|
| 51 |
+
wc_png <- file.path(wordcloud_dir, paste0("cluster_", cl, "_wordcloud.png"))
|
| 52 |
+
try({
|
| 53 |
+
png(wc_png, width = 800, height = 600)
|
| 54 |
+
chi2_vals <- suppressWarnings(as.numeric(df_stats_cl[[chi2_col]]))
|
| 55 |
+
suppressWarnings(wordcloud::wordcloud(
|
| 56 |
+
words = as.character(df_stats_cl[[terme_col]]),
|
| 57 |
+
freq = pmax(chi2_vals, 0),
|
| 58 |
+
scale = c(8, 0.8),
|
| 59 |
+
min.freq = 0,
|
| 60 |
+
random.order = FALSE,
|
| 61 |
+
max.words = nrow(df_stats_cl),
|
| 62 |
+
colors = RColorBrewer::brewer.pal(8, "Dark2")
|
| 63 |
+
))
|
| 64 |
+
dev.off()
|
| 65 |
+
}, silent = TRUE)
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
invisible(NULL)
|
| 69 |
+
}
|
penguins.csv
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Species,Island,Bill Length (mm),Bill Depth (mm),Flipper Length (mm),Body Mass (g),Sex,Year
|
| 2 |
+
Adelie,Torgersen,39.1,18.7,181,3750,male,2007
|
| 3 |
+
Adelie,Torgersen,39.5,17.4,186,3800,female,2007
|
| 4 |
+
Adelie,Torgersen,40.3,18,195,3250,female,2007
|
| 5 |
+
Adelie,Torgersen,NA,NA,NA,NA,NA,2007
|
| 6 |
+
Adelie,Torgersen,36.7,19.3,193,3450,female,2007
|
| 7 |
+
Adelie,Torgersen,39.3,20.6,190,3650,male,2007
|
| 8 |
+
Adelie,Torgersen,38.9,17.8,181,3625,female,2007
|
| 9 |
+
Adelie,Torgersen,39.2,19.6,195,4675,male,2007
|
| 10 |
+
Adelie,Torgersen,34.1,18.1,193,3475,NA,2007
|
| 11 |
+
Adelie,Torgersen,42,20.2,190,4250,NA,2007
|
| 12 |
+
Adelie,Torgersen,37.8,17.1,186,3300,NA,2007
|
| 13 |
+
Adelie,Torgersen,37.8,17.3,180,3700,NA,2007
|
| 14 |
+
Adelie,Torgersen,41.1,17.6,182,3200,female,2007
|
| 15 |
+
Adelie,Torgersen,38.6,21.2,191,3800,male,2007
|
| 16 |
+
Adelie,Torgersen,34.6,21.1,198,4400,male,2007
|
| 17 |
+
Adelie,Torgersen,36.6,17.8,185,3700,female,2007
|
| 18 |
+
Adelie,Torgersen,38.7,19,195,3450,female,2007
|
| 19 |
+
Adelie,Torgersen,42.5,20.7,197,4500,male,2007
|
| 20 |
+
Adelie,Torgersen,34.4,18.4,184,3325,female,2007
|
| 21 |
+
Adelie,Torgersen,46,21.5,194,4200,male,2007
|
| 22 |
+
Adelie,Biscoe,37.8,18.3,174,3400,female,2007
|
| 23 |
+
Adelie,Biscoe,37.7,18.7,180,3600,male,2007
|
| 24 |
+
Adelie,Biscoe,35.9,19.2,189,3800,female,2007
|
| 25 |
+
Adelie,Biscoe,38.2,18.1,185,3950,male,2007
|
| 26 |
+
Adelie,Biscoe,38.8,17.2,180,3800,male,2007
|
| 27 |
+
Adelie,Biscoe,35.3,18.9,187,3800,female,2007
|
| 28 |
+
Adelie,Biscoe,40.6,18.6,183,3550,male,2007
|
| 29 |
+
Adelie,Biscoe,40.5,17.9,187,3200,female,2007
|
| 30 |
+
Adelie,Biscoe,37.9,18.6,172,3150,female,2007
|
| 31 |
+
Adelie,Biscoe,40.5,18.9,180,3950,male,2007
|
| 32 |
+
Adelie,Dream,39.5,16.7,178,3250,female,2007
|
| 33 |
+
Adelie,Dream,37.2,18.1,178,3900,male,2007
|
| 34 |
+
Adelie,Dream,39.5,17.8,188,3300,female,2007
|
| 35 |
+
Adelie,Dream,40.9,18.9,184,3900,male,2007
|
| 36 |
+
Adelie,Dream,36.4,17,195,3325,female,2007
|
| 37 |
+
Adelie,Dream,39.2,21.1,196,4150,male,2007
|
| 38 |
+
Adelie,Dream,38.8,20,190,3950,male,2007
|
| 39 |
+
Adelie,Dream,42.2,18.5,180,3550,female,2007
|
| 40 |
+
Adelie,Dream,37.6,19.3,181,3300,female,2007
|
| 41 |
+
Adelie,Dream,39.8,19.1,184,4650,male,2007
|
| 42 |
+
Adelie,Dream,36.5,18,182,3150,female,2007
|
| 43 |
+
Adelie,Dream,40.8,18.4,195,3900,male,2007
|
| 44 |
+
Adelie,Dream,36,18.5,186,3100,female,2007
|
| 45 |
+
Adelie,Dream,44.1,19.7,196,4400,male,2007
|
| 46 |
+
Adelie,Dream,37,16.9,185,3000,female,2007
|
| 47 |
+
Adelie,Dream,39.6,18.8,190,4600,male,2007
|
| 48 |
+
Adelie,Dream,41.1,19,182,3425,male,2007
|
| 49 |
+
Adelie,Dream,37.5,18.9,179,2975,NA,2007
|
| 50 |
+
Adelie,Dream,36,17.9,190,3450,female,2007
|
| 51 |
+
Adelie,Dream,42.3,21.2,191,4150,male,2007
|
| 52 |
+
Adelie,Biscoe,39.6,17.7,186,3500,female,2008
|
| 53 |
+
Adelie,Biscoe,40.1,18.9,188,4300,male,2008
|
| 54 |
+
Adelie,Biscoe,35,17.9,190,3450,female,2008
|
| 55 |
+
Adelie,Biscoe,42,19.5,200,4050,male,2008
|
| 56 |
+
Adelie,Biscoe,34.5,18.1,187,2900,female,2008
|
| 57 |
+
Adelie,Biscoe,41.4,18.6,191,3700,male,2008
|
| 58 |
+
Adelie,Biscoe,39,17.5,186,3550,female,2008
|
| 59 |
+
Adelie,Biscoe,40.6,18.8,193,3800,male,2008
|
| 60 |
+
Adelie,Biscoe,36.5,16.6,181,2850,female,2008
|
| 61 |
+
Adelie,Biscoe,37.6,19.1,194,3750,male,2008
|
| 62 |
+
Adelie,Biscoe,35.7,16.9,185,3150,female,2008
|
| 63 |
+
Adelie,Biscoe,41.3,21.1,195,4400,male,2008
|
| 64 |
+
Adelie,Biscoe,37.6,17,185,3600,female,2008
|
| 65 |
+
Adelie,Biscoe,41.1,18.2,192,4050,male,2008
|
| 66 |
+
Adelie,Biscoe,36.4,17.1,184,2850,female,2008
|
| 67 |
+
Adelie,Biscoe,41.6,18,192,3950,male,2008
|
| 68 |
+
Adelie,Biscoe,35.5,16.2,195,3350,female,2008
|
| 69 |
+
Adelie,Biscoe,41.1,19.1,188,4100,male,2008
|
| 70 |
+
Adelie,Torgersen,35.9,16.6,190,3050,female,2008
|
| 71 |
+
Adelie,Torgersen,41.8,19.4,198,4450,male,2008
|
| 72 |
+
Adelie,Torgersen,33.5,19,190,3600,female,2008
|
| 73 |
+
Adelie,Torgersen,39.7,18.4,190,3900,male,2008
|
| 74 |
+
Adelie,Torgersen,39.6,17.2,196,3550,female,2008
|
| 75 |
+
Adelie,Torgersen,45.8,18.9,197,4150,male,2008
|
| 76 |
+
Adelie,Torgersen,35.5,17.5,190,3700,female,2008
|
| 77 |
+
Adelie,Torgersen,42.8,18.5,195,4250,male,2008
|
| 78 |
+
Adelie,Torgersen,40.9,16.8,191,3700,female,2008
|
| 79 |
+
Adelie,Torgersen,37.2,19.4,184,3900,male,2008
|
| 80 |
+
Adelie,Torgersen,36.2,16.1,187,3550,female,2008
|
| 81 |
+
Adelie,Torgersen,42.1,19.1,195,4000,male,2008
|
| 82 |
+
Adelie,Torgersen,34.6,17.2,189,3200,female,2008
|
| 83 |
+
Adelie,Torgersen,42.9,17.6,196,4700,male,2008
|
| 84 |
+
Adelie,Torgersen,36.7,18.8,187,3800,female,2008
|
| 85 |
+
Adelie,Torgersen,35.1,19.4,193,4200,male,2008
|
| 86 |
+
Adelie,Dream,37.3,17.8,191,3350,female,2008
|
| 87 |
+
Adelie,Dream,41.3,20.3,194,3550,male,2008
|
| 88 |
+
Adelie,Dream,36.3,19.5,190,3800,male,2008
|
| 89 |
+
Adelie,Dream,36.9,18.6,189,3500,female,2008
|
| 90 |
+
Adelie,Dream,38.3,19.2,189,3950,male,2008
|
| 91 |
+
Adelie,Dream,38.9,18.8,190,3600,female,2008
|
| 92 |
+
Adelie,Dream,35.7,18,202,3550,female,2008
|
| 93 |
+
Adelie,Dream,41.1,18.1,205,4300,male,2008
|
| 94 |
+
Adelie,Dream,34,17.1,185,3400,female,2008
|
| 95 |
+
Adelie,Dream,39.6,18.1,186,4450,male,2008
|
| 96 |
+
Adelie,Dream,36.2,17.3,187,3300,female,2008
|
| 97 |
+
Adelie,Dream,40.8,18.9,208,4300,male,2008
|
| 98 |
+
Adelie,Dream,38.1,18.6,190,3700,female,2008
|
| 99 |
+
Adelie,Dream,40.3,18.5,196,4350,male,2008
|
| 100 |
+
Adelie,Dream,33.1,16.1,178,2900,female,2008
|
| 101 |
+
Adelie,Dream,43.2,18.5,192,4100,male,2008
|
| 102 |
+
Adelie,Biscoe,35,17.9,192,3725,female,2009
|
| 103 |
+
Adelie,Biscoe,41,20,203,4725,male,2009
|
| 104 |
+
Adelie,Biscoe,37.7,16,183,3075,female,2009
|
| 105 |
+
Adelie,Biscoe,37.8,20,190,4250,male,2009
|
| 106 |
+
Adelie,Biscoe,37.9,18.6,193,2925,female,2009
|
| 107 |
+
Adelie,Biscoe,39.7,18.9,184,3550,male,2009
|
| 108 |
+
Adelie,Biscoe,38.6,17.2,199,3750,female,2009
|
| 109 |
+
Adelie,Biscoe,38.2,20,190,3900,male,2009
|
| 110 |
+
Adelie,Biscoe,38.1,17,181,3175,female,2009
|
| 111 |
+
Adelie,Biscoe,43.2,19,197,4775,male,2009
|
| 112 |
+
Adelie,Biscoe,38.1,16.5,198,3825,female,2009
|
| 113 |
+
Adelie,Biscoe,45.6,20.3,191,4600,male,2009
|
| 114 |
+
Adelie,Biscoe,39.7,17.7,193,3200,female,2009
|
| 115 |
+
Adelie,Biscoe,42.2,19.5,197,4275,male,2009
|
| 116 |
+
Adelie,Biscoe,39.6,20.7,191,3900,female,2009
|
| 117 |
+
Adelie,Biscoe,42.7,18.3,196,4075,male,2009
|
| 118 |
+
Adelie,Torgersen,38.6,17,188,2900,female,2009
|
| 119 |
+
Adelie,Torgersen,37.3,20.5,199,3775,male,2009
|
| 120 |
+
Adelie,Torgersen,35.7,17,189,3350,female,2009
|
| 121 |
+
Adelie,Torgersen,41.1,18.6,189,3325,male,2009
|
| 122 |
+
Adelie,Torgersen,36.2,17.2,187,3150,female,2009
|
| 123 |
+
Adelie,Torgersen,37.7,19.8,198,3500,male,2009
|
| 124 |
+
Adelie,Torgersen,40.2,17,176,3450,female,2009
|
| 125 |
+
Adelie,Torgersen,41.4,18.5,202,3875,male,2009
|
| 126 |
+
Adelie,Torgersen,35.2,15.9,186,3050,female,2009
|
| 127 |
+
Adelie,Torgersen,40.6,19,199,4000,male,2009
|
| 128 |
+
Adelie,Torgersen,38.8,17.6,191,3275,female,2009
|
| 129 |
+
Adelie,Torgersen,41.5,18.3,195,4300,male,2009
|
| 130 |
+
Adelie,Torgersen,39,17.1,191,3050,female,2009
|
| 131 |
+
Adelie,Torgersen,44.1,18,210,4000,male,2009
|
| 132 |
+
Adelie,Torgersen,38.5,17.9,190,3325,female,2009
|
| 133 |
+
Adelie,Torgersen,43.1,19.2,197,3500,male,2009
|
| 134 |
+
Adelie,Dream,36.8,18.5,193,3500,female,2009
|
| 135 |
+
Adelie,Dream,37.5,18.5,199,4475,male,2009
|
| 136 |
+
Adelie,Dream,38.1,17.6,187,3425,female,2009
|
| 137 |
+
Adelie,Dream,41.1,17.5,190,3900,male,2009
|
| 138 |
+
Adelie,Dream,35.6,17.5,191,3175,female,2009
|
| 139 |
+
Adelie,Dream,40.2,20.1,200,3975,male,2009
|
| 140 |
+
Adelie,Dream,37,16.5,185,3400,female,2009
|
| 141 |
+
Adelie,Dream,39.7,17.9,193,4250,male,2009
|
| 142 |
+
Adelie,Dream,40.2,17.1,193,3400,female,2009
|
| 143 |
+
Adelie,Dream,40.6,17.2,187,3475,male,2009
|
| 144 |
+
Adelie,Dream,32.1,15.5,188,3050,female,2009
|
| 145 |
+
Adelie,Dream,40.7,17,190,3725,male,2009
|
| 146 |
+
Adelie,Dream,37.3,16.8,192,3000,female,2009
|
| 147 |
+
Adelie,Dream,39,18.7,185,3650,male,2009
|
| 148 |
+
Adelie,Dream,39.2,18.6,190,4250,male,2009
|
| 149 |
+
Adelie,Dream,36.6,18.4,184,3475,female,2009
|
| 150 |
+
Adelie,Dream,36,17.8,195,3450,female,2009
|
| 151 |
+
Adelie,Dream,37.8,18.1,193,3750,male,2009
|
| 152 |
+
Adelie,Dream,36,17.1,187,3700,female,2009
|
| 153 |
+
Adelie,Dream,41.5,18.5,201,4000,male,2009
|
| 154 |
+
Gentoo,Biscoe,46.1,13.2,211,4500,female,2007
|
| 155 |
+
Gentoo,Biscoe,50,16.3,230,5700,male,2007
|
| 156 |
+
Gentoo,Biscoe,48.7,14.1,210,4450,female,2007
|
| 157 |
+
Gentoo,Biscoe,50,15.2,218,5700,male,2007
|
| 158 |
+
Gentoo,Biscoe,47.6,14.5,215,5400,male,2007
|
| 159 |
+
Gentoo,Biscoe,46.5,13.5,210,4550,female,2007
|
| 160 |
+
Gentoo,Biscoe,45.4,14.6,211,4800,female,2007
|
| 161 |
+
Gentoo,Biscoe,46.7,15.3,219,5200,male,2007
|
| 162 |
+
Gentoo,Biscoe,43.3,13.4,209,4400,female,2007
|
| 163 |
+
Gentoo,Biscoe,46.8,15.4,215,5150,male,2007
|
| 164 |
+
Gentoo,Biscoe,40.9,13.7,214,4650,female,2007
|
| 165 |
+
Gentoo,Biscoe,49,16.1,216,5550,male,2007
|
| 166 |
+
Gentoo,Biscoe,45.5,13.7,214,4650,female,2007
|
| 167 |
+
Gentoo,Biscoe,48.4,14.6,213,5850,male,2007
|
| 168 |
+
Gentoo,Biscoe,45.8,14.6,210,4200,female,2007
|
| 169 |
+
Gentoo,Biscoe,49.3,15.7,217,5850,male,2007
|
| 170 |
+
Gentoo,Biscoe,42,13.5,210,4150,female,2007
|
| 171 |
+
Gentoo,Biscoe,49.2,15.2,221,6300,male,2007
|
| 172 |
+
Gentoo,Biscoe,46.2,14.5,209,4800,female,2007
|
| 173 |
+
Gentoo,Biscoe,48.7,15.1,222,5350,male,2007
|
| 174 |
+
Gentoo,Biscoe,50.2,14.3,218,5700,male,2007
|
| 175 |
+
Gentoo,Biscoe,45.1,14.5,215,5000,female,2007
|
| 176 |
+
Gentoo,Biscoe,46.5,14.5,213,4400,female,2007
|
| 177 |
+
Gentoo,Biscoe,46.3,15.8,215,5050,male,2007
|
| 178 |
+
Gentoo,Biscoe,42.9,13.1,215,5000,female,2007
|
| 179 |
+
Gentoo,Biscoe,46.1,15.1,215,5100,male,2007
|
| 180 |
+
Gentoo,Biscoe,44.5,14.3,216,4100,NA,2007
|
| 181 |
+
Gentoo,Biscoe,47.8,15,215,5650,male,2007
|
| 182 |
+
Gentoo,Biscoe,48.2,14.3,210,4600,female,2007
|
| 183 |
+
Gentoo,Biscoe,50,15.3,220,5550,male,2007
|
| 184 |
+
Gentoo,Biscoe,47.3,15.3,222,5250,male,2007
|
| 185 |
+
Gentoo,Biscoe,42.8,14.2,209,4700,female,2007
|
| 186 |
+
Gentoo,Biscoe,45.1,14.5,207,5050,female,2007
|
| 187 |
+
Gentoo,Biscoe,59.6,17,230,6050,male,2007
|
| 188 |
+
Gentoo,Biscoe,49.1,14.8,220,5150,female,2008
|
| 189 |
+
Gentoo,Biscoe,48.4,16.3,220,5400,male,2008
|
| 190 |
+
Gentoo,Biscoe,42.6,13.7,213,4950,female,2008
|
| 191 |
+
Gentoo,Biscoe,44.4,17.3,219,5250,male,2008
|
| 192 |
+
Gentoo,Biscoe,44,13.6,208,4350,female,2008
|
| 193 |
+
Gentoo,Biscoe,48.7,15.7,208,5350,male,2008
|
| 194 |
+
Gentoo,Biscoe,42.7,13.7,208,3950,female,2008
|
| 195 |
+
Gentoo,Biscoe,49.6,16,225,5700,male,2008
|
| 196 |
+
Gentoo,Biscoe,45.3,13.7,210,4300,female,2008
|
| 197 |
+
Gentoo,Biscoe,49.6,15,216,4750,male,2008
|
| 198 |
+
Gentoo,Biscoe,50.5,15.9,222,5550,male,2008
|
| 199 |
+
Gentoo,Biscoe,43.6,13.9,217,4900,female,2008
|
| 200 |
+
Gentoo,Biscoe,45.5,13.9,210,4200,female,2008
|
| 201 |
+
Gentoo,Biscoe,50.5,15.9,225,5400,male,2008
|
| 202 |
+
Gentoo,Biscoe,44.9,13.3,213,5100,female,2008
|
| 203 |
+
Gentoo,Biscoe,45.2,15.8,215,5300,male,2008
|
| 204 |
+
Gentoo,Biscoe,46.6,14.2,210,4850,female,2008
|
| 205 |
+
Gentoo,Biscoe,48.5,14.1,220,5300,male,2008
|
| 206 |
+
Gentoo,Biscoe,45.1,14.4,210,4400,female,2008
|
| 207 |
+
Gentoo,Biscoe,50.1,15,225,5000,male,2008
|
| 208 |
+
Gentoo,Biscoe,46.5,14.4,217,4900,female,2008
|
| 209 |
+
Gentoo,Biscoe,45,15.4,220,5050,male,2008
|
| 210 |
+
Gentoo,Biscoe,43.8,13.9,208,4300,female,2008
|
| 211 |
+
Gentoo,Biscoe,45.5,15,220,5000,male,2008
|
| 212 |
+
Gentoo,Biscoe,43.2,14.5,208,4450,female,2008
|
| 213 |
+
Gentoo,Biscoe,50.4,15.3,224,5550,male,2008
|
| 214 |
+
Gentoo,Biscoe,45.3,13.8,208,4200,female,2008
|
| 215 |
+
Gentoo,Biscoe,46.2,14.9,221,5300,male,2008
|
| 216 |
+
Gentoo,Biscoe,45.7,13.9,214,4400,female,2008
|
| 217 |
+
Gentoo,Biscoe,54.3,15.7,231,5650,male,2008
|
| 218 |
+
Gentoo,Biscoe,45.8,14.2,219,4700,female,2008
|
| 219 |
+
Gentoo,Biscoe,49.8,16.8,230,5700,male,2008
|
| 220 |
+
Gentoo,Biscoe,46.2,14.4,214,4650,NA,2008
|
| 221 |
+
Gentoo,Biscoe,49.5,16.2,229,5800,male,2008
|
| 222 |
+
Gentoo,Biscoe,43.5,14.2,220,4700,female,2008
|
| 223 |
+
Gentoo,Biscoe,50.7,15,223,5550,male,2008
|
| 224 |
+
Gentoo,Biscoe,47.7,15,216,4750,female,2008
|
| 225 |
+
Gentoo,Biscoe,46.4,15.6,221,5000,male,2008
|
| 226 |
+
Gentoo,Biscoe,48.2,15.6,221,5100,male,2008
|
| 227 |
+
Gentoo,Biscoe,46.5,14.8,217,5200,female,2008
|
| 228 |
+
Gentoo,Biscoe,46.4,15,216,4700,female,2008
|
| 229 |
+
Gentoo,Biscoe,48.6,16,230,5800,male,2008
|
| 230 |
+
Gentoo,Biscoe,47.5,14.2,209,4600,female,2008
|
| 231 |
+
Gentoo,Biscoe,51.1,16.3,220,6000,male,2008
|
| 232 |
+
Gentoo,Biscoe,45.2,13.8,215,4750,female,2008
|
| 233 |
+
Gentoo,Biscoe,45.2,16.4,223,5950,male,2008
|
| 234 |
+
Gentoo,Biscoe,49.1,14.5,212,4625,female,2009
|
| 235 |
+
Gentoo,Biscoe,52.5,15.6,221,5450,male,2009
|
| 236 |
+
Gentoo,Biscoe,47.4,14.6,212,4725,female,2009
|
| 237 |
+
Gentoo,Biscoe,50,15.9,224,5350,male,2009
|
| 238 |
+
Gentoo,Biscoe,44.9,13.8,212,4750,female,2009
|
| 239 |
+
Gentoo,Biscoe,50.8,17.3,228,5600,male,2009
|
| 240 |
+
Gentoo,Biscoe,43.4,14.4,218,4600,female,2009
|
| 241 |
+
Gentoo,Biscoe,51.3,14.2,218,5300,male,2009
|
| 242 |
+
Gentoo,Biscoe,47.5,14,212,4875,female,2009
|
| 243 |
+
Gentoo,Biscoe,52.1,17,230,5550,male,2009
|
| 244 |
+
Gentoo,Biscoe,47.5,15,218,4950,female,2009
|
| 245 |
+
Gentoo,Biscoe,52.2,17.1,228,5400,male,2009
|
| 246 |
+
Gentoo,Biscoe,45.5,14.5,212,4750,female,2009
|
| 247 |
+
Gentoo,Biscoe,49.5,16.1,224,5650,male,2009
|
| 248 |
+
Gentoo,Biscoe,44.5,14.7,214,4850,female,2009
|
| 249 |
+
Gentoo,Biscoe,50.8,15.7,226,5200,male,2009
|
| 250 |
+
Gentoo,Biscoe,49.4,15.8,216,4925,male,2009
|
| 251 |
+
Gentoo,Biscoe,46.9,14.6,222,4875,female,2009
|
| 252 |
+
Gentoo,Biscoe,48.4,14.4,203,4625,female,2009
|
| 253 |
+
Gentoo,Biscoe,51.1,16.5,225,5250,male,2009
|
| 254 |
+
Gentoo,Biscoe,48.5,15,219,4850,female,2009
|
| 255 |
+
Gentoo,Biscoe,55.9,17,228,5600,male,2009
|
| 256 |
+
Gentoo,Biscoe,47.2,15.5,215,4975,female,2009
|
| 257 |
+
Gentoo,Biscoe,49.1,15,228,5500,male,2009
|
| 258 |
+
Gentoo,Biscoe,47.3,13.8,216,4725,NA,2009
|
| 259 |
+
Gentoo,Biscoe,46.8,16.1,215,5500,male,2009
|
| 260 |
+
Gentoo,Biscoe,41.7,14.7,210,4700,female,2009
|
| 261 |
+
Gentoo,Biscoe,53.4,15.8,219,5500,male,2009
|
| 262 |
+
Gentoo,Biscoe,43.3,14,208,4575,female,2009
|
| 263 |
+
Gentoo,Biscoe,48.1,15.1,209,5500,male,2009
|
| 264 |
+
Gentoo,Biscoe,50.5,15.2,216,5000,female,2009
|
| 265 |
+
Gentoo,Biscoe,49.8,15.9,229,5950,male,2009
|
| 266 |
+
Gentoo,Biscoe,43.5,15.2,213,4650,female,2009
|
| 267 |
+
Gentoo,Biscoe,51.5,16.3,230,5500,male,2009
|
| 268 |
+
Gentoo,Biscoe,46.2,14.1,217,4375,female,2009
|
| 269 |
+
Gentoo,Biscoe,55.1,16,230,5850,male,2009
|
| 270 |
+
Gentoo,Biscoe,44.5,15.7,217,4875,NA,2009
|
| 271 |
+
Gentoo,Biscoe,48.8,16.2,222,6000,male,2009
|
| 272 |
+
Gentoo,Biscoe,47.2,13.7,214,4925,female,2009
|
| 273 |
+
Gentoo,Biscoe,NA,NA,NA,NA,NA,2009
|
| 274 |
+
Gentoo,Biscoe,46.8,14.3,215,4850,female,2009
|
| 275 |
+
Gentoo,Biscoe,50.4,15.7,222,5750,male,2009
|
| 276 |
+
Gentoo,Biscoe,45.2,14.8,212,5200,female,2009
|
| 277 |
+
Gentoo,Biscoe,49.9,16.1,213,5400,male,2009
|
| 278 |
+
Chinstrap,Dream,46.5,17.9,192,3500,female,2007
|
| 279 |
+
Chinstrap,Dream,50,19.5,196,3900,male,2007
|
| 280 |
+
Chinstrap,Dream,51.3,19.2,193,3650,male,2007
|
| 281 |
+
Chinstrap,Dream,45.4,18.7,188,3525,female,2007
|
| 282 |
+
Chinstrap,Dream,52.7,19.8,197,3725,male,2007
|
| 283 |
+
Chinstrap,Dream,45.2,17.8,198,3950,female,2007
|
| 284 |
+
Chinstrap,Dream,46.1,18.2,178,3250,female,2007
|
| 285 |
+
Chinstrap,Dream,51.3,18.2,197,3750,male,2007
|
| 286 |
+
Chinstrap,Dream,46,18.9,195,4150,female,2007
|
| 287 |
+
Chinstrap,Dream,51.3,19.9,198,3700,male,2007
|
| 288 |
+
Chinstrap,Dream,46.6,17.8,193,3800,female,2007
|
| 289 |
+
Chinstrap,Dream,51.7,20.3,194,3775,male,2007
|
| 290 |
+
Chinstrap,Dream,47,17.3,185,3700,female,2007
|
| 291 |
+
Chinstrap,Dream,52,18.1,201,4050,male,2007
|
| 292 |
+
Chinstrap,Dream,45.9,17.1,190,3575,female,2007
|
| 293 |
+
Chinstrap,Dream,50.5,19.6,201,4050,male,2007
|
| 294 |
+
Chinstrap,Dream,50.3,20,197,3300,male,2007
|
| 295 |
+
Chinstrap,Dream,58,17.8,181,3700,female,2007
|
| 296 |
+
Chinstrap,Dream,46.4,18.6,190,3450,female,2007
|
| 297 |
+
Chinstrap,Dream,49.2,18.2,195,4400,male,2007
|
| 298 |
+
Chinstrap,Dream,42.4,17.3,181,3600,female,2007
|
| 299 |
+
Chinstrap,Dream,48.5,17.5,191,3400,male,2007
|
| 300 |
+
Chinstrap,Dream,43.2,16.6,187,2900,female,2007
|
| 301 |
+
Chinstrap,Dream,50.6,19.4,193,3800,male,2007
|
| 302 |
+
Chinstrap,Dream,46.7,17.9,195,3300,female,2007
|
| 303 |
+
Chinstrap,Dream,52,19,197,4150,male,2007
|
| 304 |
+
Chinstrap,Dream,50.5,18.4,200,3400,female,2008
|
| 305 |
+
Chinstrap,Dream,49.5,19,200,3800,male,2008
|
| 306 |
+
Chinstrap,Dream,46.4,17.8,191,3700,female,2008
|
| 307 |
+
Chinstrap,Dream,52.8,20,205,4550,male,2008
|
| 308 |
+
Chinstrap,Dream,40.9,16.6,187,3200,female,2008
|
| 309 |
+
Chinstrap,Dream,54.2,20.8,201,4300,male,2008
|
| 310 |
+
Chinstrap,Dream,42.5,16.7,187,3350,female,2008
|
| 311 |
+
Chinstrap,Dream,51,18.8,203,4100,male,2008
|
| 312 |
+
Chinstrap,Dream,49.7,18.6,195,3600,male,2008
|
| 313 |
+
Chinstrap,Dream,47.5,16.8,199,3900,female,2008
|
| 314 |
+
Chinstrap,Dream,47.6,18.3,195,3850,female,2008
|
| 315 |
+
Chinstrap,Dream,52,20.7,210,4800,male,2008
|
| 316 |
+
Chinstrap,Dream,46.9,16.6,192,2700,female,2008
|
| 317 |
+
Chinstrap,Dream,53.5,19.9,205,4500,male,2008
|
| 318 |
+
Chinstrap,Dream,49,19.5,210,3950,male,2008
|
| 319 |
+
Chinstrap,Dream,46.2,17.5,187,3650,female,2008
|
| 320 |
+
Chinstrap,Dream,50.9,19.1,196,3550,male,2008
|
| 321 |
+
Chinstrap,Dream,45.5,17,196,3500,female,2008
|
| 322 |
+
Chinstrap,Dream,50.9,17.9,196,3675,female,2009
|
| 323 |
+
Chinstrap,Dream,50.8,18.5,201,4450,male,2009
|
| 324 |
+
Chinstrap,Dream,50.1,17.9,190,3400,female,2009
|
| 325 |
+
Chinstrap,Dream,49,19.6,212,4300,male,2009
|
| 326 |
+
Chinstrap,Dream,51.5,18.7,187,3250,male,2009
|
| 327 |
+
Chinstrap,Dream,49.8,17.3,198,3675,female,2009
|
| 328 |
+
Chinstrap,Dream,48.1,16.4,199,3325,female,2009
|
| 329 |
+
Chinstrap,Dream,51.4,19,201,3950,male,2009
|
| 330 |
+
Chinstrap,Dream,45.7,17.3,193,3600,female,2009
|
| 331 |
+
Chinstrap,Dream,50.7,19.7,203,4050,male,2009
|
| 332 |
+
Chinstrap,Dream,42.5,17.3,187,3350,female,2009
|
| 333 |
+
Chinstrap,Dream,52.2,18.8,197,3450,male,2009
|
| 334 |
+
Chinstrap,Dream,45.2,16.6,191,3250,female,2009
|
| 335 |
+
Chinstrap,Dream,49.3,19.9,203,4050,male,2009
|
| 336 |
+
Chinstrap,Dream,50.2,18.8,202,3800,male,2009
|
| 337 |
+
Chinstrap,Dream,45.6,19.4,194,3525,female,2009
|
| 338 |
+
Chinstrap,Dream,51.9,19.5,206,3950,male,2009
|
| 339 |
+
Chinstrap,Dream,46.8,16.5,189,3650,female,2009
|
| 340 |
+
Chinstrap,Dream,45.7,17,195,3650,female,2009
|
| 341 |
+
Chinstrap,Dream,55.8,19.8,207,4000,male,2009
|
| 342 |
+
Chinstrap,Dream,43.5,18.1,202,3400,female,2009
|
| 343 |
+
Chinstrap,Dream,49.6,18.2,193,3775,male,2009
|
| 344 |
+
Chinstrap,Dream,50.8,19,210,4100,male,2009
|
| 345 |
+
Chinstrap,Dream,50.2,18.7,198,3775,female,2009
|
ui.R
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rôle du fichier: ui.R porte une partie du pipeline d'analyse Rainette.
|
| 2 |
+
# ui.R
|
| 3 |
+
|
| 4 |
+
library(shiny)
|
| 5 |
+
library(htmltools)
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
if (!exists("ui_options_iramuteq", mode = "function", inherits = TRUE)) {
|
| 9 |
+
app_dir <- tryCatch(shiny::getShinyOption("appDir"), error = function(e) NULL)
|
| 10 |
+
if (is.null(app_dir) || !nzchar(app_dir)) app_dir <- getwd()
|
| 11 |
+
chemin_options_iramuteq <- file.path(app_dir, "iramuteq-like", "ui_options_iramuteq.R")
|
| 12 |
+
|
| 13 |
+
if (file.exists(chemin_options_iramuteq)) {
|
| 14 |
+
source(chemin_options_iramuteq, encoding = "UTF-8", local = TRUE)
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
if (!exists("ui_resultats_chd_iramuteq", mode = "function", inherits = TRUE)) {
|
| 20 |
+
app_dir <- tryCatch(shiny::getShinyOption("appDir"), error = function(e) NULL)
|
| 21 |
+
if (is.null(app_dir) || !nzchar(app_dir)) app_dir <- getwd()
|
| 22 |
+
chemin_affichage_iramuteq <- file.path(app_dir, "iramuteq-like", "affichage_iramuteq-like.R")
|
| 23 |
+
|
| 24 |
+
if (file.exists(chemin_affichage_iramuteq)) {
|
| 25 |
+
source(chemin_affichage_iramuteq, encoding = "UTF-8", local = TRUE)
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
if (!exists("ui_aide_huggingface", mode = "function")) {
|
| 30 |
+
if (file.exists("help.md")) {
|
| 31 |
+
ui_aide_huggingface <- function() {
|
| 32 |
+
tagList(
|
| 33 |
+
tags$h2("Aide"),
|
| 34 |
+
includeMarkdown("help/help.md")
|
| 35 |
+
)
|
| 36 |
+
}
|
| 37 |
+
} else {
|
| 38 |
+
ui_aide_huggingface <- function() {
|
| 39 |
+
tagList(
|
| 40 |
+
tags$h2("Aide"),
|
| 41 |
+
tags$p("Le fichier help.md est introuvable. Ajoute help.md à la racine du projet.")
|
| 42 |
+
)
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
if (!exists("REGEX_CARACTERES_A_SUPPRIMER", inherits = TRUE)) {
|
| 48 |
+
app_dir <- tryCatch(shiny::getShinyOption("appDir"), error = function(e) NULL)
|
| 49 |
+
if (is.null(app_dir) || !nzchar(app_dir)) app_dir <- getwd()
|
| 50 |
+
chemin_nettoyage <- file.path(app_dir, "rainette", "nettoyage_rainette.R")
|
| 51 |
+
|
| 52 |
+
if (file.exists(chemin_nettoyage)) {
|
| 53 |
+
source(chemin_nettoyage, encoding = "UTF-8", local = TRUE)
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
if (!exists("REGEX_CARACTERES_A_SUPPRIMER", inherits = TRUE)) {
|
| 58 |
+
# Fallback explicite : évite d'afficher un message d'erreur permanent dans l'UI
|
| 59 |
+
# quand le fichier rainette/nettoyage_rainette.R n'a pas pu être sourcé dans cet environnement.
|
| 60 |
+
REGEX_CARACTERES_AUTORISES <- "a-zA-Z0-9àÀâÂäÄáÁåÅãéÉèÈêÊëËìÌîÎïÏíÍóÓòÒôÔöÖõÕøØùÙûÛüÜúÚçÇßœŒ’ñÑ\\.:,;!\\?'"
|
| 61 |
+
REGEX_CARACTERES_A_SUPPRIMER <- paste0("[^", REGEX_CARACTERES_AUTORISES, "]")
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
ui <- fluidPage(
|
| 65 |
+
tags$head(
|
| 66 |
+
tags$style(HTML("
|
| 67 |
+
#shiny-modal .modal-dialog {
|
| 68 |
+
width: 96vw !important;
|
| 69 |
+
max-width: 96vw !important;
|
| 70 |
+
}
|
| 71 |
+
#shiny-modal .modal-body {
|
| 72 |
+
max-height: 88vh !important;
|
| 73 |
+
overflow-y: auto !important;
|
| 74 |
+
}
|
| 75 |
+
.sidebar-section-title {
|
| 76 |
+
font-weight: 700;
|
| 77 |
+
font-size: 18px !important;
|
| 78 |
+
color: #1e5aa8 !important;
|
| 79 |
+
margin-top: 12px;
|
| 80 |
+
margin-bottom: 6px;
|
| 81 |
+
}
|
| 82 |
+
small {
|
| 83 |
+
color: #842029 !important;
|
| 84 |
+
}
|
| 85 |
+
"))
|
| 86 |
+
),
|
| 87 |
+
|
| 88 |
+
tags$h2(
|
| 89 |
+
style = "color: #1e5aa8;",
|
| 90 |
+
"IRaMuTeQ-Lite"
|
| 91 |
+
),
|
| 92 |
+
tags$p(
|
| 93 |
+
style = "font-size: 14px;",
|
| 94 |
+
"Tentaive de reproduction de la CHD (Méthode Reinert) du logiciel IRaMuTeQ",
|
| 95 |
+
tags$br(),
|
| 96 |
+
"En test j'ai également expérimenté la recherche de NER dans le corpus s'appuyant sur la librairie Spacy (modele \"md\").",
|
| 97 |
+
tags$br(),
|
| 98 |
+
"Pour plus d’informations, vous pouvez consulter mon site : www.codeandcortex.fr",
|
| 99 |
+
tags$br(),
|
| 100 |
+
"version beta 0.4 - 18-02-2026"
|
| 101 |
+
),
|
| 102 |
+
|
| 103 |
+
sidebarLayout(
|
| 104 |
+
sidebarPanel(
|
| 105 |
+
fileInput("fichier_corpus", "Uploader un corpus IRaMuTeQ (.txt)", accept = c(".txt")),
|
| 106 |
+
|
| 107 |
+
radioButtons(
|
| 108 |
+
"modele_chd",
|
| 109 |
+
"Méthode Iramuteq-like",
|
| 110 |
+
choices = c(
|
| 111 |
+
"IRaMuTeQ-like" = "iramuteq"
|
| 112 |
+
),
|
| 113 |
+
selected = "iramuteq",
|
| 114 |
+
inline = FALSE
|
| 115 |
+
),
|
| 116 |
+
|
| 117 |
+
tags$div(class = "sidebar-section-title", "Paramètres communs CHD"),
|
| 118 |
+
numericInput("segment_size", "segment_size", value = 40, min = 5, step = 1),
|
| 119 |
+
numericInput("min_docfreq", "Fréquence minimale des termes (min_docfreq)", value = 3, min = 1, step = 1),
|
| 120 |
+
numericInput("max_p", "max_p (p-value)", value = 0.05, min = 0, max = 1, step = 0.01),
|
| 121 |
+
checkboxInput(
|
| 122 |
+
"filtrer_affichage_pvalue",
|
| 123 |
+
"Filtrer l'affichage des résultats par p-value (p ≤ max_p)",
|
| 124 |
+
value = TRUE
|
| 125 |
+
),
|
| 126 |
+
|
| 127 |
+
conditionalPanel(
|
| 128 |
+
condition = "input.modele_chd == 'rainette'",
|
| 129 |
+
ui_options_rainette()
|
| 130 |
+
),
|
| 131 |
+
|
| 132 |
+
conditionalPanel(
|
| 133 |
+
condition = "input.modele_chd == 'iramuteq'",
|
| 134 |
+
ui_options_iramuteq()
|
| 135 |
+
),
|
| 136 |
+
|
| 137 |
+
tags$div(class = "sidebar-section-title", "Dictionnaire"),
|
| 138 |
+
radioButtons(
|
| 139 |
+
"source_dictionnaire",
|
| 140 |
+
"Source de lemmatisation",
|
| 141 |
+
choices = c("spaCy" = "spacy", "Lexique (fr)" = "lexique_fr"),
|
| 142 |
+
selected = "spacy",
|
| 143 |
+
inline = FALSE
|
| 144 |
+
),
|
| 145 |
+
conditionalPanel(
|
| 146 |
+
condition = "input.modele_chd == 'iramuteq'",
|
| 147 |
+
tags$small("En mode IRaMuTeQ-like, seul le dictionnaire Lexique (fr) est utilisé automatiquement."),
|
| 148 |
+
tags$small("Dans ce mode, le filtrage des stopwords utilise la liste française de quanteda (pas spaCy).")
|
| 149 |
+
),
|
| 150 |
+
conditionalPanel(
|
| 151 |
+
condition = "input.source_dictionnaire == 'spacy'",
|
| 152 |
+
selectInput(
|
| 153 |
+
"spacy_langue",
|
| 154 |
+
"Langue spaCy",
|
| 155 |
+
choices = c("Français" = "fr", "Anglais" = "en", "Espagnol" = "es", "Italien" = "it", "Allemand" = "de", "Portugais" = "pt", "Catalan" = "ca"),
|
| 156 |
+
selected = "fr"
|
| 157 |
+
)
|
| 158 |
+
),
|
| 159 |
+
conditionalPanel(
|
| 160 |
+
condition = "input.source_dictionnaire == 'spacy'",
|
| 161 |
+
checkboxInput("spacy_utiliser_lemmes", "Lemmatisation via spaCy uniquement", value = FALSE)
|
| 162 |
+
),
|
| 163 |
+
conditionalPanel(
|
| 164 |
+
condition = "input.source_dictionnaire == 'lexique_fr'",
|
| 165 |
+
checkboxInput("lexique_utiliser_lemmes", "Lemmatisation via les lemmes de lexique_fr (forme → c_lemme)", value = TRUE)
|
| 166 |
+
),
|
| 167 |
+
uiOutput("ui_spacy_langue_detection"),
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
tags$div(class = "sidebar-section-title", "Nettoyage"),
|
| 171 |
+
|
| 172 |
+
conditionalPanel(
|
| 173 |
+
condition = "input.modele_chd == 'iramuteq'",
|
| 174 |
+
tags$div(
|
| 175 |
+
style = "margin: 0 0 8px 0; padding: 8px; background: #f7fbff; border-left: 3px solid #1e5aa8;",
|
| 176 |
+
tags$strong("Options IRaMuTeQ-like (iramuteq-like/textprepa_iramuteq.py)"),
|
| 177 |
+
tags$br(),
|
| 178 |
+
tags$small("Ces options pilotent la préparation du texte avant la tokenisation en mode IRaMuTeQ-like.")
|
| 179 |
+
)
|
| 180 |
+
),
|
| 181 |
+
|
| 182 |
+
checkboxInput("nettoyage_caracteres", "Nettoyage caractères (regex)", value = FALSE),
|
| 183 |
+
checkboxInput("forcer_minuscules_avant", "Passage en minuscules avant tokenisation", value = FALSE),
|
| 184 |
+
checkboxInput("supprimer_ponctuation", "Supprimer la ponctuation", value = FALSE),
|
| 185 |
+
tags$small("Supprime la ponctuation à la tokenisation quanteda (remove_punct), pour les deux sources (spaCy et lexique_fr), par ex. . , ; : ! ? ' ’ \" - ( ) [ ] …"),
|
| 186 |
+
checkboxInput("supprimer_chiffres", "Supprimer les chiffres (0-9)", value = FALSE),
|
| 187 |
+
checkboxInput("supprimer_apostrophes", "Traiter les élisions FR (c'est→est, m'écrire→écrire)", value = FALSE),
|
| 188 |
+
checkboxInput("retirer_stopwords", "Retirer les stopwords (spaCy si source spaCy, quanteda si source Lexique fr)", value = FALSE),
|
| 189 |
+
tags$small("La normalisation en minuscules est appliquée automatiquement avant la construction du DFM."),
|
| 190 |
+
checkboxInput("filtrage_morpho", "Filtrage morphosyntaxique", value = FALSE),
|
| 191 |
+
tags$small("Le filtrage morphosyntaxique s'applique à spaCy ou lexique_fr selon la source sélectionnée."),
|
| 192 |
+
conditionalPanel(
|
| 193 |
+
condition = "input.filtrage_morpho == true",
|
| 194 |
+
conditionalPanel(
|
| 195 |
+
condition = "input.source_dictionnaire == 'spacy'",
|
| 196 |
+
selectizeInput(
|
| 197 |
+
"pos_spacy_a_conserver",
|
| 198 |
+
"POS à conserver (spaCy)",
|
| 199 |
+
choices = c(
|
| 200 |
+
"ADJ", "ADP", "ADV", "AUX", "CCONJ", "DET", "INTJ", "NOUN",
|
| 201 |
+
"NUM", "PART", "PRON", "PROPN", "PUNCT", "SCONJ", "SYM", "VERB", "X"
|
| 202 |
+
),
|
| 203 |
+
selected = c("NOUN", "VERB"),
|
| 204 |
+
multiple = TRUE,
|
| 205 |
+
options = list(plugins = list("remove_button"))
|
| 206 |
+
)
|
| 207 |
+
),
|
| 208 |
+
conditionalPanel(
|
| 209 |
+
condition = "input.source_dictionnaire == 'lexique_fr'",
|
| 210 |
+
selectizeInput(
|
| 211 |
+
"pos_lexique_a_conserver",
|
| 212 |
+
"Catégories c_morpho à conserver (lexique_fr)",
|
| 213 |
+
choices = c(
|
| 214 |
+
"NOM", "VER", "AUX", "ADJ", "ADV", "PRE", "CON", "ONO",
|
| 215 |
+
"ADJ:NUM", "ADJ:POS", "ADJ:IND", "ADJ:INT", "ADJ:DEM",
|
| 216 |
+
"PRO:PER", "PRO:POS", "PRO:DEM", "PRO:IND", "PRO:REL", "PRO:INT",
|
| 217 |
+
"ART:DEF", "ART:IND"
|
| 218 |
+
),
|
| 219 |
+
selected = c("NOM", "VER", "ADJ"),
|
| 220 |
+
multiple = TRUE,
|
| 221 |
+
options = list(plugins = list("remove_button"))
|
| 222 |
+
)
|
| 223 |
+
)
|
| 224 |
+
),
|
| 225 |
+
tags$small("Regex appliquée quand “Nettoyage caractères (regex)” est activé :"),
|
| 226 |
+
tags$pre(
|
| 227 |
+
style = "white-space: pre-wrap; font-size: 11px; border: 1px solid #ddd; padding: 6px;",
|
| 228 |
+
REGEX_CARACTERES_A_SUPPRIMER
|
| 229 |
+
),
|
| 230 |
+
tags$small("Les caractères présents dans la liste entre crochets sont conservés ; tous les autres (ex. @ # & / emoji) sont remplacés par des espaces."),
|
| 231 |
+
tags$small("L'option “Supprimer la ponctuation” pilote remove_punct, même si elle est autorisée par la regex ci-dessus."),
|
| 232 |
+
tags$small("Cette option conserve les apostrophes lexicales (ex. aujourd'hui) et ne traite que les élisions en début de mot."),
|
| 233 |
+
|
| 234 |
+
tags$div(class = "sidebar-section-title", "Paramètres SpaCy/NER"),
|
| 235 |
+
|
| 236 |
+
checkboxInput("activer_ner", "Activer NER (spaCy)", value = FALSE),
|
| 237 |
+
uiOutput("ui_ner_lexique_incompatibilite"),
|
| 238 |
+
conditionalPanel(
|
| 239 |
+
condition = "input.activer_ner == true",
|
| 240 |
+
fileInput(
|
| 241 |
+
"fichier_ner_json",
|
| 242 |
+
"Importer un dictionnaire NER (.json)",
|
| 243 |
+
accept = c(".json", "application/json")
|
| 244 |
+
),
|
| 245 |
+
tags$small("Optionnel : importez un dictionnaire NER JSON si vous voulez personnaliser les entités. Si vous ne fournissez pas de fichier, l'analyse utilise le NER spaCy classique.")
|
| 246 |
+
),
|
| 247 |
+
|
| 248 |
+
tags$hr(),
|
| 249 |
+
|
| 250 |
+
tags$div(class = "sidebar-section-title", "Paramètres AFC"),
|
| 251 |
+
|
| 252 |
+
checkboxInput("afc_reduire_chevauchement", "Réduire les chevauchements des mots (AFC)", value = FALSE),
|
| 253 |
+
|
| 254 |
+
radioButtons(
|
| 255 |
+
"afc_taille_mots",
|
| 256 |
+
"Taille des mots (AFC termes)",
|
| 257 |
+
choices = c("Fréquence" = "frequency", "Chi2" = "chi2"),
|
| 258 |
+
selected = "frequency",
|
| 259 |
+
inline = FALSE
|
| 260 |
+
),
|
| 261 |
+
|
| 262 |
+
tags$hr(),
|
| 263 |
+
|
| 264 |
+
tags$div(
|
| 265 |
+
style = "display: flex; gap: 8px; flex-wrap: wrap; align-items: center;",
|
| 266 |
+
actionButton("lancer", "Lancer l'analyse"),
|
| 267 |
+
actionButton("explor", "Explor rainette", class = "btn-primary")
|
| 268 |
+
),
|
| 269 |
+
|
| 270 |
+
tags$hr(),
|
| 271 |
+
|
| 272 |
+
downloadButton("dl_zip", "Télécharger exports (zip)"),
|
| 273 |
+
downloadButton("dl_afc_zip", "Télécharger AFC (zip)")
|
| 274 |
+
),
|
| 275 |
+
|
| 276 |
+
mainPanel(
|
| 277 |
+
tabsetPanel(
|
| 278 |
+
id = "onglets_principaux",
|
| 279 |
+
|
| 280 |
+
tabPanel(
|
| 281 |
+
"Analyse",
|
| 282 |
+
tags$h3("Statut"),
|
| 283 |
+
textOutput("statut"),
|
| 284 |
+
tags$h3("Journal"),
|
| 285 |
+
tags$pre(style = "white-space: pre-wrap;", textOutput("logs")),
|
| 286 |
+
tags$h3("Analyse du corpus (mode debug)"),
|
| 287 |
+
uiOutput("ui_table_stats_corpus"),
|
| 288 |
+
tags$div(
|
| 289 |
+
style = "width: 600px;",
|
| 290 |
+
plotOutput("plot_stats_zipf", height = "600px", width = "600px")
|
| 291 |
+
),
|
| 292 |
+
tags$h3("Répartition des classes"),
|
| 293 |
+
tableOutput("table_classes")
|
| 294 |
+
),
|
| 295 |
+
|
| 296 |
+
tabPanel(
|
| 297 |
+
"Explore rainette",
|
| 298 |
+
tags$h3("Explore_rainette"),
|
| 299 |
+
selectInput("classe_viz", "Classe", choices = c("1"), selected = "1"),
|
| 300 |
+
tabsetPanel(
|
| 301 |
+
tabPanel(
|
| 302 |
+
"CHD",
|
| 303 |
+
tags$h4("Dendrogramme CHD (Rainette)"),
|
| 304 |
+
plotOutput("plot_chd_rainette_dendro", height = "360px"),
|
| 305 |
+
tags$hr(),
|
| 306 |
+
fluidRow(
|
| 307 |
+
column(
|
| 308 |
+
4,
|
| 309 |
+
sliderInput("k_plot", "Nombre de classes (k)", min = 2, max = 2, value = 2, step = 1),
|
| 310 |
+
selectInput(
|
| 311 |
+
"measure_plot", "Statistiques",
|
| 312 |
+
choices = c(
|
| 313 |
+
"Frequency - Terms" = "frequency",
|
| 314 |
+
"Keyness - Chi-squared" = "chi2",
|
| 315 |
+
"Keyness - Likelihood ratio" = "lr",
|
| 316 |
+
"Frequency - Documents proportion" = "docprop"
|
| 317 |
+
),
|
| 318 |
+
selected = "frequency"
|
| 319 |
+
),
|
| 320 |
+
selectInput("type_plot", "Type", choices = c("bar", "cloud"), selected = "bar"),
|
| 321 |
+
numericInput("n_terms_plot", "Nombre de termes", value = 20, min = 5, max = 1000, step = 1),
|
| 322 |
+
conditionalPanel(
|
| 323 |
+
"input.measure_plot != 'docprop'",
|
| 324 |
+
checkboxInput("same_scales_plot", "Forcer les mêmes échelles", value = TRUE)
|
| 325 |
+
),
|
| 326 |
+
checkboxInput("show_negative_plot", "Afficher les valeurs négatives", value = FALSE),
|
| 327 |
+
numericInput("text_size_plot", "Taille du texte", value = 12, min = 6, max = 30, step = 1)
|
| 328 |
+
),
|
| 329 |
+
column(
|
| 330 |
+
8,
|
| 331 |
+
plotOutput("plot_chd", height = "70vh")
|
| 332 |
+
)
|
| 333 |
+
)
|
| 334 |
+
),
|
| 335 |
+
tabPanel("Concordancier HTML", uiOutput("ui_concordancier_explore")),
|
| 336 |
+
tabPanel("Nuage de mots", uiOutput("ui_wordcloud")),
|
| 337 |
+
tabPanel("Statistiques", tableOutput("table_stats_classe"))
|
| 338 |
+
)
|
| 339 |
+
),
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
ui_resultats_chd_iramuteq(),
|
| 344 |
+
|
| 345 |
+
tabPanel(
|
| 346 |
+
"Prévisu corpus",
|
| 347 |
+
tags$h3("Corpus importé"),
|
| 348 |
+
uiOutput("ui_corpus_preview")
|
| 349 |
+
),
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
tabPanel(
|
| 353 |
+
"AFC",
|
| 354 |
+
tags$h3("AFC"),
|
| 355 |
+
uiOutput("ui_afc_statut"),
|
| 356 |
+
uiOutput("ui_afc_erreurs"),
|
| 357 |
+
|
| 358 |
+
tags$h4("AFC des classes (Représentation des classes)"),
|
| 359 |
+
plotOutput("plot_afc_classes", height = "620px"),
|
| 360 |
+
|
| 361 |
+
tags$h4("AFC des termes"),
|
| 362 |
+
tags$p("Les mots sont colorés selon la classe où ils sont le plus surreprésentés (résidus standardisés) et leur taille est proportionnelle à leur fréquence globale ou chi2 (selon le choix)."),
|
| 363 |
+
plotOutput("plot_afc", height = "720px"),
|
| 364 |
+
tags$h4("Table des mots projetés (fréquence, chi2, p-value, segment exemple)"),
|
| 365 |
+
uiOutput("ui_table_afc_mots_par_classe"),
|
| 366 |
+
|
| 367 |
+
tags$h4("AFC des variables étoilées"),
|
| 368 |
+
plotOutput("plot_afc_vars", height = "720px"),
|
| 369 |
+
tags$h4("Table des modalités projetées"),
|
| 370 |
+
tableOutput("table_afc_vars"),
|
| 371 |
+
|
| 372 |
+
tags$h4("Valeurs propres"),
|
| 373 |
+
tableOutput("table_afc_eig")
|
| 374 |
+
),
|
| 375 |
+
|
| 376 |
+
tabPanel(
|
| 377 |
+
"NER (beta)",
|
| 378 |
+
tags$h3("Détection d'entités nommées (spaCy)"),
|
| 379 |
+
uiOutput("ui_ner_statut"),
|
| 380 |
+
tags$h3("Résumé"),
|
| 381 |
+
tableOutput("table_ner_resume"),
|
| 382 |
+
tags$h3("Détails"),
|
| 383 |
+
tableOutput("table_ner_details"),
|
| 384 |
+
tags$h3("Nuage de mots (entités)"),
|
| 385 |
+
plotOutput("plot_ner_wordcloud", height = "520px"),
|
| 386 |
+
tags$h3("Nuages par classe"),
|
| 387 |
+
uiOutput("ui_ner_wordcloud_par_classe")
|
| 388 |
+
),
|
| 389 |
+
|
| 390 |
+
tabPanel(
|
| 391 |
+
"Aide",
|
| 392 |
+
ui_aide_huggingface()
|
| 393 |
+
),
|
| 394 |
+
|
| 395 |
+
tabPanel(
|
| 396 |
+
"Aide POS/Spacy",
|
| 397 |
+
tags$div(
|
| 398 |
+
style = "padding: 12px;",
|
| 399 |
+
if (file.exists("pos_spacy.md")) {
|
| 400 |
+
includeMarkdown("pos_spacy.md")
|
| 401 |
+
} else {
|
| 402 |
+
tags$p("Le fichier pos_spacy.md est introuvable à la racine du projet.")
|
| 403 |
+
}
|
| 404 |
+
)
|
| 405 |
+
),
|
| 406 |
+
|
| 407 |
+
tabPanel(
|
| 408 |
+
"Aide NER",
|
| 409 |
+
tags$div(
|
| 410 |
+
style = "padding: 12px;",
|
| 411 |
+
if (file.exists("spacy_ner/ner.md")) {
|
| 412 |
+
includeMarkdown("spacy_ner/ner.md")
|
| 413 |
+
} else {
|
| 414 |
+
tags$p("Le fichier spacy_ner/ner.md est introuvable.")
|
| 415 |
+
}
|
| 416 |
+
)
|
| 417 |
+
)
|
| 418 |
+
)
|
| 419 |
+
)
|
| 420 |
+
)
|
| 421 |
+
)
|