feat(infra): auto-commit con 56 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:22:55 +02:00
parent c1071a82b3
commit 32c7336bf6
56 changed files with 5307 additions and 100 deletions
+54 -21
View File
@@ -15,40 +15,73 @@ from datascience import (
)
def _to_numeric_subset(columns: dict) -> dict:
"""Extrae las columnas numericas como {nombre: [float values]}.
def _pf(v):
"""Parsea un valor a float; devuelve None si es None/bool/no parseable."""
if v is None or isinstance(v, bool):
return None
try:
return float(v)
except (TypeError, ValueError):
return None
Solo se quedan las columnas con ``type == "numeric"``. Para cada una, los
valores se convierten a float cuando es posible y los que son None o no
parseables se descartan (la lista resultante puede ser mas corta que la
original). Mantiene el orden de aparicion de las columnas.
def _to_numeric_subset(columns: dict) -> dict:
"""Extrae las columnas numericas alineadas por fila (listwise deletion).
Solo se quedan las columnas con ``type == "numeric"``. CLAVE: la alineacion
por fila se preserva. Pasos:
1. Descarta columnas numericas con menos del 50% de valores parseables
(evita que una columna casi-toda-nula tire todas las filas en el paso 3).
2. Sobre las columnas buenas, conserva SOLO las filas en las que TODAS
tienen un valor numerico (listwise deletion).
El resultado es un mapa {nombre: [float, ...]} donde todas las listas tienen
la MISMA longitud (filas completas) — requisito de PCA/KMeans/IsolationForest
(matriz rectangular sin NaN). El bug previo descartaba None por columna,
dejando longitudes desiguales y reventando sklearn con ValueError.
Args:
columns: mapa {nombre_columna: {"values": list, "type": str}}.
columns: mapa {nombre_columna: {"values": list, "type": str}}; las listas
llegan alineadas por fila (misma longitud, None donde no hay dato).
Returns:
dict {nombre_columna: [float, ...]} solo con columnas numericas.
dict {nombre_columna: [float, ...]} con columnas numericas de igual
longitud. Vacio si no hay columnas numericas validas.
"""
numeric: dict[str, list] = {}
if not isinstance(columns, dict):
return numeric
return {}
raw: dict[str, list] = {}
for name, meta in columns.items():
if not isinstance(meta, dict):
continue
if meta.get("type") != "numeric":
continue
values = meta.get("values")
if not isinstance(values, (list, tuple)):
continue
parsed: list[float] = []
for v in values:
if v is None or isinstance(v, bool):
continue
try:
parsed.append(float(v))
except (TypeError, ValueError):
continue
numeric[name] = parsed
if isinstance(values, (list, tuple)):
raw[name] = list(values)
if not raw:
return {}
# Longitud comun (min, defensivo si llegaran desalineadas).
n = min(len(v) for v in raw.values())
if n == 0:
return {}
# 1) Parsea por celda y descarta columnas con <50% de valores parseables.
good: dict[str, list] = {}
for name, values in raw.items():
parsed = [_pf(values[i]) for i in range(n)]
if sum(1 for x in parsed if x is not None) >= 0.5 * n:
good[name] = parsed
if not good:
return {}
# 2) Listwise: conserva solo filas donde TODAS las columnas tienen valor.
names = list(good.keys())
numeric: dict[str, list] = {name: [] for name in names}
for i in range(n):
if all(good[name][i] is not None for name in names):
for name in names:
numeric[name].append(good[name][i])
return numeric