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 (ressource en anglais). 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
renvoiet
sit
est inférieur à 3, ou renvoienp.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 enpd.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") !