Préparation des données pour construire un modèle
Pour illustrer le travail de données nécessaire pour construire un modèle de Machine Learning, mais aussi nécessaire pour l’exploration de données avant de faire une régression linéaire, nous allons partir du jeu de données de résultat des élections US 2016 au niveau des comtés
Le guide utilisateur de scikit
est une référence précieuse, à consulter régulièrement. La partie sur le preprocessing est
disponible ici.
Explorer la structure des données
La première étape nécessaire à suivre avant de modéliser est de déterminer les variables à inclure dans le modèle. Les fonctionalités de pandas
sont, à ce niveau, suffisantes pour explorer des structures simples. Néanmoins, lorsqu’on est face à un jeu de données présentant de nombreuses variables explicatives (features en machine learning, covariates en économétrie), il est souvent judicieux d’avoir une première étape de sélection de variable, ce que nous verrons par la suite [LIEN]
Exercise 1: importer les données
- Importer les données (l’appeler
df
) des élections américaines et regarder les informations dont on dispose - Créer une variable
republican_winner
égale àred
quand la variablerep16_frac
est supérieure àdep16_frac
(blue
sinon) - (optionnel) Représenter une carte des résultats avec en rouge les comtés où les républicains ont gagné et en bleu ceux où se sont les démocrates
Avant d'être en mesure de sélectionner le meilleur ensemble de variables explicatives, nous allons prendre un nombre restreint et arbitraire de variables. La première tâche est de représenter les relations entre les données, notamment leur relation à la variable que l’on va chercher à expliquer (le score du parti républicain aux élections 2016) ainsi que les relations entre les variables ayant vocation à expliquer la variable dépendante.
Exercise 2: regarder la corrélation entre les variables
Créer un DataFrame plus petit avec les variables rep16_frac
et unemployment
, median_age
, asian
, black
, white_not_latino_population
,latino_population
, gini_coefficient
, less_than_high_school
, adult_obesity
, median_earnings_2010_dollars
et ensuite :
- Représenter une matrice de corrélation graphique
- Choisir quelques variables (pas plus de 4 ou 5) dont
rep16_frac
et représenter une matrice de nuages de points - (optionnel) Refaire ces figures avec
plotly
La matrice de corrélation donne, avec les fonctionalités de pandas
:
## <pandas.io.formats.style.Styler object at 0x0000000009D82790>

Alors que celle construite avec seaborn
aura l’aspect suivant:

La matrice de nuage de point aura, par exemple, l’aspect suivant:
## array([[<AxesSubplot:xlabel='rep16_frac', ylabel='rep16_frac'>,
## <AxesSubplot:xlabel='unemployment', ylabel='rep16_frac'>,
## <AxesSubplot:xlabel='median_age', ylabel='rep16_frac'>,
## <AxesSubplot:xlabel='asian', ylabel='rep16_frac'>,
## <AxesSubplot:xlabel='black', ylabel='rep16_frac'>],
## [<AxesSubplot:xlabel='rep16_frac', ylabel='unemployment'>,
## <AxesSubplot:xlabel='unemployment', ylabel='unemployment'>,
## <AxesSubplot:xlabel='median_age', ylabel='unemployment'>,
## <AxesSubplot:xlabel='asian', ylabel='unemployment'>,
## <AxesSubplot:xlabel='black', ylabel='unemployment'>],
## [<AxesSubplot:xlabel='rep16_frac', ylabel='median_age'>,
## <AxesSubplot:xlabel='unemployment', ylabel='median_age'>,
## <AxesSubplot:xlabel='median_age', ylabel='median_age'>,
## <AxesSubplot:xlabel='asian', ylabel='median_age'>,
## <AxesSubplot:xlabel='black', ylabel='median_age'>],
## [<AxesSubplot:xlabel='rep16_frac', ylabel='asian'>,
## <AxesSubplot:xlabel='unemployment', ylabel='asian'>,
## <AxesSubplot:xlabel='median_age', ylabel='asian'>,
## <AxesSubplot:xlabel='asian', ylabel='asian'>,
## <AxesSubplot:xlabel='black', ylabel='asian'>],
## [<AxesSubplot:xlabel='rep16_frac', ylabel='black'>,
## <AxesSubplot:xlabel='unemployment', ylabel='black'>,
## <AxesSubplot:xlabel='median_age', ylabel='black'>,
## <AxesSubplot:xlabel='asian', ylabel='black'>,
## <AxesSubplot:xlabel='black', ylabel='black'>]], dtype=object)

Avec plotly
, le résultat devrait ressembler au graphique suivant:
Transformer les données
Les différences d'échelle ou de distribution entre les variables peuvent
diverger des hypothèses sous-jacentes dans les modèles. Par exemple, dans le cadre
de la régression linéaire, les variables catégorielles ne sont pas traitées à la même
enseigne que les variables ayant valeur dans
Standardisation
La standardisation consiste à transformer des données pour que la distribution empirique suive une loi
Warning
Pour un statisticien, le terme normalization
dans le vocable scikit
peut avoir un sens contre-intuitif. On s’attendrait à ce que la normalisation consiste à transformer une variable de manière à ce que scikit
.
La normalisation consiste à modifier les données de manière à avoir une norme unitaire. La raison est expliquée plus bas
Exercice: standardisation
- Standardiser la variable
median_earnings_2010_dollars
(ne pas écraser les valeurs !) et regarder l’histogramme avant/après normalisation - Créer
scaler
, unTransformer
que vous construisez sur les 1000 premières lignes de votre DataFrame. Vérifier la moyenne et l'écart-type de chaque colonne sur ces mêmes observations. - Appliquer
scaler
sur les autres lignes du DataFrame et comparer les distributions obtenues de la variablemedian_earnings_2010_dollars
.
La standardisation permet d’obtenir la modification suivante de la distribution:
## <string>:1: SettingWithCopyWarning:
##
##
## A value is trying to be set on a copy of a slice from a DataFrame.
## Try using .loc[row_indexer,col_indexer] = value instead
##
## See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

On obtient bien une distribution centrée à zéro et on pourrait vérifier que la variance empirique soit bien égale à 1. On pourrait aussi vérifier que ceci est vrai également quand on transforme plusieurs colonnes à la fois
## array([[ nan, 0.11617546, 2.04845592, ..., -0.25856023,
## -0.70880879, -0.70880879],
## [ 1.24053578, -1.6256928 , 0.6475807 , ..., -0.11397491,
## -0.92185256, -0.92185256],
## [-0.5888726 , 2.03948833, -0.39784856, ..., -1.24655991,
## -0.3202505 , -0.3202505 ],
## ...,
## [ 1.18427242, 1.42257665, 0.02032314, ..., 1.38007338,
## -1.25104921, -1.25104921],
## [ 0.37827893, 2.76526677, 2.48753621, ..., 0.24748838,
## -1.58298232, -1.58298232],
## [ nan, -0.57331406, -1.88235812, ..., -0.379048 ,
## 1.35144381, 1.35144381]])
## Moyenne de chaque variable sur 1000 premières observations
## array([ nan, nan, -1.01785247e-15, -3.28626015e-17,
## 1.42108547e-17, -9.30810984e-16, 0.00000000e+00, -6.59028387e-16,
## 2.06057393e-16, nan, 4.61852778e-17, 1.42108547e-17])
## Ecart-type de chaque variable sur 1000 premières observations
## array([nan, nan, 1., 1., 1., 1., 1., 1., 1., nan, 1., 1.])
Les paramètres qui seront utilisés pour une standardisation ultérieure de la manière suivante sont stockés dans les attributs .mean_
et .scale_
scaler.mean_
## array([ 6.39799027e+01, 7.77985972e-02, 3.98028000e+01, 1.12000000e+00,
## 8.87965000e+00, 7.90814500e+01, 7.61930000e+00, 4.31567000e-01,
## 1.69358500e+01, 3.06729730e-01, 2.53057018e+04, -3.09432936e-02])
scaler.scale_
## array([1.55132618e+01, 2.75566190e-02, 4.78272434e+00, 2.62377019e+00,
## 1.41693680e+01, 1.91696807e+01, 1.20192705e+01, 3.68976898e-02,
## 7.12571100e+00, 4.14979892e-02, 4.70832094e+03, 9.27333515e-01])
Une fois appliqués à un autre DataFrame
, on peut remarquer que la distribution n’est pas exactement centrée-réduite dans le DataFrame
sur lequel les paramètres n’ont pas été estimés. C’est normal, l'échantillon initial n'était pas aléatoire, les moyennes et variances de cet échantillon n’ont pas de raison de coïncider avec les moments de l'échantillon complet.

Normalisation
La normalisation est l’action de transformer les données de manière à obtenir une norme (
Exercice: normalization
- Normaliser la variable
median_earnings_2010_dollars
(ne pas écraser les valeurs !) et regarder l’histogramme avant/après normalisation - Vérifier que la norme
est bien égale à 1.

Warning
preprocessing.Normalizer
n’accepte pas les valeurs manquantes, alors que preprocessing.StandardScaler()
s’en accomode (dans la version 0.22
de scikit). Pour pouvoir aisément appliquer le normalizer, il faut
- retirer les valeurs manquantes du DataFrame avec la méthode
dropna
:df.dropna(how = "any")
; - ou les imputer avec un modèle adéquat.
scikit
permet de le faire (info)
Encodage des valeurs catégorielles
Les données catégorielles doivent être recodées sous forme de valeurs numériques pour être intégrables dans le cadre d’un modèle. Cela peut être fait de plusieurs manières:
LabelEncoder
: transforme un vecteur["a","b","c"]
en vecteur numérique[0,1,2]
. Cette approche a l’inconvénient d’introduire un ordre dans les modalités, ce qui n’est pas toujours désirépandas.get_dummies
effectue une opération de dummy expansion. Un vecteur de taille n avec K catégories sera transformé en matrice de taille pour lequel chaque colonne sera une variable dummy pour la modalité k. Il y a ici modalité, il y a donc multicollinéarité. Avec une régression linéaire avec constante, il convient de retirer une modalité avant l’estimation.OrdinalEncoder
: une version généralisée duLabelEncoder
.OrdinalEncoder
a vocation à s’appliquer sur des matrices ( ), alors queLabelEncoder
est plutôt pour un vecteur ( )OneHotEncoder
: une version généralisée (et optimisée) de la dummy expansion. Il a plutôt vocation à s’appliquer sur les features ( ) du modèle
Warning
Prendra les variables state
et county
dans df
- Appliquer à
state
unLabelEncoder
- Regarder la dummy expansion de
state
- Appliquer un
OrdinalEncoder
àdf[['state', 'county']]
ainsi qu’unOneHotEncoder
Le résultat du label encoding est relativement intuitif, notamment quand on le met en relation avec le vecteur initial
## array([[1, 'Alaska'],
## [27, 'Nebraska'],
## [4, 'California'],
## ...,
## [48, 'West Virginia'],
## [33, 'North Carolina'],
## [20, 'Maryland']], dtype=object)
L’expansion par variables dichotomiques également:
## Alabama Alaska Arizona ... West Virginia Wisconsin Wyoming
## 0 0 1 0 ... 0 0 0
## 1 0 0 0 ... 0 0 0
## 2 0 0 0 ... 0 0 0
## 3 0 0 0 ... 0 0 0
## 4 0 0 0 ... 0 0 0
## ... ... ... ... ... ... ... ...
## 3138 0 0 0 ... 0 0 0
## 3139 0 0 0 ... 0 0 0
## 3140 0 0 0 ... 1 0 0
## 3141 0 0 0 ... 0 0 0
## 3142 0 0 0 ... 0 0 0
##
## [3143 rows x 51 columns]
Le résultat du ordinal encoding est cohérent avec celui du label encoding:
## array([ 1., 27., 4., ..., 48., 33., 20.])
Enfin, on peut noter que scikit
optimise l’objet nécessaire pour stocker le résultat d’un modèle de transformation. Par exemple, le résultat de l’encoding One Hot est un objet très volumineux. Dans ce cas, scikit utilise une matrice Sparse:
## <3143x3194 sparse matrix of type '<class 'numpy.float64'>'
## with 6286 stored elements in Compressed Sparse Row format>