Domina los Treemaps con Python y Matplotlib: Explorando Datos Jerárquicos Visualmente
Descubre cómo utilizar treemaps, una potente herramienta de visualización de datos, para representar estructuras jerárquicas y proporciones con Python. Este tutorial te guiará paso a paso en la creación y personalización de treemaps, permitiéndote explorar grandes volúmenes de datos de una manera intuitiva y atractiva.
🚀 Introducción a los Treemaps
En el vasto universo de la visualización de datos, los treemaps destacan como una herramienta excepcionalmente útil para representar datos jerárquicos y proporcionales de manera eficiente. Imagina tener una estructura de datos donde cada elemento pertenece a una categoría más grande, y a su vez, cada subcategoría tiene un tamaño o valor asociado. Los treemaps son perfectos para esto, utilizando rectángulos anidados para mostrar tanto la estructura jerárquica como las magnitudes relativas de cada componente.
¿Qué es un Treemap? 🤔
Un treemap es una visualización de datos que muestra datos jerárquicos como un conjunto de rectángulos anidados. Cada "rama" del árbol se le asigna un rectángulo, que luego se mosaica con rectángulos más pequeños que representan las subramas. El tamaño de cada rectángulo es proporcional a una cantidad definida por el usuario, como el valor, el porcentaje o la cuenta.
Ventajas de los Treemaps ✨
Los treemaps ofrecen varias ventajas significativas:
- Eficiencia espacial: Utilizan el espacio disponible de manera muy efectiva, permitiendo mostrar una gran cantidad de datos en una sola vista.
- Visualización jerárquica: Hacen evidente la relación padre-hijo entre los elementos.
- Comparación de proporciones: El tamaño de los rectángulos permite comparar rápidamente las magnitudes relativas de los diferentes elementos.
- Identificación de patrones: Facilitan la detección de los elementos más grandes o dominantes dentro de cada jerarquía.
🛠️ Herramientas Necesarias
Para este tutorial, utilizaremos Python junto con algunas de sus bibliotecas más populares para la visualización de datos. Específicamente, nos enfocaremos en:
pandas: Para la manipulación y gestión de datos.matplotlib: La biblioteca base para la creación de gráficos en Python.squarify: Una librería especializada que implementa el algoritmo squarified treemap, esencial para dibujar los rectángulos de forma óptima.
Instalación de Librerías 📦
Si aún no tienes estas librerías instaladas, puedes hacerlo fácilmente usando pip:
pip install pandas matplotlib squarify
📊 Preparación de Datos para Treemaps
Los treemaps requieren una estructura de datos específica. Generalmente, necesitamos al menos dos columnas: una para el valor que determinará el tamaño de los rectángulos y otra (o varias) para la jerarquía que definirá la anidación.
Consideremos un ejemplo práctico: datos de ventas de una empresa, desglosados por continente y producto.
Creando Datos de Ejemplo 📝
Usaremos pandas para crear un DataFrame que simule estos datos.
import pandas as pd
import numpy as np
# Datos de ejemplo: Ventas por Continente y Producto
data = {
'Continente': ['América', 'América', 'América', 'Europa', 'Europa', 'Europa', 'Asia', 'Asia', 'Asia', 'África', 'África', 'Oceanía', 'Oceanía'],
'Producto': ['Electrónica', 'Ropa', 'Alimentos', 'Electrónica', 'Ropa', 'Alimentos', 'Electrónica', 'Ropa', 'Alimentos', 'Electrónica', 'Ropa', 'Electrónica', 'Alimentos'],
'Ventas': [1200, 800, 500, 1500, 900, 600, 1000, 700, 400, 300, 200, 450, 250]
}
df = pd.DataFrame(data)
print(df)
Salida esperada:
Continente Producto Ventas
0 América Electrónica 1200
1 América Ropa 800
2 América Alimentos 500
3 Europa Electrónica 1500
4 Europa Ropa 900
5 Europa Alimentos 600
6 Asia Electrónica 1000
7 Asia Ropa 700
8 Asia Alimentos 400
9 África Electrónica 300
10 África Ropa 200
11 Oceanía Electrónica 450
12 Oceanía Alimentos 250
Agrupando Datos para la Jerarquía 🪜
Para un treemap jerárquico, primero necesitamos sumar los valores para cada nivel de la jerarquía. En nuestro caso, queremos ver las ventas totales por continente, y dentro de cada continente, las ventas por producto.
# Agrupar por Continente y Producto para obtener las ventas individuales
df_grouped = df.groupby(['Continente', 'Producto'])['Ventas'].sum().reset_index()
# Calcular las ventas totales por Continente para el nivel superior
df_continents = df.groupby('Continente')['Ventas'].sum().reset_index()
df_continents['Producto'] = '' # Necesario para la estructura del treemap
df_continents['Ventas_Total'] = df_continents['Ventas'] # Guardar total continente
# Unir para obtener la jerarquía completa
df_final = pd.concat([
df_continents[['Continente', 'Producto', 'Ventas']],
df_grouped
], ignore_index=True)
# Ordenar para una mejor visualización (opcional pero recomendado)
df_final = df_final.sort_values(by=['Continente', 'Ventas'], ascending=[True, False])
print(df_final)
Salida esperada:
Continente Producto Ventas
0 América 2500
1 América Electrónica 1200
2 América Ropa 800
3 América Alimentos 500
4 Asia 2100
5 Asia Electrónica 1000
6 Asia Ropa 700
7 Asia Alimentos 400
8 Europa 3000
9 Europa Electrónica 1500
10 Europa Ropa 900
11 Europa Alimentos 600
12 Oceanía 700
13 Oceanía Electrónica 450
14 Oceanía Alimentos 250
15 África 500
16 África Electrónica 300
17 África Ropa 200
Aquí hemos creado un DataFrame que incluye tanto los totales por continente (con Producto vacío para indicar el nivel superior) como los desgloses por producto dentro de cada continente. Esto es clave para la construcción de treemaps jerárquicos.
📈 Creando un Treemap Básico con squarify y Matplotlib
Ahora que tenemos los datos preparados, podemos proceder a crear nuestro primer treemap. squarify nos ayuda a calcular las posiciones y tamaños de los rectángulos, y matplotlib los dibuja.
Primer Treemap Sencillo 🎨
Comencemos con un treemap que solo muestra las ventas totales por continente.
import matplotlib.pyplot as plt
import squarify # Importar squarify
# Datos para el treemap: solo los continentes
continent_data = df_final[df_final['Producto'] == '']
sizes = continent_data['Ventas'].values
labels = continent_data['Continente'].values
# Definir una paleta de colores (opcional pero recomendado)
colors = [plt.cm.Spectral(i/float(len(labels))) for i in range(len(labels))]
plt.figure(figsize=(10, 6))
squarify.plot(sizes=sizes, label=labels, color=colors, alpha=0.8)
plt.title('Ventas Totales por Continente', fontsize=16)
plt.axis('off') # Eliminar ejes
plt.show()
Este código genera un treemap donde cada rectángulo representa un continente, y su tamaño es proporcional a las ventas totales de ese continente. El color ayuda a distinguir cada región.
Añadiendo Etiqueta con Porcentajes y Valores 🔥
Para hacer el treemap más informativo, a menudo queremos incluir tanto el valor absoluto como el porcentaje en la etiqueta.
# Recalcular tamaños y etiquetas para incluir valores y porcentajes
total_sales = continent_data['Ventas'].sum()
sizes = continent_data['Ventas'].values
labels = [
f"{cont}\n({sales:,} - {sales/total_sales:.1%})"
for cont, sales in zip(continent_data['Continente'], continent_data['Ventas'])
]
plt.figure(figsize=(12, 7))
squarify.plot(
sizes=sizes,
label=labels,
color=colors,
alpha=0.8,
text_kwargs={'fontsize': 10, 'color': 'white', 'fontweight': 'bold'}
)
plt.title('Ventas Totales por Continente (Valor y %)', fontsize=18, pad=20)
plt.axis('off')
plt.show()
🌳 Treemaps Jerárquicos Anidados
El verdadero poder de los treemaps se revela cuando visualizamos múltiples niveles de jerarquía. Aquí es donde la preparación de datos anterior cobra sentido.
Implementando la Jerarquía de Continente y Producto 🌍📦
Para anidar los productos dentro de los continentes, necesitamos iterar sobre cada continente y dibujar los treemaps internos. Esta es una aproximación común para lograr la anidación.
import matplotlib.pyplot as plt
import squarify
import pandas as pd
import numpy as np
# ... (código de preparación de datos df_final) ...
# Paleta de colores para los continentes (nivel superior)
continent_names = df_final[df_final['Producto'] == '']['Continente'].unique()
continent_colors = plt.cm.tab10(np.linspace(0, 1, len(continent_names)))
continent_color_map = {name: color for name, color in zip(continent_names, continent_colors)}
plt.figure(figsize=(16, 10))
ax = plt.gca()
# Generar el treemap principal (continentes)
# Usamos los tamaños de los continentes para la primera capa
continent_sizes = df_final[df_final['Producto'] == '']['Ventas'].values
continent_labels = df_final[df_final['Producto'] == '']['Continente'].values
# Calcular las geometrías de los rectángulos del treemap principal
rects = squarify.squarify(continent_sizes, 0, 0, 100, 100)
for i, rect in enumerate(rects):
continent = continent_labels[i]
continent_color = continent_color_map[continent]
# Dibujar el rectángulo del continente
plt.axvspan(rect['x'], rect['x'] + rect['dx'], ymin=rect['y']/100, ymax=(rect['y'] + rect['dy'])/100,
facecolor=continent_color, edgecolor='white', linewidth=2, alpha=0.6)
# Añadir etiqueta del continente
plt.text(rect['x'] + rect['dx'] / 2, rect['y'] + rect['dy'] - 2,
continent, ha='center', va='top', fontsize=16, color='black', fontweight='bold')
# Ahora, para los productos dentro de este continente
# Filtramos los datos de productos para el continente actual
product_data = df_final[(df_final['Continente'] == continent) & (df_final['Producto'] != '')]
if not product_data.empty:
product_sizes = product_data['Ventas'].values
product_labels = product_data['Producto'].values
# Calcular las geometrías de los rectángulos de los productos dentro del continente
# Los límites del treemap interno son los del rectángulo del continente
inner_rects = squarify.squarify(product_sizes, rect['x'], rect['y'], rect['dx'], rect['dy'])
for j, inner_rect in enumerate(inner_rects):
product = product_labels[j]
sales = product_sizes[j]
# Un color más oscuro o saturado del color base del continente para los productos
product_color = [max(0, c - 0.2) for c in continent_color[:3]] + [1.0] # Oscurecer color base
# Dibujar el rectángulo del producto
plt.axvspan(inner_rect['x'], inner_rect['x'] + inner_rect['dx'],
ymin=inner_rect['y']/100, ymax=(inner_rect['y'] + inner_rect['dy'])/100,
facecolor=product_color, edgecolor='white', linewidth=1.5, alpha=0.9)
# Añadir etiqueta del producto solo si el rectángulo es lo suficientemente grande
if inner_rect['dx'] > 5 and inner_rect['dy'] > 3: # Umbral para evitar etiquetas superpuestas
plt.text(inner_rect['x'] + inner_rect['dx'] / 2, inner_rect['y'] + inner_rect['dy'] / 2,
f"{product}\n({sales:,})", ha='center', va='center', fontsize=8, color='white')
plt.title('Ventas por Continente y Producto', fontsize=20, pad=20)
plt.axis('off')
plt.xlim(0, 100) # Establecer límites para la visualización
plt.ylim(0, 100)
plt.show()
Este código es un poco más complejo, ya que squarify en sí mismo no maneja la anidación de forma automática para múltiples niveles de jerarquía directamente en un solo plot call. En su lugar, calculamos las geometrías para los continentes y luego, dentro de los límites de cada continente, calculamos y dibujamos las geometrías para los productos.
🎨 Personalización y Mejora del Treemap
La personalización es clave para hacer que tus visualizaciones sean efectivas y atractivas. Podemos mejorar nuestros treemaps de varias maneras.
Esquemas de Colores 🌈
Elegir una buena paleta de colores es fundamental. Para treemaps, a menudo queremos que los elementos de una misma jerarquía compartan un tono base, pero con variaciones de saturación o luminosidad.
# Ejemplo de paleta de colores más sofisticada
import matplotlib.colors as mcolors
# Generar colores base para continentes
continent_names = df_final[df_final['Producto'] == '']['Continente'].unique()
continent_base_colors = plt.cm.tab20(np.linspace(0, 1, len(continent_names)))
continent_color_map = {name: mcolors.to_rgba(color) for name, color in zip(continent_names, continent_base_colors)}
plt.figure(figsize=(16, 10))
ax = plt.gca()
# ... (código para calcular rects de continentes) ...
rects = squarify.squarify(continent_sizes, 0, 0, 100, 100)
for i, rect in enumerate(rects):
continent = continent_labels[i]
base_color = continent_color_map[continent]
# Dibujar el rectángulo del continente con un color base más claro
continent_display_color = [c * 0.9 + 0.1 for c in base_color[:3]] + [0.7] # Aclarar y bajar alfa
plt.axvspan(rect['x'], rect['x'] + rect['dx'], ymin=rect['y']/100, ymax=(rect['y'] + rect['dy'])/100,
facecolor=continent_display_color, edgecolor='white', linewidth=2, alpha=0.8)
# ... (añadir etiqueta del continente) ...
plt.text(rect['x'] + rect['dx'] / 2, rect['y'] + rect['dy'] - 2,
continent, ha='center', va='top', fontsize=16, color='black', fontweight='bold',
bbox=dict(facecolor='white', alpha=0.5, edgecolor='none', boxstyle='round,pad=0.3'))
# ... (código para filtrar product_data) ...
product_data = df_final[(df_final['Continente'] == continent) & (df_final['Producto'] != '')]
if not product_data.empty:
# ... (código para product_sizes, product_labels, inner_rects) ...
product_sizes = product_data['Ventas'].values
product_labels = product_data['Producto'].values
inner_rects = squarify.squarify(product_sizes, rect['x'], rect['y'], rect['dx'], rect['dy'])
for j, inner_rect in enumerate(inner_rects):
product = product_labels[j]
sales = product_sizes[j]
# Generar colores para productos: usar el color base del continente, pero más oscuro
product_shade_color = [c * (1 - (j+1)/len(product_data)*0.3) for c in base_color[:3]] + [1.0] # Degradado de oscuridad
# Asegurarse de que el color no sea demasiado oscuro o claro
product_final_color = [max(0.1, min(0.9, c)) for c in product_shade_color[:3]] + [0.9]
plt.axvspan(inner_rect['x'], inner_rect['x'] + inner_rect['dx'],
ymin=inner_rect['y']/100, ymax=(inner_rect['y'] + inner_rect['dy'])/100,
facecolor=product_final_color, edgecolor='white', linewidth=1.5, alpha=0.9)
if inner_rect['dx'] > 5 and inner_rect['dy'] > 3:
plt.text(inner_rect['x'] + inner_rect['dx'] / 2, inner_rect['y'] + inner_rect['dy'] / 2,
f"{product}\n({sales:,})", ha='center', va='center', fontsize=8, color='white')
plt.title('Ventas por Continente y Producto - Mejorado', fontsize=20, pad=20)
plt.axis('off')
plt.xlim(0, 100)
plt.ylim(0, 100)
plt.show()
Añadiendo un Título y Descripción Claros 🎯
Un buen título y, si es necesario, un subtítulo o una breve descripción, son cruciales para que el lector entienda rápidamente el mensaje del treemap.
# ... (código del treemap) ...
plt.title('Distribución Global de Ventas por Continente y Tipo de Producto', fontsize=22, pad=25)
plt.suptitle('Explorando la Contribución de cada Mercado y Categoría de Producto', fontsize=14, color='gray', y=0.92)
# ... (plt.show()) ...
Interactividad (Consideraciones) 💡
Para treemaps realmente grandes o para aplicaciones web, la interactividad es un must. Bibliotecas como Plotly o Altair son excelentes opciones para esto, permitiendo tooltips al pasar el ratón, zoom, y filtros interactivos.
¿Por qué Matplotlib y Squarify no son ideales para interactividad?
`matplotlib` está diseñado principalmente para gráficos estáticos. Aunque existen extensiones como `mpl_toolkits.mplot3d` o `mpld3` para cierta interactividad, no ofrecen la experiencia fluida y rica que proporcionan librerías como Plotly o Bokeh, que están construidas desde cero con la interactividad en mente para entornos web.⚠️ Consideraciones y Mejores Prácticas
Aunque los treemaps son potentes, no son adecuados para todas las situaciones. Conocer sus limitaciones y cómo mitigarlas es crucial.
Cuándo Usar un Treemap ✅
- Cuando necesitas mostrar una jerarquía y las proporciones relativas de los elementos dentro de esa jerarquía.
- Cuando tienes un gran número de categorías que de otro modo harían un gráfico de barras o circular inmanejable.
- Cuando el espacio es una preocupación y necesitas maximizar la densidad de información.
Cuándo Evitar un Treemap ❌
- Cuando el orden preciso o la comparación exacta entre elementos pequeños es crítica (es difícil comparar áreas pequeñas con precisión).
- Cuando tienes muy pocas categorías o cuando las categorías no tienen una relación jerárquica clara (otros gráficos podrían ser más sencillos y directos).
- Cuando los valores son negativos o cero (los treemaps están diseñados para valores positivos).
Mejores Prácticas para Treemaps 👍
- Etiquetas Claras: Asegúrate de que las etiquetas sean legibles, informativas y no se superpongan. Puedes optar por mostrar solo las etiquetas de los elementos más grandes o usar tooltips interactivos.
- Paletas de Colores Significativas: Usa colores que ayuden a diferenciar las categorías de nivel superior y que creen un contraste suficiente con las etiquetas.
- Profundidad de Jerarquía: No uses demasiados niveles de jerarquía, ya que el gráfico puede volverse desordenado y difícil de leer. 2 o 3 niveles suelen ser óptimos.
- Ordenamiento: Ordenar los rectángulos por tamaño (generalmente de mayor a menor) dentro de cada nivel de jerarquía puede mejorar la legibilidad y ayudar a identificar rápidamente los elementos dominantes.
🎯 Conclusión
Los treemaps son una herramienta de visualización de datos excepcionalmente potente para explorar y comunicar información sobre estructuras jerárquicas y proporciones. Con pandas, matplotlib y squarify en Python, tienes un arsenal robusto para crear visualizaciones impactantes que revelen patrones y conocimientos ocultos en tus datos.
Desde un treemap simple que muestra la distribución de valores hasta una visualización jerárquica anidada que desvela relaciones complejas, este tutorial te ha proporcionado los conocimientos y el código para empezar a dominar esta técnica. ¡Ahora es tu turno de aplicarlo a tus propios conjuntos de datos y contar tus propias historias visuales!
Tutoriales relacionados
- Desentrañando Historias con Gráficos de Red: Un Enfoque Práctico en Pythonintermediate25 min
- Explorando la Biodiversidad con Heatmaps en Python: Mapas de Calor para Datos Biológicosintermediate15 min
- Visualizando Series Temporales Interactivas con Bokeh en Python: El Clima de tu Ciudadintermediate15 min
- Visualización Interactiva de Datos con Plotly Express en Python: ¡Crea Dashboards Asombrosos!intermediate18 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!