Python

Cómo comparar datos con barras en Matplotlib: agrupadas, apiladas y porcentuales

Tienes los datos de ventas de tres productos en dos años distintos y quieres saber cuál creció más. Podrías hacer dos gráficos separados… pero tu cerebro tardaría el doble en extraer conclusiones. La solución es un gráfico de barras comparativo, y en esta entrada verás exactamente cómo construirlo en Python con Matplotlib, paso a paso.

En concreto, veremos tres técnicas fundamentales:

  • Barras agrupadas (grouped bar charts): para comparar valores directamente entre grupos.
  • Barras apiladas (stacked bar charts): para ver la composición del total.
  • Barras porcentuales: para analizar proporciones y eliminar el efecto del tamaño absoluto.

Al final del artículo encontrarás una tabla resumen para saber cuándo usar cada una.

Recordatorio rápido: el gráfico de barras básico

Antes de entrar en comparaciones, repasemos la forma más simple de crear un gráfico de barras en Matplotlib:

import matplotlib.pyplot as plt

categorias = ['A', 'B', 'C']
valores = [10, 15, 7]

plt.bar(categorias, valores)
plt.title("Gráfico de barras básico")
plt.ylabel("Valores")
plt.show()

Este gráfico funciona perfectamente cuando tienes una sola variable y categorías independientes. Pero en cuanto quieres añadir una segunda serie de datos —por ejemplo, los mismos productos en un año diferente— este enfoque se queda corto. No puedes superponer dos llamadas a plt.bar() sin que las barras se solapen o se oculten entre sí.

Es justo ahí donde necesitas las técnicas que vamos a ver a continuación.

Gráficos de barras agrupadas (Grouped Bar Charts)

Las barras agrupadas son la elección correcta cuando quieres comparar directamente dos o más series dentro de la misma categoría. Por ejemplo:

  • Ventas de varios productos en distintos años.
  • Rendimiento de algoritmos en distintos conjuntos de datos.
  • Resultados de encuestas segmentadas por edad o región.

El concepto clave: posicionamiento numérico

Matplotlib no trabaja internamente con categorías de texto como 'Producto A' o 'Q1'. En realidad, todo se basa en posiciones numéricas en el eje X. Esto nos da el control total para desplazar las barras horizontalmente y evitar que se solapen.

El truco consiste en seguir los siguientes tres pasos:

  1. Convertir las categorías en números con np.arange.
  2. Definir el ancho de cada barra.
  3. Desplazar cada grupo de barras hacia la izquierda o la derecha.

Implementación con dos series

Empecemos con el caso más habitual: comparar dos años de ventas para tres productos.

import numpy as np
import matplotlib.pyplot as plt

categorias = ['Producto A', 'Producto B', 'Producto C']
ventas_2023 = [10, 15, 20]
ventas_2024 = [12, 18, 25]

# Paso 1: convertir categorías en posiciones numéricas
x = np.arange(len(categorias))  # → [0, 1, 2]

# Paso 2: definir el ancho de cada barra
width = 0.35

# Paso 3: dibujar las barras desplazadas
plt.bar(x - width/2, ventas_2023, width, label='2023')
plt.bar(x + width/2, ventas_2024, width, label='2024')

# Sustituir los números del eje X por las etiquetas reales
plt.xticks(x, categorias)
plt.ylabel('Ventas')
plt.title('Comparación de ventas por producto')
plt.legend()
plt.show()

Vamos a entender exactamente qué hace cada parte de este código:

  • x = np.arange(len(categorias)): Genera el array [0, 1, 2]. Cada número representa la posición central de un grupo de barras en el eje X.
  • width = 0.35: Controla el ancho de cada barra individual. Un valor entre 0.25 y 0.4 suele funcionar bien. Si lo haces demasiado grande las barras se solaparán; si es demasiado pequeño, el gráfico quedará lleno de espacio vacío.
  • x - width/2 y x + width/2: Aquí está la clave. En lugar de dibujar ambas barras en la posición x, desplazamos la primera hacia la izquierda (-width/2) y la segunda hacia la derecha (+width/2). El resultado es que las dos barras quedan centradas alrededor de cada posición, sin solaparse.
  • plt.xticks(x, categorias): Como el eje X contiene números (0, 1, 2), esta línea los sustituye por las etiquetas de texto originales. Sin esto, el gráfico mostraría 0, 1, 2 en lugar de los nombres de los productos.

Escalando a tres o más series

En análisis real es frecuente trabajar con más de dos grupos. Supongamos ahora tres años:

ventas_2022 = [8, 12, 18]
ventas_2023 = [10, 15, 20]
ventas_2024 = [12, 18, 25]

x = np.arange(len(categorias))
width = 0.25  # Reducimos el ancho para que quepan tres barras

plt.bar(x - width,     ventas_2022, width, label='2022')
plt.bar(x,             ventas_2023, width, label='2023')
plt.bar(x + width,     ventas_2024, width, label='2024')

plt.xticks(x, categorias)
plt.legend()
plt.title("Comparación multianual")
plt.show()

Observa que al añadir una tercera barra hemos tenido que reducir el width a 0.25 para que las tres quepan. La barra central permanece en x, y las otras dos se desplazan simétricamente.

Advertencia: a partir de 4 o 5 grupos, las barras agrupadas se vuelven difíciles de leer. Si tienes muchas series, considera usar líneas temporales, heatmaps o dividir el análisis en varios gráficos. Más información no siempre es mejor visualización.

Gráficos de barras apiladas (Stacked Bar Charts)

Las barras apiladas son ideales cuando lo que te importa no es comparar los valores entre sí, sino ver cómo varias partes componen un total. Por ejemplo:

  • Desglose de ingresos por línea de producto.
  • Tiempo invertido en distintas tareas durante la semana.
  • Contribución de cada departamento al presupuesto total.

Implementación básica

La clave aquí es el parámetro bottom. Este parámetro le indica a Matplotlib desde qué altura debe empezar a dibujar la siguiente barra. Sin él, todas las barras empezarían desde cero y se solaparían.

categorias = ['Producto A', 'Producto B', 'Producto C']
ventas_2023 = [10, 15, 20]
ventas_2024 = [12, 18, 25]

plt.bar(categorias, ventas_2023, label='2023')
plt.bar(categorias, ventas_2024, bottom=ventas_2023, label='2024')

plt.legend()
plt.title("Barras apiladas: ventas acumuladas")
plt.ylabel("Ventas totales")
plt.show()

En este caso, la barra de 2023 forma la base y la de 2024 se apila encima. El alto total de cada barra representa la suma de ambos años, lo que permite ver rápidamente qué producto tiene mayor volumen acumulado.

Apilando tres o más series

Para apilar tres series, simplemente acumulamos los bottom de forma progresiva:

ventas_2022 = np.array([8, 12, 18])
ventas_2023 = np.array([10, 15, 20])
ventas_2024 = np.array([12, 18, 25])

plt.bar(categorias, ventas_2022, label='2022')
plt.bar(categorias, ventas_2023, bottom=ventas_2022, label='2023')
plt.bar(categorias, ventas_2024, bottom=ventas_2022 + ventas_2023, label='2024')

plt.legend()
plt.title("Barras apiladas con tres series")
plt.show()

Es importante usar np.array en lugar de listas de Python para poder sumar los arrays directamente con el operador +.

Error frecuente: usar barras apiladas cuando en realidad quieres comparar. Si tu pregunta es “¿cuánto vendió el Producto A en 2023 frente a 2024?”, las barras apiladas no te ayudan, porque la segunda barra no empieza en cero. Para comparación directa, usa siempre barras agrupadas.

Comparación porcentual (barras normalizadas)

A veces los valores absolutos distorsionan la imagen. Imagina que el Producto A vende 100 unidades y el Producto B vende 10.000. Si los representas juntos con barras apiladas, la contribución de A quedará casi invisible aunque sea importante en términos relativos.

La normalización soluciona esto: convierte los valores a porcentaje sobre el total, de forma que todas las barras alcanzan exactamente el 100 %. Esto permite comparar la estructura interna de cada categoría independientemente de su tamaño.

Implementación paso a paso

import numpy as np
import matplotlib.pyplot as plt

categorias = ['Producto A', 'Producto B', 'Producto C']
ventas_2023 = np.array([10, 15, 20])
ventas_2024 = np.array([12, 18, 25])

# Calcular el total por producto
total = ventas_2023 + ventas_2024

# Convertir a porcentaje
ventas_2023_pct = ventas_2023 / total * 100
ventas_2024_pct = ventas_2024 / total * 100

# Dibujar el gráfico apilado porcentual
plt.bar(categorias, ventas_2023_pct, label='2023')
plt.bar(categorias, ventas_2024_pct, bottom=ventas_2023_pct, label='2024')

plt.ylabel('%')
plt.ylim(0, 100)
plt.title("Distribución porcentual de ventas por producto")
plt.legend()
plt.show()

Ahora todas las barras llegan al 100 % y puedes ver fácilmente si el peso relativo de cada año ha cambiado entre productos, independientemente de cuántas unidades vendieron.

Mejoras estéticas para gráficos más legibles

Un gráfico correcto técnicamente puede ser difícil de interpretar si no cuida la presentación. Estas son las mejoras más efectivas:

Estilo base

plt.style.use('seaborn-v0_8')

Este estilo añade un fondo limpio y colores predefinidos más suaves que los del estilo por defecto de Matplotlib.

Cuadrícula horizontal

plt.grid(axis='y', linestyle='--', alpha=0.7)

Una cuadrícula en el eje Y ayuda al ojo a estimar valores sin sobrecargar el gráfico. El parámetro alpha=0.7 la hace semitransparente para que no compita con los datos.

Rotación de etiquetas

plt.xticks(rotation=45, ha='right')

Cuando los nombres de categoría son largos, rotarlos 45 grados evita que se solapen. El ha='right' (horizontal alignment) asegura que el texto quede bien alineado bajo su barra.

Colores personalizados

colores = ['steelblue', 'darkorange', 'seagreen']
plt.bar(x, ventas_2023, color=colores[0])
plt.bar(x, ventas_2024, color=colores[1])

Elegir colores con buen contraste mejora la legibilidad, especialmente para lectores con daltonismo. Paletas como tab10 o Set2 de Matplotlib son buenas opciones accesibles.

Añadir etiquetas de valor sobre las barras

for i, v in enumerate(ventas_2023):
    plt.text(i - width/2, v + 0.3, str(v), ha='center', fontsize=9)

Mostrar el valor exacto encima de cada barra elimina la necesidad de que el lector estime mirando el eje, lo que hace el gráfico más autónomo.

Errores comunes y cómo evitarlos

Antes de ver el ejemplo final, conviene revisar los errores más frecuentes al construir este tipo de gráficos:

  • No usar np.arange para las posiciones: Si intentas pasar listas de texto directamente a plt.bar() con desplazamientos, Matplotlib no sabrá cómo calcular las posiciones y las barras se solaparán o darán error. Siempre convierte a posiciones numéricas primero.
  • Ancho de barra (width) mal ajustado: Con dos series y width=0.5, las barras se tocan o se superponen. Con width=0.1, quedan demasiado delgadas y el gráfico parece vacío. Ajusta el ancho en función del número de series: divide el espacio disponible (~0.8 unidades) entre el número de barras por grupo.
  • Demasiadas series en el mismo gráfico: A partir de 4–5 grupos distintos, las barras agrupadas se vuelven ilegibles. En ese caso es mejor usar líneas, pequeños múltiplos (small multiples) o un heatmap.
  • Mezclar barras apiladas y comparación directa: Recuerda que en las barras apiladas la segunda barra no empieza en cero, lo que hace imposible comparar su altura con la primera. Si necesitas comparar magnitudes, usa siempre barras agrupadas.
  • Olvidar normalizar en análisis porcentual: Si tus categorías tienen tamaños muy distintos y quieres comparar proporciones, trabajar con valores absolutos puede llevar a conclusiones erróneas. Normaliza siempre antes de representar proporciones.

Caso práctico completo

Pongamos todo junto en un ejemplo realista: comparar las ventas trimestrales de dos empresas con un gráfico bien presentado.

import numpy as np
import matplotlib.pyplot as plt

plt.style.use('seaborn-v0_8')

categorias = ['Q1', 'Q2', 'Q3', 'Q4']
empresa_a = [10, 14, 18, 20]
empresa_b = [12, 16, 22, 24]

x = np.arange(len(categorias))
width = 0.35

fig, ax = plt.subplots(figsize=(9, 5))

bars_a = ax.bar(x - width/2, empresa_a, width, label='Empresa A', color='steelblue')
bars_b = ax.bar(x + width/2, empresa_b, width, label='Empresa B', color='darkorange')

# Etiquetas de valor
for bar in bars_a:
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3,
            str(int(bar.get_height())), ha='center', va='bottom', fontsize=9)
for bar in bars_b:
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3,
            str(int(bar.get_height())), ha='center', va='bottom', fontsize=9)

ax.set_xticks(x)
ax.set_xticklabels(categorias)
ax.set_ylabel('Ventas (miles €)')
ax.set_title('Comparación trimestral de ventas: Empresa A vs Empresa B')
ax.legend()
ax.grid(axis='y', linestyle='--', alpha=0.6)

plt.tight_layout()
plt.show()

El uso de fig, ax = plt.subplots() es la forma recomendada de trabajar en Matplotlib cuando se quiere un control más preciso del gráfico. El objeto ax expone los mismos métodos que plt, pero es más explícito y facilita la creación de múltiples subgráficos en el futuro.

Resumen: ¿cuándo usar cada tipo de gráfico?

TipoÚsalo cuando quieras…Limitación principal
Barras agrupadasComparar valores directamente entre gruposDifícil de leer con más de 4 series
Barras apiladasVer la composición y el total acumuladoNo permite comparar partes entre categorías
Barras porcentualesAnalizar proporciones eliminando el tamaño absolutoPierdes información sobre los valores reales

Conclusiones

Los gráficos de barras comparativos son una de las herramientas más versátiles del análisis de datos. Con Matplotlib puedes construir desde una comparación directa entre dos grupos hasta un análisis porcentual multiserie, siempre que entiendas bien el mecanismo de posicionamiento y elijas el tipo de gráfico adecuado para tu pregunta.

La regla de oro es simple: antes de escribir el código, pregúntate qué quieres que el lector vea. ¿Una diferencia? Usa barras agrupadas. ¿Una composición? Apílalas. ¿Una proporción? Normaliza. El tipo de gráfico es siempre una consecuencia de la pregunta analítica, nunca al revés.

¿Te ha parecido de utilidad el contenido?

Daniel Rodríguez

Share
Published by
Daniel Rodríguez
Tags: Matplotlib

Recent Posts

Costes hundidos en ciencia de datos: cuándo mantener un modelo y cuándo migrar

Imagina la situación. Tu equipo lleva tres años con un modelo en producción. No es…

5 días ago

WOE e IV: La Base Matemática del Credit Scoring

Cuando un banco evalúa una solicitud de crédito necesita responder a una pregunta aparentemente simple:…

1 semana ago

Lanzamiento de la versión 1.0 del laboratorio de Analytics Lane con nuevas herramientas de scoring

En el octavo aniversario de Analytics Lane seguimos ampliando nuestro laboratorio de aplicaciones interactivas y,…

1 semana ago

¡Analytics Lane cumple ocho años!

Hoy, 2 de mayo de 2026, Analytics Lane cumple exactamente ocho años. Todo empezó con…

1 semana ago

Analytics Lane lanza una Calculadora de Rentabilidad con Flujos Irregulares basada en TIR (XIRR)

La nueva herramienta permite calcular la rentabilidad real de inversiones con múltiples aportaciones, retiradas y…

2 semanas ago

Analytics Lane lanza un Conversor CSV ↔ JSON para transformar datos en tiempo real

Analytics Lane continúa ampliando su laboratorio de utilidades para desarrolladores y analistas de datos con…

2 semanas ago

This website uses cookies.