Lab 1 - Les Bases de la Data Science en Python

Navigation : Index | Suivant >>

Objectifs d’apprentissage

A la fin de ce laboratoire, vous saurez : 1. Charger et manipuler des données avec Pandas 2. Explorer un DataFrame (info, describe, head) 3. Filtrer et sélectionner des données avec des conditions 4. Visualiser des données avec Matplotlib 5. Entraîner un modèle de régression linéaire simple

Prérequis

  • Python 3.10+
  • Bibliothèques : pandas, matplotlib, scikit-learn
  • Aucune expérience préalable en Data Science requise

Durée estimée : 30-40 minutes

Introduction à Pandas

Pandas est la bibliothèque de référence pour la manipulation de données en Python. Elle introduit deux structures : la Series (un vecteur indexé, une colonne) et le DataFrame (un tableau à deux axes — lignes indexées, colonnes nommées) qui couvre l’essentiel des besoins de la data science tabulaire. Pourquoi un DataFrame plutôt qu’une liste de listes ? Parce que chaque colonne porte un dtype contrôlé (entier, chaîne, date), que les opérations sont vectorisées (filtrer, moyenner, grouper s’écrivent en une expression, sans boucle) et que l’alignement par index empêche les erreurs d’appariement silencieuses. La cellule ci-dessous montre d’abord un DataFrame minimal construit à la main, puis construit le dataset qui servira à tout le laboratoire : des ventes quotidiennes de cinq produits sur soixante jours. Notez le np.random.seed(42) : le dataset est synthétique mais déterministe — ré-exécuter le notebook produira exactement les mêmes chiffres que ceux commentés dans la suite.

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# Créer un DataFrame avec un dataset réaliste de ventes
np.random.seed(42)  # Pour reproductibilité

# Générer 60 jours de données de ventes
n_days = 60
start_date = datetime(2024, 1, 1)
dates = [start_date + timedelta(days=i) for i in range(n_days)]

# Produits avec prix et catégories
produits = {
    'Widget A': {'prix_base': 150, 'categorie': 'Electronique'},
    'Widget B': {'prix_base': 80, 'categorie': 'Electronique'},
    'Gadget X': {'prix_base': 45, 'categorie': 'Accessoires'},
    'Gadget Y': {'prix_base': 30, 'categorie': 'Accessoires'},
    'Module Z': {'prix_base': 200, 'categorie': 'Premium'}
}

# Générer les transactions
data = []
for date in dates:
    for produit, info in produits.items():
        # Variation saisonnière + bruit aléatoire
        saison = 1 + 0.3 * np.sin(2 * np.pi * date.timetuple().tm_yday / 365)
        ventes = int(info['prix_base'] * saison * (0.8 + 0.4 * np.random.random()))
        data.append({
            'Date': date.strftime('%Y-%m-%d'),
            'Produit': produit,
            'Categorie': info['categorie'],
            'Prix_Unitaire': info['prix_base'],
            'Ventes': ventes
        })

df = pd.DataFrame(data)

# Créer aussi un DataFrame simple pour la démonstration
df_simple = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})

print("DataFrame simple :")
print(df_simple)
print(f"\nDataset de ventes : {len(df)} enregistrements sur {n_days} jours")
print(f"Produits : {df['Produit'].nunique()} | Catégories : {df['Categorie'].nunique()}")
print("\nAperçu des données :")
df.head(10)
DataFrame simple :
   col1  col2
0     1     3
1     2     4

Dataset de ventes : 300 enregistrements sur 60 jours
Produits : 5 | Catégories : 3

Aperçu des données :
Date Produit Categorie Prix_Unitaire Ventes
0 2024-01-01 Widget A Electronique 150 143
1 2024-01-01 Widget B Electronique 80 94
2 2024-01-01 Gadget X Accessoires 45 49
3 2024-01-01 Gadget Y Accessoires 30 31
4 2024-01-01 Module Z Premium 200 173
5 2024-01-02 Widget A Electronique 150 130
6 2024-01-02 Widget B Electronique 80 66
7 2024-01-02 Gadget X Accessoires 45 52
8 2024-01-02 Gadget Y Accessoires 30 31
9 2024-01-02 Module Z Premium 200 218

Lecture du dataset — la sortie résume la construction : 300 enregistrements sur 60 jours, 5 produits, 3 catégories. D’où viennent ces chiffres ? Cinq produits vendus chaque jour pendant 60 jours : 5 x 60 = 300 lignes, exactement. L’aperçu montre la structure : une ligne par couple (produit, jour), avec son prix unitaire (fixe par produit : Widget A a 150 partout) et ses ventes (tirées aléatoirement — la seule colonne qui varie pour un produit donné). La colonne Date est ici une chaîne, pas un type temporel : on le verra dans info() plus bas, et c’est une limite réaliste d’un dataset fabriqué à la main. Enfin, le np.random.seed(42) du code garantit que ces chiffres — 143 ventes de Widget A le 2024-01-01, 173 de Module Z — sont les mêmes à chaque exécution : les lectures qui suivent citent des valeurs que vous retrouverez à l’identique.

Exploration de Données

Avant toute analyse, trois questions et trois outils pour y répondre : à quoi ressemblent les données ? (head() — les premières lignes, brutes), quelle est leur structure ? (info() — nombre de lignes, colonnes, dtypes, valeurs manquantes, empreinte mémoire) et quelle est leur distribution ? (describe() — moyenne, écart-type, quartiles, extrêmes des colonnes numériques). Cette triade suffit à détecter la plupart des problèmes de données avant qu’ils ne contaminent l’analyse : colonne au mauvais dtype, valeurs manquantes, échelle aberrante. La cellule ajoute un quatrième geste, le groupby — agréger les ventes par catégorie — qui est l’opération centrale de l’analyse de données tabulaires : segmenter, agréger, comparer.

# Afficher les premières lignes
print("Premières lignes (head) :")
print(df.head())

# Obtenir des informations générales sur le DataFrame
print("\nInformations (info) :")
df.info()

# Obtenir des statistiques descriptives
print("\nStatistiques descriptives (describe) :")
print(df.describe())

# Statistiques par catégorie
print("\nVentes moyennes par catégorie :")
print(df.groupby('Categorie')['Ventes'].mean().round(2))
Premières lignes (head) :
         Date   Produit     Categorie  Prix_Unitaire  Ventes
0  2024-01-01  Widget A  Electronique            150     143
1  2024-01-01  Widget B  Electronique             80      94
2  2024-01-01  Gadget X   Accessoires             45      49
3  2024-01-01  Gadget Y   Accessoires             30      31
4  2024-01-01  Module Z       Premium            200     173

Informations (info) :
<class 'pandas.DataFrame'>
RangeIndex: 300 entries, 0 to 299
Data columns (total 5 columns):
 #   Column         Non-Null Count  Dtype
---  ------         --------------  -----
 0   Date           300 non-null    str  
 1   Produit        300 non-null    str  
 2   Categorie      300 non-null    str  
 3   Prix_Unitaire  300 non-null    int64
 4   Ventes         300 non-null    int64
dtypes: int64(2), str(3)
memory usage: 20.2 KB

Statistiques descriptives (describe) :
       Prix_Unitaire      Ventes
count     300.000000  300.000000
mean      101.000000  114.370000
std        64.636881   74.929977
min        30.000000   26.000000
25%        45.000000   46.000000
50%        80.000000   93.000000
75%       150.000000  186.000000
max       200.000000  284.000000

Ventes moyennes par catégorie :
Categorie
Accessoires      42.64
Electronique    131.33
Premium         223.90
Name: Ventes, dtype: float64

Lecture du trio d’inspection — chaque outil répond à sa question. head() : cinq lignes, une par produit du premier jour, l’ordre du dictionnaire de construction. info() : 300 entrées, 5 colonnes, aucune valeur manquante (300 non-null partout), dtypes str pour les trois colonnes textuelles et int64 pour les deux numériques, 20.2 KB en mémoire — un dataset propre, où le seul point de vigilance est le Date en chaîne. describe() : les ventes vont de 26 à 284 autour d’une moyenne de 114.37 (écart-type 74.93) ; les quartiles montrent une distribution étalée, pas concentrée. Détail révélateur : les colonnes de Prix_Unitaire ont des quartiles qui tombent exactement sur 30 / 45 / 80 / 150 / 200 — les cinq prix de base, chacun présent 60 fois (une fois par jour). Un describe qui affiche les valeurs discrètes d’une variable « continue » est toujours la trace d’une construction synthétique. Et le groupby final établit la hiérarchie commerciale : Accessoires 42.64, Electronique 131.33, Premium 223.90 — un facteur 5 entre les extrêmes, structurant pour tout ce qui suit.

Sélection et Filtrage

Deux gestes distincts qu’il ne faut pas confondre : la sélection (df['Produit']) extrait des colonnes — le résultat est une Series, le vecteur des valeurs d’une variable ; le filtrage (df[df['Ventes'] > 100]) extrait des lignes — le résultat est un DataFrame complet, réduit aux lignes qui satisfont la condition. Le filtrage exploite le masque booléen : df['Ventes'] > 100 produit d’abord un vecteur de True/False (une par ligne), puis les crochets ne gardent que les lignes vraies. C’est ce mécanisme qui rend les conditions composées naturelles — df[(df['Categorie'] == 'Electronique') & (df['Ventes'] > 100)] combine les masques avec & et | (et non and/or, qui ne savent pas se vectoriser). La cellule montre les deux, plus la sélection simultanée de colonnes via une liste df[['Date', 'Produit', 'Ventes']].

# Sélectionner une seule colonne
produits = df['Produit']
print("Colonne 'Produit' (5 premiers) :")
print(produits.head())

# Filtrer les lignes où les ventes sont supérieures à 100
ventes_elevees = df[df['Ventes'] > 100]
print(f"\nVentes supérieures à 100 : {len(ventes_elevees)} enregistrements")
print(ventes_elevees.head())

# Filtrer par catégorie
electronique = df[df['Categorie'] == 'Electronique']
print(f"\nProduits Electronique : {len(electronique)} enregistrements")
print(electronique[['Date', 'Produit', 'Ventes']].head())
Colonne 'Produit' (5 premiers) :
0    Widget A
1    Widget B
2    Gadget X
3    Gadget Y
4    Module Z
Name: Produit, dtype: str

Ventes supérieures à 100 : 135 enregistrements
          Date   Produit     Categorie  Prix_Unitaire  Ventes
0   2024-01-01  Widget A  Electronique            150     143
4   2024-01-01  Module Z       Premium            200     173
5   2024-01-02  Widget A  Electronique            150     130
9   2024-01-02  Module Z       Premium            200     218
10  2024-01-03  Widget A  Electronique            150     123

Produits Electronique : 120 enregistrements
          Date   Produit  Ventes
0   2024-01-01  Widget A     143
1   2024-01-01  Widget B      94
5   2024-01-02  Widget A     130
6   2024-01-02  Widget B      66
10  2024-01-03  Widget A     123

Lecture du filtrage — la sélection retourne bien une Series (une colonne, son dtype str, son index) ; le filtrage retourne bien un DataFrame réduit. Ventes > 100 garde 135 enregistrements sur 300 — 45 % des lignes, cohérent avec une moyenne à 114.37 : un seuil posé presque à la moyenne coupe mécaniquement la population à peine sous la moitié. Le filtre par catégorie donne 120 lignes : deux produits Electronique x 60 jours, exactement le compte attendu — quand un filtre retourne pile le produit cartésien attendu, c’est le signe que le dataset n’a pas de trou. Dernier détail instructif : les index des lignes filtrées (0, 4, 5, 9, 10…) sont non contigus et préservés — le masque ne renumérote pas, il sélectionne ; pour réindexer, il faudrait .reset_index(drop=True) explicitement.

Visualisation de Données

La visualisation n’est pas une décoration : c’est un outil de diagnostic. La cellule construit une figure à deux panneaux (plt.subplots(1, 2)) parce qu’elle pose deux questions différentes : une série temporelle (courbe) — comment les ventes d’un produit évoluent-elles dans le temps ? — et une comparaison de groupes (barres) — quelles catégories vendent le plus ? Chaque type de question a son geométrie : la courbe préserve l’ordre temporel, la barre compare des grandeurs indépendantes. Interchanger les deux (barres pour une série, courbe pour des catégories) rend le graphique illisible — c’est la première décision de design à prendre, avant toute question d’esthétique (figsize, couleurs, rotation des étiquettes).

import matplotlib.pyplot as plt

# Graphique 1 : Ventes par date (échantillon)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Ventes par date pour Widget A
widget_a = df[df['Produit'] == 'Widget A']
axes[0].plot(widget_a['Date'].values[:20], widget_a['Ventes'].values[:20], 'b-o')
axes[0].set_title('Ventes Widget A (20 premiers jours)')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('Ventes')
axes[0].tick_params(axis='x', rotation=45)

# Ventes moyennes par catégorie
ventes_cat = df.groupby('Categorie')['Ventes'].mean()
axes[1].bar(ventes_cat.index, ventes_cat.values, color=['#1f77b4', '#ff7f0e', '#2ca02c'])
axes[1].set_title('Ventes moyennes par catégorie')
axes[1].set_xlabel('Catégorie')
axes[1].set_ylabel('Ventes moyennes')

plt.tight_layout()
plt.show()

Lecture de la figure — les deux panneaux racontent la même histoire sous deux angles. À gauche, la courbe de Widget A sur 20 jours : 143, 130, 123… elle saute de jour en jour sans tendance nette — c’est du bruit autour d’un niveau, pas une croissance. Retenez cette impression visuelle : la section suivante ajustera précisément une tendance sur ces ventes, et son score faible confirmera ce que l’œil voit déjà — il n’y a presque rien à expliquer par le temps seul. À droite, les barres reprennent le groupby de l’exploration (42.64 / 131.33 / 223.90) : la hiérarchie des catégories y est lisible d’un coup d’œil, c’est exactement le travail d’une barre — comparer trois grandeurs indépendantes. Les deux graphiques ensemble posent la question qui structure la fin du labo : ce qui explique les ventes est-il le temps (courbe) ou le produit (barres) ?

Introduction au Machine Learning

Pour cette première approche du Machine Learning, nous utilisons scikit-learn (sklearn), la bibliotheque de reference en Python pour les algorithmes de ML classique (regression, classification, clustering). Elle offre une API uniforme (fit / predict) et des outils d’evaluation (train_test_split, r2_score). Ici, un modèle de regression lineaire est entraite pour predire les ventes futures.

Reference : Pedregosa, F., Varoquaux, G., Gramfort, A., et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research 12, pp. 2825-2830. arXiv:1201.0490. https://arxiv.org/abs/1201.0490

Le Machine Learning consiste à entraîner un ‘modèle’ sur des données pour qu’il puisse faire des ‘prédictions’ sur de nouvelles données. C’est un concept clé de l’IA. Ici, nous allons simuler une prédiction très simple.

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error
import numpy as np

# Préparer les données pour le ML
# Convertir la date en jour de l'année (feature numérique)
df['Jour'] = pd.to_datetime(df['Date']).dt.dayofyear

# Filtrer un seul produit pour la démonstration
widget_a = df[df['Produit'] == 'Widget A'].copy()

X = widget_a[['Jour']].values  # Feature : jour de l'année
y = widget_a['Ventes'].values  # Target : ventes

# Diviser en train/test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Créer et entraîner le modèle
model = LinearRegression()
model.fit(X_train, y_train)

# Prédictions sur le test set
y_pred = model.predict(X_test)

# Évaluer le modèle
r2 = r2_score(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))

print(f"Performance du modèle :")
print(f"  R² Score : {r2:.3f}")
print(f"  RMSE : {rmse:.2f}")
print(f"  Coefficient : {model.coef_[0]:.4f}")
print(f"  Intercept : {model.intercept_:.2f}")

# Prédire les ventes pour un jour futur
jour_futur = 90  # 31 mars
prediction = model.predict([[jour_futur]])
print(f"\nPrédiction des ventes pour le jour {jour_futur} : {prediction[0]:.2f}")
Performance du modèle :
  R² Score : 0.229
  RMSE : 20.66
  Coefficient : 0.7251
  Intercept : 148.96

Prédiction des ventes pour le jour 90 : 214.22

Lecture du modèle — un R² honnête : la sortie donne R² = 0.229, et c’est le chiffre le plus instructif du laboratoire. Le modèle régresse les ventes sur le numéro de jour seul : R² = 0.229 signifie qu’il explique 23 % de la variance — et donc que 77 % dépendent d’autre chose. L’autre chose, la figure précédente vient de la nommer : le produit (facteur 5 entre catégories). Les autres métriques se lisent ensemble : RMSE = 20.66 ventes d’erreur typique, à rapporter à la moyenne 114.37 (~18 % d’erreur relative) ; coefficient 0.7251 — le modèle n’apprend qu’une hausse de +0.73 vente par jour, un effet minuscule ; intercept 148.96 — le niveau de départ. Enfin la prédiction du jour 90 : 214.22 : 148.96 + 90 x 0.7251, la droite extrapolée… sur un dataset qui s’arrête au jour 60. Prédire au-delà du domaine observé est l’erreur classique de la régression temporelle — la sortie est arithmétiquement exacte et statistiquement téméraire. La leçon : un score faible n’est pas un échec de code mais une information — la variable explicative choisie (le temps) ne porte qu’un quart du phénomène.

Exercices Pratiques

Les trois exercices réutilisent le DataFrame df et les gestes vus plus haut (groupby, filtrage booléen, régression). Les cellules sont des stubs : complétez-les, le notebook reste exécutable de bout en bout même non rempli.

Critères de réussite (vérifiables sur vos sorties) :

  • Exercice 1 : trois sorties distinctes (total, moyenne, jour max par produit) sur Widget A et Widget B ; les moyennes doivent être de l’ordre de grandeur des ventes de la catégorie Electronique (~131 en moyenne) — une moyenne à 20 ou à 400 signale une agrégation sur la mauvaise colonne.
  • Exercice 2 : un compte d’enregistrements strictement inférieur aux 120 lignes Electronique (vous filtrez au-dessus de la moyenne globale 114.37) et cohérent avec la distribution de la catégorie.
  • Exercice 3 : deux modèles séparés, deux coefficients différents — chaque produit a sa propre dynamique ; une prédiction du jour 7 identique pour les deux produits signale que vous avez régressé sur le DataFrame complet au lieu de filtrer par produit.
# Votre code pour l'Exercice 1

# TODO: Calculez le total des ventes par produit
total_ventes_produit = None  # Utilisez groupby('Produit')['Ventes'].sum()

# TODO: Calculez la moyenne des ventes par produit
moyenne_ventes_produit = None  # Utilisez groupby('Produit')['Ventes'].mean()

# TODO: Trouvez le jour avec les ventes les plus élevées pour chaque produit
# Indice: Utilisez sort_values() puis drop_duplicates()
jour_max_ventes = None

# Affichage des résultats
print("Total des ventes par produit :")
print(total_ventes_produit)
print("\nMoyenne des ventes par produit :")
print(moyenne_ventes_produit)
print("\nJour avec ventes maximales par produit :")
print(jour_max_ventes)
Total des ventes par produit :
None

Moyenne des ventes par produit :
None

Jour avec ventes maximales par produit :
None

Exercice 2 : Filtrage conditionnel et statistiques

À partir du DataFrame df, identifiez les transactions « exceptionnelles » de la catégorie Electronique :

  1. Calculez la moyenne globale des ventes sur tout le dataset
  2. Créez un DataFrame filtré contenant uniquement les transactions de catégorie Electronique dont les ventes sont strictement supérieures à cette moyenne globale
  3. Affichez le nombre d’enregistrements filtrés et un aperçu du résultat

Indices : Utilisez df["Ventes"].mean() pour la moyenne, et combinez les deux conditions avec l’opérateur & (pensez aux parenthèses autour de chaque condition).

# Votre code pour l'Exercice 2

# TODO: Calculez la moyenne globale des ventes
moyenne_globale = None  # Utilisez df["Ventes"].mean()

# TODO: Créez le DataFrame filtré avec les deux conditions
# Conditions: Ventes > moyenne_globale ET Categorie == "Electronique"
df_filtre = None  # Utilisez des parenthèses pour chaque condition

# Affichage du résultat
if moyenne_globale is not None and df_filtre is not None:
    print(f"Moyenne globale des ventes : {moyenne_globale:.2f}")
    print(f"Nombre d enregistrements filtrés : {len(df_filtre)}")
    print("DataFrame filtré (Electronique avec ventes > moyenne) :")
    print(df_filtre.head())
else:
    print("Exercice à compléter : calculez la moyenne et filtrez le DataFrame")
Exercice à compléter : calculez la moyenne et filtrez le DataFrame

Exercice 3 : Prédictions pour chaque produit

Entraînez un modèle de régression linéaire séparé pour chaque produit (Widget A et Widget B), puis prédisez les ventes pour le jour 7 de chacun.

Indices : filtrez le DataFrame par produit avant d’entraîner (df[df['Produit'] == 'Widget A']), exactement comme la cellule de visualisation a isolé Widget A pour sa courbe. Comparez enfin les deux prédictions : l’écart entre elles mesure combien la dynamique temporelle diffère selon le produit — un avant-goût de la question « le modèle devrait-il voir le produit ? » que la lecture du modèle global posera.

# Votre code pour l'Exercice 3

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import numpy as np

# TODO: Créez deux DataFrames, un pour chaque produit (Widget A et Gadget X)
df_widget_a = None  # Filtrez df où Produit == "Widget A"
df_gadget_x = None  # Filtrez df où Produit == "Gadget X"

# TODO: Préparez les features (X = Jour) et target (y = Ventes) pour Widget A
X_a = None  # df_widget_a[["Jour"]].values
y_a = None  # df_widget_a["Ventes"].values

# TODO: Préparez les features et target pour Gadget X
X_x = None
y_x = None

# TODO: Entraînez un modèle pour chaque produit
model_a = LinearRegression()
model_x = LinearRegression()

# Appelez model.fit(X, y) pour chaque modèle

# TODO: Prédisez les ventes pour le jour 90 (31 mars)
prediction_a = None  # model_a.predict([[90]])[0]
prediction_x = None

# Affichage des résultats
if prediction_a is not None and prediction_x is not None:
    print(f"Prédiction jour 90 - Widget A : {prediction_a:.2f}")
    print(f"Prédiction jour 90 - Gadget X : {prediction_x:.2f}")
    print(f"Différence de prédictions : {abs(prediction_a - prediction_x):.2f}")
else:
    print("Exercice à compléter : filtrez les DataFrames et entraînez les modèles")
Exercice à compléter : filtrez les DataFrames et entraînez les modèles

Conclusion : De la Data aux Agents IA

Félicitations ! Vous avez exploré les briques fondamentales de la Data Science en Python : charger et manipuler des données avec Pandas, les visualiser, et même faire une prédiction simple avec un modèle de Machine Learning.

Ces briques (le DataFrame comme source de connaissance, le Modèle comme outil de raisonnement) sont exactement ce que nous allons apprendre à fournir à des Agents d’IA.

Dans la suite de ce cursus, nous verrons comment un agent peut : 1. Recevoir un Prompt en langage naturel (ex: “Donne-moi le total des ventes pour le Widget A”). 2. Utiliser le DataFrame comme un outil via une API pour trouver la réponse. 3. Utiliser un Modèle de ML pour répondre à des questions plus complexes (ex: “Quelle serait la prévision des ventes pour demain ?”).

Ce laboratoire constitue la base technique essentielle pour construire des systèmes d’IA plus autonomes et intelligents.


Navigation : Index | Suivant >> Lab 2 - RFP Analysis

References

  • Pedregosa, F., Varoquaux, G., Gramfort, A., et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research 12, pp. 2825-2830. arXiv:1201.0490. https://arxiv.org/abs/1201.0490
  • McKinney, W. (2010). Data Structures for Statistical Computing in Python. SciPy 2010 proceedings, pp. 51-56.
  • Harris, C.R., Millman, K.J., et al. (2020). Array programming with NumPy. Nature 585, pp. 357-362.
Retour au sommet