fix(eda): hallazgos de comportamiento del benchmark (H2,H3,H6,H7,H8,H10,H11)

Ronda 4 (verificada con re-corrida sobre los datasets afectados):
- H2: stl_decompose deriva periodo de la frecuencia del indice (seattle period=365
  seasonal_strength=0.84; fin del period=2 espurio)
- H3+H10: infer_fk por senal de nombre (<X>Id->X.<X>Id) + excluir no-clave -> chinook
  111->9 FK, todas reales, cero absurdas, 16-27x mas rapido; base intacta (flag off->111)
- H6: association no computa eta2 si cardinalidad~=n (Ticket-Fare espurio fuera)
- H7: id secuencial monotono excluido de correlacion y PCA/KMeans (PassengerId fuera)
- H8: correlacion de series no estacionarias marcada espuria / sobre retornos
- H11: distribution_type usa modos/cardinalidad/normalidad (quality->discrete)
- 66 tests verdes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Egutierrez
2026-06-29 06:37:47 +02:00
parent c4cff5ed5b
commit e142ef026d
12 changed files with 1028 additions and 36 deletions
@@ -156,3 +156,65 @@ def test_bonferroni_method_is_accepted():
assert result["multiple_testing"]["method"] == "bonferroni"
pair = _find_pair(result["pairs"], "size", "price")
assert pair["p_value_adjusted"] is not None
# --- H6: correlation_ratio espurio por cardinalidad casi-unica ---------------
def test_h6_categorica_casi_unica_excluida():
# Una categorica con cardinalidad ~ n (id/free-text como Ticket) hace que cada
# grupo tenga un solo valor -> varianza intra-grupo ~= 0 -> correlation_ratio
# = 1 trivial. No debe aparecer ni evaluado ni como par fuerte.
n = 60
columns = {
"ticket": {"values": [f"T{i}" for i in range(n)], "type": "categorical"},
"fare": {"values": [float(i) * 1.3 for i in range(n)], "type": "numeric"},
}
result = association_matrix(columns)
assert _find_pair(result["pairs"], "ticket", "fare") is None
assert _find_pair(result["strong"], "ticket", "fare") is None
def test_h6_categorica_dispersa_con_nulos_excluida():
# Categorica dispersa con muchos None (como Cabin: 147 distintos sobre 204
# presentes): los pocos presentes son casi todos distintos -> grupos singleton.
# Se mide sobre valores PRESENTES, no sobre n filas, para captarla.
vals = [f"C{i}" if i % 4 == 0 else None for i in range(80)] # ~20 presentes, distintos
columns = {
"cabin": {"values": vals, "type": "categorical"},
"fare": {"values": [float(i) for i in range(80)], "type": "numeric"},
}
result = association_matrix(columns)
assert _find_pair(result["pairs"], "cabin", "fare") is None
def test_h6_datetime_excluido_de_pares():
# Datetime es indice unico-ish por fila -> correlation_ratio = 1 espurio contra
# cualquier numerica. Se excluye de los pares de asociacion (las series se
# analizan aparte, no aqui).
columns = {
"date": {
"values": [f"2020-01-{i + 1:02d}" for i in range(10)],
"type": "datetime",
},
"value": {"values": [float(i) for i in range(10)], "type": "numeric"},
}
result = association_matrix(columns)
assert _find_pair(result["pairs"], "date", "value") is None
def test_h6_categorica_legitima_se_conserva():
# Edge anti-sobrefiltrado: una categorica de baja cardinalidad (grupos grandes,
# tamano medio >= 1.5) SIGUE evaluandose y su asociacion fuerte se conserva.
columns = {
"region": {
"values": ["N", "N", "S", "S", "E", "E", "W", "W"],
"type": "categorical",
},
"score": {
"values": [10.0, 11.0, 50.0, 49.0, 90.0, 91.0, 30.0, 31.0],
"type": "numeric",
},
}
result = association_matrix(columns)
assert _find_pair(result["pairs"], "region", "score") is not None
assert _find_pair(result["strong"], "region", "score") is not None