Python pour un data scientist/economist

Lino Galiana

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

  1. Importer les données (l’appeler df) des élections américaines et regarder les informations dont on dispose
  2. Créer une variable republican_winner égale à red quand la variable rep16_frac est supérieure à dep16_frac (blue sinon)
  3. (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 :

  1. Représenter une matrice de corrélation graphique
  2. Choisir quelques variables (pas plus de 4 ou 5) dont rep16_frac et représenter une matrice de nuages de points
  3. (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 R. Il est ainsi souvent nécessaire d’appliquer des tâches de preprocessing, c’est-à-dire des tâches de modification de la distribution des données pour les rendre cohérentes avec les hypothèses des modèles.

Standardisation

La standardisation consiste à transformer des données pour que la distribution empirique suive une loi N(0,1). Pour être performants, la plupart des modèles de machine learning nécessitent d’avoir des données dans cette distribution.

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 XN(0,1). C’est, en fait, la standardisation en scikit.

La normalisation consiste à modifier les données de manière à avoir une norme unitaire. La raison est expliquée plus bas

Exercice: standardisation

  1. Standardiser la variable median_earnings_2010_dollars (ne pas écraser les valeurs !) et regarder l’histogramme avant/après normalisation
  2. Créer scaler, un Transformer 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.
  3. Appliquer scaler sur les autres lignes du DataFrame et comparer les distributions obtenues de la variable median_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 (l1 ou l2) unitaire. Autrement dit, avec la norme adéquate, la somme des éléments est égale à 1. Par défaut, la norme est dans l2. Cette transformation est particulièrement utilisée en classification de texte ou pour effectuer du clustering

Exercice: normalization

  1. Normaliser la variable median_earnings_2010_dollars (ne pas écraser les valeurs !) et regarder l’histogramme avant/après normalisation
  2. Vérifier que la norme l2 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 n×K pour lequel chaque colonne sera une variable dummy pour la modalité k. Il y a ici K 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 du LabelEncoder. OrdinalEncoder a vocation à s’appliquer sur des matrices (X), alors que LabelEncoder est plutôt pour un vecteur (y)
  • OneHotEncoder: une version généralisée (et optimisée) de la dummy expansion. Il a plutôt vocation à s’appliquer sur les features (X) du modèle

Warning

Prendra les variables state et county dans df

  1. Appliquer à state un LabelEncoder
  2. Regarder la dummy expansion de state
  3. Appliquer un OrdinalEncoder à df[['state', 'county']] ainsi qu’un OneHotEncoder

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>
Last updated on 21 Oct 2020
Published on 15 Oct 2020
Edit on GitHub