!pip install --upgrade xlrd
!pip install geopandas
1 Introduction
L’introduction de cette partie présentait les enjeux de l’adoption d’une approche algorithmique plutôt que statistique pour modéliser des processus empiriques. L’objectif de ce chapitre est d’introduire à la méthodologie du machine learning, aux choix qu’impliquent une approche algorithmique sur la structuration du travail sur les données. Ce sera également l’occasion de présenter l’écosystème du machine learning en Python
et notamment la librairie centrale dans celui-ci: Scikit Learn
.
L’objectif de ce chapitre est de présenter quelques éléments de préparation des données. Il s’agit d’une étape fondamentale, à ne pas négliger. Les modèles reposent sur certaines hypothèses, généralement relatives à la distribution théorique des variables, qui y sont intégrées.
Il est nécessaire de faire correspondre la distribution empirique à ces hypothèses, ce qui implique un travail de restructuration des données. Celui-ci permettra d’avoir des résultats de modélisation plus pertinents. Nous verrons dans le chapitre sur les pipelines comment industrialiser ces étapes de preprocessing afin de se simplifier la vie pour appliquer un modèle sur un jeu de données différent de celui sur lequel il a été estimé.
Ce chapitre, comme l’ensemble de la partie machine learning, est une introduction pratique illustrée dans une perspective de prédiction électorale. En l’occurrence, il s’agit de prédire les résultats des élections américaines de 2020 au niveau comté à partir de variables socio-démographiques. L’idée sous-jacente est qu’il existe des facteurs sociologiques, économiques ou démographiques influençant le vote mais dont on ne connaît pas bien les motifs ou les interactions complexes entre plusieurs facteurs.
1.1 Présentation de l’écosystème Scikit
Scikit Learn
est aujourd’hui la librairie de référence dans l’écosystème du
Machine Learning. Il s’agit d’une librairie qui, malgré les très nombreuses
méthodes implémentées, présente l’avantage d’être un point d’entrée unifié.
Cet aspect unifié est l’une des raisons du succès précoce de celle-ci. R
n’a
bénéficié que plus récemment d’une librairie unifiée,
à savoir tidymodels
.
Une autre raison du succès de Scikit
est son approche opérationnelle : la mise
en production de modèles développés via les pipelines Scikit
est peu coûteuse.
Un chapitre spécial de ce cours est dédié aux pipelines.
Avec Romain Avouac, nous proposons un cours plus avancé
en dernière année d’ENSAE où nous présentons certains enjeux relatifs
à la mise en production de modèles développés avec Scikit
.
Le guide utilisateur de Scikit
est une référence précieuse,
à consulter régulièrement. La partie sur le preprocessing, objet de ce chapitre, est
disponible ici.
Scikit Learn
est une librairie open source issue des travaux de l’Inria 🇫🇷. Depuis plus de 10 ans, cette institution publique française développe et maintient ce package téléchargé 2 millions de fois par jour. En 2023, pour sécuriser la maintenance de ce package, une start up nommée Probabl.ai
a été créée autour de l’équipe des développeurs.euses de Scikit
.
Pour découvrir la richesse de l’écosystème Scikit
, il
est recommandé de suivre le
MOOC scikit
,
développé dans le cadre de l’initiative Inria Academy
.
1.2 Préparation des données
L’exercice 1 permet, à ceux qui le désirent, d’essayer de le reconstituer pas à pas.
Les packages suivants sont nécessaires pour importer et visualiser les données d’élection :
Les sources de données étant diverses, le code qui construit la base finale est directement fourni.
Le travail de construction d’une base unique
est un peu fastidieux mais il s’agit d’un bon exercice, que vous pouvez tenter,
pour réviser Pandas
:
Cet exercice est OPTIONNEL
- Télécharger et importer le shapefile depuis ce lien
- Exclure les Etats suivants : “02”, “69”, “66”, “78”, “60”, “72”, “15”
- Importer les résultats des élections depuis ce lien
- Importer les bases disponibles sur le site de l’USDA en faisant attention à renommer les variables de code FIPS de manière identique dans les 4 bases
- Merger ces 4 bases dans une base unique de caractéristiques socioéconomiques
- Merger aux données électorales à partir du code FIPS
- Merger au shapefile à partir du code FIPS. Faire attention aux 0 à gauche dans certains codes. Il est
recommandé d’utiliser la méthode
str.lstrip
pour les retirer - Importer les données des élections 2000 à 2016 à partir du MIT Election Lab? Les données peuvent être directement requêtées depuis l’url https://dataverse.harvard.edu/api/access/datafile/3641280?gbrecs=false
- Créer une variable
share
comptabilisant la part des votes pour chaque candidat. Ne garder que les colonnes"year", "FIPS", "party", "candidatevotes", "share"
- Faire une conversion
long
towide
avec la méthodepivot_table
pour garder une ligne par comté x année avec en colonnes les résultats de chaque candidat dans cet état. - Merger à partir du code FIPS au reste de la base.
Si vous ne faites pas l’exercice 1, pensez à charger les données en executant la fonction get_data.py
:
import requests
= "https://raw.githubusercontent.com/linogaliana/python-datascientist/main/content/modelisation/get_data.py"
url = requests.get(url, allow_redirects=True)
r open("getdata.py", "wb").write(r.content)
import getdata
= getdata.create_votes_dataframes() votes
Néanmoins, avant de se concentrer sur la préparation des données, nous allons passer un peu de temps à explorer la structure des données à partir de laquelle nous désirons construire une modélisation. Ceci est indispensable afin de comprendre la nature de celles-ci et choisir une modélisation adéquate.
Ce code introduit une base nommée votes
dans l’environnement. Il s’agit d’une base rassemblant les différentes sources. Elle a l’aspect
suivant :
!= "geometry"].head(3) votes.loc[:, votes.columns
STATEFP | COUNTYFP | COUNTYNS | AFFGEOID | GEOID | NAME | LSAD | ALAND | AWATER | FIPS_x | ... | share_2008_democrat | share_2008_other | share_2008_republican | share_2012_democrat | share_2012_other | share_2012_republican | share_2016_democrat | share_2016_other | share_2016_republican | winner | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 29 | 227 | 00758566 | 0500000US29227 | 29227 | Worth | 06 | 690564983 | 493903 | 29227 | ... | 0.363714 | 0.034072 | 0.602215 | 0.325382 | 0.041031 | 0.633588 | 0.186424 | 0.041109 | 0.772467 | republican |
1 | 31 | 061 | 00835852 | 0500000US31061 | 31061 | Franklin | 06 | 1491355860 | 487899 | 31061 | ... | 0.284794 | 0.019974 | 0.695232 | 0.250000 | 0.026042 | 0.723958 | 0.149432 | 0.045427 | 0.805140 | republican |
2 | 36 | 013 | 00974105 | 0500000US36013 | 36013 | Chautauqua | 06 | 2746047476 | 1139407865 | 36013 | ... | 0.495627 | 0.018104 | 0.486269 | 0.425017 | 0.115852 | 0.459131 | 0.352012 | 0.065439 | 0.582550 | republican |
3 rows × 382 columns
La carte choroplèthe suivante permet de visualiser rapidement les résultats (l’Alaska et Hawaï ont été exclus).
Code pour reproduire cette carte
from plotnine import *
# republican : red, democrat : blue
= {"republican": "#FF0000", "democrats": "#0000FF"}
color_dict
(
ggplot(votes)+ geom_map(aes(fill="winner"))
+ scale_fill_manual(color_dict)
+ labs(fill="Winner")
+ theme_void()
+ theme(legend_position="bottom")
)
Comme cela a été évoqué dans le chapitre consacré à la cartographie, les cartes choroplèthes peuvent donner une impression fallacieuse que le parti Républicain a gagné largement en 2020 car ce type de représentation graphique donne plus d’importance aux grands espaces plutôt qu’aux espaces denses. Ceci explique que ce type de carte ait pu servir de justification pour contester les résultats du vote.
Il existe des représentations à privilégier pour ce type de phénomènes où la densité est importante. L’une des représentations à privilégier est les ronds proportionnels (voir Insee (2018), “Le piège territorial en cartographie”). Les cercles proportionnels permettent ainsi à l’oeil de se concentrer sur les zones les plus denses et non sur les grands espaces. Cette fois, on voit bien que le vote démocrate est majoritaire, ce que cachait l’aplat de couleur.
Le GIF “Land does not vote, people do”, qui avait eu un certain succès en 2020, propose un autre mode de visualisation.
La carte originale a été construite avec JavaScript
. Cependant,
on dispose avec Python
de plusieurs outils
pour répliquer, à faible coût, cette carte
grâce à
l’une des surcouches à JavaScript
vues dans la partie visualisation.
Code pour reproduire cette carte interactive
import numpy as np
import pandas as pd
import geopandas as gpd
import plotly
import plotly.graph_objects as go
= votes.copy()
centroids = centroids.centroid
centroids.geometry "size"] = (
centroids["CENSUS_2010_POP"] / 10000
centroids[# to get reasonable plotable number
)
= {"republican": "#FF0000", "democrats": "#0000FF"}
color_dict "winner"] = np.where(
centroids["votes_gop"] > centroids["votes_dem"], "republican", "democrats"
centroids[
)
"lon"] = centroids["geometry"].x
centroids["lat"] = centroids["geometry"].y
centroids[= pd.DataFrame(
centroids "county_name", "lon", "lat", "winner", "CENSUS_2010_POP", "state_name"]]
centroids[[
)= centroids.groupby("winner")
groups
= centroids.copy()
df
"color"] = df["winner"].replace(color_dict)
df["size"] = df["CENSUS_2010_POP"] / 6000
df["text"] = (
df["CENSUS_2010_POP"]
df[int)
.astype(apply(lambda x: "<br>Population: {:,} people".format(x))
.
)"hover"] = (
df["county_name"].astype(str)
df[+ df["state_name"].apply(lambda x: " ({}) ".format(x))
+ df["text"]
)
= go.Figure(
fig_plotly =go.Scattergeo(
data="USA-states",
locationmode=df["lon"],
lon=df["lat"],
lat=df["hover"],
text="markers",
mode=df["color"],
marker_color=df["size"],
marker_size="text",
hoverinfo
)
)
fig_plotly.update_traces(={
marker"opacity": 0.5,
"line_color": "rgb(40,40,40)",
"line_width": 0.5,
"sizemode": "area",
}
)
fig_plotly.update_layout(='Reproduction of the "Acres don\'t vote, people do" map <br>(Click legend to toggle traces)',
title_text=True,
showlegend={"scope": "usa", "landcolor": "rgb(217, 217, 217)"},
geo
)
fig_plotly.show()
2 La démarche générale
Dans ce chapitre, nous allons nous focaliser sur la préparation des données à faire en amont du travail de modélisation. Cette étape est indispensable pour s’assurer de la cohérence entre les données et les hypothèses de modélisation mais aussi pour produire des analyses valides scientifiquement.
La démarche générale que nous adopterons dans ce chapitre, et qui sera ensuite raffinée dans les prochains chapitres, est la suivante :
La Figure 2.1 illustre la structuration d’un problème de machine learning.
Tout d’abord, on découpe l’ensemble des données disponibles en deux parties, échantillons d’apprentissage et de validation. Le premier sert à entraîner un modèle et la qualité des prédictions de celui-ci est évaluée sur le deuxième pour limiter le biais de surapprentissage. Le chapitre suivant approfondira cette question de l’évaluation des modèles. A ce stade de notre progression, on se concentrera dans ce chapitre sur la question des données.
La librairie Scikit
est particulièrement pratique parce qu’elle propose énormément d’algorithmes de machine learning avec quelques points d’entrée unifiée, notamment les méthodes fit
et predict
. Néanmoins, l’unification va au-delà de l’entraînement d’algorithmes. Toutes les étapes de préparation de données qui sont intégrées dans Scikit
proposent ces deux mêmes points d’entrée. Autrement dit, les préparations de données sont construites comme une estimation de paramètres qui peut être réappliquée sur un autre jeu de données. Par exemple, cette préparation de données peut être une estimation de moyenne et variance pour normaliser des variables. La moyenne et la variance seront évaluées sur l’échantillon d’apprentissage et les mêmes moyennes et variances pourront être réappliquées sur un autre jeu de données pour le normaliser de la même façon.
3 Explorer la structure des données
La première étape nécessaire à suivre avant de se lancer dans la modélisation est de déterminer les variables à inclure dans le modèle.
Les fonctionnalité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 variables,
ce que nous verrons par la suite dans la partie dédiée.
Avant d’être en mesure de sélectionner le meilleur ensemble de variables explicatives, nous allons en prendre un nombre restreint et arbitraire. La première tâche est de représenter les relations entre les données, notamment la relation des variables explicatives à la variable dépendante (le score du parti républicain) ainsi que les relations entre les variables explicatives.
Cet exercice est OPTIONNEL
- Créer un DataFrame
df2
plus petit avec les variableswinner
,votes_gop
,Unemployment_rate_2019
,Median_Household_Income_2019
,Percent of adults with less than a high school diploma, 2015-19
,Percent of adults with a bachelor's degree or higher, 2015-19
- Représenter grâce à un graphique la matrice de corrélation. Vous pouvez utiliser le package
seaborn
et sa fonctionheatmap
. - Représenter une matrice de nuages de points des variables de la base
df2
avecpd.plotting.scatter_matrix
- (optionnel) Refaire ces figures avec
Plotly
qui offre également la possibilité de faire une matrice de corrélation.
# 1. Créer le data.frame df2.
= votes.set_index("GEOID").loc[
df2
:,
["winner",
"votes_gop",
"Unemployment_rate_2019",
"Median_Household_Income_2019",
"Percent of adults with less than a high school diploma, 2015-19",
"Percent of adults with a bachelor's degree or higher, 2015-19",
], ]
La matrice construite avec seaborn
(question 2) aura l’aspect suivant :
Le nuage de point obtenu à l’issue de la question 3 ressemblera à :
# 3. Matrice de nuages de points
pd.plotting.scatter_matrix(df2)
array([[<Axes: xlabel='votes_gop', ylabel='votes_gop'>,
<Axes: xlabel='Unemployment_rate_2019', ylabel='votes_gop'>,
<Axes: xlabel='Median_Household_Income_2019', ylabel='votes_gop'>,
<Axes: xlabel='Percent of adults with less than a high school diploma, 2015-19', ylabel='votes_gop'>,
<Axes: xlabel="Percent of adults with a bachelor's degree or higher, 2015-19", ylabel='votes_gop'>],
[<Axes: xlabel='votes_gop', ylabel='Unemployment_rate_2019'>,
<Axes: xlabel='Unemployment_rate_2019', ylabel='Unemployment_rate_2019'>,
<Axes: xlabel='Median_Household_Income_2019', ylabel='Unemployment_rate_2019'>,
<Axes: xlabel='Percent of adults with less than a high school diploma, 2015-19', ylabel='Unemployment_rate_2019'>,
<Axes: xlabel="Percent of adults with a bachelor's degree or higher, 2015-19", ylabel='Unemployment_rate_2019'>],
[<Axes: xlabel='votes_gop', ylabel='Median_Household_Income_2019'>,
<Axes: xlabel='Unemployment_rate_2019', ylabel='Median_Household_Income_2019'>,
<Axes: xlabel='Median_Household_Income_2019', ylabel='Median_Household_Income_2019'>,
<Axes: xlabel='Percent of adults with less than a high school diploma, 2015-19', ylabel='Median_Household_Income_2019'>,
<Axes: xlabel="Percent of adults with a bachelor's degree or higher, 2015-19", ylabel='Median_Household_Income_2019'>],
[<Axes: xlabel='votes_gop', ylabel='Percent of adults with less than a high school diploma, 2015-19'>,
<Axes: xlabel='Unemployment_rate_2019', ylabel='Percent of adults with less than a high school diploma, 2015-19'>,
<Axes: xlabel='Median_Household_Income_2019', ylabel='Percent of adults with less than a high school diploma, 2015-19'>,
<Axes: xlabel='Percent of adults with less than a high school diploma, 2015-19', ylabel='Percent of adults with less than a high school diploma, 2015-19'>,
<Axes: xlabel="Percent of adults with a bachelor's degree or higher, 2015-19", ylabel='Percent of adults with less than a high school diploma, 2015-19'>],
[<Axes: xlabel='votes_gop', ylabel="Percent of adults with a bachelor's degree or higher, 2015-19">,
<Axes: xlabel='Unemployment_rate_2019', ylabel="Percent of adults with a bachelor's degree or higher, 2015-19">,
<Axes: xlabel='Median_Household_Income_2019', ylabel="Percent of adults with a bachelor's degree or higher, 2015-19">,
<Axes: xlabel='Percent of adults with less than a high school diploma, 2015-19', ylabel="Percent of adults with a bachelor's degree or higher, 2015-19">,
<Axes: xlabel="Percent of adults with a bachelor's degree or higher, 2015-19", ylabel="Percent of adults with a bachelor's degree or higher, 2015-19">]],
dtype=object)
Le résultat de la question 4 devrait, quant à lui, ressembler au graphique suivant :
4 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 \(\mathbb{R}\). Une variable
discrète (prenant un nombre fini de valeurs) devra être transformée en suite de
variables 0/1 (des dummies) par rapport à une modalité de référence pour être en adéquation
avec les hypothèses de la régression linéaire.
On appelle ce type de transformation
one-hot encoding, sur laquelle nous reviendrons. Il s’agit d’une transformation,
parmi d’autres, disponibles dans Scikit
pour mettre en adéquation un jeu de
données et des hypothèses mathématiques.
L’ensemble de ces tâches de préparation de données s’appelle le preprocessing ou le feature engineering. L’un des intérêts
d’utiliser Scikit
est qu’on peut considérer qu’une tâche de preprocessing
est, en fait, une tâche d’apprentissage. En effet, le preprocessing
consiste à apprendre des paramètres d’une structure
de données (par exemple estimer moyennes et variances pour les retrancher à chaque
observation) et on peut très bien appliquer ces paramètres
à des observations qui n’ont pas servi à construire
ceux-ci. Autrement dit, cette préparation de données s’intègre très bien dans le pipeline Figure 2.1.
4.1 Preprocessing de variables continues
Nous allons voir deux processus très classiques de preprocessing pour des variables continues :
La standardisation transforme des données pour que la distribution empirique suive une loi \(\mathcal{N}(0,1)\).
La normalisation transforme les données de manière à obtenir une norme (\(\mathcal{l}_1\) ou \(\mathcal{l}_2\)) unitaire. Autrement dit, avec la norme adéquate, la somme des éléments est égale à 1.
Il en existe d’autres, par exemple le MinMaxScaler
pour renormaliser les variables en fonction des bornes minimales et maximales des valeurs observées. Le choix de la méthode a mettre en oeuvre dépend du type d’algorithmes choisis par la suite: les hypothèses des k plus proches voisins (knn) seront différentes de celles d’une random forest. C’est pour cette raison que, normalement, on définit des pipelines complets, intégrant à la fois preprocessing et apprentissage. Ce sera l’objet des prochains chapitres.
Pour les statisticiens.ennes,
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 \(X \sim \mathcal{N}(0,1)\).
C’est, en fait, la standardisation en Scikit
qui fait cela.
4.1.1 Standardisation
La standardisation consiste à transformer des données pour que la distribution empirique suive une loi \(\mathcal{N}(0,1)\). Pour être performants, la plupart des modèles de machine learning nécessitent souvent d’avoir des données dans cette distribution. Même lorsque ce n’est pas indispensable, par exemple avec des régressions logistiques, cela peut accélérer la vitesse de convergence des algorithmes.
- Standardiser la variable
Median_Household_Income_2019
(ne pas écraser les valeurs !) et regarder l’histogramme avant/après normalisation. Cette transformation est à appliquer à toute la colonne ; les prochaines questions se préoccuperont du sujet de découpage d’échantillon et d’extrapolation.
Note : 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.
- Créer
scaler
, unTransformer
que vous construisez sur les 1000 premières lignes de votre DataFramedf2
à l’exception de la variable à expliquerwinner
. Vérifier la moyenne et l’écart-type de chaque colonne sur ces mêmes observations.
Note : Les paramètres qui seront utilisés pour une standardisation ultérieure sont stockés dans les attributs .mean_
et .scale_
On peut voir ces attributs comme des paramètres entraînés sur un certain jeu de données et qu’on peut réutiliser sur un autre, à condition que les dimensions coïncident.
- Appliquer
scaler
sur les autres lignes du DataFrame et comparer les distributions obtenues de la variableMedian_Household_Income_2019
.
Note : 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.
Avant standardisation, notre variable a cette distribution:
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 60'. Pick better value with 'binwidth'.
Après standardisation, l’échelle de la variable a changé.
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 60'. Pick better value with 'binwidth'.
On obtient bien une moyenne égale à 0 et une variance égale à 1, aux approximations numériques prêt :
Statistique | Valeur | |
---|---|---|
0 | Mean | 0.000000 |
1 | Variance | 1.000322 |
À la question 2, si on essaie de représenter les statistiques obtenues dans un tableau lisible, on obtient
Variable | Mean before Scaling | Std before Scaling | Mean after Scaling | Std after Scaling |
---|---|---|---|---|
votes_gop | ||||
Unemployment_rate_2019 | ||||
Median_Household_Income_2019 | ||||
Percent of adults with less than a high school diploma, 2015-19 | ||||
Percent of adults with a bachelor's degree or higher, 2015-19 | ||||
y_standard |
On voit très clairement dans ce tableau que la standardisation a bien fonctionné.
Maintenant, si on construit un transformer formel pour nos variables (question 3)
StandardScaler()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
StandardScaler()
On peut extrapoler notre standardiseur à un ensemble plus large de données. Si on regarde la distribution obtenue sur les 1000 premières lignes (question 3), on retrouve une échelle cohérente avec une loi \(\mathcal{N(0,1)}\) pour la variable de chômage:
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 71'. Pick better value with 'binwidth'.
En revanche on voit que cette distribution ne correspond pas à celle qui permettrait de normaliser vraiment le reste des données.
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 40'. Pick better value with 'binwidth'.
C’est une illustration d’un problème classique en machine learning, le data drift, qui arrive lorsqu’on essaie d’extrapoler à des données dont la distribution ne correspond plus à celle des données d’apprentissage. Ce type de situation arrive typiquement lorsqu’on a entraîné un algorithme sur un échantillon biaisé de la population ou lorsqu’on a des séries temporelles non stationnaires. Il est donc important de bien réfléchir à la constitution de l’échantillon d’apprentissage et aux possibilités d’extrapolation sur une population plus large : la validité externe du modèle - préparation des données ou algorithme d’apprentissage - peut être nulle si cette étape a été réalisée de manière hâtive.
Le data drift désigne un changement dans la distribution des données au fil du temps, entraînant une dégradation des performances d’un modèle de machine learning qui, par construction, a été entraîné sur des données passées.
Ce phénomène peut survenir à cause de variations dans la population cible, de changements dans les caractéristiques des données ou de facteurs externes.
Il est crucial de détecter le data drift pour ajuster ou réentraîner le modèle, afin de maintenir sa pertinence et sa précision. Les techniques de détection incluent des tests statistiques et le suivi de métriques spécifiques.
4.1.2 Normalisation
La normalisation est l’action de transformer les données de manière
à obtenir une norme (\(\mathcal{l}_1\) ou \(\mathcal{l}_2\)) unitaire.
Autrement dit, avec la norme adéquate, la somme des éléments est égale à 1.
Par défaut, la norme utilisée par Scikit
est une norme \(\mathcal{l}_2\).
Cette transformation est particulièrement utilisée en classification de texte ou pour effectuer du clustering.
Au passage, ceci est l’occasion de découvrir comment découper ses données en plusieurs échantillons grâce à la fonction train_test_split
de Scikit
. Nous allons faire un échantillon de 70% des données pour estimer les paramètres de normalisation (phase d’apprentissage) et extrapoler aux 30% de données restantes. Cette répartition est assez classique mais est bien-sûr adaptable selon les projets. L’avantage d’utiliser train_test_split
plutôt que de faire soi-même les échantillonnages avec la méthode sample
de Pandas
est que la fonction de Scikit
permettra d’aller beaucoup plus loin dans le paramétrage de l’échantillonnage, notamment si on désire de la stratification, tout en étant fiable. Faire ceci de manière manuelle est fastidieux et risqué car potentiellement complexe à mettre en oeuvre sans erreur.
- A l’aide de la documentation de la fonction
train_test_split
deScikit
, créer deux échantillons (respectivement 70% et 30% des données). - Normaliser la variable
Median_Household_Income_2019
(ne pas écraser les valeurs !) et regarder l’histogramme avant/après normalisation. - Vérifier que la norme \(\mathcal{l}_2\) est bien égale à 1 (grâce à la fonction
np.linalg.norm
et l’argumentaxis=1
pour les 10 premières observations, sur l’ensemble d’entraînement puis sur les autres observations.
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 48'. Pick better value with 'binwidth'.
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 104'. Pick better value with 'binwidth'.
/opt/conda/lib/python3.12/site-packages/plotnine/stats/stat_bin.py:109: PlotnineWarning: 'stat_bin()' using 'bins = 78'. Pick better value with 'binwidth'.
Enfin, si on calcule la norme, on obtient bien le résultat attendu à la fois sur l’échantillon train et sur l’échantillon extrapolé.
X_train_norm2 | X_test_norm2 | |
---|---|---|
0 | 1.0 | 1.0 |
1 | 1.0 | 1.0 |
2 | 1.0 | 1.0 |
3 | 1.0 | 1.0 |
4 | 1.0 | 1.0 |
4.2 Encodage des valeurs catégorielles
Les données catégorielles doivent être recodées sous forme de valeurs numériques pour être intégrés aux modèles de machine learning.
Cela peut être fait de plusieurs manières avec Scikit
:
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 souhaitable.OrdinalEncoder
: une version généralisée duLabelEncoder
qui a vocation à s’appliquer sur des matrices (\(X\)), alors queLabelEncoder
s’applique plutôt à un vecteur (\(y\)).
En ce qui concerne le one hot encoding, il est possible d’utiliser plusieurs méthodes :
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 \times K\) pour lequel chaque colonne sera une variable dummy pour la modalité k. Il y a ici \(K\) modalités et il y a donc multicolinéarité. Avec une régression linéaire avec constante, il convient de retirer une modalité avant l’estimation.OneHotEncoder
est une version généralisée (et optimisée) de la dummy expansion. C’est la méthode recommandée.
4.3 Imputation
Les données peuvent souvent contenir des valeurs manquantes, autrement dit des observations de notre DataFrame contenant un NaN
. Ces trous dans les données peuvent être à l’origine de bugs ou de mauvaises interprétations lorsque l’on passe à la modélisation.
Pour y remédier, une première approche peut être de retirer toutes les observations présentant un NaN
dans au moins une des ses colonnes.
Cependant, si notre table contient beaucoup de NaN
, ou bien que ces derniers sont répartis sur de nombreuses colonnes,
c’est aussi prendre le risque de retirer un nombre important de lignes, et avec cela de l’information importante pour un modèle car les valeurs manquantes sont rarement réparties de manière aléatoire.
Même si dans plusieurs situations, cette solution reste tout à fait viable, il existe une autre approche plus robuste appelée imputation. Cette méthode consiste à remplacer les valeurs manquantes par une valeur donnée. Par exemple :
- Imputation par la moyenne : remplacer tous les
NaN
dans une colonne par la valeur moyenne de la colonne ; - Imputation par la médiane sur le même principe, ou par la valeur de la colonne la plus fréquente pour les variables catégorielles ;
- Imputation par régression : se servir d’autres variables pour essayer d’interpoler une valeur de remplacement adaptée.
Des méthodes plus complexes existent mais dans de nombreux cas, les approches ci-dessus peuvent suffire pour donner des résultats beaucoup plus satisfaisants.
Le package Scikit
permet de faire de l’imputation de manière très simple (documentation ici).
4.4 Gestion des valeurs aberrantes (outliers)
Les valeurs aberrantes (outliers en anglais) sont des observations qui se situent significativement à l’extérieur de la tendance générale des autres observations dans un ensemble de données. En d’autres termes, ce sont des points de données qui se démarquent de manière inhabituelle par rapport à la distribution globale des données. Cela peut être dû à des erreurs de remplissage, des personnes ayant mal répondu à un questionnaire, ou parfois simplement des valeurs extrêmes qui peuvent biaiser un modèle de façon trop importante.
A titre d’exemple, cela va être 3 individus mesurant plus de 4 mètres dans une population, ou bien des revenus de ménage dépassant les 10M d’euros par mois sur l’échelle d’un pays, etc.
Une bonne pratique peut donc être de systématiquement regarder la distribution des variables à disposition, pour se rendre compte si certaines valeurs s’éloignent de façon trop importante des autres. Ces valeurs vont parfois nous intéresser, si, par exemple, on se concentre uniquement sur les très hauts revenus (top 0.1%) en France. Cependant, ces données vont souvent nous gêner plus qu’autre chose, surtout si elles n’ont pas de sens dans le monde réel.
Si l’on estime que la présence de ces données extrêmes, ou outliers, dans notre base de données vont être problématiques plus qu’autre chose, alors il est tout à fait entendable et possible de simplement les retirer. La plupart du temps, on va se donner une proportion des données à retirer, par exemple 0.1%, 1% ou 5%, puis retirer dans les deux queues de la distribution les valeurs extrêmes correspondantes.
Plusieurs packages permettent de faire ce type d’opérations, qui sont parfois plus complexes si on s’intéresse aux outlier sur plusieurs variables.
On pourra notamment citer la fonction IsolationForest()
du package sklearn.ensemble
.
4.5 Exercice d’application
Les transformers créent un mapping entre des modalités textuelles et des valeurs numériques. Cela présuppose que les données sur lesquelles a été construit ce mapping intègrent l’ensemble des valeurs possibles pour les modalités textuelles.
Néanmoins, si de nouvelles modalités apparaissent, le classifieur ne saura pas comment celles-ci doivent être transformées en valeurs numériques. Cela provoquera une erreur pour Scikit
. Cette erreur technique est logique puisqu’il faudrait mettre à jour non seulement le mapping mais aussi l’estimation des paramètres sous-jacents.
Créer
df
qui conserve uniquement les variablesstate_name
etcounty_name
dansvotes
.Appliquer à
state_name
unLabelEncoder
Note : Le résultat du label encoding est relativement intuitif, notamment quand on le met en relation avec le vecteur initial.Regarder la dummy expansion de
state_name
Appliquer un
OrdinalEncoder
àdf[['state_name', 'county_name']]
Note : Le résultat du ordinal encoding est cohérent avec celui du label encodingAppliquer un
OneHotEncoder
àdf[['state_name', 'county_name']]
Note : 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.
Si on regarde les labels et leurs transpositions numériques via LabelEncoder
array([[23, 'Missouri'],
[25, 'Nebraska'],
[30, 'New York'],
...,
[41, 'Texas'],
[41, 'Texas'],
[41, 'Texas']], dtype=object)
Alabama | Arizona | Arkansas | California | Colorado | Connecticut | Delaware | District of Columbia | Florida | Georgia | ... | South Dakota | Tennessee | Texas | Utah | Vermont | Virginia | Washington | West Virginia | Wisconsin | Wyoming | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
1 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
2 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
3 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
4 | False | False | False | False | False | False | False | False | False | False | ... | False | True | False | False | False | False | False | False | False | False |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
3102 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
3103 | False | False | False | False | False | False | False | False | False | False | ... | False | False | False | False | False | False | False | False | False | False |
3104 | False | False | False | False | False | False | False | False | False | False | ... | False | False | True | False | False | False | False | False | False | False |
3105 | False | False | False | False | False | False | False | False | False | False | ... | False | False | True | False | False | False | False | False | False | False |
3106 | False | False | False | False | False | False | False | False | False | False | ... | False | False | True | False | False | False | False | False | False | False |
3107 rows × 49 columns
Si on regarde l’OrdinalEncoder
:
array([23., 25., 30., ..., 41., 41., 41.])
<3107x1891 sparse matrix of type '<class 'numpy.float64'>'
with 6214 stored elements in Compressed Sparse Row format>
Références
Informations additionnelles
environment files have been tested on.
Latest built version: 2024-11-20
Python version used:
'3.12.6 | packaged by conda-forge | (main, Sep 30 2024, 18:08:52) [GCC 13.3.0]'
Package | Version |
---|---|
affine | 2.4.0 |
aiobotocore | 2.15.1 |
aiohappyeyeballs | 2.4.3 |
aiohttp | 3.10.8 |
aioitertools | 0.12.0 |
aiosignal | 1.3.1 |
alembic | 1.13.3 |
altair | 5.4.1 |
aniso8601 | 9.0.1 |
annotated-types | 0.7.0 |
appdirs | 1.4.4 |
archspec | 0.2.3 |
asttokens | 2.4.1 |
attrs | 24.2.0 |
babel | 2.16.0 |
bcrypt | 4.2.0 |
beautifulsoup4 | 4.12.3 |
black | 24.8.0 |
blinker | 1.8.2 |
blis | 0.7.11 |
bokeh | 3.5.2 |
boltons | 24.0.0 |
boto3 | 1.35.23 |
botocore | 1.35.23 |
branca | 0.7.2 |
Brotli | 1.1.0 |
cachetools | 5.5.0 |
cartiflette | 0.0.2 |
Cartopy | 0.24.1 |
catalogue | 2.0.10 |
cattrs | 24.1.2 |
certifi | 2024.8.30 |
cffi | 1.17.1 |
charset-normalizer | 3.3.2 |
click | 8.1.7 |
click-plugins | 1.1.1 |
cligj | 0.7.2 |
cloudpathlib | 0.20.0 |
cloudpickle | 3.0.0 |
colorama | 0.4.6 |
comm | 0.2.2 |
commonmark | 0.9.1 |
conda | 24.9.1 |
conda-libmamba-solver | 24.7.0 |
conda-package-handling | 2.3.0 |
conda_package_streaming | 0.10.0 |
confection | 0.1.5 |
contextily | 1.6.2 |
contourpy | 1.3.0 |
cryptography | 43.0.1 |
cycler | 0.12.1 |
cymem | 2.0.8 |
cytoolz | 1.0.0 |
dask | 2024.9.1 |
dask-expr | 1.1.15 |
databricks-sdk | 0.33.0 |
debugpy | 1.8.6 |
decorator | 5.1.1 |
Deprecated | 1.2.14 |
diskcache | 5.6.3 |
distributed | 2024.9.1 |
distro | 1.9.0 |
docker | 7.1.0 |
duckdb | 0.10.1 |
en-core-web-sm | 3.7.1 |
entrypoints | 0.4 |
et_xmlfile | 2.0.0 |
exceptiongroup | 1.2.2 |
executing | 2.1.0 |
fastexcel | 0.11.6 |
fastjsonschema | 2.20.0 |
fiona | 1.10.1 |
Flask | 3.0.3 |
folium | 0.17.0 |
fontawesomefree | 6.6.0 |
fonttools | 4.54.1 |
frozendict | 2.4.4 |
frozenlist | 1.4.1 |
fsspec | 2023.12.2 |
gensim | 4.3.2 |
geographiclib | 2.0 |
geopandas | 1.0.1 |
geoplot | 0.5.1 |
geopy | 2.4.1 |
gitdb | 4.0.11 |
GitPython | 3.1.43 |
google-auth | 2.35.0 |
graphene | 3.3 |
graphql-core | 3.2.4 |
graphql-relay | 3.2.0 |
graphviz | 0.20.3 |
great-tables | 0.12.0 |
greenlet | 3.1.1 |
gunicorn | 22.0.0 |
h2 | 4.1.0 |
hpack | 4.0.0 |
htmltools | 0.6.0 |
hyperframe | 6.0.1 |
idna | 3.10 |
imageio | 2.36.0 |
importlib_metadata | 8.5.0 |
importlib_resources | 6.4.5 |
inflate64 | 1.0.0 |
ipykernel | 6.29.5 |
ipython | 8.28.0 |
itsdangerous | 2.2.0 |
jedi | 0.19.1 |
Jinja2 | 3.1.4 |
jmespath | 1.0.1 |
joblib | 1.4.2 |
jsonpatch | 1.33 |
jsonpointer | 3.0.0 |
jsonschema | 4.23.0 |
jsonschema-specifications | 2024.10.1 |
jupyter-cache | 1.0.0 |
jupyter_client | 8.6.3 |
jupyter_core | 5.7.2 |
kaleido | 0.2.1 |
kiwisolver | 1.4.7 |
langcodes | 3.5.0 |
language_data | 1.3.0 |
lazy_loader | 0.4 |
libmambapy | 1.5.9 |
locket | 1.0.0 |
lxml | 5.3.0 |
lz4 | 4.3.3 |
Mako | 1.3.5 |
mamba | 1.5.9 |
mapclassify | 2.8.1 |
marisa-trie | 1.2.1 |
Markdown | 3.6 |
markdown-it-py | 3.0.0 |
MarkupSafe | 2.1.5 |
matplotlib | 3.9.2 |
matplotlib-inline | 0.1.7 |
mdurl | 0.1.2 |
menuinst | 2.1.2 |
mercantile | 1.2.1 |
mizani | 0.11.4 |
mlflow | 2.16.2 |
mlflow-skinny | 2.16.2 |
msgpack | 1.1.0 |
multidict | 6.1.0 |
multivolumefile | 0.2.3 |
munkres | 1.1.4 |
murmurhash | 1.0.10 |
mypy-extensions | 1.0.0 |
narwhals | 1.14.1 |
nbclient | 0.10.0 |
nbformat | 5.10.4 |
nest_asyncio | 1.6.0 |
networkx | 3.3 |
nltk | 3.9.1 |
numpy | 1.26.4 |
opencv-python-headless | 4.10.0.84 |
openpyxl | 3.1.5 |
opentelemetry-api | 1.16.0 |
opentelemetry-sdk | 1.16.0 |
opentelemetry-semantic-conventions | 0.37b0 |
OWSLib | 0.28.1 |
packaging | 24.1 |
pandas | 2.2.3 |
paramiko | 3.5.0 |
parso | 0.8.4 |
partd | 1.4.2 |
pathspec | 0.12.1 |
patsy | 0.5.6 |
Pebble | 5.0.7 |
pexpect | 4.9.0 |
pickleshare | 0.7.5 |
pillow | 10.4.0 |
pip | 24.2 |
platformdirs | 4.3.6 |
plotly | 5.24.1 |
plotnine | 0.13.6 |
pluggy | 1.5.0 |
polars | 1.8.2 |
preshed | 3.0.9 |
prometheus_client | 0.21.0 |
prometheus_flask_exporter | 0.23.1 |
prompt_toolkit | 3.0.48 |
protobuf | 4.25.3 |
psutil | 6.0.0 |
ptyprocess | 0.7.0 |
pure_eval | 0.2.3 |
py7zr | 0.20.8 |
pyarrow | 17.0.0 |
pyarrow-hotfix | 0.6 |
pyasn1 | 0.6.1 |
pyasn1_modules | 0.4.1 |
pybcj | 1.0.2 |
pycosat | 0.6.6 |
pycparser | 2.22 |
pycryptodomex | 3.21.0 |
pydantic | 2.9.2 |
pydantic_core | 2.23.4 |
Pygments | 2.18.0 |
PyNaCl | 1.5.0 |
pynsee | 0.1.8 |
pyogrio | 0.10.0 |
pyOpenSSL | 24.2.1 |
pyparsing | 3.1.4 |
pyppmd | 1.1.0 |
pyproj | 3.7.0 |
pyshp | 2.3.1 |
PySocks | 1.7.1 |
python-dateutil | 2.9.0 |
python-dotenv | 1.0.1 |
python-magic | 0.4.27 |
pytz | 2024.1 |
pyu2f | 0.1.5 |
pywaffle | 1.1.1 |
PyYAML | 6.0.2 |
pyzmq | 26.2.0 |
pyzstd | 0.16.2 |
querystring_parser | 1.2.4 |
rasterio | 1.4.2 |
referencing | 0.35.1 |
regex | 2024.9.11 |
requests | 2.32.3 |
requests-cache | 1.2.1 |
retrying | 1.3.4 |
rich | 13.9.4 |
rpds-py | 0.21.0 |
rsa | 4.9 |
ruamel.yaml | 0.18.6 |
ruamel.yaml.clib | 0.2.8 |
s3fs | 2023.12.2 |
s3transfer | 0.10.2 |
scikit-image | 0.24.0 |
scikit-learn | 1.5.2 |
scipy | 1.13.0 |
seaborn | 0.13.2 |
setuptools | 74.1.2 |
shapely | 2.0.6 |
shellingham | 1.5.4 |
six | 1.16.0 |
smart-open | 7.0.5 |
smmap | 5.0.0 |
sortedcontainers | 2.4.0 |
soupsieve | 2.5 |
spacy | 3.7.5 |
spacy-legacy | 3.0.12 |
spacy-loggers | 1.0.5 |
SQLAlchemy | 2.0.35 |
sqlparse | 0.5.1 |
srsly | 2.4.8 |
stack-data | 0.6.2 |
statsmodels | 0.14.4 |
tabulate | 0.9.0 |
tblib | 3.0.0 |
tenacity | 9.0.0 |
texttable | 1.7.0 |
thinc | 8.2.5 |
threadpoolctl | 3.5.0 |
tifffile | 2024.9.20 |
toolz | 1.0.0 |
topojson | 1.9 |
tornado | 6.4.1 |
tqdm | 4.66.5 |
traitlets | 5.14.3 |
truststore | 0.9.2 |
typer | 0.13.1 |
typing_extensions | 4.12.2 |
tzdata | 2024.2 |
Unidecode | 1.3.8 |
url-normalize | 1.4.3 |
urllib3 | 1.26.20 |
wasabi | 1.1.3 |
wcwidth | 0.2.13 |
weasel | 0.4.1 |
webdriver-manager | 4.0.2 |
websocket-client | 1.8.0 |
Werkzeug | 3.0.4 |
wheel | 0.44.0 |
wordcloud | 1.9.3 |
wrapt | 1.16.0 |
xgboost | 2.1.1 |
xlrd | 2.0.1 |
xyzservices | 2024.9.0 |
yarl | 1.13.1 |
yellowbrick | 1.5 |
zict | 3.0.0 |
zipp | 3.20.2 |
zstandard | 0.23.0 |
View file history
SHA | Date | Author | Description |
---|---|---|---|
64c12dc | 2024-11-07 19:28:22 | lgaliana | English version |
e945ff4 | 2024-11-07 18:02:05 | lgaliana | update |
1a8267a | 2024-11-07 17:11:44 | lgaliana | Finalize chapter and fix problem |
63b581f | 2024-11-07 10:48:51 | lgaliana | Normalisation |
a251709 | 2024-11-07 09:31:19 | lgaliana | Exemple |
e072890 | 2024-11-06 18:17:32 | lgaliana | Continue le cleaning |
f3bbddc | 2024-11-06 16:48:47 | lgaliana | Commence revoir premier chapitre modélisation |
c641de0 | 2024-08-22 11:37:13 | Lino Galiana | A series of fix for notebooks that were bugging (#545) |
1cdcd27 | 2024-08-08 07:19:23 | linogaliana | change url |
580cba7 | 2024-08-07 18:59:35 | Lino Galiana | Multilingual version as quarto profile (#533) |
06d003a | 2024-04-23 10:09:22 | Lino Galiana | Continue la restructuration des sous-parties (#492) |
005d89b | 2023-12-20 17:23:04 | Lino Galiana | Finalise l’affichage des statistiques Git (#478) |
3fba612 | 2023-12-17 18:16:42 | Lino Galiana | Remove some badges from python (#476) |
417fb66 | 2023-12-04 18:49:21 | Lino Galiana | Corrections partie ML (#468) |
1f23de2 | 2023-12-01 17:25:36 | Lino Galiana | Stockage des images sur S3 (#466) |
a06a268 | 2023-11-23 18:23:28 | Antoine Palazzolo | 2ème relectures chapitres ML (#457) |
b68369d | 2023-11-18 18:21:13 | Lino Galiana | Reprise du chapitre sur la classification (#455) |
fd3c955 | 2023-11-18 14:22:38 | Lino Galiana | Formattage des chapitres scikit (#453) |
889a71b | 2023-11-10 11:40:51 | Antoine Palazzolo | Modification TP 3 (#443) |
9a4e226 | 2023-08-28 17:11:52 | Lino Galiana | Action to check URL still exist (#399) |
a8f90c2 | 2023-08-28 09:26:12 | Lino Galiana | Update featured paths (#396) |
8082302 | 2023-08-25 17:48:36 | Lino Galiana | Mise à jour des scripts de construction des notebooks (#395) |
3bdf3b0 | 2023-08-25 11:23:02 | Lino Galiana | Simplification de la structure 🤓 (#393) |
78ea2cb | 2023-07-20 20:27:31 | Lino Galiana | Change titles levels (#381) |
29ff3f5 | 2023-07-07 14:17:53 | linogaliana | description everywhere |
f21a24d | 2023-07-02 10:58:15 | Lino Galiana | Pipeline Quarto & Pages 🚀 (#365) |
ebca985 | 2023-06-11 18:14:51 | Lino Galiana | Change handling precision (#361) |
129b001 | 2022-12-26 20:36:01 | Lino Galiana | CSS for ipynb (#337) |
f5f0f9c | 2022-11-02 19:19:07 | Lino Galiana | Relecture début partie modélisation KA (#318) |
f10815b | 2022-08-25 16:00:03 | Lino Galiana | Notebooks should now look more beautiful (#260) |
494a85a | 2022-08-05 14:49:56 | Lino Galiana | Images featured ✨ (#252) |
d201e3c | 2022-08-03 15:50:34 | Lino Galiana | Pimp la homepage ✨ (#249) |
640b960 | 2022-06-10 15:42:04 | Lino Galiana | Finir de régler le problème plotly (#236) |
5698e30 | 2022-06-03 18:28:37 | Lino Galiana | Finalise widget (#232) |
7b9f27b | 2022-06-03 17:05:15 | Lino Galiana | Essaie régler les problèmes widgets JS (#231) |
12965ba | 2022-05-25 15:53:27 | Lino Galiana | :launch: Bascule vers quarto (#226) |
9c71d6e | 2022-03-08 10:34:26 | Lino Galiana | Plus d’éléments sur S3 (#218) |
8f99cd3 | 2021-12-08 15:26:28 | linogaliana | hide otuput |
9c5f718 | 2021-12-08 14:36:59 | linogaliana | format dico :sob: |
41c8986 | 2021-12-08 14:25:18 | linogaliana | correction erreur |
6474746 | 2021-12-08 14:08:06 | linogaliana | dict ici aussi |
8e73912 | 2021-12-08 12:32:17 | linogaliana | coquille plotly :sob: |
3704213 | 2021-12-08 11:57:51 | linogaliana | essaye avec un dict classique |
85565d5 | 2021-12-08 08:15:29 | linogaliana | reformat |
9ace7b9 | 2021-12-07 17:49:18 | linogaliana | évite la boucle crado |
3514e09 | 2021-12-07 16:05:28 | linogaliana | sépare et document |
63e67e6 | 2021-12-07 15:15:34 | Lino Galiana | Debug du plotly (temporaire) (#193) |
65cecdd | 2021-12-07 10:29:18 | Lino Galiana | Encore une erreur de nom de colonne (#192) |
81b7023 | 2021-12-07 09:27:35 | Lino Galiana | Mise à jour liste des colonnes (#191) |
c3bf4d4 | 2021-12-06 19:43:26 | Lino Galiana | Finalise debug partie ML (#190) |
d91a5eb | 2021-12-06 18:53:33 | Lino Galiana | La bonne branche c’est master |
d86129c | 2021-12-06 18:02:32 | Lino Galiana | verbose |
fb14d40 | 2021-12-06 17:00:52 | Lino Galiana | Modifie l’import du script (#187) |
37ecfa3 | 2021-12-06 14:48:05 | Lino Galiana | Essaye nom différent (#186) |
2c8fd0d | 2021-12-06 13:06:36 | Lino Galiana | Problème d’exécution du script import data ML (#185) |
5d0a5e3 | 2021-12-04 07:41:43 | Lino Galiana | MAJ URL script recup data (#184) |
5c10490 | 2021-12-03 17:44:08 | Lino Galiana | Relec (antuki?) partie modelisation (#183) |
2a8809f | 2021-10-27 12:05:34 | Lino Galiana | Simplification des hooks pour gagner en flexibilité et clarté (#166) |
2e4d586 | 2021-09-02 12:03:39 | Lino Galiana | Simplify badges generation (#130) |
49e2826 | 2021-05-13 18:11:20 | Lino Galiana | Corrige quelques images n’apparaissant pas (#108) |
4cdb759 | 2021-05-12 10:37:23 | Lino Galiana | :sparkles: :star2: Nouveau thème hugo :snake: :fire: (#105) |
7f9f97b | 2021-04-30 21:44:04 | Lino Galiana | 🐳 + 🐍 New workflow (docker 🐳) and new dataset for modelization (2020 🇺🇸 elections) (#99) |
347f50f | 2020-11-12 15:08:18 | Lino Galiana | Suite de la partie machine learning (#78) |
671f75a | 2020-10-21 15:15:24 | Lino Galiana | Introduction au Machine Learning (#72) |
Citation
@book{galiana2023,
author = {Galiana, Lino},
title = {Python pour la data science},
date = {2023},
url = {https://pythonds.linogaliana.fr/},
doi = {10.5281/zenodo.8229676},
langid = {fr}
}