Accéder au contenu principal

Analyse exploratoire des bières artisanales : data profiling

Dans ce tutoriel, vous apprendrez l’analyse exploratoire des données (EDA) en Python, et plus précisément le data profiling avec pandas.
Actualisé 19 sept. 2026  · 14 min lire

Explorer avec l’IA

ChatGPTClaudePerplexity

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());

distribution plot

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’IBU manquent. Pourquoi ? Quel impact sur notre analyse ?
  • La distribution de l’IBU présente deux pics. Comment l’expliquer ?
  • Qu’est-ce qui explique la corrélation entre IBU et ABV ? Quelle est l’influence du style de bière dans cette corrélation ?
  • Observe‑t‑on des différences d’IBU, d’ABV ou de Style selon 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.

Sujets
Analyse des données
Science des données
Python

Pour aller plus loin avec Python

Cours

Analyse de données bayésienne en Python

4 h
16.1K
Découvrez tous les avantages de l'analyse bayésienne des données et appliquez-la à divers cas d'utilisation concrets.
Afficher les détailsRight Arrow
Commencer Le Cours
Voir plusRight Arrow