NumPy et Pandas sont les fondations de la Data Science Python. NumPy apporte le calcul vectorisé sur des matrices, Pandas apporte les DataFrames pour manipuler des données tabulaires — comme Excel, mais avec du code.
Définition : Un ndarray est un tableau multidimensionnel homogène de NumPy — tous les éléments ont le même type de données (int, float, etc.). Contrairement aux listes Python, les éléments sont stockés en mémoire de manière contiguë, ce qui permet des opérations très rapides.
But : Fournir une structure de données optimisée pour les calculs numériques et scientifiques, en remplaçant les boucles Python lentes par des opérations vectorisées compilées.
Pourquoi ici : NumPy est la base de tout traitement de données en Python. Comprendre le concept d'ndarray est essentiel avant de manipuler les données avec Pandas.
import numpy as np
# ── Créer des tableaux ──
arr1d = np.array([1, 2, 3, 4, 5])
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2d.shape) # (2, 3) — 2 lignes, 3 colonnes
print(arr2d.dtype) # int64
print(arr2d.ndim) # 2
# ── Tableaux spéciaux ──
zeros = np.zeros((3, 4)) # Matrice 3×4 de zéros
ones = np.ones((2, 3), dtype=np.float32)
eye = np.eye(4) # Matrice identité 4×4
rand = np.random.randn(100, 10) # 100×10 — distribution normale
seq = np.arange(0, 10, 0.5) # [0, 0.5, 1.0, ..., 9.5]
# ── Opérations vectorisées (SANS boucle !) ──
prix = np.array([100, 200, 150, 300, 250])
# Appliquer une TVA de 20% à tous les prix en une ligne
prix_ttc = prix * 1.20 # [120, 240, 180, 360, 300]
prix_reduits = np.where(prix > 200, prix * 0.9, prix) # -10% si prix > 200
# ── Broadcasting ──
arr_3 = np.array([1, 2, 3]) # shape (3,)
arr_3x1 = np.array([[10], [20], [30]]) # shape (3, 1)
result = arr_3 + arr_3x1 # Broadcasting — résultat shape (3, 3)
# [[11, 12, 13], [21, 22, 23], [31, 32, 33]]
# ── Statistiques ──
print(f"Moyenne : {prix.mean():.2f}") # 200.00
print(f"Médiane : {np.median(prix):.2f}")
print(f"Écart-type : {prix.std():.2f}")
print(f"Min/Max : {prix.min()} / {prix.max()}")
print(f"Percentile 75% : {np.percentile(prix, 75)}")
.mean(), .std() et .percentile() fonctionnent toutes sans boucle Python explicite, ce qui les rend extrêmement rapides même sur des millions d'éléments.Définition : Mécanisme de NumPy qui permet de faire des opérations entre tableaux de formes différentes en "répétant" automatiquement les petits tableaux pour correspondre aux dimensions plus grandes.
But : Éviter de créer explicitement des copies ou de boucler manuellement pour aligner les formes.
Pourquoi ici : Le broadcasting est une source fréquente d'erreurs et de confusion — c'est un concept clé à maîtriser pour écrire du code NumPy efficace. Exemple concret : ajouter un vecteur de shape (3,) à une matrice de shape (3,1) donne une matrice (3,3). NumPy répète automatiquement le vecteur sur chaque ligne, puis le vecteur colonne sur chaque colonne.
arr_3 + arr_3x1. NumPy détecte que les shapes ne correspondent pas (3,) vs (3,1). Il applique les règles de broadcasting : aligner à droite, puis étendre les dimensions de taille 1. Le résultat est une matrice 3×3 où chaque ligne reçoit une copie du vecteur arr_3, mais avec chaque élément modifié par la valeur correspondante de arr_3x1 (10, 20 ou 30 selon la ligne).Définition : Technique qui remplace les boucles Python explicites par des opérations sur des tableaux entiers. La vectorisation confie le calcul à du code optimisé (C/Fortran) au lieu de Python.
But : Accélérer drastiquement les calculs numériques — une opération vectorisée est typiquement 10 à 100 fois plus rapide qu'une boucle Python équivalente.
Pourquoi ici : Comprendre la vectorisation est central pour écrire du code Data Science performant. Au lieu de faire for i in range(len(arr)): result[i] = arr[i] * 2, on écrit simplement arr * 2. La différence de performance s'accroît avec la taille des données — sur 1 million d'éléments, c'est la différence entre quelques millisecondes et plusieurs secondes.
prix * 1.20 s'exécute entièrement en C compilé, sans passer par la boucle interpreter Python. NumPy traite tous les 5 prix simultanément (ou presque) en utilisant les instructions SIMD (Single Instruction Multiple Data) du CPU. C'est pourquoi NumPy est si rapide pour le calcul numérique.import numpy as np
# ── Algèbre linéaire — cas pratique ML ──
# Régression linéaire : y = Xw (forme matricielle)
# Données : 5 maisons avec [surface, chambres, âge]
X = np.array([
[50, 2, 10],
[80, 3, 5],
[120, 4, 2],
[40, 1, 20],
[100, 3, 8]
], dtype=np.float64)
y = np.array([200000, 320000, 480000, 150000, 380000])
# Normalisation (standardisation)
X_mean = X.mean(axis=0) # Moyenne de chaque colonne
X_std = X.std(axis=0)
X_scaled = (X - X_mean) / X_std
# Ajouter colonne de biais (intercept)
X_b = np.column_stack([np.ones(len(X)), X_scaled])
# Équation normale : w = (X^T X)^-1 X^T y
w = np.linalg.lstsq(X_b, y, rcond=None)[0]
print(f"Paramètres : {w}")
# Prédiction pour une nouvelle maison [75m², 3ch, 7 ans]
new_house = np.array([75, 3, 7])
new_scaled = (new_house - X_mean) / X_std
prediction = np.dot([1, *new_scaled], w)
print(f"Prix prédit : {prediction:,.0f} €")
# ── Opérations matricielles ──
A = np.random.randn(4, 4)
print(f"Déterminant : {np.linalg.det(A):.4f}")
print(f"Rang : {np.linalg.matrix_rank(A)}")
eigenvalues, _ = np.linalg.eig(A)
print(f"Valeurs propres : {eigenvalues}")
np.dot) entre les caractéristiques normalisées et les poids appris.Définition : Un DataFrame est une table bidimensionnelle labellisée avec lignes et colonnes, similaire à une feuille Excel ou une table SQL. Les colonnes peuvent avoir des types différents (int, string, datetime, etc.), contrairement aux ndarrays.
But : Représenter et manipuler des données tabulaires réelles avec étiquettes intelligentes pour chaque axe (index pour les lignes, noms pour les colonnes).
Pourquoi ici : Les DataFrames sont la structure de données centrale en Pandas — 90% du travail de Data Science passe par les DataFrames. Un DataFrame combine la puissance du calcul NumPy avec l'usabilité des labels SQL.
Définition : Un Series est une colonne unique d'un DataFrame — un tableau unidimensionnel étiqueté avec un index. Elle combine un ndarray NumPy avec des étiquettes.
But : Représenter une seule variable ou dimension de données avec des étiquettes d'index, permettant l'accès par label ou position.
Pourquoi ici : Une Series est ce qu'on obtient quand on accède à une colonne d'un DataFrame (ex: df['name']). Comprendre les Series aide à comprendre les DataFrames — un DataFrame est en réalité un dictionnaire aligné de Series.
import pandas as pd
import numpy as np
# ── Créer un DataFrame ──
data = {
'name': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'],
'age': [28, 34, 45, 29, 38],
'salary': [45000, 62000, 85000, 51000, 72000],
'department': ['Tech', 'Tech', 'Sales', 'HR', 'Tech'],
'hire_date': pd.to_datetime(['2020-03-15', '2019-07-01', '2015-01-20', '2022-11-05', '2018-04-12'])
}
df = pd.DataFrame(data)
# ── Exploration ──
print(df.info()) # Types, valeurs nulles
print(df.describe()) # Statistiques descriptives des colonnes numériques
print(df.head(3)) # Premières lignes
# ── Sélection ──
tech_employees = df[df['department'] == 'Tech']
high_earners = df[df['salary'] > 60000][['name', 'salary']]
# ── Nouvelles colonnes calculées ──
df['years_employed'] = (pd.Timestamp.now() - df['hire_date']).dt.days / 365
df['seniority'] = pd.cut(
df['years_employed'],
bins=[0, 2, 5, 10, float('inf')],
labels=['Junior', 'Mid', 'Senior', 'Lead']
)
.info() pour voir les types et les valeurs manquantes, .describe() pour des statistiques rapides (moyenne, écart-type, quartiles), et .head() pour visualiser les premières lignes. La sélection conditionnelle avec df[condition] filtre les lignes selon un critère booléen. Les colonnes calculées enrichissent le DataFrame avec de nouvelles variables dérivées (années d'emploi, catégories de séniorité).import pandas as pd
import numpy as np
# ── Simuler des données de ventes ──
np.random.seed(42)
dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')
sales_df = pd.DataFrame({
'date': dates,
'product': np.random.choice(['Laptop', 'Phone', 'Tablet'], len(dates)),
'quantity': np.random.randint(1, 20, len(dates)),
'unit_price': np.random.choice([999, 599, 449], len(dates)),
'region': np.random.choice(['Nord', 'Sud', 'Est', 'Ouest'], len(dates))
})
sales_df['revenue'] = sales_df['quantity'] * sales_df['unit_price']
sales_df['month'] = sales_df['date'].dt.to_period('M')
# ── GroupBy — agrégations ──
monthly = sales_df.groupby('month').agg(
total_revenue=('revenue', 'sum'),
avg_daily_revenue=('revenue', 'mean'),
transactions=('revenue', 'count')
).reset_index()
print(monthly.tail(3))
Définition : Opération qui divise un DataFrame en groupes selon les valeurs d'une ou plusieurs colonnes, applique une fonction à chaque groupe, puis combine les résultats. Ce pattern s'appelle split-apply-combine.
But : Calculer des statistiques ou des transformations séparément pour chaque groupe de données (par catégorie, par période, par région, etc.).
Pourquoi ici : GroupBy est une des opérations les plus puissantes en Pandas. Au lieu de boucler manuellement sur chaque groupe, on décrit l'opération et Pandas l'applique automatiquement — c'est à la fois plus rapide et plus lisible. Dans l'exemple, on regroupe par mois, puis on calcule le revenu total, la moyenne et le nombre de transactions pour chaque mois.
sales_df.groupby('month').agg(...) divise d'abord les 365 lignes en 12 groupes (un par mois). Ensuite, pour chaque mois, Pandas calcule la somme des revenus, la moyenne des revenus, et le nombre de transactions. Enfin, .reset_index() ramène le 'month' en colonne ordinaire pour que le résultat soit un DataFrame simple avec une ligne par mois. C'est beaucoup plus efficace qu'une boucle Python manuelle.# ── Pivot table ──
pivot = pd.pivot_table(
sales_df,
values='revenue',
index='region',
columns='product',
aggfunc='sum',
fill_value=0
)
print(pivot)
Définition : Réorganisation de données tabulaires en réarrangeant les lignes et colonnes, avec agrégation des valeurs (sum, mean, count, etc.). Le résultat est une table où une dimension devient les lignes, une autre les colonnes, et la troisième est agrégée dans les cellules.
But : Résumer et croiser deux dimensions de données pour voir les patterns et les comparaisons rapidement — utile pour l'analyse croisée et les rapports.
Pourquoi ici : Pivot vs GroupBy : GroupBy est pour des résumés simples ou complexes (total par mois, avec multiples agrégations), pivot_table est pour croiser deux dimensions et voir le résultat dans une grille (revenu par région ET produit). Pivot_table appelle GroupBy en interne mais offre une interface plus intuitive pour ce cas d'usage spécifique.
fill_value=0 remplace les combinaisons manquantes par 0 (au lieu de NaN).# ── Nettoyage de données réelles ──
dirty_df = pd.DataFrame({
'price': ['€1,299', 'N/A', '€899', '', '€1,499'],
'date': ['15/01/2024', '2024-01-20', 'invalid', '22/01/2024', '23-01-2024']
})
# Nettoyer le prix
dirty_df['price_clean'] = (
dirty_df['price']
.str.replace('[€,]', '', regex=True)
.replace(['N/A', ''], pd.NA)
.astype('Float64')
)
# Analyser les valeurs manquantes
print(dirty_df.isnull().sum()) # Compte par colonne
print(dirty_df.isnull().mean() * 100) # % de valeurs manquantes
# Imputation — remplacer les NaN par la médiane
dirty_df['price_imputed'] = dirty_df['price_clean'].fillna(dirty_df['price_clean'].median())
.isnull().sum() compte les NaN par colonne pour diagnostiquer les problèmes. .fillna() impute les valeurs manquantes — ici avec la médiane, une stratégie robuste aux outliers. Ce type de nettoyage peut prendre 80% du temps en Data Science réel..apply(), .str, .dt) qui sont 10 à 100x plus rapides. Par exemple, df['price'].str.replace(...) applique la transformation à toute la colonne en une seule opération optimisée, pas élément par élément.