Nettoyer et structurer l’information dans les données textuelles

Les corpus textuels étant des objets de très grande dimension où le ratio signal/bruit est faible, il est nécessaire de mettre en oeuvre une série d’étapes de nettoyage de texte. Ce chapitre va explorer quelques méthodes classiques de nettoyage en s’appuyant sur des corpus littéraires: le Comte de Monte Cristo d’Alexandre Dumas et des auteurs fantastiques anglo-saxons (Lovecraft, Poe, Shelley).

NLP
Tutoriel
Author

Lino Galiana

Published

2024-07-26

Les chapitres suivants seront utiles au cours de ce chapitre:

!pip install pywaffle
!pip install spacy
!pip install plotnine
!pip install great_tables
!pip install wordcloud
!pip install cleantext
Requirement already satisfied: pywaffle in /opt/mamba/lib/python3.11/site-packages (1.1.1)
Requirement already satisfied: fontawesomefree in /opt/mamba/lib/python3.11/site-packages (from pywaffle) (6.6.0)
Requirement already satisfied: matplotlib in /opt/mamba/lib/python3.11/site-packages (from pywaffle) (3.8.3)
Requirement already satisfied: contourpy>=1.0.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (1.2.1)
Requirement already satisfied: cycler>=0.10 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (4.51.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (1.4.5)
Requirement already satisfied: numpy<2,>=1.21 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (1.26.4)
Requirement already satisfied: packaging>=20.0 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (23.2)
Requirement already satisfied: pillow>=8 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (10.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (3.1.2)
Requirement already satisfied: python-dateutil>=2.7 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->pywaffle) (2.9.0)
Requirement already satisfied: six>=1.5 in /opt/mamba/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib->pywaffle) (1.16.0)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Requirement already satisfied: spacy in /opt/mamba/lib/python3.11/site-packages (3.7.5)
Requirement already satisfied: spacy-legacy<3.1.0,>=3.0.11 in /opt/mamba/lib/python3.11/site-packages (from spacy) (3.0.12)
Requirement already satisfied: spacy-loggers<2.0.0,>=1.0.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (1.0.5)
Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (1.0.10)
Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /opt/mamba/lib/python3.11/site-packages (from spacy) (2.0.8)
Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /opt/mamba/lib/python3.11/site-packages (from spacy) (3.0.9)
Requirement already satisfied: thinc<8.3.0,>=8.2.2 in /opt/mamba/lib/python3.11/site-packages (from spacy) (8.2.5)
Requirement already satisfied: wasabi<1.2.0,>=0.9.1 in /opt/mamba/lib/python3.11/site-packages (from spacy) (1.1.3)
Requirement already satisfied: srsly<3.0.0,>=2.4.3 in /opt/mamba/lib/python3.11/site-packages (from spacy) (2.4.8)
Requirement already satisfied: catalogue<2.1.0,>=2.0.6 in /opt/mamba/lib/python3.11/site-packages (from spacy) (2.0.10)
Requirement already satisfied: weasel<0.5.0,>=0.1.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (0.4.1)
Requirement already satisfied: typer<1.0.0,>=0.3.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (0.12.3)
Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (4.66.2)
Requirement already satisfied: requests<3.0.0,>=2.13.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (2.31.0)
Requirement already satisfied: pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4 in /opt/mamba/lib/python3.11/site-packages (from spacy) (2.8.2)
Requirement already satisfied: jinja2 in /opt/mamba/lib/python3.11/site-packages (from spacy) (3.1.3)
Requirement already satisfied: setuptools in /opt/mamba/lib/python3.11/site-packages (from spacy) (69.2.0)
Requirement already satisfied: packaging>=20.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (23.2)
Requirement already satisfied: langcodes<4.0.0,>=3.2.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (3.4.0)
Requirement already satisfied: numpy>=1.19.0 in /opt/mamba/lib/python3.11/site-packages (from spacy) (1.26.4)
Requirement already satisfied: language-data>=1.2 in /opt/mamba/lib/python3.11/site-packages (from langcodes<4.0.0,>=3.2.0->spacy) (1.2.0)
Requirement already satisfied: annotated-types>=0.4.0 in /opt/mamba/lib/python3.11/site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy) (0.7.0)
Requirement already satisfied: pydantic-core==2.20.1 in /opt/mamba/lib/python3.11/site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy) (2.20.1)
Requirement already satisfied: typing-extensions>=4.6.1 in /opt/mamba/lib/python3.11/site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy) (4.11.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/mamba/lib/python3.11/site-packages (from requests<3.0.0,>=2.13.0->spacy) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/mamba/lib/python3.11/site-packages (from requests<3.0.0,>=2.13.0->spacy) (3.6)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/mamba/lib/python3.11/site-packages (from requests<3.0.0,>=2.13.0->spacy) (1.26.18)
Requirement already satisfied: certifi>=2017.4.17 in /opt/mamba/lib/python3.11/site-packages (from requests<3.0.0,>=2.13.0->spacy) (2024.2.2)
Requirement already satisfied: blis<0.8.0,>=0.7.8 in /opt/mamba/lib/python3.11/site-packages (from thinc<8.3.0,>=8.2.2->spacy) (0.7.11)
Requirement already satisfied: confection<1.0.0,>=0.0.1 in /opt/mamba/lib/python3.11/site-packages (from thinc<8.3.0,>=8.2.2->spacy) (0.1.5)
Requirement already satisfied: click>=8.0.0 in /opt/mamba/lib/python3.11/site-packages (from typer<1.0.0,>=0.3.0->spacy) (8.1.7)
Requirement already satisfied: shellingham>=1.3.0 in /opt/mamba/lib/python3.11/site-packages (from typer<1.0.0,>=0.3.0->spacy) (1.5.4)
Requirement already satisfied: rich>=10.11.0 in /opt/mamba/lib/python3.11/site-packages (from typer<1.0.0,>=0.3.0->spacy) (13.7.1)
Requirement already satisfied: cloudpathlib<1.0.0,>=0.7.0 in /opt/mamba/lib/python3.11/site-packages (from weasel<0.5.0,>=0.1.0->spacy) (0.18.1)
Requirement already satisfied: smart-open<8.0.0,>=5.2.1 in /opt/mamba/lib/python3.11/site-packages (from weasel<0.5.0,>=0.1.0->spacy) (7.0.4)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/mamba/lib/python3.11/site-packages (from jinja2->spacy) (2.1.5)
Requirement already satisfied: marisa-trie>=0.7.7 in /opt/mamba/lib/python3.11/site-packages (from language-data>=1.2->langcodes<4.0.0,>=3.2.0->spacy) (1.2.0)
Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/mamba/lib/python3.11/site-packages (from rich>=10.11.0->typer<1.0.0,>=0.3.0->spacy) (3.0.0)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/mamba/lib/python3.11/site-packages (from rich>=10.11.0->typer<1.0.0,>=0.3.0->spacy) (2.17.2)
Requirement already satisfied: wrapt in /opt/mamba/lib/python3.11/site-packages (from smart-open<8.0.0,>=5.2.1->weasel<0.5.0,>=0.1.0->spacy) (1.16.0)
Requirement already satisfied: mdurl~=0.1 in /opt/mamba/lib/python3.11/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<1.0.0,>=0.3.0->spacy) (0.1.2)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Requirement already satisfied: plotnine in /opt/mamba/lib/python3.11/site-packages (0.13.6)
Requirement already satisfied: matplotlib>=3.7.0 in /opt/mamba/lib/python3.11/site-packages (from plotnine) (3.8.3)
Requirement already satisfied: pandas<3.0.0,>=2.1.0 in /opt/mamba/lib/python3.11/site-packages (from plotnine) (2.2.1)
Requirement already satisfied: mizani~=0.11.0 in /opt/mamba/lib/python3.11/site-packages (from plotnine) (0.11.4)
Requirement already satisfied: numpy>=1.23.0 in /opt/mamba/lib/python3.11/site-packages (from plotnine) (1.26.4)
Requirement already satisfied: scipy>=1.7.0 in /opt/mamba/lib/python3.11/site-packages (from plotnine) (1.13.0)
Requirement already satisfied: statsmodels>=0.14.0 in /opt/mamba/lib/python3.11/site-packages (from plotnine) (0.14.1)
Requirement already satisfied: contourpy>=1.0.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (1.2.1)
Requirement already satisfied: cycler>=0.10 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (4.51.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (1.4.5)
Requirement already satisfied: packaging>=20.0 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (23.2)
Requirement already satisfied: pillow>=8 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (10.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (3.1.2)
Requirement already satisfied: python-dateutil>=2.7 in /opt/mamba/lib/python3.11/site-packages (from matplotlib>=3.7.0->plotnine) (2.9.0)
Requirement already satisfied: pytz>=2020.1 in /opt/mamba/lib/python3.11/site-packages (from pandas<3.0.0,>=2.1.0->plotnine) (2024.1)
Requirement already satisfied: tzdata>=2022.7 in /opt/mamba/lib/python3.11/site-packages (from pandas<3.0.0,>=2.1.0->plotnine) (2024.1)
Requirement already satisfied: patsy>=0.5.4 in /opt/mamba/lib/python3.11/site-packages (from statsmodels>=0.14.0->plotnine) (0.5.6)
Requirement already satisfied: six in /opt/mamba/lib/python3.11/site-packages (from patsy>=0.5.4->statsmodels>=0.14.0->plotnine) (1.16.0)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Requirement already satisfied: great_tables in /opt/mamba/lib/python3.11/site-packages (0.10.0)
Requirement already satisfied: commonmark>=0.9.1 in /opt/mamba/lib/python3.11/site-packages (from great_tables) (0.9.1)
Requirement already satisfied: htmltools>=0.4.1 in /opt/mamba/lib/python3.11/site-packages (from great_tables) (0.5.3)
Requirement already satisfied: importlib-metadata in /opt/mamba/lib/python3.11/site-packages (from great_tables) (7.1.0)
Requirement already satisfied: typing-extensions>=3.10.0.0 in /opt/mamba/lib/python3.11/site-packages (from great_tables) (4.11.0)
Requirement already satisfied: numpy>=1.22.4 in /opt/mamba/lib/python3.11/site-packages (from great_tables) (1.26.4)
Requirement already satisfied: Babel>=2.13.1 in /opt/mamba/lib/python3.11/site-packages (from great_tables) (2.15.0)
Requirement already satisfied: importlib-resources in /opt/mamba/lib/python3.11/site-packages (from great_tables) (6.4.0)
Requirement already satisfied: packaging>=20.9 in /opt/mamba/lib/python3.11/site-packages (from htmltools>=0.4.1->great_tables) (23.2)
Requirement already satisfied: zipp>=0.5 in /opt/mamba/lib/python3.11/site-packages (from importlib-metadata->great_tables) (3.17.0)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Requirement already satisfied: wordcloud in /opt/mamba/lib/python3.11/site-packages (1.9.3)
Requirement already satisfied: numpy>=1.6.1 in /opt/mamba/lib/python3.11/site-packages (from wordcloud) (1.26.4)
Requirement already satisfied: pillow in /opt/mamba/lib/python3.11/site-packages (from wordcloud) (10.3.0)
Requirement already satisfied: matplotlib in /opt/mamba/lib/python3.11/site-packages (from wordcloud) (3.8.3)
Requirement already satisfied: contourpy>=1.0.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (1.2.1)
Requirement already satisfied: cycler>=0.10 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (4.51.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (1.4.5)
Requirement already satisfied: packaging>=20.0 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (23.2)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (3.1.2)
Requirement already satisfied: python-dateutil>=2.7 in /opt/mamba/lib/python3.11/site-packages (from matplotlib->wordcloud) (2.9.0)
Requirement already satisfied: six>=1.5 in /opt/mamba/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib->wordcloud) (1.16.0)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Collecting cleantext
  Downloading cleantext-1.1.4-py3-none-any.whl.metadata (3.5 kB)
Requirement already satisfied: nltk in /opt/mamba/lib/python3.11/site-packages (from cleantext) (3.8.1)
Requirement already satisfied: click in /opt/mamba/lib/python3.11/site-packages (from nltk->cleantext) (8.1.7)
Requirement already satisfied: joblib in /opt/mamba/lib/python3.11/site-packages (from nltk->cleantext) (1.3.2)
Requirement already satisfied: regex>=2021.8.3 in /opt/mamba/lib/python3.11/site-packages (from nltk->cleantext) (2023.12.25)
Requirement already satisfied: tqdm in /opt/mamba/lib/python3.11/site-packages (from nltk->cleantext) (4.66.2)
Downloading cleantext-1.1.4-py3-none-any.whl (4.9 kB)
Installing collected packages: cleantext
Successfully installed cleantext-1.1.4
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

1 Introduction

1.1 Rappel

Comme évoqué dans l’introduction de cette partie sur le traitement automatique du langage, l’objectif principal des techniques que nous allons explorer est la représentation synthétique du langage.

Le natural language processing (NLP) ou traitement automatisé du langage (TAL) en Français, vise à extraire de l’information de textes à partir d’une analyse statistique du contenu. Cette définition permet d’inclure de nombreux champs d’applications au sein du NLP (traduction, analyse de sentiment, recommandation, surveillance, etc. ).

Cette approche implique de transformer un texte, qui est une information compréhensible par un humain, en un nombre, information appropriée pour un ordinateur dans le cadre d’une approche statistique ou algorithmique.

Transformer une information textuelle en valeurs numériques propres à une analyse statistique n’est pas une tâche évidente. Les données textuelles sont non structurées puisque l’information cherchée, qui est propre à chaque analyse, est perdue au milieu d’une grande masse d’informations qui doit, de plus, être interprétée dans un certain contexte (un même mot ou une phrase n’ayant pas la même signification selon le contexte).

Si cette tâche n’était pas assez difficile comme ça, on peut ajouter d’autres difficultés propres à l’analyse textuelle car ces données sont :

  • bruitées : ortographe, fautes de frappe…
  • changeantes : la langue évolue avec de nouveaux mots, sens…
  • complexes : structures variables, accords…
  • ambiguës : synonymie, polysémie, sens caché…
  • propres à chaque langue : il n’existe pas de règle de passage unique entre deux langues
  • de grande dimension : des combinaisons infinies de séquences de mots

1.2 Objectif du chapitre

Dans ce chapitre, nous allons nous restreindre aux méthodes fréquentistes dans le paradigme bag of words. Celles-ci sont un peu old school par rapport aux approches plus raffinées que nous évoquerons ultérieurement. Néanmoins, les présenter nous permettra d’évoquer un certain nombre d’enjeux typiques des données textuelles qui restent centraux dans le NLP moderne.

Le principal enseignement à retenir de cette partie est que les données textuelles étant à très haute dimension - le langage étant un objet riche - nous avons besoin de méthodes pour réduire le bruit de nos corpus textuels afin de mieux prendre en compte le signal en leur sein.

Cette partie est une introduction s’appuyant sur quelques ouvrages classiques de la littérature française ou anglo-saxonne. Seront notamment présentées quelques librairies faisant parti de la boite à outil minimale des data scientists: NLTK et SpaCy. Les chapitres suivants permettront de se focaliser sur la modélisation du langage.

La librairie SpaCy

NTLK est la librairie historique d’analyse textuelle en Python. Elle existe depuis les années 1990. L’utilisation industrielle du NLP dans le monde de la data science est néanmoins plus récente et doit beaucoup à la collecte accrue de données non structurées par les réseaux sociaux. Cela a amené à un renouvellement du champ du NLP, tant dans le monde de la recherche que dans sa mise en application dans l’industrie de la donnée.

Le package spaCy est l’un des packages qui a permis cette industrialisation des méthodes de NLP. Conçu autour du concept de pipelines de données, il est beaucoup plus pratique à mettre en oeuvre pour une chaîne de traitement de données textuelles mettant en oeuvre plusieurs étapes de transformation des données.

1.3 Méthode

L’analyse textuelle vise à transformer le texte en données numériques manipulables. Pour cela il est nécessaire de se fixer une unité sémantique minimale. Cette unité textuelle peut être le mot ou encore une séquence de n mots (un ngram) ou encore une chaîne de caractères (e.g. la ponctuation peut être signifiante). On parle de token.

On peut ensuite utiliser diverses techniques (clustering, classification supervisée) suivant l’objectif poursuivi pour exploiter l’information transformée. Mais les étapes de nettoyage de texte sont indispensables. Sinon un algorithme sera incapable de détecter une information pertinente dans l’infini des possibles.

2 Bases d’exemple

2.1 Le Comte de Monte Cristo

La base d’exemple est le Comte de Monte Cristo d’Alexandre Dumas. Il est disponible gratuitement sur le site http://www.gutenberg.org (Project Gutemberg) comme des milliers d’autres livres du domaine public.

La manière la plus simple de le récupérer est de télécharger avec le package request le fichier texte et le retravailler légèrement pour ne conserver que le corpus du livre :

from urllib import request

url = "https://www.gutenberg.org/files/17989/17989-0.txt"
response = request.urlopen(url)
raw = response.read().decode("utf8")

dumas = raw.split(
    "*** START OF THE PROJECT GUTENBERG EBOOK LE COMTE DE MONTE-CRISTO, TOME I ***"
)[1].split(
    "*** END OF THE PROJECT GUTENBERG EBOOK LE COMTE DE MONTE-CRISTO, TOME I ***"
)[
    0
1]

import re


def clean_text(text):
    text = text.lower()  # mettre les mots en minuscule
    text = " ".join(text.split())
    return text


dumas = clean_text(dumas)

dumas[10000:10500]
1
On extrait de manière un petit peu simpliste le contenu de l’ouvrage
" mes yeux. --vous avez donc vu l'empereur aussi? --il est entré chez le maréchal pendant que j'y étais. --et vous lui avez parlé? --c'est-à-dire que c'est lui qui m'a parlé, monsieur, dit dantès en souriant. --et que vous a-t-il dit? --il m'a fait des questions sur le bâtiment, sur l'époque de son départ pour marseille, sur la route qu'il avait suivie et sur la cargaison qu'il portait. je crois que s'il eût été vide, et que j'en eusse été le maître, son intention eût été de l'acheter; mais je lu"

2.2 Le corpus anglo-saxon

Nous allons utiliser une base anglo-saxonne présentant trois auteurs de la littérature fantastique:

Les données sont disponibles sur un CSV mis à disposition sur Github. L’URL pour les récupérer directement est https://github.com/GU4243-ADS/spring2018-project1-ginnyqg/raw/master/data/spooky.csv.

Le fait d’avoir un corpus confrontant plusieurs auteurs nous permettra de comprendre la manière dont les nettoyages de données textuelles favorisent les analyses comparatives.

Nous pouvons utiliser le code suivant pour lire et préparer ces données:

import pandas as pd

url = "https://github.com/GU4243-ADS/spring2018-project1-ginnyqg/raw/master/data/spooky.csv"
# 1. Import des données
horror = pd.read_csv(url, encoding="latin-1")
# 2. Majuscules aux noms des colonnes
horror.columns = horror.columns.str.capitalize()
# 3. Retirer le prefixe id
horror["ID"] = horror["Id"].str.replace("id", "")
horror = horror.set_index("Id")

Le jeu de données met ainsi en regard un auteur avec une phrase qu’il a écrite :

horror.head()
Text Author ID
Id
id26305 This process, however, afforded me no means of... EAP 26305
id17569 It never once occurred to me that the fumbling... HPL 17569
id11008 In his left hand was a gold snuff box, from wh... EAP 11008
id27763 How lovely is spring As we looked from Windsor... MWS 27763
id12958 Finding nothing else, not even gold, the Super... HPL 12958

On peut se rendre compte que les extraits des 3 auteurs ne sont pas forcément équilibrés dans le jeu de données. Si on utilise ultérieurement ce corpus pour de la modélisation, il sera nécessaire de tenir compte de ce déséquilibre.

(horror.value_counts("Author").plot(kind="barh"))

3 Premières analyses de fréquence

L’approche usuelle en statistique, qui consiste à faire une analyse descriptive avant de mettre en oeuvre une modélisation, s’applique également à l’analyse de données textuelles. La fouille de documents textuels implique ainsi, en premier lieu, une analyse statistique afin de déterminer la structure du corpus.

Avant de s’adonner à une analyse systématique du champ lexical de chaque auteur, on va se focaliser dans un premier temps sur un unique mot, le mot fear.

3.1 Exploration ponctuelle

Tip

L’exercice ci-dessous présente une représentation graphique nommée waffle chart. Il s’agit d’une approche préférable aux camemberts (pie chart) qui sont des graphiques manipulables car l’oeil humain se laisse facilement berner par cette représentation graphique qui ne respecte pas les proportions.

Exercice 1 : Fréquence d’un mot

Dans un premier temps, nous allons nous concentrer sur notre corpus anglo-saxon (horror)

  1. Compter le nombre de phrases, pour chaque auteur, où apparaît le mot fear.
  2. Utiliser pywaffle pour obtenir les graphiques ci-dessous qui résument de manière synthétique le nombre d’occurrences du mot “fear” par auteur.
  3. Refaire l’analyse avec le mot “horror”.

Le comptage obtenu devrait être le suivant

wordtoplot
Author
EAP 70
HPL 160
MWS 211

Ceci permet d’obtenir le waffle chart suivant :

Figure 3.1: Répartition du terme fear dans le corpus de nos trois auteurs

On remarque ainsi de manière très intuitive le déséquilibre de notre jeu de données lorsqu’on se focalise sur le terme “peur” où Mary Shelley représente près de 50% des observations.

Si on reproduit cette analyse avec le terme “horror”, on retrouve la figure suivante:

Figure 3.2: Répartition du terme horror dans le corpus de nos trois auteurs

3.2 Transformation d’un texte en tokens

Dans l’exercice précédent, nous faisions une recherche ponctuelle, qui ne passe pas vraiment à l’échelle. Pour généraliser cette approche, on découpe généralement un corpus en unités sémantiques indépendantes : les tokens.

Tip

Nous allons avoir besoin d’importer un certain nombre de corpus prêts à l’emploi pour utiliser les librairies NTLK ou SpaCy.

Pour la première, nous allons avoir besoin de faire tourner ce code

import nltk

nltk.download("stopwords")
nltk.download("punkt")
nltk.download("genesis")
nltk.download("wordnet")
nltk.download("omw-1.4")

Pour la seconde,

!python -m spacy download fr_core_news_sm
!python -m spacy download en_core_web_sm

Plutôt que d’implémenter soi-même un tokenizer inefficace, il est plus approprié d’en appliquer un issu d’une librairie spécialisée. Historiquement, le plus simple était de prendre le tokenizer de NLTK, la librairie historique de text mining en Python:

from nltk.tokenize import word_tokenize

word_tokenize(dumas[10000:10500])

Comme on le voit, cette librairie ne fait pas les choses dans le détail et a quelques incohérences: j'y étais est séparé en 4 sèmes (['j', "'", 'y', 'étais']) là où l'acheter reste un unique sème. NLTK est en effet une librairie anglo-saxonne et l’algorithme de séparation n’est pas toujours adapté aux règles grammaticales françaises. Il vaut mieux dans ce cas privilégier SpaCy, la librairie plus récente pour faire ce type de tâche. En plus d’être très bien documentée, elle est mieux adaptée pour les langues non anglo-saxonnes. En l’occurrence, comme le montre l’exemple de la documentation sur les tokenizers, l’algorithme de séparation présente un certain raffinement

Exemple d’algorithme de tokenisation

Exemple d’algorithme de tokenisation

Celui-ci peut être appliqué de cette manière:

import spacy
from spacy.tokenizer import Tokenizer

nlp = spacy.load("fr_core_news_sm")
doc = nlp(dumas[10000:10500])

text_tokenized = []
for token in doc:
    text_tokenized += [token.text]

", ".join(text_tokenized)
" , mes, yeux, ., --vous, avez, donc, vu, l', empereur, aussi, ?, --il, est, entré, chez, le, maréchal, pendant, que, j', y, étais, ., --et, vous, lui, avez, parlé, ?, --c', est, -, à, -, dire, que, c', est, lui, qui, m', a, parlé, ,, monsieur, ,, dit, dantès, en, souriant, ., --et, que, vous, a, -t, -il, dit, ?, --il, m', a, fait, des, questions, sur, le, bâtiment, ,, sur, l', époque, de, son, départ, pour, marseille, ,, sur, la, route, qu', il, avait, suivie, et, sur, la, cargaison, qu', il, portait, ., je, crois, que, s', il, eût, été, vide, ,, et, que, j', en, eusse, été, le, maître, ,, son, intention, eût, été, de, l', acheter, ;, mais, je, lu"

Comme on peut le voir, il reste encore beaucoup d’éléments polluants notre structuration de corpus, à commencer par la ponctuation. Nous allons néanmoins pouvoir facilement retirer ceux-ci ultérieurement, comme nous le verrons.

3.3 Le nuage de mot: une première analyse généralisée

A ce stade, nous n’avons encore aucune appréhension de la structure de notre corpus : nombre de mots, mots les plus représentés, etc.

Pour se faire une idée de la structure de notre corpus, on peut commencer par compter la distribution des mots dans l’oeuvre de Dumas. Commençons par le début de l’oeuvre, à savoir les 30 000 premiers mots et comptons les mots uniques :

from collections import Counter

doc = nlp(dumas[:30000])

# Extract tokens, convert to lowercase and filter out punctuation and spaces
tokens = [
    token.text.lower() for token in doc if not token.is_punct and not token.is_space
]

# Count the frequency of each token
token_counts = Counter(tokens)

Nous avons déjà de nombreux mots différents dans le début de l’oeuvre.

len(token_counts)
1401

Nous voyons la haute dimensionnalité du corpus puisque nous avons près de 1500 mots différents sur les 30 000 premiers mots de l’oeuvre de Dumas.

token_count_all = list(token_counts.items())

# Create a DataFrame from the list of tuples
token_count_all = pd.DataFrame(token_count_all, columns=["word", "count"])

Si on regarde la distribution de la fréquence des mots, exercice que nous prolongerons ultérieurement en évoquant la loi de Zipf, nous pouvons voir que de nombreux mots sont unique (près de la moitié des mots), que la densité de fréquence descend vite et qu’il faudrait se concentrer un peu plus sur la queue de distribution que ne le permet la figure suivante :

from plotnine import *

(ggplot(token_count_all) + geom_histogram(aes(x="count")) + scale_x_log10())
/opt/mamba/lib/python3.11/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 42'. Pick better value with 'binwidth'.

Maintenant, si on regarde les 25 mots les plus fréquents, on peut voir que ceux-ci ne sont pas très intéressants pour analyser le sens de notre document :

# Sort the tokens by frequency in descending order
sorted_token_counts = token_counts.most_common(25)
sorted_token_counts = pd.DataFrame(sorted_token_counts, columns=["word", "count"])
/tmp/ipykernel_5509/3224890599.py:18: MapWithoutReturnDtypeWarning: Calling `map_elements` without specifying `return_dtype` can lead to unpredictable results. Specify `return_dtype` to silence this warning.
/tmp/ipykernel_5509/3224890599.py:23: MapWithoutReturnDtypeWarning: Calling `map_elements` without specifying `return_dtype` can lead to unpredictable results. Specify `return_dtype` to silence this warning.
Mot Nombre d'occurrences
de 176
le 149
et 124
à 116
l' 102
que 100
vous 88
la 83
il 81
un 77
je 73
en 70
est 61
qui 53
dantès 53
d' 50
dit 50
les 49
du 46
a 41
ne 37
n' 37
mon 37
son 36
pas 36
Nombre d'apparitions sur les 30 000 premiers caractères du Comte de Monte Cristo

Si on représente graphiquement ce classement

(
    ggplot(sorted_token_counts, aes(x="word", y="count"))
    + geom_point(stat="identity", size=3, color="red")
    + scale_x_discrete(limits=sorted_token_counts.sort_values("count")["word"].tolist())
    + coord_flip()
    + theme_minimal()
    + labs(title="Word Frequency", x="Word", y="Count")
)

Nous nous concentrerons ultérieurement sur ces mots-valises car il sera important d’en tenir compte pour les analyses approfondies de nos documents.

Nous avons pu, par ces décomptes de mots, avoir une première intutition de la nature de notre corpus. Néanmoins, une approche un peu plus visuelle serait pertinente pour avoir un peu plus d’intuitions. Les nuages de mots (wordclouds) sont des représentations graphiques assez pratiques pour visualiser les mots les plus fréquents, lorsqu’elles ne sont pas utilisées à tort et à travers. Les wordclouds sont très simples à implémenter en Python avec le module Wordcloud. Quelques paramètres de mise en forme permettent même d’ajuster la forme du nuage à une image.

Exercice 3 : Wordcloud
  1. En utilisant la fonction wordCloud, faire trois nuages de mot pour représenter les mots les plus utilisés par chaque auteur du corpus horror1.
  2. Faire un nuage de mot du corpus dumas en utilisant un masque comme celui-ci
Exemple de masque pour la question 2

URL de l’image: https://minio.lab.sspcloud.fr/lgaliana/generative-art/pythonds/book.png

URL de l’image: https://minio.lab.sspcloud.fr/lgaliana/generative-art/pythonds/book.png

Les nuages de points obtenus à la question 1 sont les suivants:

Figure 3.3: Lovercraft
Figure 3.4: Poe
Figure 3.5: Shelley

Alors que celui obtenu à partir de l’oeuvre de Dumas prend la forme

Figure 3.6: Nuage de mot produit à partir du Comte de Monte Cristo

Si nous n’en étions pas convaincus, ces visualisations montrent clairement qu’il est nécessaire de nettoyer notre texte. Par exemple, en ce qui concerne l’oeuvre du Dumas, le nom du personnage principal, Dantès, est ainsi masqué par un certain nombre d’articles ou mots de liaison qui perturbent l’analyse. En ce qui concerne le corpus anglo-saxon, ce sont des termes similaires, comme “the”, “of”, etc.

Ces mots sont des stop words. Ceci est une démonstration par l’exemple qu’il vaut mieux nettoyer le texte avant de l’analyser (sauf si on est intéressé par la loi de Zipf, cf. exercice suivant).

3.4 Aparté: la loi de Zipf

Zipf, dans les années 1930, a remarqué une régularité statistique dans Ulysse, l’oeuvre de Joyce. Le mot le plus fréquent apparaissait \(x\) fois, le deuxième mot le plus fréquent 2 fois moins, le suivant 3 fois moins que le premier, etc. D’un point de vue statistique, cela signifie que la fréquence d’occurrence \(f(n_i)\) d’un mot est liée à son rang \(n_i\) dans l’ordre des fréquences par une loi de la forme

\[f(n_i) = c/n_i\]

\(c\) est une constante.

Plus généralement, on peut dériver la loi de Zipf d’une distribution exponentiellement décroissante des fréquences : \(f(n_i) = cn_{i}^{-k}\). Sur le plan empirique, cela signifie qu’on peut utiliser les régressions poissonniennes pour estimer les paramètres de la loi, ce qui prend la spécification suivante

\[ \mathbb{E}\bigg( f(n_i)|n_i \bigg) = \exp(\beta_0 + \beta_1 \log(n_i)) \]

Les modèles linéaires généralisés (GLM) permettent de faire ce type de régression. En Python, ils sont disponibles par le biais du package statsmodels, dont les sorties sont très inspirées des logiciels payants spécialisés dans l’économétrie comme Stata.

count_words = pd.DataFrame(
    {
        "counter": horror.groupby("Author")
        .apply(lambda s: " ".join(s["Text"]).split())
        .apply(lambda s: Counter(s))
        .apply(lambda s: s.most_common())
        .explode()
    }
)
count_words[["word", "count"]] = pd.DataFrame(
    count_words["counter"].tolist(), index=count_words.index
)
count_words = count_words.reset_index()

count_words = count_words.assign(
    tot_mots_auteur=lambda x: (x.groupby("Author")["count"].transform("sum")),
    freq=lambda x: x["count"] / x["tot_mots_auteur"],
    rank=lambda x: x.groupby("Author")["count"].transform("rank", ascending=False),
)
/tmp/ipykernel_5509/4009929367.py:3: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.

Commençons par représenter la relation entre la fréquence et le rang:

from plotnine import *

g = (
    ggplot(count_words)
    + geom_point(aes(y="freq", x="rank", color="Author"), alpha=0.4)
    + scale_x_log10()
    + scale_y_log10()
    + theme_minimal()
)

Nous avons bien, graphiquement, une relation log-linéaire entre les deux :

Avec statsmodels, vérifions plus formellement cette relation:

import statsmodels.api as sm
import numpy as np

exog = sm.add_constant(np.log(count_words["rank"].astype(float)))

model = sm.GLM(
    count_words["freq"].astype(float), exog, family=sm.families.Poisson()
).fit()

# Afficher les résultats du modèle
print(model.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                   freq   No. Observations:                69301
Model:                            GLM   Df Residuals:                    69299
Model Family:                 Poisson   Df Model:                            1
Link Function:                    Log   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -23.011
Date:                Fri, 26 Jul 2024   Deviance:                     0.065676
Time:                        12:06:42   Pearson chi2:                   0.0656
No. Iterations:                     5   Pseudo R-squ. (CS):          0.0002431
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const         -2.4388      1.089     -2.239      0.025      -4.574      -0.303
rank          -0.9831      0.189     -5.196      0.000      -1.354      -0.612
==============================================================================

Le coefficient de la régression est presque 1 ce qui suggère bien une relation quasiment log-linéaire entre le rang et la fréquence d’occurrence d’un mot. Dit autrement, le mot le plus utilisé l’est deux fois plus que le deuxième mot le plus fréquent qui l’est trois plus que le troisième, etc. On retrouve bien empiriquement cette loi sur ce corpus de trois auteurs.

4 Nettoyage de textes

4.1 Retirer les stop words

Nous l’avons vu, que ce soit en Français ou Anglais, un certain nombre de mots de liaisons, nécessaires sur le plan grammatical mais peu porteur d’information, nous empêchent de saisir les principaux mots vecteurs d’information dans notre corpus.

Il est donc nécessaire de nettoyer notre corpus en retirant ces termes. Ce travail de nettoyage va d’ailleurs au-delà d’un simple retrait de mots. C’est également l’occasion de retirer d’autres sèmes gênants, par exemple la ponctuation.

Commençons par télécharger le corpus de stopwords

import nltk

nltk.download("stopwords")
[nltk_data] Downloading package stopwords to /github/home/nltk_data...
[nltk_data]   Package stopwords is already up-to-date!
True

La liste des stopwords anglais dans NLTK est la suivante:

from nltk.corpus import stopwords

", ".join(stopwords.words("english"))
"i, me, my, myself, we, our, ours, ourselves, you, you're, you've, you'll, you'd, your, yours, yourself, yourselves, he, him, his, himself, she, she's, her, hers, herself, it, it's, its, itself, they, them, their, theirs, themselves, what, which, who, whom, this, that, that'll, these, those, am, is, are, was, were, be, been, being, have, has, had, having, do, does, did, doing, a, an, the, and, but, if, or, because, as, until, while, of, at, by, for, with, about, against, between, into, through, during, before, after, above, below, to, from, up, down, in, out, on, off, over, under, again, further, then, once, here, there, when, where, why, how, all, any, both, each, few, more, most, other, some, such, no, nor, not, only, own, same, so, than, too, very, s, t, can, will, just, don, don't, should, should've, now, d, ll, m, o, re, ve, y, ain, aren, aren't, couldn, couldn't, didn, didn't, doesn, doesn't, hadn, hadn't, hasn, hasn't, haven, haven't, isn, isn't, ma, mightn, mightn't, mustn, mustn't, needn, needn't, shan, shan't, shouldn, shouldn't, wasn, wasn't, weren, weren't, won, won't, wouldn, wouldn't"

Celle de SpaCy est plus riche (nous avons déjà téléchargé le corpus en_core_web_sm en question):

nlp_english = spacy.load("en_core_web_sm")
stop_words_english = nlp_english.Defaults.stop_words
", ".join(stop_words_english)
"not, done, above, hers, noone, ourselves, without, bottom, latter, who, i, onto, move, nevertheless, show, much, yourself, together, meanwhile, whatever, beyond, from, becomes, last, anyone, such, fifty, back, therein, have, after, being, him, whereupon, ‘re, until, ’ve, now, hundred, less, the, once, call, put, whom, over, only, make, just, by, been, thereby, four, whoever, own, two, more, why, is, even, doing, a, herein, whose, within, empty, must, mine, afterwards, between, around, somewhere, nothing, her, nobody, beside, moreover, none, thence, eight, wherein, rather, six, would, does, as, hereupon, had, whence, please, sometime, on, and, somehow, further, via, yours, ‘s, re, beforehand, although, thus, n‘t, what, during, no, three, those, however, each, using, anything, keep, 're, next, than, you, off, ’ll, already, with, former, thereafter, serious, something, see, regarding, it, else, here, should, still, seem, ten, often, enough, hereby, made, someone, where, do, am, are, when, get, ever, anyway, become, say, themselves, seems, never, our, them, whole, twenty, same, five, has, one, formerly, there, well, my, ’d, in, again, give, thru, below, or, ‘ve, used, against, everywhere, so, before, ’s, 've, amongst, could, 'd, towards, your, yourselves, though, yet, really, others, some, many, several, always, ’m, whereas, about, these, down, became, this, seemed, can, few, nor, neither, other, their, ’re, too, ‘ll, either, except, front, otherwise, anyhow, its, cannot, if, ‘d, almost, latterly, itself, quite, ours, twelve, top, perhaps, among, n’t, mostly, did, up, nine, then, hence, therefore, whither, us, will, besides, take, sixty, part, alone, she, sometimes, seeming, might, amount, also, behind, herself, he, because, due, 's, fifteen, we, 'll, first, while, ca, whenever, which, any, whereafter, hereafter, throughout, namely, nowhere, an, ‘m, becoming, everything, they, into, how, for, forty, but, another, through, whereby, elsewhere, be, eleven, were, since, third, his, least, may, under, both, go, me, every, of, upon, 'm, along, n't, everyone, himself, was, to, full, unless, myself, most, thereupon, indeed, side, wherever, very, all, across, per, at, that, out, whether, name, anywhere, various, toward"

Si cette fois on prend la liste des stopwords français dans NLTK:

", ".join(stopwords.words("french"))
'au, aux, avec, ce, ces, dans, de, des, du, elle, en, et, eux, il, ils, je, la, le, les, leur, lui, ma, mais, me, même, mes, moi, mon, ne, nos, notre, nous, on, ou, par, pas, pour, qu, que, qui, sa, se, ses, son, sur, ta, te, tes, toi, ton, tu, un, une, vos, votre, vous, c, d, j, l, à, m, n, s, t, y, été, étée, étées, étés, étant, étante, étants, étantes, suis, es, est, sommes, êtes, sont, serai, seras, sera, serons, serez, seront, serais, serait, serions, seriez, seraient, étais, était, étions, étiez, étaient, fus, fut, fûmes, fûtes, furent, sois, soit, soyons, soyez, soient, fusse, fusses, fût, fussions, fussiez, fussent, ayant, ayante, ayantes, ayants, eu, eue, eues, eus, ai, as, avons, avez, ont, aurai, auras, aura, aurons, aurez, auront, aurais, aurait, aurions, auriez, auraient, avais, avait, avions, aviez, avaient, eut, eûmes, eûtes, eurent, aie, aies, ait, ayons, ayez, aient, eusse, eusses, eût, eussions, eussiez, eussent'

On voit que celle-ci n’est pas très riche et mériterait d’être plus complète. Celle de SpaCy correspond mieux à ce qu’on attend

stop_words_french = nlp.Defaults.stop_words
", ".join(stop_words_french)
"afin, y, sa, que, voila, vôtres, semblable, toi, antérieure, comme, auraient, i, suivantes, celle, dit, hem, autre, tellement, être, te, semble, dix-neuf, peux, houp, seulement, memes, spécifiques, cinquième, parlent, cependant, serait, té, malgré, autrement, vous, semblent, pu, vont, personne, désormais, toi-meme, mille, etaient, onze, assez, derrière, doit, enfin, notamment, toujours, directement, tous, avoir, revoilà, quelles, dans, dessus, dessous, où, treize, ô, moi-meme, faisant, car, quinze, a, quatre-vingt, rendre, anterieur, revoila, soi-meme, uns, certains, douzième, près, celles-la, combien, deux, allaient, malgre, permet, moi-même, étant, maint, telle, quatrièmement, ceci, quelconque, la, pour, sept, ouvert, seize, ma, trois, semblaient, si, moins, quarante, etre, devra, lors, hormis, également, façon, six, aura, ait, cinquante, celui-la, ai, suivante, as, etait, duquel, delà, mien, quand, on, l’, soixante, ont, exactement, selon, attendu, quelques, doivent, dès, ou, desquels, en, miennes, via, c', celle-ci, ceux, fais, specifique, suffisante, siens, toutes, quelque, â, ces, son, pendant, plus, faisaient, s’, celui-ci, puis, tienne, nombreuses, suivre, basee, n', dejà, parfois, vingt, ouias, possible, vos, j', certaines, sera, pas, votres, pense, da, ni, dite, c’, o, eux, hui, debout, votre, concernant, maintenant, depuis, tenant, vers, chez, entre, etais, tout, excepté, dix, nôtre, lès, anterieure, là, outre, possibles, compris, egalement, ton, procedant, sait, sienne, sont, mêmes, des, directe, tiens, derriere, n’, autrui, suit, sent, douze, certes, sinon, différentes, cinq, ta, lui-même, suivants, laisser, quatrième, s', deja, aupres, dire, ne, parler, voici, quoi, peuvent, celle-la, stop, moindres, mais, plutot, neanmoins, seraient, retour, les, elle-même, auxquels, jusque, partant, plutôt, bas, restent, anterieures, celles-là, envers, feront, ceux-là, certain, autres, gens, chacun, onzième, premier, celui, or, pourrait, avait, eu, soi, tiennes, avant, revoici, différents, sous, quant-à-soi, septième, au, premièrement, puisque, celui-là, sixième, cette, elles-memes, surtout, cinquantième, specifiques, dixième, chacune, se, notre, hors, m’, suffit, vas, troisièmement, ès, ouverte, pres, miens, tel, abord, divers, precisement, préalable, tres, siennes, tien, est, ce, aux, etant, parce, pourquoi, eh, dont, quel, donc, déjà, fait, avaient, encore, nos, tente, differentes, differents, ouste, ci, toi-même, auxquelles, importe, nous, m', touchant, peut, d’, qu', certaine, l', ouverts, leur, quoique, seront, très, suis, elle, soi-même, sauf, cet, deuxième, hi, differente, pourrais, ayant, tenir, lesquels, effet, plusieurs, suffisant, une, devers, peu, vu, aussi, vais, ainsi, ses, j’, comment, lequel, hou, va, cent, tant, il, néanmoins, jusqu, seule, par, lorsque, elles-mêmes, dits, ils, desormais, vôtre, antérieur, celles, étais, alors, antérieures, mes, merci, longtemps, lui, seules, précisement, apres, tu, quant, font, parle, soit, celle-là, deuxièmement, tes, na, diverses, avec, huit, qu’, dix-sept, auront, tend, rend, sur, vé, et, après, chaque, sans, different, quels, diverse, restant, étaient, je, aie, elles, cela, reste, ha, seul, quatorze, souvent, juste, auquel, hé, environ, huitième, dix-huit, toute, vous-mêmes, hep, le, quatre, t', meme, un, spécifique, es, leurs, quelqu'un, etc, nombreux, voilà, avais, même, sien, différent, à, pouvait, cinquantaine, seuls, relativement, proche, nouveau, durant, aurait, d', facon, nul, avons, telles, elle-meme, nous-mêmes, me, mon, lesquelles, allons, tels, ah, ça, neuvième, troisième, trente, laquelle, dehors, prealable, lui-meme, eux-mêmes, quiconque, était, moi, ceux-ci, différente, parmi, première, qui, bat, dedans, desquelles, quelle, suivant, mienne, de, relative, hue, ho, devant, t’, nôtres, unes, celles-ci, déja, du"
Exercice 4 : Nettoyage du texte
  1. Reprendre l’ouvrage de Dumas et nettoyer celui-ci avec Spacy. Refaire le nuage de mots et conclure.
  2. Faire ce même exercice sur le jeu de données anglo-saxon. Idéalement, vous devriez être en mesure d’utiliser la fonctionnalité de pipeline de SpaCy.

Ces retraitements commencent à porter leurs fruits puisque des mots ayant plus de sens commencent à se dégager, notamment les noms des personnages (Dantès, Danglart, etc.):

Figure 4.1: Nuage de mot produit à partir du Comte de Monte Cristo après nettoyage
Figure 4.2: Lovercraft
Figure 4.3: Poe
Figure 4.4: Shelley

4.2 Racinisation et lemmatisation

Pour aller plus loin dans l’harmonisation d’un texte, il est possible de mettre en place des classes d’équivalence entre des mots. Par exemple, quand on désire faire une analyse fréquentiste, on peut être intéressé par considérer que “cheval” et “chevaux” sont équivalents. Selon les cas, différentes formes d’un même mot (pluriel, singulier, conjugaison) pourront être considérées comme équivalentes et seront remplacées par une même forme dite canonique.

Il existe deux approches dans le domaine :

  • la lemmatisation qui requiert la connaissance des statuts grammaticaux (exemple : “chevaux” devient “cheval”) ;
  • la racinisation (stemming) plus fruste mais plus rapide, notamment en présence de fautes d’orthographes. Dans ce cas, “chevaux” peut devenir “chev” mais être ainsi confondu avec “chevet” ou “cheveux”.

Cette approche a l’avantage de réduire la taille du vocabulaire à maîtriser pour l’ordinateur et le modélisateur. Il existe plusieurs algorithmes de stemming, notamment le Porter Stemming Algorithm ou le Snowball Stemming Algorithm.

Note

Pour disposer du corpus nécessaire à la lemmatisation, il faut, la première fois, télécharger celui-ci grâce aux commandes suivantes :

import nltk

nltk.download("wordnet")
nltk.download("omw-1.4")

Prenons cette chaine de caractère,

"naples. comme d'habitude, un pilote côtier partit aussitôt du port, rasa le château d'if, et alla aborder le navire entre le cap de morgion et l'île de rion. aussitôt, co"

La version racinisée est la suivante:

"napl,.,comm,d'habitud,,,un,pilot,côti,part,aussitôt,du,port,,,ras,le,château,d'if,,,et,alla,abord,le,navir,entre,le,cap,de,morgion,et,l'îl,de,rion,.,aussitôt,,,co"

A ce niveau, les mots commencent à être moins intelligibles par un humain mais peuvent rester intelligible pour la machine. Ce choix n’est néanmoins pas neutre et sa pertinence dépend du cas d’usage.

Les lemmatiseurs permettront des harmonisations plus subtiles. Ils s’appuient sur des bases de connaissance, par exemple WordNet, une base lexicographique ouverte. Par exemple, les mots “women”, “daughters” et “leaves” seront ainsi lemmatisés de la manière suivante :

from nltk.stem import WordNetLemmatizer

lemm = WordNetLemmatizer()

for word in ["women", "daughters", "leaves"]:
    print(f"The lemmatized form of {word} is: {lemm.lemmatize(word)}")
The lemmatized form of women is: woman
The lemmatized form of daughters is: daughter
The lemmatized form of leaves is: leaf
Exercice 5 : Lemmatisation avec nltk

Sur le modèle précédent, utiliser un WordNetLemmatizer sur le corpus dumas[1030:1200] et observer le résultat.

La version lemmatisée de ce petit morceau de l’oeuvre de Dumas est la suivante:

"naples, ., comme, d'habitude, ,, un, pilote, côtier, partit, aussitôt, du, port, ,, rasa, le, château, d'if, ,, et, alla, aborder, le, navire, entre, le, cap, de, morgion, et, l'île, de, rion, ., aussitôt, ,, co"

4.3 Limite

Dans les approches fréquentistes, où on recherche la proximité entre des textes par la co-occurrence de termes, cette question de la création de classes d’équivalence est fondamentale. Les mots sont identiques ou différents, il n’y a pas de différence subtile entre eux. Par exemple, on devra soit déclarer que “python” et “pythons” sont équivalents, soient qu’ils sont différents, sans différence de degré entre “pythons”, “anaconda” ou “table” par rapport à “python”. Les approches modernes, non plus exclusivement basées sur des fréquences d’apparition, permettront d’introduire de la subtilité dans la synthétisation de l’information présente dans les données textuelles.

Informations additionnelles

environment files have been tested on.

Latest built version: 2024-07-26

Python version used:

'3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:40:35) [GCC 12.3.0]'
Package Version
affine 2.4.0
aiobotocore 2.12.2
aiohttp 3.9.3
aioitertools 0.11.0
aiosignal 1.3.1
alembic 1.13.1
aniso8601 9.0.1
annotated-types 0.7.0
appdirs 1.4.4
archspec 0.2.3
astroid 3.1.0
asttokens 2.4.1
attrs 23.2.0
Babel 2.15.0
bcrypt 4.1.2
beautifulsoup4 4.12.3
black 24.4.2
blinker 1.7.0
blis 0.7.11
bokeh 3.4.0
boltons 23.1.1
boto3 1.34.51
botocore 1.34.51
branca 0.7.1
Brotli 1.1.0
cachetools 5.3.3
cartiflette 0.0.2
Cartopy 0.23.0
catalogue 2.0.10
cattrs 23.2.3
certifi 2024.2.2
cffi 1.16.0
charset-normalizer 3.3.2
chromedriver-autoinstaller 0.6.4
cleantext 1.1.4
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.18.1
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
commonmark 0.9.1
conda 24.3.0
conda-libmamba-solver 24.1.0
conda-package-handling 2.2.0
conda_package_streaming 0.9.0
confection 0.1.5
contextily 1.6.0
contourpy 1.2.1
cryptography 42.0.5
cycler 0.12.1
cymem 2.0.8
cytoolz 0.12.3
dask 2024.4.1
dask-expr 1.0.10
debugpy 1.8.1
decorator 5.1.1
dill 0.3.8
distributed 2024.4.1
distro 1.9.0
docker 7.0.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et-xmlfile 1.1.0
exceptiongroup 1.2.0
executing 2.0.1
fastjsonschema 2.19.1
fiona 1.9.6
flake8 7.0.0
Flask 3.0.2
folium 0.16.0
fontawesomefree 6.6.0
fonttools 4.51.0
fr-core-news-sm 3.7.0
frozenlist 1.4.1
fsspec 2023.12.2
GDAL 3.8.4
gensim 4.3.2
geographiclib 2.0
geopandas 0.12.2
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.29.0
graphene 3.3
graphql-core 3.2.3
graphql-relay 3.2.0
graphviz 0.20.3
great-tables 0.10.0
greenlet 3.0.3
gunicorn 21.2.0
h11 0.14.0
htmltools 0.5.3
hvac 2.1.0
idna 3.6
imageio 2.34.2
importlib_metadata 7.1.0
importlib_resources 6.4.0
inflate64 1.0.0
ipykernel 6.29.3
ipython 8.22.2
ipywidgets 8.1.2
isort 5.13.2
itsdangerous 2.1.2
jedi 0.19.1
Jinja2 3.1.3
jmespath 1.0.1
joblib 1.3.2
jsonpatch 1.33
jsonpointer 2.4
jsonschema 4.21.1
jsonschema-specifications 2023.12.1
jupyter-cache 1.0.0
jupyter_client 8.6.1
jupyter_core 5.7.2
jupyterlab_widgets 3.0.10
kaleido 0.2.1
kiwisolver 1.4.5
kubernetes 29.0.0
langcodes 3.4.0
language_data 1.2.0
lazy_loader 0.4
libmambapy 1.5.7
llvmlite 0.42.0
locket 1.0.0
lxml 5.2.2
lz4 4.3.3
Mako 1.3.2
mamba 1.5.7
mapclassify 2.6.1
marisa-trie 1.2.0
Markdown 3.6
markdown-it-py 3.0.0
MarkupSafe 2.1.5
matplotlib 3.8.3
matplotlib-inline 0.1.6
mccabe 0.7.0
mdurl 0.1.2
menuinst 2.0.2
mercantile 1.2.1
mizani 0.11.4
mlflow 2.11.3
mlflow-skinny 2.11.3
msgpack 1.0.7
multidict 6.0.5
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.10
mypy 1.9.0
mypy-extensions 1.0.0
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.8.1
numba 0.59.1
numpy 1.26.4
oauthlib 3.2.2
opencv-python-headless 4.9.0.80
openpyxl 3.1.5
outcome 1.3.0.post0
OWSLib 0.28.1
packaging 23.2
pandas 2.2.1
paramiko 3.4.0
parso 0.8.4
partd 1.4.1
pathspec 0.12.1
patsy 0.5.6
Pebble 5.0.7
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.3.0
pip 24.0
pkgutil_resolve_name 1.3.10
platformdirs 4.2.0
plotly 5.19.0
plotnine 0.13.6
pluggy 1.4.0
polars 0.20.31
preshed 3.0.9
prometheus_client 0.20.0
prometheus-flask-exporter 0.23.0
prompt-toolkit 3.0.42
protobuf 4.25.3
psutil 5.9.8
ptyprocess 0.7.0
pure-eval 0.2.2
py7zr 0.20.8
pyarrow 15.0.0
pyarrow-hotfix 0.6
pyasn1 0.5.1
pyasn1-modules 0.3.0
pybcj 1.0.2
pycodestyle 2.11.1
pycosat 0.6.6
pycparser 2.21
pycryptodomex 3.20.0
pydantic 2.8.2
pydantic_core 2.20.1
pyflakes 3.2.0
Pygments 2.17.2
PyJWT 2.8.0
pylint 3.1.0
PyNaCl 1.5.0
pynsee 0.1.7
pyOpenSSL 24.0.0
pyparsing 3.1.2
pyppmd 1.1.0
pyproj 3.6.1
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.1
PyYAML 6.0.1
pyzmq 25.1.2
pyzstd 0.16.0
QtPy 2.4.1
querystring-parser 1.2.4
rasterio 1.3.10
referencing 0.34.0
regex 2023.12.25
requests 2.31.0
requests-cache 1.2.1
requests-oauthlib 2.0.0
rich 13.7.1
rpds-py 0.18.0
rsa 4.9
Rtree 1.2.0
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.1
scikit-image 0.24.0
scikit-learn 1.4.1.post1
scipy 1.13.0
seaborn 0.13.2
selenium 4.23.1
setuptools 69.2.0
shapely 2.0.3
shellingham 1.5.4
six 1.16.0
smart_open 7.0.4
smmap 5.0.0
sniffio 1.3.1
snuggs 1.4.7
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.5
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.29
sqlparse 0.4.4
srsly 2.4.8
stack-data 0.6.2
statsmodels 0.14.1
tabulate 0.9.0
tblib 3.0.0
tenacity 8.2.3
texttable 1.7.0
thinc 8.2.5
threadpoolctl 3.4.0
tifffile 2024.7.24
tomli 2.0.1
tomlkit 0.12.4
toolz 0.12.1
topojson 1.9
tornado 6.4
tqdm 4.66.2
traitlets 5.14.2
trio 0.26.0
trio-websocket 0.11.1
truststore 0.8.0
typer 0.12.3
typing_extensions 4.11.0
tzdata 2024.1
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.18
wasabi 1.1.3
wcwidth 0.2.13
weasel 0.4.1
webdriver-manager 4.0.2
websocket-client 1.8.0
Werkzeug 3.0.2
wheel 0.43.0
widgetsnbextension 4.0.10
wordcloud 1.9.3
wrapt 1.16.0
wsproto 1.2.0
xgboost 2.0.3
xlrd 2.0.1
xyzservices 2024.4.0
yarl 1.9.4
yellowbrick 1.5
zict 3.0.0
zipp 3.17.0
zstandard 0.22.0

View file history

SHA Date Author Description
8d23a53 2024-07-10 18:45:54 Julien PRAMIL Modifs 02_exoclean.qmd (#523)
7595008 2024-07-08 17:24:29 Julien PRAMIL Changes into NLP/01_intro.qmd (#517)
56b6442 2024-07-08 15:05:57 Lino Galiana Version anglaise du chapitre numpy (#516)
a3dc832 2024-06-24 16:15:19 Lino Galiana Improve homepage images (#508)
e660d76 2024-06-19 14:31:09 linogaliana improve output NLP 1
4f41cf6 2024-06-14 15:00:41 Lino Galiana Une partie sur les sacs de mots plus cohérente (#501)
06d003a 2024-04-23 10:09:22 Lino Galiana Continue la restructuration des sous-parties (#492)
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3437373 2023-12-16 20:11:06 Lino Galiana Améliore l’exercice sur le LASSO (#473)
4cd44f3 2023-12-11 17:37:50 Antoine Palazzolo Relecture NLP (#474)
deaafb6 2023-12-11 13:44:34 Thomas Faria Relecture Thomas partie NLP (#472)
4c060a1 2023-12-01 17:44:17 Lino Galiana Update book image location
1f23de2 2023-12-01 17:25:36 Lino Galiana Stockage des images sur S3 (#466)
a1ab3d9 2023-11-24 10:57:02 Lino Galiana Reprise des chapitres NLP (#459)
a771183 2023-10-09 11:27:45 Antoine Palazzolo Relecture TD2 par Antoine (#418)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
8082302 2023-08-25 17:48:36 Lino Galiana Mise à jour des scripts de construction des notebooks (#395)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
f2905a7 2023-08-11 17:24:57 Lino Galiana Introduction de la partie NLP (#388)
78ea2cb 2023-07-20 20:27:31 Lino Galiana Change titles levels (#381)
a9b384e 2023-07-18 18:07:16 Lino Galiana Sépare les notebooks (#373)
29ff3f5 2023-07-07 14:17:53 linogaliana description everywhere
f21a24d 2023-07-02 10:58:15 Lino Galiana Pipeline Quarto & Pages 🚀 (#365)
164fa68 2022-11-30 09:13:45 Lino Galiana Travail partie NLP (#328)
f10815b 2022-08-25 16:00:03 Lino Galiana Notebooks should now look more beautiful (#260)
494a85a 2022-08-05 14:49:56 Lino Galiana Images featured ✨ (#252)
d201e3c 2022-08-03 15:50:34 Lino Galiana Pimp la homepage ✨ (#249)
12965ba 2022-05-25 15:53:27 Lino Galiana :launch: Bascule vers quarto (#226)
9c71d6e 2022-03-08 10:34:26 Lino Galiana Plus d’éléments sur S3 (#218)
3299f1d 2022-01-08 16:50:11 Lino Galiana Clean NLP notebooks (#215)
495599d 2021-12-19 18:33:05 Lino Galiana Des éléments supplémentaires dans la partie NLP (#202)
4f67528 2021-12-12 08:37:21 Lino Galiana Improve website appareance (#194)
2a8809f 2021-10-27 12:05:34 Lino Galiana Simplification des hooks pour gagner en flexibilité et clarté (#166)
04f8b8f 2021-09-08 11:55:35 Lino Galiana echo = FALSE sur la page tuto NLP
048e3dd 2021-09-02 18:36:23 Lino Galiana Fix problem with Dumas corpus (#134)
2e4d586 2021-09-02 12:03:39 Lino Galiana Simplify badges generation (#130)
49e2826 2021-05-13 18:11:20 Lino Galiana Corrige quelques images n’apparaissant pas (#108)
4cdb759 2021-05-12 10:37:23 Lino Galiana :sparkles: :star2: Nouveau thème hugo :snake: :fire: (#105)
48ed9d2 2021-05-01 08:58:58 Lino Galiana lien mort corrigé
7f9f97b 2021-04-30 21:44:04 Lino Galiana 🐳 + 🐍 New workflow (docker 🐳) and new dataset for modelization (2020 🇺🇸 elections) (#99)
d164635 2020-12-08 16:22:00 Lino Galiana :books: Première partie NLP (#87)
Back to top

Footnotes

  1. Pour avoir les mêmes résultats que ci-dessous, vous pouvez fixer l’argument random_state=21.↩︎

Citation

BibTeX citation:
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python Pour La Data Science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {en}
}
For attribution, please cite this work as:
Galiana, Lino. 2023. Python Pour La Data Science. https://doi.org/10.5281/zenodo.8229676.