KDE e histogramas: visualización de distribuciones
Al finalizar este tema
Podrás explicar la diferencia entre un histograma y un gráfico KDE, y visualizar la distribución de datos con Seaborn para comprender su forma.
Por qué es importante analizar las distribuciones
Al analizar datos, la media y la desviación estándar no son suficientes:
import numpy as np
data_a = np.array([50, 50, 50, 50, 50])data_b = np.array([10, 30, 50, 70, 90])
print(f"Media A: {data_a.mean()}, Media B: {data_b.mean()}") # Ambas son 50La media es la misma, pero la distribución de los datos es completamente diferente. Los datos de A están agrupados, mientras que los de B están más dispersos. Al visualizar la distribución, estas diferencias se hacen evidentes.
Histograma: visualización de frecuencias con barras
Un histograma divide los datos en intervalos y representa el número de datos en cada intervalo mediante la altura de las barras.
import matplotlib.pyplot as pltimport numpy as np
np.random.seed(42)data = np.random.normal(170, 10, 1000) # Altura de 1000 personas con media 170 y desviación estándar 10
plt.figure(figsize=(8, 4))plt.hist(data, bins=30, color='steelblue', edgecolor='white', alpha=0.7)plt.xlabel('Altura (cm)')plt.ylabel('Frecuencia')plt.title('Distribución de altura (histograma)')plt.show()bins=30 indica que el rango total se divide en 30 intervalos. La forma varía según la cantidad de intervalos:
fig, axes = plt.subplots(1, 3, figsize=(12, 3))for ax, b in zip(axes, [5, 30, 100]): ax.hist(data, bins=b, color='steelblue', edgecolor='white') ax.set_title(f'bins={b}')plt.tight_layout()plt.show()- Si el número de intervalos es demasiado bajo: se pierde la estructura detallada.
- Si el número de intervalos es demasiado alto: aparece ruido.
- Encontrar el número adecuado de intervalos es el desafío del histograma.
KDE: visualización de la densidad con una curva suavizada
KDE (estimación de densidad del núcleo) es la versión suavizada del histograma. Se coloca una pequeña curva con forma de campana (núcleo) sobre cada punto de datos y se suman todas.
import seaborn as sns
plt.figure(figsize=(8, 4))sns.kdeplot(data, fill=True, color='steelblue', alpha=0.5)plt.xlabel('Altura (cm)')plt.title('Distribución de altura (KDE)')plt.show()Ventajas de KDE:
- No hace falta elegir el número de intervalos (al ser una curva continua)
- Aspecto visualmente más suave — permite comprender intuitivamente la forma de la distribución
- Facilita la comparación de dos distribuciones — resulta claro al superponerlas
Visualizar histograma y KDE juntos
plt.figure(figsize=(8, 4))sns.histplot(data, bins=30, kde=True, color='steelblue', edgecolor='white', alpha=0.5, stat='density')plt.xlabel('Altura (cm)')plt.title('Histograma + KDE')plt.show()Al configurar stat='density', el eje y del histograma cambia de frecuencia a densidad, de modo que la escala coincida con la curva KDE.
Comparación entre dos grupos
np.random.seed(42)male = np.random.normal(175, 8, 500)female = np.random.normal(162, 7, 500)
plt.figure(figsize=(8, 4))sns.kdeplot(male, fill=True, label='Masculino', alpha=0.4)sns.kdeplot(female, fill=True, label='Femenino', alpha=0.4)plt.xlabel('Altura (cm)')plt.legend()plt.title('Comparación de distribución de altura por género')plt.show()La estimación de densidad de kernel (KDE) muestra las áreas superpuestas de forma transparente, lo que permite comprender intuitivamente las diferencias y las intersecciones entre dos grupos.
Diversas formas de distribución
np.random.seed(42)fig, axes = plt.subplots(1, 4, figsize=(16, 3))
# Distribución normal — forma de campana simétrica izquierda-derechanormal = np.random.normal(0, 1, 1000)sns.kdeplot(normal, ax=axes[0], fill=True)axes[0].set_title('Distribución normal')
# Cola derecha — la mayoría son pequeños, unos pocos grandes (distribución de ingresos)skewed = np.random.exponential(2, 1000)sns.kdeplot(skewed, ax=axes[1], fill=True)axes[1].set_title('Asimetría positiva')
# Distribución bimodal — dos grupos mezcladosbimodal = np.concatenate([np.random.normal(-2, 0.5, 500), np.random.normal(2, 0.5, 500)])sns.kdeplot(bimodal, ax=axes[2], fill=True)axes[2].set_title('Distribución bimodal')
# Distribución uniforme — todos los valores tienen frecuencia similaruniform = np.random.uniform(0, 10, 1000)sns.kdeplot(uniform, ax=axes[3], fill=True)axes[3].set_title('Distribución uniforme')
plt.tight_layout()plt.show()Al observar la forma de la distribución, se pueden identificar directamente las características de los datos. Si aparece una distribución bimodal, se obtiene la intuición de que "hay dos grupos mezclados dentro de los datos".
Criterios para elegir entre histograma y KDE
| Histograma | KDE | |
|---|---|---|
| Frecuencia exacta | ✅ Permite verificar el conteo por intervalo | ❌ Estimación de densidad |
| Forma de la distribución | Depende del número de bins | ✅ Curva suave y consistente |
| Comparación de grupos | Difícil de leer si se superponen | ✅ La superposición transparente es clara |
| Datos discretos | ✅ Adecuado | ❌ Adecuado para datos continuos |
| Datos de muestra pequeña | ✅ Refleja los datos reales | ⚠️ Riesgo de suavizado excesivo |
Es más seguro utilizar ambos métodos conjuntamente: verificar la distribución real con el histograma y comprender la tendencia general con KDE.
Parámetro bandwidth de KDE
El ajuste más importante en KDE es bandwidth (ancho de banda), que determina el ancho del núcleo colocado sobre cada punto de datos:
fig, axes = plt.subplots(1, 3, figsize=(12, 3))bandwidths = [0.1, 0.5, 2.0]
for ax, bw in zip(axes, bandwidths): sns.kdeplot(data, bw_adjust=bw, ax=ax, fill=True) ax.set_title(f'bw_adjust={bw}')
plt.tight_layout()plt.show()- ancho de banda pequeño (0.1): refleja incluso el ruido; aspecto irregular
- ancho de banda adecuado (0.5~1.0): capta bien las tendencias de los datos
- ancho de banda grande (2.0): suaviza en exceso, perdiendo la estructura detallada
En seaborn, bw_adjust es el factor que se multiplica por el ancho de banda calculado automáticamente. El valor predeterminado es 1.0 y funciona bien en la mayoría de los casos.
En la práctica: detección de valores atípicos
La visualización de distribuciones es eficaz para detectar valores atípicos:
np.random.seed(42)normal_data = np.random.normal(100, 15, 1000)outliers = np.array([200, 210, 220, -50])data_with_outliers = np.concatenate([normal_data, outliers])
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Verificar valores atípicos con histogramaaxes[0].hist(data_with_outliers, bins=50, color='steelblue', edgecolor='white')axes[0].set_title('Histograma: se observan valores atípicos en la cola')
# Se vuelve más claro al verlo junto con un diagrama de cajaaxes[1].boxplot(data_with_outliers, vert=False)axes[1].set_title('Diagrama de caja: los valores atípicos se muestran como puntos')
plt.tight_layout()plt.show()Si la cola de la distribución observada en un diagrama de densidad de kernel (KDE) o en un histograma es más larga de lo esperado, se deben sospechar valores atípicos. Su uso combinado con diagramas de caja permite un diagnóstico más preciso.
displot de Seaborn: visualización integrada de distribuciones
# displot proporciona histograma, KDE y ECDF en una sola APIsns.displot(data, kind="hist", kde=True, bins=30, height=4, aspect=2)plt.show()
# ECDF — Función de distribución acumulada empíricasns.displot(data, kind="ecdf", height=4, aspect=2)plt.show()displot es una interfaz unificada que se introdujo en seaborn 0.11+. Mediante el parámetro kind, se puede seleccionar entre "hist", "kde" y "ecdf", y también se pueden realizar comparaciones por grupo de forma sencilla con el parámetro hue.
Visualizar las distribuciones es el primer paso del análisis de datos. Los patrones que se pierden al basarse únicamente en datos numéricos como la media y la desviación estándar se hacen evidentes con una sola gráfica.