tutoriales.com

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.

Intermedio15 min de lectura6 views
Reportar error

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

💡 Consejo: Los treemaps son ideales para mostrar la composición de un todo, como la distribución de ventas por producto y región, el uso del espacio en disco por tipo de archivo, o la población por continente y país.

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
📌 Nota: Es recomendable usar un entorno virtual para gestionar las dependencias de tus proyectos.

📊 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()
Asia 40% Ventas Europa 25% Ventas Norteamérica 20% Latinoamérica 10% África (4%) Oceanía (1%)

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()
🔥 Importante: Personalizar las etiquetas es crucial para la legibilidad del treemap. Asegúrate de que el texto sea lo suficientemente grande y contrastante para ser leído.

🌳 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()
Europa Electrónica Ropa Alimentos Asia Tecnología Textil América Maquinaria Agrícola

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.

💡 Consejo: Para treemaps con muchas jerarquías o elementos, considera usar bibliotecas como `plotly` o `altair`, que ofrecen una implementación más directa de treemaps jerárquicos interactivos, aunque `matplotlib` y `squarify` son excelentes para un control más fino y para gráficos estáticos.

🎨 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()
80% Personalización

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 👍

  1. 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.
  2. 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.
  3. 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.
  4. 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.
Paso 1: Entender los Datos: Identifica si tus datos tienen una estructura jerárquica y una métrica de tamaño.
Paso 2: Preparar los Datos: Agrupa y estructura tu `DataFrame` adecuadamente para el nivel de detalle que quieres mostrar.
Paso 3: Construir el Treemap Base: Usa `squarify.plot` para los elementos principales.
Paso 4: Añadir Jerarquía y Personalización: Itera para crear capas anidadas y ajusta colores, etiquetas y estilos.
Paso 5: Iterar y Refinar: Ajusta tamaños de figura, fuentes, y colores hasta que la visualización sea clara y efectiva.

🎯 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

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!