• 15 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 18/02/2020

TP : Nettoyez votre jeu de données

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous allons nettoyer le jeu de données du chapitre précédent. Nous illustrerons ceci en Python. Si vous préférez R, un code équivalent est donné en bas de chapitre ;).

Nous commencerons par charger l'échantillon à partir de ce fichier CSV (qu'il vous faut télécharger) dans une variable que nous appellerons  data. Cette variable sera donc un dataframe.

Ensuite, nous allons parcourir chacune des colonnes pour détecter les erreurs, les corriger, puis actualiser les colonnes en conséquence. Que ce soit en Python ou en R, actualiser une colonne d'un dataframe se fait de cette manière :

data["nom_colonne"] = nouvelle_colonne

 Ici, on cherche à remplacer les valeurs de la colonne (ou variable)  nom_colonne. Si le dataframe a 7 lignes, alors la colonne  nom_colonne  contient 7 valeurs. Pour les remplacer,  nouvelle_colonne  doit ainsi être une liste de 7 valeurs.

Les méthodes apply et map

Il reste encore à savoir comment remplir  nouvelle_colonne. En fait, elle sera calculée à partir de  nom_colonne. Ce qu'il nous faut, c'est parcourir chaque valeur de  nom_colonne, vérifier si elle est correcte ou pas, et la corriger si besoin.

Pour cela, nous utilisons la méthode  apply. C'est une méthode qui est appelée sur une colonne du dataframe, et qui permet de parcourir toutes ses valeurs. Alternativement, on peut utiliser la méthode  map, qui lui est (à peu près) équivalente. Pour chaque valeur, elle applique une fonction destinée à la vérification/correction :

import pandas as pd # On importe la librairie Pandas, que l'on surnomme 'pd'

def lower_case(value): 
    print('Voici la valeur que je traite:', value)
    return value.lower()

data = pd.DataFrame([['A',1],
                     ['B',2],
                     ['C',3]], columns = ['lettre','position'])

nouvelle_colonne = data['lettre'].apply(lower_case)
nouvelle_colonne = nouvelle_colonne.values
print(nouvelle_colonne)
data['lettre'] = nouvelle_colonne
print(data)

En ligne 7, on crée notre dataframe. Celui-ci est un tableau avec 2 colonnes ('lettre' et 'position') et 3 lignes. En ligne 3, on crée une fonction nommée  lower_case, qui prend en paramètre une valeur  value, qui l'affiche (ligne 4), la transforme en minuscules (ligne 5), puis la retourne.

Ensuite, on sélectionne la colonne  lettre  de  data, on appelle la méthode  apply, et on spécifie que chacune des valeurs doit être envoyée une à une à la fonction  lower_case  (ligne 13).

En ligne 11,  nouvelle_colonne  est de type "colonne" (car la méthode  apply  renvoie une colonne). Dans la librairie Pandas, le type exact d'une colonne est appelé Series. Pour obtenir les valeurs de cette colonne sous forme de liste, on appelle nouvelle_colonne.values  (ligne 12). Voici ce que ce programme affichera :

Voici la valeur que je traite: A
Voici la valeur que je traite: B
Voici la valeur que je traite: C
['a' 'b' 'c']
  lettre  position
0      a         1
1      b         2
2      c         3

Les lignes 1 à 3 affichent ce que fait la fonction  lower_case, la ligne 4 affiche le résultat du traitement, c'est-à-dire les 3 lettres en minuscule, et les autres lignes affichent le dataframe dont la colonne lettre a été actualisée avec les minuscules !

Attaquons !

Charger les données

Commencez par télécharger le fichier CSV qui correspond à l'exemple des chapitres précédents (donné en haut de chapitre), puis chargez-le grâce à ces lignes de code :

# import des librairies dont nous aurons besoin
import pandas as pd
import numpy as np
import re

# chargement et affichage des données
data = pd.read_csv('personnes.csv')
print(data)

Traiter les pays

Vous l'avez compris, il nous faudra une fonction par traitement. Oublions  lower_case, et écrivons une fonction qui vérifie si les pays présents dans la colonne pays sont corrects. Pour ceci, il nous faut une liste des pays acceptés :

VALID_COUNTRIES = ['France', 'Côte d\'ivoire', 'Madagascar', 'Bénin', 'Allemagne'
                  , 'USA']
                  
def check_country(country):
    if country not in VALID_COUNTRIES:
        print(' - "{}" n\'est pas un pays valide, nous le supprimons.' \
            .format(country))
        return np.NaN
    return country

Ici, si le pays présent dans la variable  country  n'est pas présent dans la liste  VALID_COUNTRIES,  on affiche le message des lignes 6 et 7. Ensuite, on retourne  np.NaN, qui est la valeur utilisée par les librairies  Numpy  et  Pandas  pour spécifier qu'une valeur est inconnue. C'est en quelque sorte un équivalent de  None.

Sinon, si le pays est valide, on renvoie simplement ce pays (ligne 9) !

Traiter les emails

Au tour des emails maintenant ! Le problème avec cette colonne, c'est qu'il y a parfois 2 adresses email par ligne. Nous ne souhaitons prendre que la première. Nous créons donc la fonction  first  :

def first(string):
    parts = string.split(',')
    first_part = parts[0]
    if len(parts) >= 2:
        print(' - Il y a plusieurs parties dans "{}", ne gardons que {}.'\
            .format(parts,first_part))  
    return first_part

Lorsqu'il y a plusieurs emails par ligne, ceux-ci sont séparés par des virgules. Nous séparons donc la chaîne de caractère de la variable  string  selon les virgules grâce à la méthode split (ligne 2). Le résultat est donc une liste avec autant d'éléments que d'adresses email: cette liste est placée dans la variable  parts.

Comme  parts  contient au moins 1 élément, on place celui-ci dans la variable  first_part. Ensuite, on compte le nombre d'éléments que contient  parts  grâce à la fonction  len. S'il y en a au moins 2, alors on affiche le message des lignes 5 et 6. Finalement, on retourne  first_part, qui contient le premier email !

Traiter les tailles

 Nous aurons ici 2 fonctions :  convert_height, qui convertira les chaînes de caractères de type  "1,34 m"  en nombre décimal, ainsi que  fill_height, qui remplacera les valeurs manquantes par la moyenne des tailles de l'échantillon.

def convert_height(height):
    found = re.search('\d\.\d{2}m', height)
    if found is None:
        print('{} n\'est pas au bon format. Il sera ignoré.'.format(height))
        return np.NaN
    else:
        value = height[:-1] # on enlève le dernier caractère, qui est 'm'
        return float(value)

def fill_height(height, replacement):
    if pd.isnull(height):
        print('Imputation par la moyenne : {}'.format(replacement))
        return replacement
    return height

La première fonction est un peu plus élaborée. Vous pouvez soit lui accorder une confiance aveugle, soit tenter de percer son mystère dans la section Aller plus loin en bas de ce chapitre ;).

Passons à la seconde fonction. Ah ! elle prend 2 paramètres :  height  et  replacement. Le premier est la hauteur, comme d'habitude. Le second est la valeur que nous devrons renvoyer en cas de valeur manquante. La ligne 11 vérifie si la valeur de  height  est manquante (None, NaN ou Nat). Si c'est le cas, on retourne la valeur de remplacement (ligne 13), sinon, on retourne  height.

Appliquons toutes ces fonctions

Maintenant que ces fonctions sont définies, il faut les exécuter ! A la fin de votre programme, ajoutez ceci :

data['email'] = data['email'].apply(first)
data['pays'] = data['pays'].apply(check_country)
data['taille'] = [convert_height(t) for t in data['taille']]
data['taille'] = [t if t<3 else np.NaN for t in data['taille']]
mean_height = data['taille'].mean()
data['taille'] = [fill_height(t, mean_height) for t in data['taille']]
data['date_naissance'] = pd.to_datetime(data['date_naissance'], 
                                           format='%d/%m/%Y', errors='coerce')
print(data)

Vous vous souvenez de la syntaxe que nous avons vu tout en haut de ce chapitre, pour actualiser une colonne ? Nous l'employons ici dans les lignes 1 à 4, 6 et 7. Vous connaissez la syntaxe utilisée dans les lignes 1 et 2. Par contre, pour les lignes 3, 4 et 6 il faudra peut-être vous rafraîchir la mémoire sur les compréhensions de listes. Si cela ne vous dit rien, rendez-vous un peu plus bas, dans la section Aller plus loin.

Que vous dire d'autre... Vous savez l'essentiel. Bon quelques petites précisions mineures :

  • t if t<3 else np.NaN  renvoie  t  si  t  est inférieur à 3, ou renvoie  np.NaN  sinon. On l'utilise pour supprimer les tailles supérieures à 3 m, qui sont aberrantes.

  • data['taille'].mean()  renvoie une unique valeur, qui est la moyenne des tailles.

  • La colonne date_naissance  contient des chaînes de caractères. On les convertit en dates, en spécifiant le format d'écriture des dates. Les chaînes de caractères qui ne respectent pas ce format seront transformées en  pd.NaT  (c'est le cas de la date de naissance de Radia).

La ligne 9 affiche ici le résultat final :

Aller plus loin : Les compréhensions de listes

Les compréhensions de listes, c'est une syntaxe très pratique, car elle permet en une ligne d'écrire une boucle for qui construit une liste. Par exemple, la ligne 3 du tout dernier bout de code ci-dessus est équivalente à ces 4 lignes :

data = pd.read_csv('personnes.csv')

nouvelle_colonne = []
for t in data['taille']:
    nouvelle_colonne.append(convert_height(t))
data['taille'] = nouvelle_colonne

On aurait très bien pu utiliser  apply  :

data = pd.read_csv('personnes.csv')

data['taille'] = data['taille'].apply(convert_height)

Aller plus loin : le traitement des tailles

Reprenons la fonction que nous avions laissée de côté :

def convert_height(height):
    found = re.search('\d\.\d{2}m', height)
    if found is None:
        print('{} n\'est pas au bon format. Il sera ignoré.'.format(height))
        return np.NaN
    else:
        value = height[:-1] # on enlève le dernier caractère, qui est 'm'
        return float(value)

Normalement, Pandas détecte automatiquement si une colonne provenant d'un fichier CSV contient des chaînes de caractères ou des nombres. Mais ici, la colonne "taille" contient des "m". Comme c'est une lettre, Pandas considère que "1,34 m" est une chaîne de caractères, pas un nombre ! Il faudra donc le convertir nous-même.

Alors... c'est vrai, la ligne 2 est difficile à comprendre. Elle vérifie si la taille est bien formatée : un chiffre, suivi d'un point, puis 2 chiffres, puis un "m". Ainsi, "1,34 m" est correct, alors que "153 cm" n'est pas correct.

Vous avez normalement toutes les clés en main pour comprendre le reste de cette fonction. Remarquez que  float(value)  permet de convertir une chaîne de caractères qui représente un nombre en... un vrai nombre (dont le type est "float") !

Exemple de certificat de réussite
Exemple de certificat de réussite