Huecos en los datos
Las tablas de precios reales tienen huecos: una acción no cotizó ese día, un feed perdió una cotización, un mercado estaba de fiesta. pandas los marca como NaN («not a number»). Antes de decidir qué hacer, mira: df.isna() da una tabla de Verdadero/Falso, .sum() cuenta los huecos por columna y .mean() da su proporción. Una columna vacía al 40 % es un problema distinto de una a la que le falta un solo valor.
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 holesArrastrar o eliminar
Para los precios, el relleno natural es el último precio conocido: hasta que llega una cotización nueva, el mercado sigue en la antigua. Eso es ffill() (relleno hacia delante). Un hueco largo probablemente significa datos obsoletos, así que ffill(limit=2) rellena como máximo dos días seguidos. Los NaN del mismo comienzo siguen siendo NaN, porque no hay precio anterior que arrastrar. La alternativa es eliminar: dropna() quita toda fila con un hueco, y dropna(subset=["AAPL"]) solo mira las columnas que te importan. Eliminar es seguro, pero reduce los datos y cambia el espaciado entre fechas.
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 knownInterpolación, y lo que nunca se debe rellenar
interpolate() rellena un hueco con una recta entre los valores conocidos a ambos lados. Sirve para magnitudes suaves (una curva de tipos, un nivel de volatilidad) y rara vez para precios, que saltan. Cuidado en los bordes: por defecto interpolate() también copia el último valor en los NaN finales, lo que inventa datos. limit_area="inside" lo restringe a huecos entre dos valores conocidos. Nunca rellenes a ciegas con 0: un precio de 0 es un desplome, un rendimiento de 0 es un día plano, y fillna(0) en la tabla equivocada cambia en silencio todas las medias que calcules después.
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() e interpolate() usan valores posteriores al hueco, así que en la fecha del hueco emplean información que aún no existía. En un backtest eso es sesgo de anticipación. Usa ffill() para todo lo que una estrategia ve en tiempo real y reserva la interpolación para limpiar datos históricos que se analizan en conjunto.df.isna().sum()cuenta los huecos por columna; mira los datos antes de decidir cómo tratarlos.ffill(limit=n)arrastra el último valor conocido (la opción correcta para precios);dropna(subset=...)quita las filas incompletas.interpolate(limit_area="inside")solo salva los huecos interiores; nunca rellenes con 0 ni con el futuro por costumbre.
A continuación: la detección de valores atípicos, encontrar los valores presentes que no pueden ser correctos.
Lab: qué se califica
- Contar los huecos de cada columna con
count_missing25 pts - Arrastrar el último precio con
forward_fill25 pts - Conservar solo las filas fiables con
complete_rows25 pts - Rellenar los huecos dentro de los datos con
fill_gaps25 pts