Des trous dans les données
Les vrais tableaux de prix ont des trous : une action n'a pas été échangée ce jour-là, un flux a perdu une cotation, un marché était fermé. pandas les note NaN (« not a number »). Avant de choisir quoi faire, regardez : df.isna() donne un tableau Vrai/Faux, .sum() compte les trous par colonne et .mean() donne leur part. Une colonne vide à 40 % est un autre problème qu'une colonne à qui il manque une seule valeur.
import pandas as pd
nan = float("nan")
df = pd.DataFrame(
{"AAPL": [184.0, nan, 186.5, 187.0, nan], "MSFT": [410.0, 411.0, nan, nan, 415.0]},
index=pd.bdate_range("2026-03-02", periods=5),
)
print(df.isna().sum().to_dict()) # {'AAPL': 2, 'MSFT': 2} holes per column
print(df.isna().mean().round(2).to_dict()) # {'AAPL': 0.4, 'MSFT': 0.4} share of holesReporter, ou supprimer
Pour des prix, le remplissage naturel est le dernier prix connu : tant qu'une nouvelle cotation n'arrive pas, le marché est toujours à l'ancien niveau. C'est ffill() (remplissage vers l'avant). Un long trou signifie probablement des données périmées : ffill(limit=2) ne remplit que deux jours de suite au plus. Les NaN tout au début restent NaN, faute de prix antérieur à reporter. L'alternative est de supprimer : dropna() retire toute ligne avec un trou, et dropna(subset=["AAPL"]) ne regarde que les colonnes qui vous importent. Supprimer est sûr mais réduit les données et modifie l'espacement des dates.
msft = df["MSFT"]
print(msft.ffill().tolist()) # [410.0, 411.0, 411.0, 411.0, 415.0]
print(msft.ffill(limit=1).tolist()) # [410.0, 411.0, 411.0, nan, 415.0] only one day carried
print(len(df.dropna())) # 1 only one row is complete
print(len(df.dropna(subset=["AAPL"]))) # 3 rows where AAPL is knownInterpolation, et ce qu'il ne faut jamais remplir
interpolate() comble un trou par une droite entre les valeurs connues de part et d'autre. Il convient aux grandeurs lisses (une courbe de taux, un niveau de volatilité) et rarement aux prix, qui sautent. Attention aux bords : par défaut, interpolate() recopie aussi la dernière valeur dans les NaN de fin, ce qui invente des données. limit_area="inside" le limite aux trous entre deux valeurs connues. Ne remplissez jamais aveuglément avec 0 : un prix de 0 est un krach, un rendement de 0 est une journée plate, et fillna(0) sur le mauvais tableau modifie sans bruit toutes les moyennes calculées ensuite.
gappy = pd.Series([nan, nan, 10.0, nan, nan, 16.0, nan])
print(gappy.interpolate().tolist())
# [nan, nan, 10.0, 12.0, 14.0, 16.0, 16.0] the last 16.0 is invented
print(gappy.interpolate(limit_area="inside").tolist())
# [nan, nan, 10.0, 12.0, 14.0, 16.0, nan] only between known values# Filling with 0 is a decision, not a default: it changes the statistics
print(gappy.fillna(0).tolist()) # [0.0, 0.0, 10.0, 0.0, 0.0, 16.0, 0.0]
print(gappy.mean()) # 13.0 NaN are skipped
print(gappy.fillna(0).mean()) # 3.7142857142857144 the zeros drag the average downbfill() et interpolate() utilisent des valeurs postérieures au trou : à la date du trou, ils emploient une information qui n'existait pas encore. Dans un backtest, c'est un biais d'anticipation. Utilisez ffill() pour tout ce que voit une stratégie en temps réel, et réservez l'interpolation au nettoyage de données historiques analysées dans leur ensemble.df.isna().sum()compte les trous par colonne ; regardez les données avant de décider du traitement.ffill(limit=n)reporte la dernière valeur connue (le bon choix pour des prix) ;dropna(subset=...)retire les lignes incomplètes.interpolate(limit_area="inside")ne comble que les trous intérieurs ; ne remplissez jamais avec 0 ni avec le futur par habitude.
À suivre : la détection des valeurs aberrantes, trouver les valeurs présentes qui ne peuvent pas être justes.
Lab : ce qui est noté
- Compter les trous de chaque colonne avec
count_missing25 pts - Reporter le dernier prix avec
forward_fill25 pts - Ne garder que les lignes fiables avec
complete_rows25 pts - Combler les trous à l'intérieur des données avec
fill_gaps25 pts