Pandas Intermédiaire

Maîtriser les Opérations Avancées de Pandas pour le Traitement de Données en Production

Découvrez les techniques professionnelles de Pandas pour manipuler, transformer et optimiser vos données à grande échelle. De la gestion des données manquantes aux performances critiques, apprenez les patterns que utilisent les data scientists en entreprise.

Preparetoi.academy 30 min

1. Gestion Avancée des Données Manquantes et Imputations

Définition

La gestion des données manquantes est l'art de traiter les valeurs NaN, NULL ou vides dans un DataFrame de manière stratégique. Ce n'est pas simplement supprimer les lignes défectueuses, mais choisir la meilleure stratégie d'imputation selon le contexte métier et les caractéristiques des données.

Explication Détaillée

En production, les données manquantes sont inévitables. Elles proviennent de sources défaillantes, d'erreurs de collecte, ou de processus métier incomplets. Ignorer ce problème compromet la qualité des modèles de machine learning. Les stratégies d'imputation varient selon le type de données (numériques vs catégoriques), la proportion de valeurs manquantes, et l'impact business attendu. Une mauvaise imputation introduit des biais systématiques qui faussent les analyses ultérieures.

Bloc de Code

import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer, KNNImputer

# Création d'un dataset avec valeurs manquantes
df = pd.DataFrame({
    'age': [25, np.nan, 35, 28, np.nan, 42],
    'salaire': [50000, 60000, np.nan, 55000, 65000, np.nan],
    'departement': ['IT', 'HR', 'IT', np.nan, 'Finance', 'IT'],
    'experience': [2, 5, np.nan, 3, 7, 4]
})

print("Dataset original:")
print(df)
print("\nPourcentage de données manquantes par colonne:")
print((df.isnull().sum() / len(df) * 100).round(2))

# Stratégie 1: Suppression conditionnelle
df_drop = df.dropna(subset=['age', 'salaire'], how='any')
print("\nAprès suppression des lignes avec NaN dans age/salaire:")
print(df_drop)

# Stratégie 2: Imputation par la médiane (données numériques)
imputer_numeric = SimpleImputer(strategy='median')
df['age'] = imputer_numeric.fit_transform(df[['age']])
print("\nAprès imputation médiane pour l'âge:")
print(df['age'])

# Stratégie 3: Imputation par la mode (données catégoriques)
df['departement'].fillna(df['departement'].mode()[0], inplace=True)
print("\nAprès imputation mode pour département:")
print(df['departement'])

# Stratégie 4: Imputation KNN (utilise la similarité avec voisins)
knn_imputer = KNNImputer(n_neighbors=3)
numeric_cols = ['age', 'salaire', 'experience']
df[numeric_cols] = knn_imputer.fit_transform(df[numeric_cols])
print("\nAprès imputation KNN pour variables numériques:")
print(df[numeric_cols])

# Stratégie 5: Créer un indicateur de valeur imputée
df['salaire_impute'] = df['salaire'].isnull()
df['salaire'].fillna(df['salaire'].mean(), inplace=True)
print("\nDataFrame final avec indicateur d'imputation:")
print(df)

Tableau Comparatif des Stratégies

Stratégie Avantages Inconvénients Cas d'Usage
Suppression Simple, conserve les données réelles Perte d'informations, réduit le dataset Peu de données manquantes (<5%)
Moyenne/Médiane Rapide, préserve les statistiques globales Réduit la variance, introduit des biais Données numériques continues
Mode Bon pour catégories Peut surreprésenter une classe Variables catégoriques
KNN Prend en compte les similitudes Coûteux en calcul, sensible à l'échelle Données multidimensionnelles
Forward Fill Préserve tendances temporelles Inadapté pour données non-temporelles Séries temporelles

Astuce Professionnelle

Toujours créer un indicateur binaire (_is_imputed) avant l'imputation pour tracer quelles données étaient manquantes. Cela permet au modèle de capturer si la missingness elle-même est informative. En production, cette colonne devient précieuse pour auditer et déboguer.

⚠️ Attention Critique

Ne jamais imputer les données manquantes AVANT de splitter train/test. L'imputation doit apprendre des paramètres (médiane, mode, voisins) uniquement sur l'ensemble d'entraînement, puis appliquer ces paramètres au test. Sinon, vous causez une fuite de données (data leakage) qui invalide vos métriques.


2. Groupby, Agrégations et Transformations Complexes

Définition

L'opération groupby segmente un DataFrame en groupes selon une ou plusieurs colonnes, puis applique des fonctions d'agrégation ou de transformation. C'est le cœur des analyses exploratoires et des feature engineering en data science.

Explication Détaillée

groupby est essentiellement un split-apply-combine: vous divisez les données en groupes logiques, appliquez une opération à chaque groupe, puis consolidez les résultats. Contrairement aux simples filtres, groupby permet des analyses par segment, des calculs de statistiques par groupe, et la création de features dépendantes du contexte. Maîtriser les différences entre agg, transform, et apply est crucial pour écrire du code Pandas efficace et lisible.

Bloc de Code

import pandas as pd
import numpy as np

# Dataset de ventes réalistes
df_ventes = pd.DataFrame({
    'date': pd.date_range('2023-01-01', periods=100, freq='D'),
    'vendeur': np.random.choice(['Alice', 'Bob', 'Charlie', 'David'], 100),
    'produit': np.random.choice(['Laptop', 'Phone', 'Tablet'], 100),
    'ventes': np.random.randint(1000, 10000, 100),
    'region': np.random.choice(['Nord', 'Sud', 'Est', 'Ouest'], 100)
})

print("Dataset original (5 premières lignes):")
print(df_ventes.head())

# Agrégation simple: total des ventes par vendeur
print("\n=== AGREGATION SIMPLE ===")
ventes_par_vendeur = df_ventes.groupby('vendeur')['ventes'].sum()
print("Total des ventes par vendeur:")
print(ventes_par_vendeur)

# Agrégations multiples
print("\n=== AGREGATIONS MULTIPLES ===")
statistiques = df_ventes.groupby('vendeur')['ventes'].agg([
    ('total', 'sum'),
    ('moyenne', 'mean'),
    ('min', 'min'),
    ('max', 'max'),
    ('count', 'count'),
    ('std', 'std')
])
print("Statistiques complètes par vendeur:")
print(statistiques)

# Groupby multi-colonnes
print("\n=== GROUPBY MULTI-COLONNES ===")
ventes_produit_region = df_ventes.groupby(['produit', 'region'])['ventes'].agg({
    'total': 'sum',
    'nombre_transactions': 'count',
    'moyenne': 'mean'
}).reset_index()
print("Ventes par produit et région:")
print(ventes_produit_region)

# TRANSFORM vs AGG (différence cruciale)
print("\n=== TRANSFORM vs AGG ===")
# AGG: retourne une valeur par groupe
agg_result = df_ventes.groupby('vendeur')['ventes'].agg('mean')
print("AGG - Moyenne par vendeur (1 ligne par groupe):")
print(agg_result)
print(f"Shape: {agg_result.shape}")

# TRANSFORM: retourne une valeur pour chaque ligne originale
df_ventes['moyenne_vendeur'] = df_ventes.groupby('vendeur')['ventes'].transform('mean')
print("\nTRANSFORM - Moyenne répliquée (même nombre de lignes):")
print(df_ventes[['vendeur', 'ventes', 'moyenne_vendeur']].head(10))
print(f"Shape: {df_ventes[['moyenne_vendeur']].shape}")

# Custom aggregation avec lambda
print("\n=== CUSTOM AGGREGATION ===")
custom_agg = df_ventes.groupby('vendeur').agg({
    'ventes': [
        ('coeff_var', lambda x: x.std() / x.mean()),  # Coefficient de variation
        ('percentile_75', lambda x: x.quantile(0.75)),
        ('range', lambda x: x.max() - x.min())
    ]
})
print("Agrégations personnalisées:")
print(custom_agg)

# Groupby avec filtrage: sales > 5000
print("\n=== GROUPBY AVEC FILTRAGE ===")
vendeurs_hauts_revenus = df_ventes.groupby('vendeur')['ventes'].sum()
vendeurs_filtres = vendeurs_hauts_revenus[vendeurs_hauts_revenus > 200000]
print("Vendeurs avec ventes > 200000:")
print(vendeurs_filtres)

# Ranking au sein des groupes
print("\n=== RANKING DANS LES GROUPES ===")
df_ventes['rang_ventes'] = df_ventes.groupby('vendeur')['ventes'].rank(ascending=False)
print("Ranking des ventes par vendeur:")
print(df_ventes[['vendeur', 'ventes', 'rang_ventes']].head(15))

# Apply pour logique complexe
print("\n=== APPLY POUR LOGIQUE COMPLEXE ===")
def categorie_performance(groupe):
    moyenne = groupe['ventes'].mean()
    if moyenne > 5000:
        return 'Top Performer'
    elif moyenne > 3000:
        return 'Standard'
    else:
        return 'À Développer'

df_ventes['performance'] = df_ventes.groupby('vendeur').apply(
    lambda x: pd.Series([categorie_performance(x)] * len(x))
).values
print("Catégorisation de performance:")
print(df_ventes[['vendeur', 'ventes', 'performance']].drop_duplicates(subset=['vendeur']))

Tableau des Opérations Groupby

Opération Retourne Cas d'Usage Exemple
agg() 1 valeur par groupe Résumés, tableaux de bord groupby('vendeur')['ventes'].sum()
transform() N valeurs (original shape) Features engineered Normaliser par groupe
apply() Flexible, DataFrames Logiques complexes Catégorisation conditionnelle
filter() Groupes entiers Filtrer par critère groupe Garder vendeurs > median
ngroup() Identifiant groupe Labeling, sampling Attribuer ID unique groupe

Astuce Professionnelle

Pour déboguer un groupby complexe, utilisez groupby().head(2) ou groupby().get_group('nom_groupe') pour inspecter rapidement un groupe. En production, nommez explicitement vos agrégations avec un dictionnaire au lieu de tuples: {'ventes': ['sum', 'mean']} plutôt que simples listes.

⚠️ Attention Critique

L'ordre des lignes n'est pas garanti après groupby() + agg() en versions anciennes de Pandas. Toujours utiliser reset_index() ou sort_values() si l'ordre importe. Aussi, attention à la NaN-semantics: par défaut, les NaN ne participent pas aux groupes (utilisez dropna=False pour les inclure).


3. Fusion et Jointures Multi-Tables Optimisées

Définition

Les fusions (merge, join) combinent deux ou plusieurs DataFrames selon une clé commune. C'est l'opération fondamentale pour enrichir des données provenant de sources multiples, un scénario très courant en data engineering.

Explication Détaillée

En pratique, vos données proviennent rarement d'une seule source. Vous devez fusionner client_data, transaction_data, product_catalog, etc. Les jointures SQL dans Pandas offrent quatre stratégies (inner, outer, left, right) avec des implications importantes sur la taille du résultat et les valeurs NaN introduites. Comprendre quand utiliser quelle jointure, gérer les clés dupliquées, et optimiser les performances avec merge_ordered ou merge_asof sont des compétences critiques.

Bloc de Code

import pandas as pd
import numpy as np

# Création de datasets à fusionner
clients = pd.DataFrame({
    'client_id': [1, 2, 3, 4, 5],
    'nom': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    'segment': ['Premium', 'Standard', 'Premium', 'Standard', 'Premium']
})

commandes = pd.DataFrame({
    'commande_id': [101, 102, 103, 104, 105, 106],
    'client_id': [1, 1, 2, 3, 3, 6],  # Note: 6 n'existe pas dans clients
    'montant': [500, 300, 800, 1200, 450, 600],
    'date': pd.date_range('2023-01-01', periods=6, freq='M')
})

produits = pd.DataFrame({
    'commande_id': [101, 102, 103, 104, 105],
    'produit': ['Laptop', 'Mouse', 'Monitor', 'Keyboard', 'Monitor'],
    'prix_unitaire': [800, 20, 300, 50, 300]
})

print("=== DATASETS ORIGINAUX ===")
print("Clients:\n", clients)
print("\nCommandes:\n", commandes)
print("\nProduits:\n", produits)

# MERGE INNER (intersection)
print("\n=== MERGE INNER ===")
result_inner = pd.merge(clients, commandes, on='client_id', how='inner')
print("Clients avec au moins 1 commande:")
print(result_inner)
print(f"Lignes: {len(result_inner)} (client_id 6 éliminé)")

# MERGE LEFT (tous les clients, même sans commande)
print("\n=== MERGE LEFT ===")
result_left = pd.merge(clients, commandes, on='client_id', how='left')
print("Tous les clients avec leurs commandes (NaN si aucune):")
print(result_left)
print(f"Lignes: {len(result_left)}")

# MERGE OUTER (union)
print("\n=== MERGE OUTER ===")
result_outer = pd.merge(clients, commandes, on='client_id', how='outer')
print("Tous les enregistrements (clients ET commandes):")
print(result_outer)
print(f"Lignes: {len(result_outer)}")

# Merge sur plusieurs clés
print("\n=== MERGE SUR PLUSIEURS CLÉS ===")
result_multi = pd.merge(
    commandes, 
    produits, 
    on='commande_id', 
    how='left'
)
print("Commandes avec détails produits:")
print(result_multi)

# Fusion multi-tables (chaînée)
print("\n=== FUSION MULTI-TABLES ===")
# D'abord clients + commandes, puis ajouter produits
merged = pd.merge(clients, commandes, on='client_id', how='left')
merged = pd.merge(merged, produits, on='commande_id', how='left')
print("Vue consolidée (clients + commandes + produits):")
print(merged[['nom', 'segment', 'montant', 'produit']].head(10))

# Suffixes pour colonnes dupliquées
print("\n=== GESTION DES COLONNES DUPLIQUÉES ===")
meta1 = pd.DataFrame({
    'id': [1, 2, 3],
    'prix': [100, 200, 300],
    'qualite': ['A', 'B', 'A']
})

meta2 = pd.DataFrame({
    'id': [1, 2, 3],
    'prix': [95, 210, 290],  # Colonne prix dans les deux
    'origine': ['China', 'USA', 'Germany']
})

result_suffix = pd.merge(meta1, meta2, on='id', suffixes=('_fournisseur1', '_fournisseur2'))
print("Fusion avec suffixes (colonnes prix dupliquées):")
print(result_suffix)

# MERGE_ASOF (fusion sur condition de proximité - timeseries!)
print("\n=== MERGE_ASOF (pour séries temporelles) ===")
trades = pd.DataFrame({
    'timestamp': pd.to_datetime(['2023-01-01 09:00', '2023-01-01 10:30', '2023-01-01 11:45']),
    'prix_achat': [100, 102, 101]
}).sort_values('timestamp')

quotes = pd.DataFrame({
    'timestamp': pd.to_datetime(['2023-01-01 09:30', '2023-01-01 10:00', '2023-01-01 11:00', '2023-01-01 12:00']),
    'prix_marche': [99, 101, 102, 100]
}).sort_values('timestamp')

result_asof = pd.merge_asof(trades, quotes, on='timestamp', direction='backward')
print("Achats appairés avec prix de marché le plus proche (antérieur):")
print(result_asof)

# Index-based join
print("\n=== JOIN PAR INDEX ===")
serie1 = pd.Series([10, 20, 30], index=['A', 'B', 'C'], name='valeur1')
serie2 = pd.Series([100, 200, 300], index=['A', 'B', 'C'], name='valeur2')
df_join = pd.concat([serie1, serie2], axis=1)
print("Concaténation par index (join implicite):")
print(df_join)

# Performance: merge_ordered
print("\n=== MERGE_ORDERED (fusion et tri) ===")
result_ordered = pd.merge_ordered(
    clients[['client_id', 'nom']], 
    commandes[['client_id', 'montant']], 
    on='client_id'
)
print("Fusion ordonnée (résultat trié):")
print(result_ordered)

Tableau des Types de Jointures

Type Description Lignes Cas d'Usage
INNER Seulement les correspondances Min(left, right) Données intégrées validées
LEFT Tous gauche + matches droite Même que left Enrichissement client
RIGHT Tous droite + matches gauche Même que right Moins courant, inverse du LEFT
OUTER Tous des deux côtés Max(left, right) Réconciliation complète
CROSS Produit cartésien left × right Générer combinaisons

Astuce Professionnelle

Avant une fusion, nettoyez toujours les clés (strip whitespace, lowercase, types cohérents). Utilisez .merge(..., validate='m:1') ou '1:1' pour vérifier que la cardinalité est celle attendue—cela capture les bugs silencieux comme des clés dupliquées non-intentionnelles. Pour les grosses fusions, considérez pd.merge(..., indicator=True) pour tracer quelles lignes proviennent de quelle source.

⚠️ Attention Critique

Les fusions peuvent exploser en taille! Un inner join sur deux tables de 1M lignes avec clés mal définis peut créer un Cartésien accidentel (milliards de lignes). Vérifiez les doublons dans les clés avec .duplicated() avant toute fusion. Aussi, attention aux types: un client_id en int64 vs string ne matchera jamais—convertissez explicitement.


4. Optimisation des Performances et Gestion de la Mémoire

Définition

L'optimisation en Pandas concerne réduire l'empreinte mémoire et accélérer les opérations sur gros volumes. C'est essentiel quand vous travaillez avec des fichiers multi-gigaoctets ou des pipelines production à latence critique.

Explication Détaillée

Par défaut, Pandas charge tout en RAM. Sur un dataset de 10 GB, c'est irréaliste. Les stratégies incluent: réduire les types de données (int64 → int32, float64 → float32), utiliser les catégories pour colonnes répétitives, charger incrementalement avec chunksize, utiliser des formats efficaces (parquet vs CSV), et paralléliser avec Dask. Connaître le profil mémoire de votre DataFrame et identifier les goulots d'étranglement est une compétence data science/engineering critique.

Bloc de Code

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

# Création d'un dataset "gros" pour la démo
print("=== DIAGNOSTIC MÉMOIRE ===")
df_large = pd.DataFrame({
    'id': range(100000),
    'timestamp': [datetime.now() - timedelta(seconds=i) for i in range(100000)],
    'valeur': np.random.randn(100000),
    'categorie': np.random.choice(['A', 'B', 'C', 'D', 'E'], 100000),
    'description': ['texte_' + str(i) for i in range(100000)]
})

# Inspection mémoire avant optimisation
print("Mémoire utilisée AVANT optimisation:")
print(df_large.memory_usage(deep=True))
print(f"\nTotal: {df_large.memory_usage(deep=True).sum() / 1024**2:.2f} MB")

# Analyse par colonne
print("\n=== ANALYSE PAR COLONNE ===")
for col in df_large.columns:
    dtype = df_large[col].dtype
    mem = df_large[col].memory_usage(deep=True) / 1024
    print(f"{col:15} | Dtype: {str(dtype):15} | Mémoire: {mem:8.2f} KB")

# OPTIMISATION 1: Réduire les types numériques
print("\n=== OPTIMISATION 1: RÉDUCTION TYPES NUMÉRIQUES ===")
df_opt = df_large.copy()

# int64 → int32 (réduit de 50% si possible)
if df_opt['id'].min() >= 0 and df_opt['id'].max() < 2**31 - 1:
    df_opt['id'] = df_opt['id'].astype('int32')
    
# float64 → float32 (réduit de 50%)
df_opt['valeur'] = df_opt['valeur'].astype('float32')

print("Avant: id=int64, valeur=float64")
print("Après: id=int32, valeur=float32")
print(f"Mémoire économisée: {(df_large.memory_usage(deep=True).sum() - df_opt.memory_usage(deep=True).sum()) / 1024:.2f} KB")

# OPTIMISATION 2: Catégories pour colonnes répétitives
print("\n=== OPTIMISATION 2: CATÉGORIES ===")
df_opt['categorie'] = df_opt['categorie'].astype('category')

mem_avant = df_large['categorie'].memory_usage(deep=True) / 1024
mem_apres = df_opt['categorie'].memory_usage(deep=True) / 1024
print(f"Colonne 'categorie' - Avant: {mem_avant:.2f} KB, Après: {mem_apres:.2f} KB")
print(f"Réduction: {((mem_avant - mem_apres) / mem_avant * 100):.1f}%")

# OPTIMISATION 3: Datetime parsé correctement
print("\n=== OPTIMISATION 3: DATETIME ===")
df_opt['timestamp'] = pd.to_datetime(df_opt['timestamp'])
print(f"Type timestamp: {df_opt['timestamp'].dtype}")
print(f"Mémoire timestamp: {df_opt['timestamp'].memory_usage(deep=True) / 1024:.2f} KB")

# Mémoire totale après optimisations
print("\n=== RÉSUMÉ OPTIMISATION ===")
print(f"Avant: {df_large.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
print(f"Après: {df_opt.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
taux_reduction = ((df_large.memory_usage(deep=True).sum() - df_opt.memory_usage(deep=True).sum()) / df_large.memory_usage(deep=True).sum() * 100)
print(f"Réduction totale: {taux_reduction:.1f}%")

# LECTURE CHUNKED (pour fichiers énormes)
print("\n=== LECTURE CHUNKED (simulation) ===")
# Créer un CSV temporaire
df_large.to_csv('/tmp/gros_dataset.csv', index=False)

# Lire par chunks
chunk_size = 10000
chunks = []
for chunk in pd.read_csv('/tmp/gros_dataset.csv', chunksize=chunk_size, dtype={
    'id': 'int32',
    'valeur': 'float32',
    'categorie': 'category'
}):
    # Traitement par chunk
    chunk['valeur_carre'] = chunk['valeur'] ** 2
    chunks.append(chunk)

df_processed = pd.concat(chunks, ignore_index=True)
print(f"Traité {len(df_processed)} lignes par chunks de {chunk_size}")
print(f"Mémoire finale: {df_processed.memory_usage(deep=True).sum() / 1024**2:.2f} MB")

# PARQUET (format compressé efficace)
print("\n=== PARQUET VS CSV ===")
# Écrire en parquet
df_opt.to_parquet('/tmp/dataset.parquet', compression='snappy')

import os
csv_size = os.path.getsize('/tmp/gros_dataset.csv') / 1024**2
parquet_size = os.path.getsize('/tmp/dataset.parquet') / 1024**2
print(f"CSV size: {csv_size:.2f} MB")
print(f"Parquet size: {parquet_size:.2f} MB")
print(f"Compression: {((csv_size - parquet_size) / csv_size * 100):.1f}%")

# Temps de lecture
print("\nTemps de lecture (rough benchmark):")
import time

start = time.time()
df_csv = pd.read_csv('/tmp/gros_dataset.csv')
time_csv = time.time() - start

start = time.time()
df_parquet = pd.read_parquet('/tmp/dataset.parquet')
time_parquet = time.time() - start

print(f"CSV: {time_csv*1000:.2f}ms")
print(f"Parquet: {time_parquet*1000:.2f}ms")

# Sélection de colonnes en parquet (lazy evaluation)
print("\n=== LAZY LOADING PARQUET ===")
df_subset = pd.read_parquet('/tmp/dataset.parquet', columns=['id', 'valeur'])
print(f"Chargement seulement 2 colonnes: {df_subset.memory_usage(deep=True).sum() / 1024**2:.2f} MB")

# Copy() vs View()
print("\n=== COPY vs VIEW (piège courant) ===")
df1 = df_opt.copy()  # Copie vraie (nouveau memory)
df2 = df_opt[['id', 'valeur']]  # View (même memory)
print(f"Après copy(): {df1.memory_usage(deep=True).sum() / 1024**2:.2f} MB (indépendant)")
print(f"Après slicing: {df2.memory_usage(deep=True).sum() / 1024**2:.2f} MB (plus léger)")

Tableau d'Optimisation

Technique Réduction Mémoire Complexité Impact Performance
int64 → int32 50% (si possible) Très faible Nulle
float64 → float32 50% Très faible Mineure
Catégories 80-95% (données répétitives) Basse Peut ralentir certaines ops
Parquet vs CSV 60-80% Basse Lecture 2-5x plus rapide
Chunking RAM constante Moyenne Dépend du traitement
Sparsity Variable Haute Très rapide pour sparse data

Astuce Professionnelle

Toujours profiler votre code avec %timeit ou line_profiler. 90% du temps, le goulot est une boucle implicite ou un copie DataFrame non-nécessaire. Préférez les opérations vectorisées Numpy/Pandas aux boucles Python. Utilisez .loc (label-based) plutôt que .iloc (integer-based) quand vous connaissez les labels—c'est souvent plus rapide.

⚠️ Attention Critique

Les catégories créent une dépendance: si vous mergez deux DataFrames avec catégories, les catégories non-matchées deviennent NaN. Évitez les .astype('category') après filtrage si les anciennes catégories manquent—nettoyez avec .cat.remove_unused_categories(). Aussi, attention au .copy() implicite: assigner un DataFrame à une variable crée une vue, pas une copie.


5. Feature Engineering et Pipelines de Transformation Robustes

Définition

Le feature engineering est l'art de créer ou modifier des colonnes (features) pour améliorer la performance des modèles ML. Un pipeline robuste automatise et reproduit ces transformations de manière testable et versionnable.

Explication Détaillée

Accédez à des centaines d'examens QCM — Découvrir les offres Premium