AccueilBlogPython data avancé
Guide recrutement data

Test technique Python data avancé : pandas, DuckDB, Polars, optimisation

Au-delà des bases de pandas, un Data Engineer Senior maîtrise la vectorisation, la gestion mémoire, et sait quand remplacer pandas par DuckDB ou Polars pour des performances 10x à 100x supérieures.

Data Builder·Juin 2025·9 min de lecture·Data Engineer · Data Scientist
Sommaire
  1. pandas : pièges et anti-patterns
  2. Optimisation mémoire
  3. Vectorisation vs boucles
  4. DuckDB : SQL sur DataFrames
  5. Polars : le successeur de pandas ?
  6. Profiling et diagnostic
  7. Grille par niveau

Un Data Engineer qui utilise des boucles iterrows() sur des DataFrames pandas en production livre de la dette technique. Les pièges de performance, la vectorisation et la maîtrise de DuckDB ou Polars sont les vrais discriminants en entretien.

1pandas : les pièges classiques

Question discriminante

Quelle est la différence entre .loc, .iloc et .at ? Pourquoi faut-il absolument éviter iterrows() sur un grand DataFrame ?

# MAUVAIS : boucle Python sur un DataFrame (tres lent) for index, row in df.iterrows(): df.at[index, 'profit'] = row['revenue'] - row['cost'] # BON : vectorisation directe df['profit'] = df['revenue'] - df['cost'] # BON : np.where pour les conditions simples import numpy as np df['tier'] = np.where(df['amount'] > 1000, 'gold', np.where(df['amount'] > 100, 'silver', 'bronze')) # Selection par label vs position df.loc[df['status'] == 'active', 'revenue'] # par label/condition df.iloc[0:10, 2:5] # par position entiere df.at[42, 'revenue'] # acces scalaire rapide par label
  • iterrows() — itere en Python pur : 100x a 1000x plus lent que la vectorisation. Jamais en production
  • Vectorisation — operations sur toute la colonne en une instruction (C compile via NumPy)
  • apply() — plus flexible qu'une operation vectorisee, mais plus lent. Dernier recours
  • SettingWithCopyWarning — toujours travailler sur une copie explicite (.copy()) pour eviter les modifications silencieuses
  • Perf rule — vectorisation > np.where > pd.apply() > itertuples() > iterrows()

Signal GO Senior : le candidat cite spontanement iterrows() comme anti-pattern, explique la vectorisation et connait np.where.

2Optimisation memoire : reduire la taille des DataFrames

Question discriminante

Comment reduisez-vous la consommation memoire d'un DataFrame pandas de 3 Go ?

import pandas as pd # Diagnostic memoire print(df.memory_usage(deep=True).sum() / 1024**2, "MB") # Reduction des types numeriques df['age'] = df['age'].astype('int8') # int64 -> int8 : 8x moins df['score'] = df['score'].astype('float32') # float64 -> float32 : 2x moins df['status'] = df['status'].astype('category') # string -> category : -80% df['pays'] = df['pays'].astype('category') # Types specifies a l'import (optimal) df = pd.read_csv("data.csv", dtype={ 'age': 'int8', 'status': 'category', 'amount': 'float32' }) # Lecture par chunks pour les tres gros fichiers for chunk in pd.read_csv("big_file.csv", chunksize=100_000): process(chunk)
  • int64 → int8/int16/int32 — selon les valeurs min/max reelles de la colonne
  • float64 → float32 — precision reduite mais acceptable pour la majorite des analyses
  • string → category — essentiel pour les colonnes a faible cardinalite (status, pays, type). Peut reduire de 80% la memoire
  • Lecture par chunks — traiter les fichiers plus grands que la RAM en morceaux successifs

3Vectorisation et NumPy : pourquoi c'est plus rapide

Question discriminante

Expliquez pourquoi une operation vectorisee NumPy est 500x plus rapide qu'une boucle Python equivalente.

import numpy as np # Boucle Python : ~5 secondes sur 1M de lignes distances = [] for i in range(len(df)): d = np.sqrt(df['x'].iloc[i]**2 + df['y'].iloc[i]**2) distances.append(d) # Vectorisation NumPy : ~10 millisecondes (500x plus rapide) distances = np.sqrt(df['x']**2 + df['y']**2) # pd.cut pour les decoupages en tranches df['age_group'] = pd.cut(df['age'], bins=[0, 18, 35, 50, 100], labels=['junior', 'adulte', 'senior', 'aine'] )
  • Pourquoi vectorise est plus rapide — les operations vectorisees s'executent en C compile, sans l'overhead de l'interpreteur Python ni du dispatch de boucle
  • Parallelisme SIMD — NumPy exploite les instructions SIMD du processeur pour traiter plusieurs elements simultanement
  • pd.cut / pd.qcut — decoupage en tranches ou quantiles, vectorise nativement

4DuckDB : SQL sur des DataFrames et des fichiers Parquet

Question discriminante

Dans quel cas utilisez-vous DuckDB plutot que pandas ? Quelle est sa principale force sur les fichiers Parquet ?

import duckdb # DuckDB lit directement les fichiers Parquet sans tout charger en RAM result = duckdb.sql(""" SELECT country, SUM(revenue) AS total_revenue, COUNT(*) AS nb_orders FROM 'data/orders_*.parquet' WHERE order_date >= '2024-01-01' GROUP BY country ORDER BY total_revenue DESC LIMIT 20 """).df() # convertit en DataFrame pandas si necessaire # DuckDB peut aussi requeter directement un DataFrame pandas existant conn = duckdb.connect() result = conn.execute(""" SELECT customer_id, SUM(amount) AS total FROM df_orders GROUP BY customer_id HAVING total > 1000 """).df()
  • DuckDB vs pandas — 10x a 100x plus rapide sur les agregations et jointures grace a son moteur columnar vectorise
  • Out-of-core — traite des fichiers plus grands que la RAM grace au streaming
  • Parquet natif — lit les fichiers Parquet/CSV directement, avec predicate et projection pushdown
  • Quand l'utiliser — agregations, jointures, filtres sur gros volumes. Garder pandas pour la manipulation fine (reshape, pivot, string operations)

Alternative zero-installation : DuckDB fonctionne en process sans serveur. pip install duckdb et c'est pret. Idéal pour les scripts ETL locaux sur des fichiers volumineux.

5Polars : performances maximales, API lazy

Question discriminante

Quelle est la difference entre l'API eager et lazy de Polars ? Quand migrer de pandas vers Polars ?

import polars as pl # API Eager : execution immediate (comme pandas) df = pl.read_csv("data.csv") result = ( df .filter(pl.col("amount") > 100) .group_by("category") .agg(pl.col("amount").sum().alias("total")) ) # API Lazy : execution differee et optimisee (comme Spark) result = ( pl.scan_csv("data.csv") # scan = lecture lazy .filter(pl.col("amount") > 100) .group_by("category") .agg(pl.col("amount").sum().alias("total")) .collect() # declenche l'execution optimisee ) # Polars optimise automatiquement : predicate pushdown, # projection pushdown, parallelisme multi-thread natif
  • Polars vs pandas — 5x a 50x plus rapide grace a Rust, execution multi-thread parallelisee nativement
  • API Lazy — le query planner optimise les operations avant de les executer (comme Spark). A privilegier pour les gros volumes
  • Quand migrer — volumes > 500 MB en RAM, besoin de performance critique, equipe prete a apprendre l'API
  • Compatibilite — conversion facile depuis/vers pandas via .to_pandas() et pl.from_pandas()

6Profiling et diagnostic de performance

Question discriminante

Comment identifiez-vous le goulot d'etranglement dans un script Python data lent ?

import cProfile import timeit # cProfile : profiling par fonction cProfile.run('process_dataframe(df)', sort='cumulative') # timeit : comparer deux approches t1 = timeit.timeit( 'df.groupby("category").agg({"amount": "sum"})', globals=globals(), number=100 ) # memory_profiler : profiling memoire ligne par ligne from memory_profiler import profile @profile def process_data(df): result = df.groupby('category').agg({'amount': 'sum'}) return result # YData Profiling : rapport qualite automatique from ydata_profiling import ProfileReport report = ProfileReport(df, title="Data Quality Report") report.to_file("report.html")
  • cProfile — identifier les fonctions qui consomment le plus de temps CPU
  • memory_profiler — identifier les pics de consommation memoire ligne par ligne
  • %timeit dans Jupyter — comparer rapidement deux approches en quelques lignes
  • line_profiler — profiling ligne par ligne du temps CPU (plus precis que cProfile pour les fonctions)

7Grille par niveau

NiveauMaitrise attendueSignal GONO-GO
Juniorpandas basique, vectorisation, groupby/merge/pivotEvite iterrows(), utilise .loc correctement, sait faire un merge multi-clesUtilise des boucles for sur les DataFrames, ne sait pas ce qu'est la vectorisation
ConfirmeOptimisation memoire, DuckDB, profiling, types CategoryReduit la memoire via les dtypes, a utilise DuckDB sur un projet, sait utiliser cProfileNe connait pas DuckDB, ne sait pas reduire la consommation memoire
SeniorPolars, streaming gros volumes, benchmarks, tests de perfA utilise Polars sur un projet reel, traite des fichiers plus grands que la RAM avec DuckDB ou chunksN'a jamais entendu parler de Polars, ne sait pas traiter un fichier plus grand que la RAM
LeadArchitecture data locale vs distribuee, choix Spark vs DuckDB vs Polars selon le contexteJustifie le choix DuckDB vs Spark selon le volume et le contexte, a defini les standards Python de son equipeNe peut pas expliquer quand utiliser Spark plutot que DuckDB