In [1]:
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).

In [2]:
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
Out[2]:
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
In [3]:
df.drop_duplicates(subset=["foo", "baz"], inplace=True)
df
Out[3]:
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 :

In [4]:
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()
Out[4]:
0    False
1     True
2    False
3    False
4    False
dtype: bool

Retirer les données inutiles¶

Pour cela la commande est drop. On peut choisir de retirer des colonnes ou des lignes.

Comme pour la méthode précédente, il faut affecter le résultat ou utiliser inplace pour que la modification soit prise en compte.

In [5]:
df.drop(columns='foo')
Out[5]:
bar
0 0
1 0
2 3
3 0
4 1
In [6]:
df.drop(index=[2,3])  # 2 & 3 are labels
Out[6]:
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 :

In [7]:
to_be_dropped = df[df.foo % 2 == 0].index
df.drop(index = to_be_dropped)
Out[7]:
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
In [8]:
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
Out[8]:
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 :

In [9]:
df.isna()     # shows NaN cells
df.isnull()   # shows position of NaN for numerics, of None for objects and of NaT for DateTime
Out[9]:
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 :

In [10]:
df.bar.fillna(7, inplace=True)
df
Out[10]:
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 :

In [11]:
df.dropna(inplace=True)
df
Out[11]:
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.

In [12]:
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
Out[12]:
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.

In [13]:
temperature.ffill(limit=1) # forward fill (backward is bfill)
Out[13]:
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.

In [14]:
temperature.interpolate(method='linear')
Out[14]:
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
In [15]:
temperature.interpolate(method='time').iloc[2]
Out[15]:
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’}

lin-quad

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.

In [16]:
df2 = pd.DataFrame({'foo': np.random.randint(10,size=5000), 'bar': np.random.randint(10,size=5000)})
In [17]:
%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.

In [ ]: