---
name: association_matrix
kind: function
lang: py
domain: datascience
version: "1.1.0"
purity: pure
signature: "def association_matrix(columns: dict, strong_threshold: float = 0.5, top_n: int = 20, alpha: float = 0.05, fdr_method: str = \"bh\") -> dict"
description: "Matriz de asociacion unificada de una tabla con tipos mezclados: elige la metrica correcta por par de tipos (Pearson/Spearman num-num, Cramer's V cat-cat, correlation ratio num-cat) y calcula informacion mutua normalizada comun para todos los pares. Cada par lleva su p-valor (test de correlacion / chi-cuadrado / ANOVA) y se corrige por comparaciones multiples (FDR) para combatir el sesgo de mineria de datos: el subconjunto fuerte se basa en la significancia corregida, no solo en superar el umbral de magnitud."
tags: [eda, correlation, association, statistics, mixed-types, mutual-information, multiple-testing, p-value, fdr]
params:
  - name: columns
    desc: "dict {nombre_columna: {\"values\": list, \"type\": \"numeric\"|\"categorical\"|\"datetime\"|\"boolean\"|\"text\"}}. datetime/boolean/text se tratan como categoricas; text de cardinalidad ~ n se salta como ruido."
  - name: strong_threshold
    desc: "Umbral de magnitud en [0, 1]. Condicion necesaria (ya no suficiente) para ser fuerte: abs(value) >= umbral o extra.mi >= umbral. Default 0.5."
  - name: top_n
    desc: "Maximo de pares fuertes a devolver, ordenados por relevancia (max(abs(value), mi)) desc. Default 20."
  - name: alpha
    desc: "Nivel de significancia tras la correccion FDR (default 0.05). Un par con p-valor disponible solo es fuerte si ademas su p-valor ajustado <= alpha."
  - name: fdr_method
    desc: "Metodo de correccion de comparaciones multiples: 'bh' (Benjamini-Hochberg, FDR; default) o 'bonferroni' (FWER, mas conservador)."
output: "dict {pairs: lista de todos los pares {a, b, a_type, b_type, method, value, extra, p_value, p_value_adjusted, significant}; strong: subconjunto con magnitud >= umbral Y significativo tras FDR (pares sin test se admiten por magnitud), ordenado por relevancia desc truncado a top_n; methods_legend: dict metodo->descripcion; n_tests: nº total de pares evaluados (== len(pairs)); multiple_testing: {method, alpha, n_tests, n_rejected}}. Pura: con dict vacio o 1 columna devuelve pairs=[] y strong=[]."
uses_functions:
  - pearson_py_datascience
  - spearman_corr_py_datascience
  - cramers_v_py_datascience
  - theils_u_py_datascience
  - correlation_ratio_py_datascience
  - mutual_info_columns_py_datascience
  - fdr_correction_py_datascience
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [scipy]
tested: true
tests: ["test_two_correlated_numerics_strong_pearson", "test_numeric_explained_by_category_strong_correlation_ratio", "test_independent_pair_not_strong", "test_empty_dict_does_not_crash", "test_single_column_returns_empty", "test_pairs_carry_significance_fields", "test_result_reports_multiple_testing_summary", "test_strong_requires_corrected_significance", "test_bonferroni_method_is_accepted"]
test_file_path: "python/functions/datascience/association_matrix_test.py"
file_path: "python/functions/datascience/association_matrix.py"
---

## Ejemplo

```python
from datascience import association_matrix

columns = {
    # Numerica correlada linealmente con "size" (y ~ 2x + ruido pequeno).
    "size": {"values": [1, 2, 3, 4, 5, 6, 7, 8], "type": "numeric"},
    "price": {"values": [2.1, 4.0, 5.9, 8.1, 10.0, 12.2, 13.8, 16.1], "type": "numeric"},
    # Categorica que explica la varianza de "score" (cada region -> nivel distinto).
    "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, strong_threshold=0.5, top_n=10)

# Pares fuertes detectados (orden por relevancia):
for p in result["strong"]:
    print(p["a"], p["b"], p["method"], round(p["value"], 2))
# size price pearson/spearman 1.0      -> num-num lineal casi perfecta
# region score correlation_ratio 0.99  -> la categoria explica la numerica

print(result["methods_legend"]["correlation_ratio"])
```

## Cuando usarla

Cuando necesites una **matriz de relaciones de una tabla entera mezclando tipos**
(numericas, categoricas, fechas, booleanos) en una sola pasada, sin tener que
elegir a mano que metrica aplicar a cada par. Ideal en la fase EDA para detectar
de un vistazo que columnas estan asociadas (y por que metodo), priorizando los
pares fuertes. Reusa las funciones atomicas del registry (`pearson`,
`spearman_corr`, `cramers_v`, `theils_u`, `correlation_ratio`,
`mutual_info_columns`) y anade informacion mutua normalizada como medida comun
no-lineal a todos los pares.

## Notas

- Pura: las atomicas que compone son puras y deterministas; no hace I/O.
- `pearson` no limpia None/NaN internamente, asi que los pares num-num se
  limpian aqui antes de llamarla (se emparejan por indice y se descartan pares
  con algun lado no numerico).
- En num-num el `value` principal es el de mayor valor absoluto entre Pearson y
  Spearman; ambos quedan en `extra` (`pearson`, `spearman`).
- En cat-cat el `value` es Cramer's V (simetrico) y `extra` lleva Theil's U
  direccional en ambos sentidos (`u_ab` = U(a|b), `u_ba` = U(b|a)).
- En num-cat el `value` es el correlation ratio (eta) llamando siempre con la
  categorica como primer argumento y la numerica como segundo.
- Se saltan columnas con menos de 3 valores validos, y columnas `text` cuya
  cardinalidad sea >= 90% del numero de filas (identificadores / free-text).

## Gotchas

- **Ahora corrige multiple-testing (v1.1.0).** El subconjunto `strong` ya no
  depende solo de la magnitud: un par con magnitud alta pero p-valor ajustado
  > `alpha` NO entra en `strong`. Esto combate el sesgo de mineria de datos
  (data-mining bias, Aronson cap. 6): al evaluar todos los pares a la vez, el
  azar produce correlaciones espurias que el umbral de magnitud por si solo
  dejaria pasar.
- Cada par lleva `p_value` (test del metodo principal: correlacion de
  Pearson/Spearman, chi-cuadrado de independencia para Cramer's V, ANOVA de una
  via para correlation ratio) y `p_value_adjusted` (tras `fdr_correction`). La
  informacion mutua no tiene test asociado, por lo que un par cuyo metodo
  principal sea degenerado puede tener `p_value = None`; esos pares se admiten en
  `strong` por magnitud (no hay p-valor que corregir).
- `n_tests` (top-level) es el numero total de pares evaluados (`len(pairs)`),
  mientras que `multiple_testing.n_tests` es el numero de p-valores **validos**
  que entraron en la correccion (puede ser menor si algun par no tiene test).
- Sigue siendo pura, pero ahora importa `scipy.stats` (`pearsonr`, `spearmanr`,
  `chi2_contingency`, `f_oneway`) para los p-valores; scipy ya vive en
  `python/.venv`.
- Sube `alpha` o usa `fdr_method="bonferroni"` segun lo costoso que sea un falso
  positivo: BH controla la tasa de falsos descubrimientos (mas potencia),
  Bonferroni la probabilidad de cualquier falso positivo (mas cautela).

## Capability growth log

- v1.1.0 (28/06/2026) — anade p-valor por par (Pearson/Spearman, chi-cuadrado,
  ANOVA) + correccion de comparaciones multiples via `fdr_correction` (BH /
  Bonferroni). `strong` pasa a basarse en la significancia corregida, no solo en
  el umbral de magnitud. Nuevos parametros `alpha` y `fdr_method`; nuevas claves
  `p_value`/`p_value_adjusted`/`significant` por par y `n_tests`/
  `multiple_testing` en el resultado. Retrocompatible: no quita claves previas.
