EDA series temporales: período estacional roto + correlación de niveles + to_returns ciego
pendiente
bugfix
registry-quality
registry-only
alta
0173
0175
0176
0177
2026-06-29
2026-06-29
eda
datascience
stl_decompose
profile_table
to_returns
series
benchmark
0174 — EDA series temporales: período estacional + correlación de niveles
Contexto
El benchmark /eda (29/06/2026, temp/eda_benchmark/EVALUATION.md) confirmó que la
estacionariedad (ADF+KPSS), la autocorrelación (Ljung-Box) y el aviso de espuriedad
Granger-Newbold están bien (verificados a mano con statsmodels). Pero el detector de
período estacional está roto, lo que produce falsos negativos de estacionalidad, y la
correlación de precios se calcula sobre niveles (espuria para uso financiero).
Hallazgos cubiertos:
Hallazgo
Severidad
Evidencia del benchmark
H2 — período estacional sale 2 casi siempre → seasonal_strength=0
crítico
seattle temp_max reporta "sin estacionalidad" (period=2); STL real con period=365 da fuerza estacional 0.843. UNRATE (mensual) debería usar 12, no 2
H8 — correlación de precios sobre niveles marcada sig=sí
medio-alto
aapl/btc Close–Open=0.998 sig=sí: espuria por construcción (niveles autocorrelados no estacionarios)
H13 — to_returns sugerido ciegamente a temperatura (sin sentido físico)
bajo
seattle temp_max: "convertir a retornos"; debería ser "diferencias"
Causa raíz H2 (verificada en código, READ-ONLY)
python/functions/datascience/stl_decompose.py:34-58 (_infer_period) busca el lag entre 2 y
max_period que maximiza la autocorrelación cruda de la serie. En cualquier serie con
tendencia (precios, temperatura), la autocorrelación decae monótonamente desde el lag mínimo, así
que el lag 2 casi siempre gana → period=2 espurio y un STL con componente estacional que es
ruido (seasonal_strength≈0). Además, python/functions/pipelines/profile_table.py:175
(_build_series_block) llama stl_decompose(series_vals)sin pasar el período, pese a que el
pipeline ya conoce la columna de orden temporal (order_col) y podría derivar la frecuencia.
Tareas
H2 — arreglar la inferencia de período en stl_decompose.py:34-58. Opciones (preferir la
robusta): (a) detrend antes de autocorrelar; (b) buscar picos en el periodograma/FFT en vez del
primer lag; (c) derivar el período de la frecuencia del índice datetime (mensual→12,
diario→7 y/o 365) — la señal más fiable.
H2 — pasar el período desde el pipeline: en profile_table.py:_build_series_block, cuando
exista order_col datetime, inferir la frecuencia del índice y pasar period= explícito a
stl_decompose. Si no se puede determinar un período fiable, que stl_decomposeno reporte
seasonal_strength=0 como conclusión: devolver note "período no determinado" (ya hay una
rama así en :139-145; extenderla a los casos que hoy caen en period=2).
H8 — correlación sobre retornos para series no estacionarias: en la sección de correlaciones
de profile_table.py:346-384, cuando una columna sea una serie no estacionaria de niveles
(verdict non_stationary/inconclusive, ya detectado), correlacionar sobre retornos/diferencias
(to_returns, ya importado) o marcar esos pares de niveles como "posible espuria" junto a la
tabla. El aviso global existe pero está lejos de los números.
H13 — retornos vs diferencias por semántica: en profile_table.py:189 / to_returns.py,
elegir "retornos" (financiero, estrictamente positivo multiplicativo) vs "diferencias" (físico,
aditivo) según la naturaleza, o usar "diferencias" por defecto cuando no haya señal financiera.
Tests: stl_decompose_test.py (serie sintética mensual con estacionalidad anual → período
correcto y seasonal_strength alta; serie con tendencia sin estacionalidad → nota, no
period=2); cobertura de _build_series_block con order_col datetime.
Definition of Done
Escenario
Tipo
Comando / evidencia
Resultado esperado
Golden: estacionalidad anual
e2e
re-correr profile_table con run_series=True sobre seattle temp_max
seasonal_strength ≈ 0.84 con período ≈ 365 (NO "sin estacionalidad", NO period=2)
Edge: serie mensual
unit
stl_decompose_test.py serie mensual sintética con ciclo 12
período inferido 12 y fuerza estacional alta
Edge: sin estacionalidad
unit
stl_decompose_test.py serie con solo tendencia
note "período no determinado", NO seasonal_strength=0 como conclusión
Error: serie corta
unit
stl_decompose([...]<2*period)
nota "serie corta", sin crash (contrato actual)
H8
e2e
re-correr profile_table sobre aapl/btc
pares de niveles no estacionarios marcados como posible espuria o correlación sobre retornos
Mecánica
—
./fn run stl_decompose_py_datascience; fn index
tests verdes; índice limpio
Re-correr el benchmark sobre seattle, fred-unrate, aapl y btc y confirmar que la estacionalidad se
detecta donde existe y no se inventa donde no.
Notas
Issue derivado de temp/eda_benchmark/EDA_ISSUES.md. H2 es el segundo bloqueante de fiabilidad: un
"sin estacionalidad" donde la hay es un falso negativo que un decisor creería. La estacionariedad ya
funciona — no tocarla. Hermanos: 0173, 0175, 0176, 0177.