dimanche 16 avril 2023

A la recherche de la donnée perdue: avec Pandas et sklearn

 Les jeux de données pour alimenter un dispositif d'apprentissage automatique ne sont pas toujours homogènes ou cohérents. Il y a parfois des 'trous' dans les données. Ces manques peuvent avoir diverses origines: mauvais relevé, erreur de type ou d'encodage.

 La méthode info() d'un dataframe permet de connaitre l'état général des données : type et remplissage!;

 
dt.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20 entries, 0 to 19
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   X       16 non-null     float64
 1   y       20 non-null     float64
dtypes: float64(2)
memory usage: 448.0 bytes
ici , on traitera des données de type 'float' et 4 données de la première colonne sont manquantes:
None ou 'nan' :not a number

Le détail par ligne: 
dt[dt.isnull().values]
        X	    y
3	NaN	-52.517783
5	NaN	-23.697502
10	NaN	-13.255462
12	NaN	8.366929
Il n'est pas recommandé de conserver des données nulles dans son jeu de données. Les frameworks d'apprentissage
risquent de dysfonctionner. Face à ce problème, il peut y avoir plusieurs stratégies:
Supprimer la ligne entière ou essayer de 'combler' les trous.
Supprimer les lignes incomplètes est une option valable quand le nombre de lignes 'saines' est important.
Dans le cas présent, le jeu de données serait amputé de  4 lignes sur 20 => 1/5eme, la proportion est trop importante.
l'opération est plutôt simple, elle se fait par la méthode dropna()
Penser à ajouter l'option inplace=True pour mettre à jour le dataframe.
dt.dropna()
 
X	y
0	-1.913280	-87.286608
1	-1.724918	-79.815577
2	-1.412304	-58.442734
4	-0.908024	-27.520687
6	-0.469474	-19.956148
La ligne 3 a été supprimée.

Les solutions pour combler les manques.


On peut utiliser des  méthodes issues de Pandas ou d'autres plus sophistiquées portées par le framework scikit-learn (sklearn).

Les solutions Pandas

Elles sont couvertes par les méthodes:  replace() , fillna() , interpolate().
Replace est la plus généraliste: elle permet de remplacer une donnée par une autre.
Dans le cas présent il faut déterminer la valeur de remplacement. On peut comme ici mettre la valeur 0 en lieu et place des valeurs indéfinies.
La valeur  a remplacer est portée par le module numpy
dt.replace(np.nan , 0)
X	y
0	-1.913280	-87.286608
1	-1.724918	-79.815577
2	-1.412304	-58.442734
3	0.000000	-52.517783
4	-0.908024	-27.520687

La méthode fillna() est plus puissante , on peut choisir la valeur de remplacement suivant plusieurs méthodes:
 ‘backfill’, ‘bfill’, ‘ffill’, None   et  defaut None
Pandas utilisera la valeur, avant ou apres pour remplir les manques.
Exemple 
dt.fillna(method = 'bfill')  # ou dt.bfill()
	X	y
0	-1.913280	-87.286608
1	-1.724918	-79.815577
2	-1.412304	-58.442734
3	-0.908024	-52.517783
4	-0.908024	-27.520687

L'ordre des données est important, peut-être faut il trier les valeurs avant.
Ci dessous, un jeu de données est généré à partir de sklearn, assemblé dans un dataframe puis trié sur la colonne 'X'.

from sklearn.datasets import make_regression
X, y = make_regression(n_samples=20, n_features=1, noise=5, random_state=42)
print(X.shape)
print(y.shape)
dt = pd.DataFrame(zip(X.flat, y), columns = ['X', 'y'])
dt.sort_values('X', inplace =True)
 
 
(20, 1)
(20,)
           X          y
15 -1.913280 -87.286608
3  -1.724918 -79.815577
5  -1.412304 -58.442734
7  -1.012831 -52.517783
4  -0.908024 -27.520687
Pour terminer avec Pandas, la méthode  interpolate()  offre la possibilité de prédire les données manquantes en utilisant une regression linéaire.  Ici l'ordre de presentation importe peu.
Elle propose un bon nombre de résolution : 
  • 'linear’
  • ‘time’ pour les dates
  • ‘index’, ‘values’
  • ‘pad’ 
  • ‘nearest’
  • ,‘zero’
  • ‘slinear’
  • ‘quadratic’
  •  ‘cubic’
  • ‘from_derivatives’
 etc.

SKLearn , propose trois méthodes principales:

  • SimpleImputer : très basique, il remplace les valeurs par la moyenne , la médiane etc.
  • KNNImputer: utilise un algorithme basé sur le calcul de distance de voisinage de point
  • IterativeImputer: module experimental et très puissant.
Pour utiliser ces modèles, il est préférable de décharger le dataframe dans des structures numpy:


from sklearn.impute import KNNImputer,SimpleImputer
imp = IterativeImputer(random_state=0)
np1 = dt[['X', 'y']].to_numpy()
np2 = imp.fit_transform(np1)
simpl = SimpleImputer()
np3 = simpl.fit_transform(np1)
kn  = KNNImputer()
np4 = kn.fit_transform(np1)

Le graphisme ci-dessous visualise les résultats obtenus avec les différentes méthodes.
On commence par introduire des valeurs np.nan dans l'axe des X.

Par:
dt.iat[3, 0] = np.nan
dt.iat[10, 0] = np.nan
dt.iat[12, 0] = np.nan
dt.iat[5,0] = np.nan





Conclusion: ceux qui s'en sortent le mieux sont :
Pour Pandas : interpolate()
Pour SKLearn : KNNImputer (le meilleur) suivi de IterativeImputer mais il faudrait tester pour chacun les options disponibles.

from sklearn.metrics import mean_squared_error
print(mean_squared_error(dt_nan.X, np3[:,0] ))# SimpleImputer
print(mean_squared_error(dt_nan.X, np4[:,0] )) #KNNImputer
print(mean_squared_error(dt_nan.X, np2[:,0] )) # iterativeImputer
print(mean_squared_error(dt_nan.X, dt_inter.X )) #pandas interpolate
 
0.05726013724292493   # SimpleImputer
0.000988070710285649  #KNNImputer
0.002632595263623769  # iterativeImputer
0.003185180237325761  #pandas interpolate
Pour rappel, il n'est pas possible de connaitre à l'avance quel sera la meilleure solution.

Aucun commentaire: