import numpy as np
import pandas as pd
np.random.seed(1) # set random seed to get the same random series
Nettoyer les données¶
Le nettoyage des données est une étape fondamentale dans le traitement des données. Dès lors que vous avez un grand nombre de données vous avez de grandes chances qu'il y ait
- des doublons (que Pandas trouve facilement),
- des données inutiles (parfois redondantes, parfois pas utilisées, elles occupent de la mémoire alors autant les retirer),
- des erreurs (des NaN si on a de la chance)
- des incohérences (pas toujours simples à trouver mais des courbes ou des statistiques peuvent les faire apparaître),
- des imprécisions (comme pour les incohérences),
- des données manquantes (que Pandas trouve au chargement ou en tant que NaN).
Dans les 4 derniers cas, une fois les cas problématiques trouvés, se pose la question de comment les corriger.
Remettre tout au propre peut prendre beaucoup de temps.
Retirer les doublons¶
Pandas offre la méthode drop_duplicates
pour retirer les doublons.
df1 = df1.drop_duplicates()
df1.drop_duplicates(inplace = True)
df1.drop_duplicates(subset=["A", "C"], inplace = True)
Attention cette méthode comme beaucoup d’autres ne modifie pas son DataFrame par défaut.
Avec subset
vous pouvez ne tester les doublons que sur les colonnes désignées (ici A et C).
np.random.seed(0)
df = pd.DataFrame({'foo': np.random.randint(3,size=7), 'bar': np.random.randint(3,size=7), 'baz': np.random.randint(3,size=7)})
df.iloc[1] = df.iloc[0]
df
foo | bar | baz | |
---|---|---|---|
0 | 0 | 2 | 2 |
1 | 0 | 2 | 2 |
2 | 0 | 0 | 1 |
3 | 1 | 0 | 1 |
4 | 1 | 2 | 1 |
5 | 2 | 1 | 1 |
6 | 0 | 2 | 0 |
df.drop_duplicates(subset=["foo", "baz"], inplace=True)
df
foo | bar | baz | |
---|---|---|---|
0 | 0 | 2 | 2 |
2 | 0 | 0 | 1 |
3 | 1 | 0 | 1 |
5 | 2 | 1 | 1 |
6 | 0 | 2 | 0 |
Si on désire travailler sur les doublons, alors il y a la commande duplicated
pour savoir quelles sont les lignes en double :
df = pd.DataFrame({'foo': np.random.randint(5,size=5), 'bar': np.random.randint(5,size=5)})
df.iloc[1] = df.iloc[0]
df.duplicated()
0 False 1 True 2 False 3 False 4 False dtype: bool
df.drop(columns='foo')
bar | |
---|---|
0 | 0 |
1 | 0 |
2 | 3 |
3 | 0 |
4 | 1 |
df.drop(index=[2,3]) # 2 & 3 are labels
foo | bar | |
---|---|---|
0 | 1 | 0 |
1 | 1 | 0 |
4 | 3 | 1 |
On peut aussi spécifier les lignes à retirer en prenant le résultat d'un filtre logique :
to_be_dropped = df[df.foo % 2 == 0].index
df.drop(index = to_be_dropped)
foo | bar | |
---|---|---|
0 | 1 | 0 |
1 | 1 | 0 |
2 | 3 | 3 |
4 | 3 | 1 |
Gérer les NaN¶
Un NaN, Not a Number, est le résultat d'une erreur de calcul ou d'une donnée manquante si la méthode de chargement des données a choisi cette facon de l'indiquer (cf http://pandas.pydata.org/pandas-docs/stable/missing_data.html ). Par exemple :
- 0/0 donne un NaN
- ajouter une colonne à un tableau sans spécifier toutes les valeurs va mettre des NaN là où l'information manque
df.at[0,'bar'] = 0
df.at[0,'foo'] = 0 # to get a Not a Number after division
df.at[1,'foo'] = 3 # makes df.at[1,'bar'] = Nan since line 1 has been removed
df.at[2,'bar'] = None # None makes NaN
df['div'] = df.bar / df.foo
df
foo | bar | div | |
---|---|---|---|
0 | 0 | 0.0 | NaN |
1 | 3 | 0.0 | 0.000000 |
2 | 3 | NaN | NaN |
3 | 0 | 0.0 | NaN |
4 | 3 | 1.0 | 0.333333 |
On peut extraire les cases NaN avec les filtres logiques suivant :
df.isna() # shows NaN cells
df.isnull() # shows position of NaN for numerics, of None for objects and of NaT for DateTime
foo | bar | div | |
---|---|---|---|
0 | False | False | True |
1 | False | False | False |
2 | False | True | True |
3 | False | False | True |
4 | False | False | False |
et s'en servir pour remplace les NaN avec df.bar[df.bar.isna()] = 7
ou directement avec fillna
:
df.bar.fillna(7, inplace=True)
df
foo | bar | div | |
---|---|---|---|
0 | 0 | 0.0 | NaN |
1 | 3 | 0.0 | 0.000000 |
2 | 3 | 7.0 | NaN |
3 | 0 | 0.0 | NaN |
4 | 3 | 1.0 | 0.333333 |
Parfois on préfère retirer les lignes contenant des NaN :
df.dropna(inplace=True)
df
foo | bar | div | |
---|---|---|---|
1 | 3 | 0.0 | 0.000000 |
4 | 3 | 1.0 | 0.333333 |
Remplacer les NaN¶
On peut pafois estimer les données manquantes ou fausses. Si les données sont ordonnées on peut même faire une interpolation pour boucher les trous ou trouver les données incohérentes.
dates = pd.date_range('2016-08-01', periods=8, freq='D')
temperature = pd.DataFrame({'temp': [21.5, 24, 25.5, None, 25.2, None, None, 20.1]}, index=dates)
temperature.drop(temperature.index[2], inplace=True) # so index is not linear anymore
temperature
temp | |
---|---|
2016-08-01 | 21.5 |
2016-08-02 | 24.0 |
2016-08-04 | NaN |
2016-08-05 | 25.2 |
2016-08-06 | NaN |
2016-08-07 | NaN |
2016-08-08 | 20.1 |
On peut simplement indiquer que la valeur manquante copie la valeur précédente.
Si plusieurs NaN se suivent il faut spécifier si l'on désire qu'ils prennent la valeur de la dernière valeur qui n'est pas un NaN. Cela se fait en spécifiant limit
qui indique pour combien de NaN consécutifs on fait cette opération.
temperature.ffill(limit=1) # forward fill (backward is bfill)
temp | |
---|---|
2016-08-01 | 21.5 |
2016-08-02 | 24.0 |
2016-08-04 | 24.0 |
2016-08-05 | 25.2 |
2016-08-06 | 25.2 |
2016-08-07 | NaN |
2016-08-08 | 20.1 |
On peut aussi interpoler.
Attention la seule méthode qui prenne en compte les dates dans l'index est time
.
temperature.interpolate(method='linear')
temp | |
---|---|
2016-08-01 | 21.5 |
2016-08-02 | 24.0 |
2016-08-04 | 24.6 |
2016-08-05 | 25.2 |
2016-08-06 | 23.5 |
2016-08-07 | 21.8 |
2016-08-08 | 20.1 |
temperature.interpolate(method='time').iloc[2]
temp 24.8 Name: 2016-08-04 00:00:00, dtype: float64
Les méthodes possibles sont décrites dans Scipy.
method : {‘linear’, ‘time’, ‘index’, ‘values’, ‘nearest’, ‘zero’,
‘slinear’, ‘quadratic’, ‘cubic’, ‘barycentric’, ‘krogh’, ‘polynomial’, ‘spline’,
‘piecewise_polynomial’, ‘from_derivatives’, ‘pchip’, ‘akima’}
Remplacer des valeurs avec replace
¶
Parfois on désire remplacer d'autres valeurs que les NaN. Cela peut être fait avec un filtre mais replace
est plus rapide pour les cas simples.
df2 = pd.DataFrame({'foo': np.random.randint(10,size=5000), 'bar': np.random.randint(10,size=5000)})
%timeit df2.replace([1,2],[11,12]) # replace 1 and 2 by 11 and 12 respectively
%timeit df2.replace([3,4],134) # replace 3 and 4 by 134
%timeit df2[df2==5] = 105
%timeit df2[(df2==6) | (df2==7)] = 167
134 µs ± 2.31 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) 138 µs ± 6.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) 286 µs ± 7.87 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 402 µs ± 21.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
replace
peut aussi utiliser les expressions régulières.