--- 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.