---
id: theils_u_py_datascience
name: theils_u
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def theils_u(a: list, b: list) -> float"
description: "Theil's U (coeficiente de incertidumbre) DIRECCIONAL entre dos columnas categoricas: U(a|b) = fraccion de la incertidumbre de `a` que se elimina conociendo `b`, en [0,1]. ASIMETRICO (theils_u(a,b) != theils_u(b,a)), a diferencia de Cramer's V, lo que permite detectar dependencias direccionales (p.ej. ciudad->pais). Funcion pura."
tags: [eda, correlation, association, categorical, entropy, datascience]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
example: |
  from datascience import theils_u
  ciudad = ["Madrid", "Madrid", "Paris", "Paris", "Roma", "Roma"]
  pais   = ["ES",     "ES",     "FR",    "FR",    "IT",   "IT"]
  theils_u(ciudad, pais)  # ~1.0 — saber el pais determina mucho la ciudad? no.
  theils_u(pais, ciudad)  # 1.0  — la ciudad determina el pais (N:1)
tested: true
tests:
  - "test_b_determines_a_gives_one"
  - "test_asymmetry_n_to_one_relation"
  - "test_independence_gives_zero"
  - "test_fewer_than_two_pairs_returns_zero"
  - "test_constant_a_returns_zero"
  - "test_none_pairs_discarded"
  - "test_result_in_unit_interval"
test_file_path: "python/functions/datascience/theils_u_test.py"
file_path: "python/functions/datascience/theils_u.py"
params:
  - name: a
    desc: >
      Columna categorica objetivo, cuya incertidumbre se mide. Lista de valores
      (str, int, etc.). Se empareja por indice con `b`. Los pares en los que `a`
      o `b` sean None se descartan antes de calcular.
  - name: b
    desc: >
      Columna categorica condicionante: el conocimiento cuyo poder explicativo
      sobre `a` se evalua. Misma longitud y emparejamiento por indice que `a`.
output: >
  Theil's U(a|b) como float en [0.0, 1.0]. 1.0 = conocer `b` determina `a` por
  completo; 0.0 = `b` no aporta informacion sobre `a` (independencia). Devuelve
  0.0 (nunca None ni excepcion) si hay menos de 2 pares validos o si `a` es
  constante (H(a)==0). El valor es DIRECCIONAL: U(a|b) generalmente difiere de
  U(b|a).
---

## Ejemplo

```python
from datascience import theils_u

# Relacion N:1 — varias ciudades por pais. La ciudad determina el pais,
# pero el pais NO determina la ciudad.
ciudad = ["Madrid", "Barcelona", "Paris", "Lyon", "Roma", "Milan"]
pais   = ["ES",     "ES",        "FR",    "FR",   "IT",   "IT"]

# Conocer la ciudad elimina TODA la incertidumbre del pais (cada ciudad
# pertenece a un unico pais) -> ~1.0
theils_u(pais, ciudad)    # 1.0   (U(pais | ciudad))

# Conocer el pais solo reduce parte de la incertidumbre de la ciudad
# (cada pais tiene 2 ciudades posibles) -> < 1.0
theils_u(ciudad, pais)    # ~0.5  (U(ciudad | pais))

# La ASIMETRIA es la gracia: theils_u(a, b) != theils_u(b, a).
# Una medida simetrica como Cramer's V daria el mismo numero en ambos sentidos
# y ocultaria la direccion de la dependencia.
```

## Cuando usarla

Cuando exploras un dataset (grupo `eda`) y quieres detectar **dependencias
direccionales** entre columnas categoricas: que columna determina a otra, no solo
si estan asociadas. Casos tipicos: jerarquias geograficas (ciudad -> pais,
codigo_postal -> provincia), claves derivadas (sku -> categoria), o cualquier
relacion N:1 donde te interesa saber el sentido. Usa Theil's U en vez de
Cramer's V precisamente cuando la simetria de Cramer's V te impediria ver que
`a` explica a `b` pero no al reves. Tambien sirve para construir una matriz de
asociacion asimetrica que revele la estructura causal/jerarquica candidata antes
de modelar.

## Gotchas

Funcion pura, sin I/O ni dependencias externas (solo `math` y `collections`).
Notas de uso:

- Es **direccional**: `theils_u(a, b)` mide U(a|b) (incertidumbre de `a`
  explicada por `b`). No asumas simetria.
- Pensada para columnas **categoricas**. Si pasas numerica continua de alta
  cardinalidad, cada valor sera casi unico y el resultado tendera a inflar la
  asociacion (cuidado al interpretar).
- Empareja por indice y **descarta** pares con algun None; con menos de 2 pares
  validos devuelve 0.0.
- Si `a` es constante (H(a)==0), devuelve 0.0: no hay incertidumbre que eliminar.
- El resultado se clampa a [0, 1] para absorber error de coma flotante; nunca
  lanza excepcion ni devuelve None.
