feat(eda): series temporales + rigor anti-data-mining + PDF movil + /eda + benchmark issues

Bloque del grupo eda (sesion ausente EDA-benchmark):
- 8 funciones nuevas: adf_kpss_stationarity, acf_pacf, stl_decompose, to_returns,
  fdr_correction, suggest_reexpression, exploratory_caveats, render_eda_pdf
- integracion: profile_table (run_series, emit_pdf), association_matrix (FDR Benjamini-Hochberg),
  render_eda_markdown (secciones series/reexpresion/caveats)
- slash commands /eda y /capitulos
- issues 0173-0177: mejoras del /eda derivadas del benchmark sobre 12 datasets reales
  (outlier_pct x100, periodo estacional, FK inference, render models, tipos id-like)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Egutierrez
2026-06-29 03:34:01 +02:00
parent 02301aaed3
commit 7ac69ab4fb
33 changed files with 3995 additions and 51 deletions
@@ -0,0 +1,69 @@
---
name: adf_kpss_stationarity
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def adf_kpss_stationarity(values: list, alpha: float = 0.05) -> dict"
description: "Test de estacionariedad de una serie temporal combinando ADF (H0=raiz unitaria/no estacionaria) y KPSS (H0=estacionaria) de statsmodels. Devuelve por test estadistico, p_value, lags y conclusion, mas un veredicto de consenso ('stationary'|'non_stationary'|'inconclusive'). Avisa de correlacion espuria (Granger-Newbold) cuando la serie no es estacionaria. Descarta None/NaN/infinitos; <8 puntos validos -> nota 'datos insuficientes'."
tags: [statistics, timeseries, stationarity, adf, kpss, unit-root, eda, forecasting, python]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [math, warnings, statsmodels]
params:
- name: values
desc: "serie temporal de valores numericos en orden cronologico. None/NaN/infinitos/no-numericos se descartan antes del test."
- name: alpha
desc: "nivel de significancia para ambos contrastes (default 0.05). p<alpha rechaza la hipotesis nula del test correspondiente."
output: "dict con 'adf' y 'kpss' (cada uno: stat, p_value, lags, stationary bool, conclusion), un 'verdict' de consenso ('stationary'|'non_stationary'|'inconclusive'), y 'warning' (texto sobre correlacion espuria si el veredicto no es stationary, si no None). Con <8 puntos validos: {'n', 'note': 'datos insuficientes', 'verdict': None}. Nunca lanza excepcion."
tested: true
tests: ["test_random_walk_es_no_estacionario", "test_ruido_blanco_es_estacionario", "test_serie_con_tendencia_no_es_estacionaria", "test_muestra_insuficiente_devuelve_nota", "test_descarta_none_y_nan", "test_warning_presente_si_no_estacionaria", "test_estructura_basica_del_dict"]
test_file_path: "python/functions/datascience/adf_kpss_stationarity_test.py"
file_path: "python/functions/datascience/adf_kpss_stationarity.py"
---
## Ejemplo
```python
from datascience import adf_kpss_stationarity
# Ruido blanco: estacionario (ADF rechaza raiz unitaria, KPSS no rechaza estacionariedad)
import numpy as np
rng = np.random.default_rng(0)
ruido = rng.normal(0, 1, 300).tolist()
adf_kpss_stationarity(ruido)["verdict"] # -> "stationary"
# Random walk (suma acumulada): NO estacionario
paseo = np.cumsum(rng.normal(0, 1, 300)).tolist()
res = adf_kpss_stationarity(paseo)
res["verdict"] # -> "non_stationary"
res["warning"] # -> aviso de correlacion espuria
```
## Cuando usarla
Antes de correlacionar, regresionar o modelar (ARIMA, VAR) una serie temporal,
para saber si es estacionaria. Es el primer paso obligatorio del analisis de
series: una serie no estacionaria (con tendencia o raiz unitaria) rompe los
supuestos de la regresion OLS clasica y, si la correlacionas con otra serie no
estacionaria, obtienes una correlacion alta pero **espuria** (Granger-Newbold).
Si el veredicto no es `"stationary"`, diferencia la serie o pasala a retornos
(`to_returns`) y vuelve a testear.
## Gotchas
- Es pura pero importa `statsmodels.tsa.stattools` (instalado en `python/.venv`).
- ADF y KPSS tienen hipotesis nulas OPUESTAS: en ADF `p<alpha` significa
estacionaria; en KPSS `p<alpha` significa NO estacionaria. La funcion ya
normaliza ambos a un campo `stationary` coherente — no inviertas tu la logica.
- KPSS interpola el p-valor sobre una tabla acotada `[0.01, 0.10]`: si el
estadistico cae fuera, statsmodels recorta el p-valor al extremo y lo marca en
`kpss.p_value_clipped = True`. Un p recortado a 0.01 o 0.10 es un limite, no un
valor exacto.
- El veredicto `"inconclusive"` suele indicar serie estacionaria-en-tendencia o
que necesita diferenciacion; no es un fallo, es informacion.
- Necesita al menos 8 puntos validos tras limpiar; con menos devuelve una nota.