
Le prétraitement (preprocessing) est une étape incontournable avant de lancer vos tests et analyses.
Prétraitement = normalisation du texte !
Plusieurs étapes de normalisation sont possibles (liste non exhaustive qui dépend de vos objectifs) :
- Supprimer les stopwords
- Normaliser les accents du texte (cette normalisation permet de supprimer des doublons liés à des fautes d’orthographe par exemple)
- Normaliser le texte en minuscules
- Nettoyage des caractères isolés
- Supprimer les chiffres
- Supprimer les URLs
- Supprimer les caractères spéciaux
- Gérer les Entités Nommées
-
Normaliser : Racinisation (Stemming) et Lemmatisation
Ici, nous allons utiliser des expressions régulières (Regex) en Python pour normaliser les textes avant les traitements statistiques et l’exploration sémantique.
Les fonctions “Regex” du module “re” de Python permettent d’extraire et de réécrire/transformer des formes récurrentes (chiffres, nombres, URL,…) que l’on (peut) trouve(r) dans un texte.
Ce que l’on peut réaliser à l’aide des fonctions Regex, votre éditeur de texte est également capable de le réaliser, notamment avec la fonction : « Rechercher/Remplacer ». C’est simple et efficace, mais souvent plus chronophage et moins reproductible qu’un script Python qui vise a automatiser la normalisation du texte.
Un conseil : dupliquez votre corpus avant de commencer à travailler sur la normalisation de votre texte 😉
La maîtrise des Regex demande de la pratique… et c’est pas hyper sexy à coder… À l’heure des LLM (Grok, ChatGPT,…), vous pouvez vous appuyer sur ces outils pour vérifier une syntaxe, faire expliquer un motif, ou même générer un script Python à partir d’un objectif de prétraitement : C’est un réel gain de temps.
Enfin, vous pouvez tester vos règles dans des environnements dédiés pour valider leur comportement avant intégration dans votre workflow : https://regex101.com/ et https://extendsclass.com/regex-tester.html
1. Utilisation des expressions régulières REGEX
En Python, « RegEx » (REGular EXpressions) désigne le langage de “motifs” que le module re
utilise.
(librairie “re”, “sub” -> substituer) est une fonction du module qui à pour fonction de faire des “remplacements” de “motifs”. Le “motif” est l’élément que vous souhaitez chercher ou extraire.
re.sub
2. Chevauchement des règles de normalisation
Plusieurs règles peuvent se chevauchent. Par exemple, supprimer les URLs qui commencent par “http(s)” recoupe la « suppression des caractères spéciaux » parce qu’une URL contient précisément des caractères que votre filtre général aura peut être déjà supprimé… ( (:// ? & = % etc…).
Attention, l’ordre d’application des règles de normalisation peut biaiser le résultat final.
Si vous supprimez d’abord les « caractères spéciaux », l’URL peut être fragmentée en “https exemple com…”, ce qui peut empêcher la règle « supprimer http… » de fonctionner ensuite.
La bonne pratique est d’appliquer d’abord les règles spécifiques (URLs, emails, @mentions, #hashtags,…), puis les règles générales.
Ainsi, on supprime d’abord ce qu’on reconnaît précisément,
ensuite on passe le grand coup de balai !
Enfin, un dernier point : faites attention à conserver des “repères” (ponctuations) pour ensuite réaliser une segmentation du texte, si nécessaire.
Les principaux logiciels d’analyse statistique textuelle travaillent à partir de l’unité lexicale qu’est le segment de texte. Dans ce cas précis, la ponctuation (. ? ! , 😉 ne devra jamais être supprimée.
3. Suppression des stopwords
Les stopwords sont des mots très fréquents (mots-outils) tels que les articles (« le », « la », « un »), prépositions (« de », « à », « en »), pronoms (« il », « elle », « nous »), conjonctions (« et », « mais », « ou »).
Ces mots-outils n’apportent pas forcement de plus-value dans l’analyse textuelle, cette étape est donc quasi incontournable.
Les retirer permet de dégager les termes porteurs de sens. La distribution de fréquence s’épure immédiatement : les mots restants correspondent mieux aux notions centrales du texte.
Dans cette approche en Python il existe deux librairies simple et efficace permettant de supprimer les stopwords : NLTK et SpaCy (modeles : sm, md, lg).
3.1 Utilisation de la librairie NLTK
# pip install nltk import re # charger les stopwords français nltk try: from nltk.corpus import stopwords stop_fr = set(stopwords.words("french")) except LookupError: import nltk nltk.download("stopwords") from nltk.corpus import stopwords stop_fr = set(stopwords.words("french")) def supprimer_mots_vides_nltk(texte): """supprime les mots vides français avec nltk et conserve l'ordre des mots""" mots = re.findall(r"\w+", texte.lower(), flags=re.UNICODE) mots_filtres = [m for m in mots if m not in stop_fr] return " ".join(mots_filtres) # exécution directe phrase = input("Entrez une phrase : ") print(supprimer_mots_vides_nltk(phrase))
3.2 Utilisation de la librairie SpaCy
Dans cet exemple, nous utiliserons le modèle small (sm).
Pour l’analyse textuelle, il est toutefois conseillé d’utiliser, a minima, le modèle medium (md) ou large (lg).
# pip install spacy # python -m spacy download fr_core_news_sm import spacy # charger le modèle français de spaCy # installez-le au besoin : python -m spacy download fr_core_news_sm nlp = spacy.load("fr_core_news_sm") def supprimer_mots_vides_spacy(texte): """supprime les mots vides français avec spacy et conserve l'ordre des mots""" doc = nlp(texte) mots = [t.text for t in doc if not t.is_stop and not t.is_punct and not t.is_space] return " ".join(mots) # exécution directe phrase = input("Entrez une phrase : ") print(supprimer_mots_vides_spacy(phrase))
4. Normaliser les caractères accentués
Normaliser les caractères accentués consiste à transformer chaque lettre accentuée en sa lettre de base. La normalisation Unicode « NFKD » décompose une lettre accentuée en deux éléments : la lettre sans accent et un petit signe d’accent. On supprime ensuite ces signes d’accent (caractères de catégorie Unicode commençant par « M »), ce qui donne « é → e », « À → A ». Avant cela, on remplace explicitement les ligatures pour conserver le sens : « œ → oe », « æ → ae ». Résultat : « Élève → Eleve », « cœur → coeur ».
L’argument « NFKD » de decomp = unicodedata.normalize("NFKD", texte)
met le texte dans une forme standard et décompose les lettres accentuées.
Ensuite, en supprimant les accents, on garde la lettre de base et on perd seulement l’accent.
unicodedata.normalize("NFKD", texte)
« divise » chaque lettre accentuée en deux morceaux :
la lettre sans accent + un petit signe d’accent.
Exemples :"é"
devient "e" + (accent aigu)
;"À"
devient "A" + (accent grave)
.
# normalisation des mots avec accents import unicodedata def supprimer_accents(texte): """supprime accents et ligatures (œ→oe, æ→ae) en conservant casse et ponctuation""" # gérer les ligatures courantes texte = (texte .replace("œ", "oe").replace("Œ", "OE") .replace("æ", "ae").replace("Æ", "AE")) # normaliser puis retirer tous les diacritiques (catégories Unicode M*) decomp = unicodedata.normalize("NFKD", texte) return "".join(ch for ch in decomp if not unicodedata.category(ch).startswith("M")) # exécution directe phrase = input("Entrez une phrase avec accents et ligatures : ") resultat = supprimer_accents(phrase) print("\nRésultat :") print(resultat) # Exemple à tester : # À Â Ä Ç É È Ê Ë Î Ï Ô Ö Ù Û Ü Ÿ — à â ä ç é è ê ë î ï ô ö ù û ü ÿ — œ, Œ, æ, Æ.
5. Normaliser le texte en minuscule
Ici on passe tout le texte en minuscule.
# fonction de nettoyage : mettre le texte en minuscules def nettoyer_texte(texte, conserver_casse=True): """retourne le texte en minuscules si conserver_casse est False, sinon tel quel.""" if not conserver_casse: texte = texte.lower() return texte # exécution texte_utilisateur = input("Entrez un texte à tester : ") resultat = nettoyer_texte(texte_utilisateur, conserver_casse=False) print("\nRésultat :") print(resultat)
6. Nettoyage des caractères isolés
re.sub
(librairie “re”, “sub” -> substituer) remplace toutes les occurrences d’un motif d’expression régulière par une chaîne donnée ; ici, elle est utilisée deux fois.
La première, re.sub(r"(?<!\w)[A-Za-zÀ-ÖØ-öø-ÿ](?!\w|')", " ", texte)
, remplace par un espace chaque mot d’une seule lettre isolé et non suivi d’une apostrophe, afin de conserver les c’, j’, l’.
La seconde, re.sub(r"\s+", " ", texte).strip()
, condense toutes les suites d’espaces (tabulations, retours à la ligne…) en un seul espace puis retire les espaces de début et de fin.
# Nettoyage de caractères isolés import re def supprimer_caracteres_isoles(texte): """supprime les mots d'une seule lettre tout en conservant les formes avec apostrophe (c', j', l', etc.)""" # normaliser l’apostrophe typographique en apostrophe droite texte = texte.replace("’", "'") # retirer une lettre isolée (pas précédée/suivie d'un caractère de mot) et non suivie d'une apostrophe texte = re.sub(r"(?<!\w)[A-Za-zÀ-ÖØ-öø-ÿ](?!\w|')", " ", texte) # nettoyer les espaces multiples texte = re.sub(r"\s+", " ", texte).strip() return texte # exécution phrase = input("Entrez une phrase : ") resultat = supprimer_caracteres_isoles(phrase) print("\nRésultat :") print(resultat)
7. Suppression des chiffres
return re.sub(r"\d+", " ", texte)
On renvoie le résultat de re.sub
, c’est-à-dire une nouvelle chaîne où :
r"\d+"
est l’expression régulière (regex) qui repère toute suite d’au moins un chiffre.
veut dire « un chiffre » et
\d+
veut dire « une ou plusieurs fois ». Le préfixer
indique une chaîne « brute » pour que\d
soit transmis tel quel au moteur regex." "
est la chaîne de remplacement : un espace.texte
est la chaîne d’entrée à transformer.
Concrètement, chaque bloc de chiffres est remplacé par un espace. Exemple :
Entrée : Rapport 2024 avec 3 annexes
Sortie : Rapport avec annexes
import re def nettoyer_texte(texte): """supprime tous les chiffres du texte.""" return re.sub(r"\d+", " ", texte) # exécution directe texte_utilisateur = input("Entrez une phrase contenant des chiffres : ") resultat = nettoyer_texte(texte_utilisateur) print("\nRésultat :") print(resultat)
Pour éviter le double espace :
# Suppression des chiffres et du double espaces import re def nettoyer_texte(texte): """supprime tous les chiffres du texte et normalise les espaces.""" return re.sub(r"\s+", " ", re.sub(r"\d+", " ", texte)).strip() # exécution directe texte_utilisateur = input("Entrez une phrase contenant des chiffres : ") resultat = nettoyer_texte(texte_utilisateur) print("\nRésultat :") print(resultat)
7. Suppression des URLs
re.sub(r"http\S+", " ", texte)
cherche tout morceau de texte qui commence par http
(ou https
) et continue jusqu’au prochain séparateur blanc.
\S+ signifie « un ou plusieurs caractères qui ne sont pas des espaces ». On remplace chaque lien trouvé par un espace.
Exemple :"voir https://exemple.org/page?id=3 merci"
devient "voir merci"
.
Ensuite, re.sub(r"\s+", " ", texte).strip()
sert à nettoyer la mise en forme. \s+
regroupe toutes les suites d’espaces, tabulations ou retours à la ligne en un seul espace.
strip() enlève l’espace en début et fin de chaîne. Avec l’exemple précédent, on obtient "voir merci"
.
# -*- coding: utf-8 -*- import re def supprimer_http(texte): """supprime les expressions qui commencent par http (ou https) et tout ce qui suit jusqu'au prochain espace""" # enlever chaque segment 'http...' (jusqu'au premier séparateur d'espace) texte = re.sub(r"http\S+", " ", texte) # normaliser les espaces return re.sub(r"\s+", " ", texte).strip() # exécution directe phrase = input("Entrez une phrase contenant des liens http : ") resultat = supprimer_http(phrase) print("\nRésultat :") print(resultat)
8. Suppression des caractères spéciaux
motif = r"[^0-9A-Za-zÀ-ÖØ-öø-ÿ\s\.\,\;\:\!\?\'\"\«\»\(\)\-\–\—\…]"
C’est une expression régulière qui décrit “ce qu’on veut enlever”.
Les crochets [...]
définissent un ensemble de caractères autorisés. Le chapeau ^
placé juste après le [
veut dire “tout sauf”.
Donc ce “motif ” repère chaque caractère qui n’est PAS dans la liste autorisée.
Que contient la liste autorisée ?
– les chiffres 0-9
– les lettres latines non accentuées A–Z
et a–z
– les lettres accentuées usuelles en français via les plages À-Ö
, Ø-ö
, ø-ÿ
– les espaces et retours à la ligne \s
– la ponctuation que vous voulez garder : .
,
;
:
!
?
'
"
«
»
(
)
-
–
—
…
Les barres obliques inverses \
servent juste à « échapper » certains signes pour dire qu’on les prend au sens littéral dans le motif.
texte = re.sub(motif, " ", texte)
Ici on applique le motif : chaque caractère qui n’est pas autorisé est remplacé par un espace.
On obtient une nouvelle chaîne où seuls les lettres/chiffres/espaces et la ponctuation listée sont conservés.
import re def supprimer_speciaux_sauf_ponctuation(texte): """supprime les caractères spéciaux en conservant lettres, chiffres, espaces et ponctuation courante""" # on autorise : . , ; : ! ? ' " « » ( ) - – — … motif = r"[^0-9A-Za-zÀ-ÖØ-öø-ÿ\s\.\,\;\:\!\?\'\"\«\»\(\)\-\–\—\…]" texte = re.sub(motif, " ", texte) # normaliser les espaces return re.sub(r"\s+", " ", texte).strip() # exécution directe chaine = input("Entrez un texte : ") print(supprimer_speciaux_sauf_ponctuation(chaine))
9. Gestion des entités nommées (NER)
Dans l’absolu, la NER (Named Entity Recognition) renvoie à l’utilisation d’un modèle de langage tel que spaCy afin d’entraîner le modèle à reconnaître les entités nommées, (termes/mots absents de son lexique).
Ici, nous aborderons cette notion sans entraîner le modèle : nous appliquerons une méthode manuelle, en transformant ces entités.
Ici, ces “groupes de mots” : « Paris Match » ou “Steve Jobs” doivent être traités comme une seule unité.
- sans traitement particulier, « Paris » et « Match » apparaissent séparément,
- avec un regroupement un peu en mode “bricolage”, ils deviennent « Paris_Match » et leur fréquence est correctement calculée.
# texte : # Paris Match publie un dossier sur Steve Jobs et son impact sur l’industrie. # L’article met aussi en regard les promesses de l’intelligence artificielle. # Certains lecteurs comparent Paris match d’hier et d’aujourd’hui, tandis que d’autres évoquent l’Intelligence Artificielle dans les produits d’Apple inspirés par Steve Jobs. import re def remplacer_occurrences_multiples(texte, remplacements): """remplace toutes les occurrences par leur valeur correspondante""" for a_chercher, remplacement in remplacements.items(): motif = re.escape(a_chercher) # traiter la clé comme texte littéral texte = re.sub(motif, remplacement, texte, flags=re.IGNORECASE) # on ignore la CASE return texte # saisir le texte à transformer texte_source = input("Entrez le texte à transformer : ") # DÉFINIR LES RÈGLES DE REMPLACEMENT # exemple : "Paris Match" -> "Paris_Match" remplacements = { "Paris Match": "Paris_Match", "Steve Jobs": "Steve_Jobs", "intelligence artificielle": "intelligence_artificielle" # ajoutez d'autres lignes si nécessaire : } # appliquer les remplacements et afficher le résultat resultat = remplacer_occurrences_multiples(texte_source, remplacements) print("\nTexte transformé :") print(resultat)
10. Racinisation (stemming) et lemmatisation
Il existe deux procédés :
10.1 Lemmatisation
Ce procédé est fondé sur un dictionnaire linguistique (SpaCy,…). Le résultat est la forme canonique du mot (le lemme), telle qu’elle figure dans le dictionnaire.
mangeaient → manger (verbe à l’infinitif)
étudiants → étudiant (nom au singulier)
À noter que le lexique intégré de la librairie Python “SpaCy” n’est pas toujours la solution idéale : l’idéal serait d’intégrer votre propre dictionnaire (cf. Le logiciel IRaMuTeQ), afin de pouvoir modifier et enrichir le formes.
10.2 Stemming (racinisation)
Le stemming consiste à raccourcir mécaniquement les mots pour garder une racine commune, en supprimant surtout les suffixes.
parlaient → parl (racine non lexicale)
organisations → organis (racine non lexicale)
Ces deux méthodes de normalisation seront détaillée dans un prochain article.
Conclusion
En une phrase : pas de prétraitement,… pas d’analyse textuelle…