Introduction au Machine Learning

Améliorer et nettoyer un jeu de données pour l'ML

Nettoyez un jeu de données

La data est souvent bruitée. Il faut la nettoyer.
80% du temps de l'activité d'un data scientist
-> faire référence a cours https://openclassrooms.com/fr/courses/7410486-nettoyez-et-analysez-votre-jeu-de-donnees

Palliez les données manquantes

Comment pallier les données manquantes : remplacer par la moyenne, les supprimer, leur donner une valeur particulière?

Détectez les anomalies

Détecter les outliers / anomalies et décider de la bonne approche de remédiation: suppression, cap, …

Normalisez les valeurs numériques

Normaliser les valeurs numériques pour éviter une trop forte disparité entre les variables. # À vous de jouer !
utiliser le dataset des arbres de Paris qui est très bruité et illustre presque tous les cas cités

Améliorez un jeu de données

Nous avons jusqu’à présent travaillé sur des jeux de données simples, de faible volume et surtout très propres. Aucune anomalie ni de valeurs manquantes et les variables sont explicites.

C’est une situation idéale malheureusement peu représentative de la réalité professionnelle où l’on est confronté à des données plus chaotiques.

Je vous incite à suivre le cours https://openclassrooms.com/fr/courses/7410486-nettoyez-et-analysez-votre-jeu-de-donnees pour tout ce qui concerne l’analyse et le nettoyage des jeux de données.

Dans ce chapitre nous allons travailler sur un “vrai” jeu de données que je trouve plutôt sympathique.

Le dataset des arbres de Paris qui contient des informations sur plus de 200k arbres sur Paris intra muros et sa périphérie proche.

Les données sont mises à jour toutes les semaines, j’ai donc fait une copie pour avoir une version stable de base de travail.

Vous trouverez le dataset original sur le portail open data de Paris https://opendata.paris.fr/explore/dataset/les-arbres/information et la copie sur laquelle nous allons travailler dans le github du cours. https://raw.githubusercontent.com/OpenClassrooms-Student-Center/8063076-Initiez-vous-au-Machine-Learning/master/data/paris-arbres-2023-09-07.csv

Ce qui est formidable avec ce dataset, hormis de pouvoir visualiser tous les arbres d’une ville comme Paris, c’est qu’il illustre les principaux problèmes qu’un data scientist peut rencontrer. A savoir:

Le page du dataset sur le portail opendata ne fournit pas de description des champs ni d’information sur le mode de récolte des données. Toutefois les noms des champs sont assez parlant et nous nous en contenterons.

Données manquantes

Commençons donc par les données manquantes pour une variable donnée.

On a 3 stratégies possibles

Regardons ce qu’il en est sur le dataset des arbres.

Le dataset étant assez riche, on va se limiter aux Platanes. Cela nous laisse plus de 42.500 d’arbres.

df = df[df.libelle_francais == ‘Platane’].copy()

la variable stade_de_developpement a 3350 valeurs manquantes (NaN)

stade_de_developpement Adulte 21620 Jeune (arbre)Adulte 8356 Jeune (arbre) 5916 NaN 3350 Mature 3346

On suppose que la valeur “Jeune (arbre)Adulte” n’est pas un erreur de saisie mais plutôt une valeur intermédiaire entre Jeune (arbre) et Adulte. On a donc la graduation Jeune (arbre), Jeune (arbre)Adulte, Adulte puis Mature.

A ce stade on peut choisir de simplement supprimer tous les échantillons où stade_de_developpement est absent. Cela ne concerne que 7,8% des données.

On peut aussi essayer de remédier aux valeurs manquantes en observant la relation entre les mesures des arbres et leur stade de développement. Notre hypothèse est que les arbres jeunes ou Mature sont nettement plus petit que les arbres Adulte.

Le boxplot montre la répartition de la hauteur et de la circonference par stade de développement.

sns.boxplot(df = df, y=”circonference_cm”, x=”stade_de_developpement”) sns.boxplot(df = df, y=”hauteur_m”, x=”stade_de_developpement”)

(après avoir supprimé les valeurs extrêmes avec df = df[(df.circonference_cm < 1000) & (df.hauteur_m < 100)].copy())

On observe bien une nette difference entre les arbres jeunes ou Mature par rapport aux arbres Adulte.

Donc on peut établir une règle certes arbitraire mais qui fait sens pour identifier les arbres Mature parmi les données manquantes. Par exemple, en fixant un seuil minimum de hauteur_m > 20 et circonference_cm > 200 au vue de la figure précédente.

cond = (df.stade_de_developpement.isna()) & (df.hauteur_m > 20) & (df.circonference_cm > 200) df[cond].shape

ce qui donne 22 arbres que l’on peut maintenant labelliser comme Mature

de même pour les jeunes arbres cond = (df.stade_de_developpement.isna()) & (df.hauteur_m < 8) & (df.circonference_cm < 50) df[cond].shape

ce qui donne 2903 arbres que l’on peut maintenant labelliser comme ‘Jeune (arbre)’

L’étape d’après, plus complexe serait d’utiliser une régression logistique pour identifier le stade de développement à partir des variables significatives, hauteur, circonférence mais aussi par exemple la domanialité et l’espèce.

cf le notebook du chapitre.

Les outliers

Contrairement aux données manquantes ou l’absence de valeurs sautent aux yeux, le cas des outliers est plus délicat. Une valeur peut paraître extrême mais l’est elle vraiment ?

Une age de 200 ans est évidemment impossible (pour le moment), mais qu’en est il d’un âge de 80 ans dans un dataset de coureurs de marathon ? S’agit-il d’une erreur ou bien d’un senior en pleine forme ? Il n’y a pas de moyen d’apporter une réponse systématique à la question. Cela dépend vraiment du contexte.

Il y a 2 types d’outliers. Les données vraiment absurdes (200 ans d’âge) et les données extrêmes mais toutefois pertinentes.

Les outliers ont une influence directe sur le modèle en biaisant les statistiques de la distribution de la variable.

Prenez le cas de la moyenne de cette suite de chiffres moyenne ([-1.2, 0.15, -0.64, 0.46, -1.26, -1.89, -0.34, 0.66, 1.48, -1.26]) = 0.17 Mais si on ajoute la valeur 10 moyenne ([-1.2, 0.15, -0.64, 0.46, -1.26, -1.89, -0.34, 0.66, 1.48, -1.26, 10]) = 1.06 Une valeur extreme peut avoir un fort impact sur la statistique et donc sur le modèle.

Visualiser la distribiution de la variable par un histogram, ou un boxplot donne déjà une bonne indication de la présence d’outliers.

Traçons par exemple, la hauteur et la circonférence des platanes. On trouve un platane de 700 m de hauteur et 2 platanes de plus de 10m de circonférence.

./figs/p3c2_01_outliers.png

On peut enlever ces échantillons sans se poser trop de questions df = df[(df.circonference_cm < 1000) & (df.hauteur_m < 100)].copy()

Il existe aussi des techniques de calcul d’identification des outliers. Il nous appartiendra ensuite de les traiter en tant que tel ou non. Nous allons calculer un score qui traduit l’écart de la valeur par rapport à la distribution de la variable.

Z-score

Le z-score mesure de combien d’écart-types une valeur est éloigné de la moyenne de la variable. On considère que un zscore de 2 ou 3 correspond à un outlier.

from scipy import stats df[‘z_circonference’] = stats.zscore(df.circonference_cm) df[‘z_hauteur’] = stats.zscore(df.hauteur_m)

Si on plot les zscore pour la hauteur et la circonférence on observe

./figs/p3c2_02_zscore.png

de nombreux échantillons ont un zscore élevé > 2 ou > 3 Note: On observe aussi que la hauteur prend des valeurs plus discrète que continue. Cela est sûrement dû à la méthode de mesure de la hauteur des arbres. Il y a un effet d’arrondi. C’est là que l’on souhaiterait en savoir plus sur la méthode utilisée pour constituer ce dataset.

Un seuil zscore à 2 sur la hauteur (resp circonférence) détectera 793 échantillons (resp. 1429) et à 3, 33 échantillons (resp. 349)

On peut aussi regarder la méthode IQR.

L’IQR, est la différence entre le 25eme centile (Q1) et le 75eme centile (Q3) des données. Les points de données qui tombent en dessous de Q1 - 1,5 * IQR ou au-dessus de Q3 + 1,5 * IQR sont considérés comme des valeurs aberrantes.

iqr = np.quantile(df.hauteur_m, q=[0.25, 0.75]) limite_basse = iqr[0] - 1.5(iqr[1] - iqr[0]) limite_haute = iqr[1] + 1.5(iqr[1] - iqr[0])

Ce qui donne -5.5 pour la limite basse. pas très utile vu que les arbres n’ont pas de hauteur négative! et 30.5 pour la limite_haute, plus intéressant. Pour la circonférence on obtient une limite haute à 305 cm.

Une limite haute pour la hauteur à 30.5 identifiera 44 arbres comme outliers. et pour la circonférence 485 arbres.

En résumé, nombre de’chantillons identifiés comme ayant une valeur aberrante | hauteur | circonference zscore < 2 | 793 | 1429 zscore < 3 | 33 | 349 IQR | 44 | 485

Donc un grand éventail de résultats selon la méthode et les seuils choisis. A ce stade le bon choix ne peut se faire qu’à partir d’une connaissance du sujet.

Note: N’étant pas un expert en arbre et foret, meme si le sujet m’interesse, j’etais bien en mal de detected des valeurs aberrantes pour la circonference des arbres. Est ce qu’un Platane eut faire 5 m de circonference? soit 1, … me de diametre? Improbable mais on ne sait jamais. J’ai donc pris les coordonnées geographiques de arbres les plus gros (colonne geo_point_2d) que j’ai soumis dans google maps en mode street view. Et j’ai pu constater qu’il n’y avait pas de giga arbre dans ces rues.

Une fois identifiés, il peut y avoir plusieurs façons moins drastiques de traiter les outliers.

On citera:

On utilise pour cela les méthodes qcut et cut de pandas

qcut va scinder la variables en intervalles de volume sensiblement égale en fonction de leur fréquence

pd.qcut(df.hauteur_m, 3, labels=[“petit”, “moyen”, “grand”]).value_counts() hauteur_m petit 17875 moyen 12568 grand 12142

et cut va scinder les données en intervalles d’ecart équivalent

pour des outliers on aura donc

pd.cut(df.hauteur_m, 3, labels=[“petit”, “moyen”, “grand”]).value_counts() hauteur_m petit 40058 moyen 2524 grand 3

Normalisez les valeurs numériques

Certains algorithmes de machine learning tels que la régression linéaire ou logistique sont sensibles à l’amplitude de valeurs des variables. Celles dont les valeurs sont plus grandes dominent le processus d’apprentissage. Les variables de valeurs plus petites sont cachées. Le modele a plus de difficultés a les exploiter et voit ses performances se degrader.

Il faut donc normaliser les variables pour qu’elles aient des amplitudes similaires.

Les 2 methodes de normalisation les plus courantes sont

Les données ont toutes une moyenne nulle et un écart type de 1

En python sur la hauteur et circonference des arbres, cela conne:

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() df[‘hauteur_standard’] = scaler.fit_transform(df.hauteur_m.values.reshape(-1, 1)) df[‘circonference_standard’] = scaler.fit_transform(df.circonference_cm.values.reshape(-1, 1))

fig:

On peut aussi transformer la variable en considérant son logarithme si elle est positive. Cela va reduire fortement son amplitude sans perte d’information pour le modèle.

df[‘circonference_log’] = np.log(df.circonference_cm + 1) df[‘hauteur_log’] = np.log(df.hauteur_m + 1)

(Note toujours ajouter +1 avant de prendre le log pour eviter les log(0))

fig: ./figs/p3c2_03_log.png

À vous de jouer

Il se trouve que 1688 platames ont une hauteur nulle égale à 0 et 1592 ont une circonférence aussi égale à 0

En ce qui concerne la hauteur, il se peut que cela soit dû à la discrétisation observé précedemment et donc au mode de mesure utilisé.

Pour la circonférence par contre, ces valeurs nulles sont plus probablement des valeurs manquantes.

Dans ce chapitre nous avons traités 3 des problèmes les plus fréquents que l’on peut rencontrer sur un vrai jeu de données: