Cet article fait partie de la série « Un projet Data Science de bout en bout ». J’y présente les travaux réalisés dans le cadre de la formation Big Data pour l’entreprise numérique proposée par Centrale Supelec Exced.

Ce projet a pour objectif d’analyser les offres d’emploi Ressources Humaines afin d’identifier automatiquement des tendances. Cet article présente comment j’ai nettoyé les descriptions d’offres d’emploi afin de les rendre lisible par la machine.

Avertissement : cet article contient du code pouvant heurter la sensibilité des plus jeunes. L’ensemble du code est disponible ici.

Un texte est une succession de phrases, une phrase est une succession de mots, un mot est une succession de lettres.

Cette organisation constitue l’écriture telle que nous la connaissons aujourd’hui. Il est le résultat d’une évolution qui a débuté il y a 6 000 ans en Mésopotamie.

Ce langage est riche, il permet aux humains de transmettre des idées complexes, des émotions. Il est un fondement essentiel de notre civilisation.

Mais il est difficile à interpréter pour une machine. Elle n’a pas la capacité d’abstraction du cerveau humain.

Je vous propose donc de découvrir les étapes que j’ai suivies afin de nettoyer les textes d’offres d’emploi.

Data Cleaning

Au commencement, le corpus de texte

Dans la partie précédente, nous avons découvert comment nous avons obtenu les descriptions des offres d’emploi Ressources Humaines sur LinkedIn.

Concrètement, à l’issu de ce travail, nous disposions d’un tableau contenant :

  • Un identifiant unique pour chaque offre d’emploi
  • La description de l’offre d’emploi

Nous allons maintenant décrire les opérations que nous avons réalisées pour nettoyer ces données textuelles.

Filtrer les données

Certaines observations ne sont pas exploitables, et ce, pour plusieurs raisons :

  • Il n’y a pas de description, parce qu’il faut être connecté à LinkedIn pour voir l’offre d’emploi. Cela représentait 2% de nos données.
  • Il y a des doublons, parce que le poste a été récolté plusieurs fois ou qu’il est ouvert à plusieurs géographies. Cela représentait 18% des données.
  • Il y a des descriptions dans d’autres langues que le français. Or cela risque de biaiser notre analyse. Cela représentait à peine 1% des données.

Voici le code que nous avons utilisé pour ces 3 opérations

import pandas as pd

# Conserver les lignes pour lesquelles il existe une description

nb_lines_ante = df.shape[0]
df = df[df['Description'].notna()]

# Supprimer les éventuels doublons
nb_lines_ante = df.shape[0]
df.drop_duplicates(keep = 'first', inplace=True,subset=['Description'])
nb_lines_poste = df.shape[0]

#Supprimer les données dans une autre langue que le français
from langdetect import *

def langage_detection(text):
    language_code = detect(text)
    return language_code

df["Language"] = df["Description"].apply(lambda x : langage_detection(x))

Cette opération nous a permis de ne conserver que les données que nous pouvons et souhaitons exploiter. Mais il reste du travail !

Nettoyer les données

Nous prenons en compte plusieurs éléments pour nettoyer du texte. En effet, les offres d’emplois sont des données réelles, avec tous les problèmes de qualité de données que l’on peut connaître.

L’objectif de ces opérations est d’homogénéiser autant que possible les données ainsi que de retirer les informations qui pourraient parasiter l’analyse.

Voici la liste des opérations que nous avons réalisées :

  • Retirer les urls : les éléments qui commencent par « http », « https », « www ».
  • Retirer les émoticônes : nous avons utilisé deux scripts. En effet, la bibliothèque clean-text n’était pas capable d’en retirer l’intégralité.
  • Retirer les nombres : nous souhaitons utiliser le contenu du texte. Aucun chiffre n’est considéré comme pertinent pour notre analyse
  • Mettre les mots en minuscule : nos algorithmes sont sensibles à la casse
  • Retirer les mails : ils ne portent pas de sens
  • Retirer les symboles monétaires : nous ne souhaitons pas les utiliser dans notre analyse, mais pourraient être utiles dans le cadre d’offres d’emploi
  • Nettoyage ad hoc : nous avons remarqué que certains signes de ponctuation et symboles parasitent notre analyse. Nous les remplaçons par un espace.
  • Retirer la ponctuation : Nous aurions pu utiliser des phrases complètes. Néanmoins, la complexité des formulations implique que nous allons devoir nous concentrer sur les mots.

Voici le code qui nous a permis de réaliser ces opérations

#Import des bibliothèques
from cleantext import clean
import string
import re

def cleaning(text):
#Retirer les urls
    text = re.sub(r"http\S+", "", text)
    text = re.sub(r"www\S+", "", text)
    return re.sub(r"http\S+", "", text)

#Retirer les emojis
    text = clean(text, no_emoji=True)

#Retirer les chiffres
    text= "".join([i for i in text if not i.isdigit()])

#Retirer monnaie et mail
    text = clean(text, no_currency_symbols=True, no_emails = True)

#Retirer la ponctuation
    text="".join([i for i in text if i not in string.punctuation])

#Appliquer la formule
df["clean_description"]= df["description"].apply(lambda x:cleaning(x))

# Mettre en minuscule

df["clean_description"] = df["clean_description"].apply(lambda x: x.lower())

Gestion des synonymes

La diversité du langage fait que de nombreux mots sont utilisés pour exprimer la même idée. Or, notre connaissance du métier nous permet de tenir un dictionnaire. Par exemple « SIRH » devra être « système d’information des ressources humaines » et « GPEC » devra être « Gestion Prévisionnelle des emplois et des Compétences ».

Pour ce faire, nous avons créé un fichier de correspondance que nous avons remplacé chaque synonyme par le terme que nous avons conservé. Voici le code que nous avons utilisé

#Import du dictionnaire des synonymes

with open("synonymes.txt") as f:
    synonyme_dic = f.read()

synonyme_dic = json.loads(synonyme_dic)

# Créer la fonction de remplacement des synonymes

def replace_synonymes(text,dic):
    for i,j in dic.items():
        text = text.replace(i,j)
    return text

df["clean_description"] = df["clean_description"].apply(lambda x:replace_strings(x,synonyme_dic))

Tokeniser les verbatims

Chaque description est actuellement une donnée textuelle entière. Or, nous souhaitons réaliser des opérations sur chacun des mots. Pour cela, il faut représenter chaque mot comme une donnée. C’est le principe de la tokenisation.

Voici le code que nous avons utilisé pour tokeniser :

#Créer la fonction tokeniser
def tokenization(text):
    tokens = text.split()
    return tokens

#Appliquer la fonction

df["tokenied_description"]= df["clean_description"].apply(lambda x: tokenization(x))

Retirer les stopwords

Les stopwords sont des mots de liaison : « de », « à », « que », « il »… Ils n’apportent pas de sens aux phrases. C’est la raison pour laquelle ils doivent être retirés. Il est possible de trouver des listes de stopwords en ligne pour créer votre propre bibliothèque.

Voici le code que nous avons appliqué pour retirer les stopwords

# Retirer les stopword

# Ouverture de la liste des stop words

with open("stop_words.txt",encoding='utf-8') as f:
    stopwords = f.read().splitlines()

# Créer la fonction

def remove_stopwords(text):
    output= [i for i in text if i not in stopwords]
    return output

# Appliquer la fonction
df["noSw_description"] = ""
df["noSw_description"] = df["tokenied_description"].apply(lambda x: remove_stopwords(x))

Data Featuring

Les données étant nettoyées, il est possible de réaliser les premiers calculs. Encore une fois, ces opérations sont spécifiques à l’analyse de données textuelles et font une transition parfaite entre le nettoyage et l’analyse de données.

Stemming

Le stemming est l’opération qui consiste à découper des mots pour les ramener à leur forme radicale. Ceci permet d’homogénéiser les mots afin de s’affranchir des déclisaisons et accords.

Par exemple :

Opérationnelles → Oper
Opérant→ Oper

Le principal inconvénient du stemming est l’altération des mots. Il devient alors plus difficle de lire le contenu. La lemmatization permet de conserver l’information de la nature des mots mais est plus couteuse en ressources.

Pour notre exercice, nous avons utilisé le stemming proposé par le package Spacy. Voici le code :

import spacy
from spacy_lefff import LefffLemmatizer
from spacy.language import Language

# Instancier le lemmatizer
@Language.factory('french_lemmatizer')
def create_french_lemmatizer(nlp, name):
    return LefffLemmatizer()

nlp = spacy.load('fr_dep_news_trf')
#Il existe deux modèles avec 2 objectifs 
# accuracy : fr_dep_news_trf
# efficiency : fr_core_news_sm

#french_lemmatizer = LefffLemmatizer()
nlp.add_pipe("french_lemmatizer", name='lefff')

# Créer la fonction
def lemmatize(text):
    doc = nlp(text)
    for d in doc :
        return d.lemma_ #lemmatizer simple
        #return d._.lefff_lemma #lemmatizer complet
    
# Possible d'utiliser ._.lefff_lemma
# Mais il est très incomplet et retourne des None pour des mots en anglais ou inconnus.
# Demande de revoir la gestion des synonymes
# Problème dans la gestion des accès également à réaliser après.

list1=[]

# Ouvrir chaque cellule, lemmatizer chaque mot puis regrouper chaque cellule
for cell in df["noSw_description"]:
    #créer une liste pour chaque offre
    for word in cell :
        list0 = []
        lemmatized = lemmatize(word)
        print(word)
        print(lemmatized)
        print()
        list0.append(lemmatized)
    list1.append(list0)
    

df["lemma"] = list1

N-Grams

Pfiou, voici la dernière étape. Vous qui arrivez jusqu’ici, bravo, c’est une des phases les plus importantes. En effet, tous les mots sont indépendants en l’état. Cependant, nous savons que des formules existent et doivent être représentées dans les données. Par exemple « Ressources Humaines » et « Gestion Administrative ».

Nous allons donc produire des n-grams. Ce sont des regroupements de mots qui figurent régulièrement ensemble.

Pour cela, nous avons utilisé la bibliothèque de Gensim. Voici le code

import gensim
from gensim.models import Phrases

# Occurence minimal du terme dans le texte
min_count = 2

# Seuil d'acceptation des bigrams
threshold = 10

#Création du modèle de bigram
bigram_mod = gensim.models.Phrases(df["Stemmed"], min_count, threshold)


#Création de la fonction de bigram
def bigram(texts):
  return [bigram_mod[doc] for doc in texts]


df["Bigram"] = bigram(df["Stemmed"])

Conclusion

Nous avons suivi de nombreuses étapes afin de nettoyer le texte que nous souhaitons anlayser. Nous avons réalisé de nombreuses opérations . Comme nous pouvons le voir, nous avons grandement altéré le texte et il n’est presque plus possible de le comprendre :

Avant et après les transformations

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *