Un valor que no puede ser correcto
Un rendimiento diario de +30 % en una gran capitalización es una OPA o un error de datos, como un split olvidado o una coma mal puesta. Un valor atípico está lo bastante lejos de los demás como para exigir una decisión: conservarlo, corregirlo o quitarlo. Nunca lo borres solo porque estorba, ya que los choques reales son justo lo que los modelos de riesgo deben ver. El primer paso es siempre hacerlos visibles con una regla explícita y repetible.
La regla del z-score y por qué puede fallar
La regla clásica marca los valores a más de 2 o 3 desviaciones típicas de la media: z = (x - mean) / std. Es simple y funciona con datos limpios. Pero el propio atípico infla la media y sobre todo la desviación típica, así que se esconde de la regla. En el ejemplo siguiente el día de +30 % solo obtiene un z-score de 2,85 y con un umbral de 3 no se marcaría. Cuanto más extremo es el error, más se protege.
import pandas as pd
returns = pd.Series([0.004, -0.002, 0.001, 0.003, -0.004, 0.002, 0.30, -0.001, 0.0, 0.002])
z = (returns - returns.mean()) / returns.std()
print(z.round(2).tolist())
# [-0.28, -0.34, -0.31, -0.29, -0.36, -0.3, 2.85, -0.33, -0.32, -0.3]
print(returns.mean().round(4), returns.std().round(4)) # 0.0305 0.0947 one day moved the mean tenfold
print(returns.drop(6).std().round(4)) # 0.0026 the std without that dayReglas robustas: IQR y MAD
Las estadísticas robustas apenas se mueven por los atípicos que persiguen. El rango intercuartílico IQR = Q3 - Q1 mide la dispersión de la mitad central de los datos, y los valores más allá de Q1 - 1.5 * IQR o Q3 + 1.5 * IQR quedan fuera de los límites de un diagrama de caja. El MAD (desviación absoluta mediana) sustituye la desviación típica por una mediana, y el z-score modificado 0.6745 * (x - median) / MAD se lee como un z-score normal (3,5 es un corte habitual). Ambos marcan sin problema el día de +30 %.
# The fences of a box plot
q1, q3 = returns.quantile(0.25), returns.quantile(0.75)
spread = q3 - q1
lower, upper = q1 - 1.5 * spread, q3 + 1.5 * spread
print(round(lower, 4), round(upper, 4)) # -0.006 0.008
print(returns[(returns < lower) | (returns > upper)].to_dict()) # {6: 0.3}
# The modified z-score, built on the median
median = returns.median()
mad = (returns - median).abs().median()
modified_z = 0.6745 * (returns - median) / mad
print(modified_z.round(1).tolist()) # [0.8, -1.2, -0.2, 0.5, -1.9, 0.2, 100.7, -0.8, -0.5, 0.2]Winsorización: recortar en lugar de borrar
Borrar una fila también borra la información de sus otras columnas y rompe el espaciado de las fechas. La winsorización conserva todas las filas y acerca los valores extremos a cuantiles elegidos, por ejemplo los de 1 % y 99 %: lo que está por debajo del cuantil de 1 % pasa a ser ese cuantil, lo que está por encima del de 99 % pasa a ser este. En pandas es clip(lower, upper) con los límites tomados de quantile. La forma de la serie sobrevive y los extremos pierden su influencia sobre medias y regresiones.
low, high = returns.quantile(0.1), returns.quantile(0.9)
print(round(low, 4), round(high, 4)) # -0.0022 0.0336
clipped = returns.clip(lower=low, upper=high)
print(clipped.round(4).tolist())
# [0.004, -0.002, 0.001, 0.003, -0.0022, 0.002, 0.0336, -0.001, 0.0, 0.002]
# ^ raised ^ the 30% day pulled back to the 90% quantile# Flag with a rule, then look at what is flagged before deciding anything
mask = z.abs() > 2
print(returns[mask].to_dict()) # {6: 0.3} the +30% day
print(returns[~mask].std().round(4)) # 0.0026 the volatility of the remaining days- La regla del z-score
abs((x - mean) / std) > kes simple, pero los atípicos inflan la media y la desviación y pueden esconderse. - Las reglas robustas usan cuantiles y medianas: límites IQR (
Q1 - 1.5 IQR,Q3 + 1.5 IQR) y z-score modificado basado en el MAD. clip(lower, upper)con límites de cuantiles (winsorización) conserva las filas; nunca borres un choque sin saber qué es.
A continuación: las operaciones corporativas, los splits y dividendos que hacen que un precio antiguo no sea comparable con el de hoy.
Lab: qué se califica
- Marcar los rendimientos lejanos con
zscore_outliers25 pts - Calcular los límites del diagrama de caja con
iqr_bounds25 pts - Marcar atípicos con una puntuación robusta con
mad_outliers25 pts - Domar los extremos con
winsorize25 pts