Cours
Note de l’éditeur : Jean-Nicholas Hould est data scientist chez Intel Security à Montréal et il explique sur son blog comment démarrer en data science.
L’analyse exploratoire des données (EDA) est une approche statistique qui vise à découvrir et à résumer un jeu de données. À cette étape du processus de data science, vous cherchez à explorer la structure de votre jeu de données, les variables et leurs relations.
Dans cet article, vous vous concentrerez sur un aspect de l’EDA : le data profiling.
Le data profiling consiste à résumer votre jeu de données à l’aide de statistiques descriptives. Vous mobilisez toute une palette de mesures pour mieux le comprendre. Types de données, valeurs manquantes, moyenne, médiane et écart type ne sont que quelques-uns des nombreux éléments à relever lors du profilage d’un jeu de données. L’objectif est d’obtenir une compréhension solide de vos données afin de pouvoir ensuite les interroger et les visualiser sous différents angles.
Savoir d’où viennent vos données
Avant de vous lancer dans une EDA, renseignez-vous au maximum sur la provenance des données que vous analysez. Comprenez comment elles ont été collectées et traitées. Des transformations antérieures pourraient-elles affecter votre analyse ?
Vous devriez pouvoir répondre aux questions suivantes à propos de votre jeu de données :
- Comment a-t-il été collecté ?
- Est-ce un échantillon ?
- L’échantillonnage a-t-il été correctement réalisé ?
- Le jeu de données a-t-il subi des transformations ?
- Connaît-on des problèmes sur ce jeu de données ?
Si vous ne savez pas d’où viennent les données, il sera difficile d’en tirer des conclusions pertinentes. Vous risquez aussi de commettre des erreurs d’analyse majeures.
Assurez-vous en outre que le jeu de données est structuré selon un format standardisé. Le format recommandé est la troisième forme normale, également appelée tidy data. Un jeu de données « propre » présente les caractéristiques suivantes :
- Chaque variable forme une colonne et contient des valeurs
- Chaque observation forme une ligne
- Chaque type d’entité observée forme une table
Respecter ce format standard accélérera votre analyse, car il est compatible avec de nombreux outils et bibliothèques.
Data profiling
Dans cet article, vous utiliserez un jeu de données sur les bières artisanales issu du site CraftCans. Ce jeu ne contient que des données sur des bières en canette provenant de brasseries aux États-Unis. Le site n’indique pas clairement s’il recense toutes les bières en canette brassées aux États‑Unis. Par prudence, nous considérerons qu’il s’agit d’un échantillon pouvant comporter des biais.
Voici la structure des jeux de données utilisés :
Beers :
ID: identifiant unique de la bière.Name: nom de la bière.ABV: titrage alcoolique (Alcohol by volume) de la bière.IBU: unités d’amertume internationales (International Bittering Units) de la bière.Style: style de la bière.Ounces: volume de bière en onces.
Breweries :
ID: identifiant unique de la brasserie.Name: nom de la brasserie.City: ville où se situe la brasserie.State: État où se situe la brasserie.
Types de données
La première étape consiste à comprendre la composition de votre jeu de données.
Quelles variables avez-vous entre les mains ?
En général, les données se rangent dans l’une de ces catégories :
- Numérique
- Catégorielle
- Texte
- Date
Vous commencerez par importer les jeux de données disponibles dans ce dépôt avec la fonction from_csv de pandas. Vous joindrez aussi les jeux beers et breweries pour faciliter la suite de l’analyse.
import pandas as pd
beers = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_beers.csv")
breweries = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_breweries.csv")
beers_and_breweries = pd.merge(beers,
breweries,
how='inner',
left_on="brewery_id",
right_on="id",
sort=True,
suffixes=('_beer', '_brewery'))
Avec la bibliothèque pandas, vous pouvez exécuter la fonction dtypes pour lister chaque colonne et son type de données.
beers.dtypes
Ce qui donne le résultat suivant :
abv float64
ibu float64
id int64
name object
style object
brewery_id int64
ounces float64
dtype: object
Comme vous le voyez, cette fonction ne regroupe pas proprement les différents types de données. Les types numériques (float64 et int64) ne sont pas réunis dans une seule catégorie comme on le souhaiterait. De plus, certaines colonnes sont listées comme objets, ce qui n’est pas très parlant.
Pour contourner cela, vous pouvez créer votre propre fonction qui déterminera la catégorie de chaque colonne d’un DataFrame.
def get_var_category(series):
unique_count = series.nunique(dropna=False)
total_count = len(series)
if pd.api.types.is_numeric_dtype(series):
return 'Numerical'
elif pd.api.types.is_datetime64_dtype(series):
return 'Date'
elif unique_count==total_count:
return 'Text (Unique)'
else:
return 'Categorical'
def print_categories(df):
for column_name in df.columns:
print(column_name, ": ", get_var_category(df[column_name]))
Beers : variables
print_categories(beers)
La commande ci-dessus renvoie le résultat suivant :
abv : Numerical
ibu : Numerical
id : Numerical
name : Categorical
style : Categorical
brewery_id : Numerical
ounces : Numerical
Breweries : variables
print_categories(breweries)
Ce qui donne le résultat suivant :
name : Categorical
city : Categorical
state : Categorical
id : Numerical
Avec ces informations, vous comprenez déjà mieux le jeu de données. Vous savez que vous ne traitez que des données catégorielles et numériques. Les variables numériques permettent de calculer de nombreuses mesures (moyenne, écart type, etc.). Les variables catégorielles servent souvent à segmenter et regrouper les données. Par exemple, vous pourriez chercher à comprendre comment l’IBU varie selon les styles de bière.
Statistiques descriptives
Dans cette section, vous passerez en revue diverses statistiques descriptives utiles pour mieux appréhender les données. Vous constaterez que, prises isolément, ces mesures sont peu parlantes. C’est leur combinaison qui permet d’en tirer le plus de valeur.
Nous allons nous concentrer sur la variable IBU car il s’agit d’une variable numérique. Ce type de variable offre un éventail de mesures plus large que les variables catégorielles. Vous pouvez certes calculer des mesures sur des variables catégorielles, mais vous serez bien plus limité.
Longueur
La fonction len compte le nombre d’observations dans une Series. Elle compte toutes les observations, qu’il y ait des valeurs manquantes ou nulles.
length = len(beers["ibu"])
print(length)
Dans cette Series, nous avons au total 2410 observations.
Effectif non nul
La fonction count renvoie le nombre d’observations non NA/non nulles dans une Series.
count = beers["ibu"].count()
print(count)
Comme vous le voyez, il y a 1405 observations non nulles dans la Series.
Valeurs manquantes
Avec la Length et le Count, nous pouvons calculer le nombre de valeurs manquantes. C’est la différence entre la Length et le Count.
number_of_missing_values = length - count
pct_of_missing_values = float(number_of_missing_values / length)
pct_of_missing_values = "{0:.1f}%".format(pct_of_missing_values*100)
print(pct_of_missing_values)
Pour afficher le pourcentage de valeurs manquantes, il suffit de diviser le nombre de valeurs manquantes par le nombre total d’observations (la longueur). La fonction float garantit la prise en compte des décimales dans la division. La fonction format permet une mise en forme propre en pourcentage.
Dans ce cas, il manque presque 42 % de la variable IBU. C’est une information clé car elle impactera votre analyse. La plupart des statistiques descriptives ignoreront ces valeurs manquantes, ce qui introduira certainement un biais.
Valeur minimale/maximale
Les valeurs minimale et maximale d’un jeu de données s’obtiennent facilement avec les fonctions min et max sur une Series.
print("Minimum value: ", beers["ibu"].min())
print("Maximum value: ", beers["ibu"].max())
Ces valeurs aident à comprendre l’étendue de la variable. Ici, l’IBU varie de 4 à 138.
Mode
Le mode est la valeur la plus fréquente d’un jeu de données. Il s’obtient avec la fonction mode sur une Series.
print(beers["ibu"].mode())
Dans une distribution normale, le mode est égal à la mean et à la median.
Dans notre cas, le mode de la variable IBU est 20. C’est l’IBU le plus fréquent de notre jeu de données.
Moyenne
La moyenne est une mesure de tendance centrale. Elle correspond à la somme des valeurs divisée par le nombre d’observations non manquantes.
Elle s’obtient avec la fonction mean sur une Series.
mean = beers["ibu"].mean()
La moyenne est sensible aux valeurs extrêmes. Quelques valeurs très élevées ou très faibles peuvent la tirer vers le haut ou vers le bas.
Médiane
La médiane est également une mesure de tendance centrale. C’est la valeur qui se situe exactement au milieu d’une liste ordonnée de valeurs numériques.
median = beers["ibu"].median()
En présence de distributions asymétriques, la médiane est souvent plus représentative que la moyenne.
Pour la distribution de l’IBU, la moyenne et la médiane sont du même ordre de grandeur.
Écart type
L’écart type est une mesure de dispersion. Un écart type élevé indique que les valeurs sont réparties sur une large plage. L’écart type s’exprime dans la même unité que les valeurs.
standarddev = beers["ibu"].std()
Ici, l’écart type est d’environ 26 (25,954065911259324 exactement). Si la distribution de l’IBU était normale, ~68 % des observations se trouveraient à une distance d’un écart type autour de la moyenne.
Statistiques de quantiles
Les quantiles sont des points de coupure qui partagent une distribution en parts égales. Plusieurs quantiles ont leur propre nom. Si vous partagez une distribution en quatre groupes égaux, on parle de quartiles. Vous pouvez calculer des quantiles avec la fonction quantile sur une Series. Passez-lui un tableau des quantiles souhaités. Ci-dessous, nous divisons la distribution en quatre groupes égaux.
quantile = beers["ibu"].quantile([.25, .5, .75])
| 0,25 | 21,0 |
| 0,50 | 35,0 |
| 0,75 | 64,0 |
| Name: ibu, | dtype: float64 |
Comme vous le voyez, le quantile 50 % est égal à la médiane : c’est la valeur qui coupe le jeu de données en deux. Vous pouvez aussi noter que 75 % des observations sont inférieures ou égales à 64 IBU. Par ailleurs, 50 % de la distribution se situe entre 21 et 64 IBU. Remarque : les valeurs manquantes ne sont pas prises en compte dans ces métriques.
Graphiques de distribution
Les visualisations sont très utiles en EDA. Nous ne développerons pas ici l’ensemble du sujet, mais on ne peut pas parler de data profiling sans mentionner l’importance d’un graphique de distribution des fréquences. C’est l’une des visualisations les plus simples et pourtant des plus puissantes : elle montre la fréquence de chaque valeur dans notre jeu de données.
Pour créer cette visualisation, nous utilisons la bibliothèque seaborn avec la fonction displot. Cette fonction attend une Series sans valeurs manquantes.
import seaborn as sns
sns.set(color_codes=True)
sns.set_palette(sns.color_palette("muted"))
sns.distplot(beers["ibu"].dropna());

Sur ce graphique, on distingue clairement plusieurs des valeurs calculées précédemment. La valeur minimale est proche de 0 IBU et la valeur maximale avoisine 140 IBU. La valeur la plus fréquente est proche de 20 IBU. En outre, on observe un pic près de 60 IBU.
Pourquoi observe-t-on deux pics dans cette distribution ?
Qu’est-ce qui peut l’expliquer ? C’est un point à explorer dans la deuxième phase de l’EDA.
Corrélations
Les corrélations sont un excellent moyen de déceler des relations entre variables numériques. Il existe plusieurs façons de calculer une corrélation. Le coefficient de corrélation de Pearson est largement utilisé pour mesurer la dépendance linéaire entre deux variables. Il varie de -1 à 1 : 1 indique une corrélation positive totale, -1 une corrélation négative totale et 0 l’absence de corrélation linéaire. Nous pouvons effectuer ce calcul avec la fonction corr sur une Series. Par défaut, elle utilise le coefficient de Pearson, mais d’autres méthodes sont disponibles.
beers[["abv", "ibu", "ounces"]].corr()
| abv | ibu | ounces | |
| abv | 1 | 0.670621 | 0.172529 |
| ibu | 0.670621 | 1 | 0.054691 |
| ounces | 0.172529 | 0.054691 | 1 |
Comme attendu, la corrélation de l’IBU avec lui-même est de 1. Plus intéressant : la corrélation entre l’ABV et l’IBU vaut 0.670621. Ce n’est pas une corrélation parfaite, mais elle reste forte. Voilà un axe d’exploration intéressant pour la suite.
Quelques remarques sur les variables non numériques
Les métriques examinées ci-dessus s’appliquent surtout aux valeurs numériques. Si vous travaillez avec d’autres types de données, comme les données catégorielles, vous pouvez tout de même en tirer des mesures utiles. Par exemple, calculer la fréquence de chaque modalité.
Les DataFrame disposent d’une fonction describe qui résume le jeu de données. Si votre DataFrame ne contient que des valeurs catégorielles ou textuelles, le résumé sera adapté à ce type de données.
beers[["name", "style"]].describe()
| name | style | |
| count | 2410 | 2405 |
| unique | 2305 | 99 |
| top | Nonstop Hef Hop | American IPA |
| freq | 12 | 424 |
Bibliothèques de profiling
Comme vous l’avez vu, rassembler des statistiques descriptives peut être fastidieux. Heureusement, des bibliothèques font ce travail pour vous et produisent un profil très clair de vos données. pandas-profiling en fait partie. Elle propose un profilage statistique prêt à l’emploi de votre jeu de données. Puisque notre jeu est propre et standardisé, nous pouvons l’utiliser immédiatement.
import pandas_profiling
pandas_profiling.ProfileReport(beers_and_breweries)
Et maintenant ?
En général, une fois votre jeu de données profilé, vous avez plus de questions qu’au départ. C’est une bonne chose : ces nouvelles questions alimentent votre EDA.
Voici quelques questions soulevées lors de ce profilage :
- 41,7 % des valeurs d’
IBUmanquent. Pourquoi ? Quel impact sur notre analyse ? - La distribution de l’
IBUprésente deux pics. Comment l’expliquer ? - Qu’est-ce qui explique la corrélation entre
IBUetABV? Quelle est l’influence du style de bière dans cette corrélation ? - Observe‑t‑on des différences d’
IBU, d’ABVou deStyleselon les régions ? Et entre la côte Est et la côte Ouest ?
Le data profiling n’est pas un processus linéaire. À mesure que vous filtrez et segmentez votre jeu, vous y revenez pour calculer des statistiques descriptives sur des sous‑groupes.
Prochaines étapes
Dans cet article, vous avez vu comment profiler un jeu de données. Vous savez désormais affecter les variables à des types de données, calculer différentes statistiques descriptives et interpréter ces mesures. Vous avez aussi découvert des bibliothèques qui automatisent ce travail de profilage. Surtout, vous avez généré de nouvelles questions pour nourrir votre analyse exploratoire.
Découvrez le tutoriel Python sur l’analyse exploratoire des données de DataCamp.