feat(eda): capítulo TIMESERIES del AutomaticEDA (evolución + análisis de serie)
Capítulo nuevo build_timeseries(profile, ctx) -> Chapter|None del motor AutomaticEDA. Cuando la tabla tiene columna de fecha/datetime, grafica la evolución de cada columna numérica por periodo (valor agregado + conteo de filas) y los paneles de descomposición STL y autocorrelación (ACF), con el análisis de la serie: estacionariedad (ADF+KPSS), autocorrelación (Ljung-Box), fuerzas de tendencia/estacionalidad (Hyndman) y la transformación sugerida (retornos o diferencias) para evitar correlaciones espurias. Sin columna temporal devuelve None. Consolida series OHLC casi idénticas en un único gráfico conservando el análisis de cada columna. La serie cruda llega por ctx['timeseries_raw'] (mismo patrón que modelos con raw_numeric); las figuras son perezosas (Figure.make) y el paginador del núcleo garantiza no-corte en PDF y PPTX. CHAPTER_VERSION 1.0.0. Cubre los MUST del diseño (report 2043): MUST-9.1 (línea valor-vs-tiempo + conteo por periodo), MUST-9.2 (paneles STL + ACF), MUST-9.3 (perfil datetime + consolidación OHLC). Funciones nuevas del registry (grupo eda), delegadas a fn-constructor, no inline: - detect_time_column (pure): detecta la columna temporal y las numéricas - profile_datetime (pure): rango/frecuencia/regularidad/huecos de la fecha - resample_timeseries (pure): agrega la serie por periodo + conteo - extract_timeseries_raw (impure): lee la serie cruda ordenada de DuckDB/PG Verificación: 69 tests verdes (capítulo 9 + funciones 28 + núcleo/renderers); golden real sobre seattle-weather (estacional) y aapl (OHLC) con PDF+PPTX sin cortar nada (cols_cortadas=[]). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"""Detecta la columna temporal y las columnas numericas de un TableProfile (grupo eda).
|
||||
|
||||
Funcion pura y determinista: a partir de la lista de columnas de un TableProfile
|
||||
producido por el grupo de capacidad `eda` (cada elemento es un ColumnProfile dict),
|
||||
decide cual es la columna de orden temporal y que columnas numericas hay disponibles
|
||||
para graficar una serie en el tiempo. Es la pieza que usa el capitulo TIMESERIES del
|
||||
AutomaticEDA para decidir si la tabla admite analisis de serie temporal.
|
||||
|
||||
Lectura 100% defensiva al estilo "dict-no-throw" del grupo eda: nunca lanza
|
||||
excepcion, siempre devuelve el mismo conjunto de claves.
|
||||
"""
|
||||
|
||||
# semantic_type que el profiler (infer_semantic_type) emite para fechas/datetimes.
|
||||
_DATETIME_SEMANTICS = ("datetime_iso", "date_eu")
|
||||
|
||||
|
||||
def detect_time_column(columns: list) -> dict:
|
||||
"""Detecta la columna temporal y las numericas de una lista de ColumnProfile.
|
||||
|
||||
Recorre los ColumnProfile de un TableProfile y clasifica cada columna como
|
||||
temporal o numerica leyendo de forma defensiva sus claves. Una columna es
|
||||
temporal si su ``inferred_type == "datetime"`` o si su ``semantic_type`` esta
|
||||
en {``"datetime_iso"``, ``"date_eu"``}. La columna temporal elegida
|
||||
(``time_col``) es la PRIMERA temporal en el orden de la lista. Las numericas
|
||||
(``numeric_cols``) son las de ``inferred_type == "numeric"``, en orden.
|
||||
|
||||
Funcion pura: no hace I/O, no muta el input, es determinista.
|
||||
|
||||
Args:
|
||||
columns: lista de ColumnProfile dict del grupo eda. Cada elemento suele
|
||||
tener claves como ``name``, ``inferred_type``, ``semantic_type`` y
|
||||
``numeric``. Los elementos que no sean dict se ignoran. Si ``columns``
|
||||
es None, no es lista o esta vacia, se devuelve el dict "no aplica".
|
||||
|
||||
Returns:
|
||||
Siempre un dict con las mismas claves::
|
||||
|
||||
{
|
||||
"time_col": str | None, # columna temporal elegida (None si no hay)
|
||||
"time_semantic": str, # semantic_type de la temporal ("" si no aplica)
|
||||
"numeric_cols": [str, ...], # columnas con inferred_type == "numeric"
|
||||
"n_datetime_cols": int, # nº de columnas temporales detectadas
|
||||
"datetime_cols": [str, ...],# todas las temporales, en orden de aparicion
|
||||
"reason": str, # frase corta (en espanol) que explica la eleccion
|
||||
}
|
||||
"""
|
||||
# Caso "no aplica": entrada invalida o vacia.
|
||||
if not isinstance(columns, list) or not columns:
|
||||
return {
|
||||
"time_col": None,
|
||||
"time_semantic": "",
|
||||
"numeric_cols": [],
|
||||
"n_datetime_cols": 0,
|
||||
"datetime_cols": [],
|
||||
"reason": "no se detecto columna de fecha/datetime",
|
||||
}
|
||||
|
||||
datetime_cols: list[str] = []
|
||||
datetime_semantics: list[str] = []
|
||||
numeric_cols: list[str] = []
|
||||
|
||||
for col in columns:
|
||||
# Ignora elementos que no sean dict sin fallar.
|
||||
if not isinstance(col, dict):
|
||||
continue
|
||||
|
||||
name = col.get("name")
|
||||
if name is None:
|
||||
name = ""
|
||||
else:
|
||||
name = str(name)
|
||||
|
||||
inferred_type = col.get("inferred_type") or ""
|
||||
semantic_type = col.get("semantic_type") or ""
|
||||
|
||||
is_datetime = inferred_type == "datetime" or semantic_type in _DATETIME_SEMANTICS
|
||||
if is_datetime:
|
||||
datetime_cols.append(name)
|
||||
datetime_semantics.append(semantic_type)
|
||||
|
||||
if inferred_type == "numeric":
|
||||
numeric_cols.append(name)
|
||||
|
||||
if not datetime_cols:
|
||||
return {
|
||||
"time_col": None,
|
||||
"time_semantic": "",
|
||||
"numeric_cols": numeric_cols,
|
||||
"n_datetime_cols": 0,
|
||||
"datetime_cols": [],
|
||||
"reason": "no se detecto columna de fecha/datetime",
|
||||
}
|
||||
|
||||
time_col = datetime_cols[0]
|
||||
time_semantic = datetime_semantics[0]
|
||||
|
||||
if len(datetime_cols) == 1:
|
||||
reason = f"columna temporal '{time_col}' detectada"
|
||||
else:
|
||||
reason = (
|
||||
f"{len(datetime_cols)} columnas temporales; se elige la primera "
|
||||
f"'{time_col}'"
|
||||
)
|
||||
|
||||
return {
|
||||
"time_col": time_col,
|
||||
"time_semantic": time_semantic,
|
||||
"numeric_cols": numeric_cols,
|
||||
"n_datetime_cols": len(datetime_cols),
|
||||
"datetime_cols": datetime_cols,
|
||||
"reason": reason,
|
||||
}
|
||||
Reference in New Issue
Block a user