---
id: mutual_info_columns_py_datascience
name: mutual_info_columns
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def mutual_info_columns(a: list, b: list, a_numeric: bool = False, b_numeric: bool = False, bins: int = 10, normalized: bool = True) -> float"
description: "Informacion mutua entre dos columnas pareadas del grupo eda: detector general de dependencia que capta relaciones de cualquier forma (lineal o no, num-num, cat-cat, num-cat). Discretiza numericas por cuantiles, factoriza categoricas, devuelve NMI en [0,1] (normalized) o MI en nats. Funcion pura."
tags: [eda, correlation, mutual-information, association, profiling, datascience]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
example: |
  from datascience import mutual_info_columns
  a = [i - 1000 for i in range(2000)]
  b = [abs(x) for x in a]  # V-shape: no lineal, Pearson ~ 0
  mutual_info_columns(a, b, a_numeric=True, b_numeric=True)  # ~0.69, NMI alto
tested: true
tests:
  - "test_identical_categoricals_nmi_near_one"
  - "test_nonlinear_numeric_relation_has_positive_nmi"
  - "test_independent_columns_near_zero"
  - "test_fewer_than_two_pairs_returns_zero"
  - "test_none_pairs_are_discarded"
  - "test_constant_column_returns_zero_when_normalized"
  - "test_unnormalized_returns_mi_in_nats"
  - "test_always_returns_float_never_none"
test_file_path: "python/functions/datascience/mutual_info_columns_test.py"
file_path: "python/functions/datascience/mutual_info_columns.py"
params:
  - name: a
    desc: >
      Lista de valores de la primera columna, pareada posicion a posicion con
      `b`. None se descarta (junto con su contraparte en `b`).
  - name: b
    desc: >
      Lista de valores de la segunda columna, pareada con `a` (mismo criterio
      de descarte de None).
  - name: a_numeric
    desc: >
      Si True, `a` se discretiza en `bins` cubos por cuantiles antes de medir;
      si False se trata como categorica (factorizacion valor->id entero).
  - name: b_numeric
    desc: "Idem que a_numeric pero para la columna `b`."
  - name: bins
    desc: >
      Numero de cubos por cuantiles para las columnas numericas. Cuantiles
      repetidos colapsan en menos cubos (columnas de baja variacion).
  - name: normalized
    desc: >
      Si True devuelve la informacion mutua normalizada NMI = MI / sqrt(H(a)*H(b))
      en [0, 1] (1 = dependencia total). Si False devuelve la MI cruda en nats.
output: >
  float. NMI en [0, 1] cuando normalized=True; MI en nats (>= 0) cuando
  normalized=False. Devuelve 0.0 si hay menos de 2 pares validos o si alguna
  columna discretizada tiene entropia 0 (constante) bajo normalized. Nunca
  devuelve None ni lanza excepcion.
---

## Ejemplo

```python
from datascience import mutual_info_columns
import math

# Relacion NO lineal: b = |a| (forma de V). Pearson ~ 0, pero la dependencia es real.
a = [i - 1000 for i in range(2000)]            # -1000 .. 999
b = [abs(x) for x in a]

mutual_info_columns(a, b, a_numeric=True, b_numeric=True)
# -> ~0.69 (NMI alto: a determina b por completo dentro de cada cubo)

# Comparalo con la correlacion lineal, que no ve la relacion:
from datascience import pearson
pearson([float(x) for x in a], [float(x) for x in b])  # -> ~0.0

# Tambien capta relaciones oscilantes resueltas por los bins:
ax = [2 * math.pi * i / 2000 for i in range(2000)]   # un periodo de seno
bx = [1.0 if math.sin(x) >= 0 else -1.0 for x in ax]
mutual_info_columns(ax, bx, a_numeric=True)          # -> ~0.55, Pearson ~ -0.87

# Dos categoricas identicas -> dependencia total.
c = ["red", "green", "blue", "red", "green", "blue"]
mutual_info_columns(c, list(c))  # -> ~1.0

# MI cruda en nats (sin normalizar).
mutual_info_columns(c, list(c), normalized=False)  # -> ~log(3) nats
```

## Cuando usarla

Cuando necesites un **detector general de dependencia** entre dos columnas y no
sepas (o no quieras asumir) la forma de la relacion. Pearson solo ve lineal y
solo num-num; `cramers_v` solo cat-cat. La informacion mutua funciona para
**cualquier par de tipos** (num-num, cat-cat, num-cat) y capta relaciones no
lineales (sinusoidales, escalon, agrupamientos) que la correlacion lineal pasa
por alto. Es la celda "comodin" de una matriz de asociacion en un EDA: usala
para descubrir relaciones ocultas antes de modelar, o para rankear que columnas
predicen mejor un objetivo. Activa `a_numeric`/`b_numeric` por columna segun su
tipo y deja `normalized=True` para obtener un score comparable en [0, 1].

## Notas

Funcion pura y determinista: misma entrada -> misma salida (sin estado, sin
I/O, sin aleatoriedad; `sklearn.metrics.mutual_info_score` es determinista).

- **Discretizacion**: numericas via `np.digitize` sobre los bordes de cuantil
  (`np.quantile`); categoricas via mapa valor->id en orden de aparicion. La
  eleccion de `bins` afecta la estimacion de MI en columnas numericas: pocos
  bins suavizan, muchos bins capturan mas estructura pero inflan el ruido en
  muestras pequenas. Una relacion que oscila mas rapido que la resolucion de
  los bins (p.ej. un seno de muchos periodos sobre el rango de `a`) da NMI bajo
  con `bins` pequeno aunque la dependencia sea real: sube `bins` para resolverla.
- **Sesgo de la MI**: en muestras pequenas la MI cruda tiende a sobreestimarse
  (sesgo positivo). La normalizacion NMI lo atenua parcialmente pero no lo
  elimina; para columnas independientes con muchos bins y pocos datos el valor
  puede no ser exactamente 0.
- **Entropia 0**: si una columna discretizada es constante, H = 0 y la NMI se
  define como 0.0 (no hay informacion compartida medible); la MI cruda tambien
  es 0 en ese caso.
- **NMI** = MI / sqrt(H(a) * H(b)), clampada a [0, 1] por seguridad numerica.
