Curso
En esta guía paso a paso, veremos qué es un diagrama de Gantt, por qué y cuándo es útil, cómo crear un diagrama de Gantt en Python con matplotlib y cómo personalizarlo. Por el camino, construiremos algunos ejemplos de diagramas de Gantt en matplotlib.
¿Qué es un diagrama de Gantt y por qué es útil?
Un diagrama de Gantt es un tipo de gráfico de barras que se utiliza para ilustrar un plan y calendario completos de planificación y gestión de proyectos. Suele mostrar elementos como los nombres de las tareas del proyecto, sus fechas de inicio y fin, los equipos a los que se asignan, el estado de finalización en porcentaje o fracción, el rendimiento por equipo y los hitos importantes del proyecto.
Los nombres de las tareas se colocan en el eje y del diagrama, normalmente ordenados por su fecha de inicio en orden cronológico de arriba a abajo. Por su parte, el eje x representa periodos de tiempo, y el ancho de cada barra corresponde a la duración de ejecución de cada tarea.
Como un diagrama de Gantt es, en esencia, una variante específica de un gráfico de barras horizontal, podemos crearlo con muchas librerías de visualización de datos en Python, como matplotlib, Plotly, Bokeh y Altair. En este tutorial nos centraremos en crear diagramas de Gantt con la más popular de ellas: matplotlib.
Si quieres explorar formas alternativas de crear un diagrama de Gantt y otros tipos de gráficos en Python, echa un vistazo al curso Introduction to Data Visualization with Plotly in Python. Si necesitas repasar o mejorar tus habilidades con matplotlib, te serán útiles estos cursos: Introduction to Data Visualization with Matplotlib y Python for MATLAB Users.
Cómo hacer un diagrama de Gantt en Python con Matplotlib
Usando barh()
Empecemos creando un diagrama de Gantt básico en matplotlib con el método barh() de matplotlib.pyplot.
Primero, importamos las librerías necesarias:
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import datetime as dt
El siguiente paso es crear unos datos de ejemplo con los que trabajar:
df = pd.DataFrame({'task': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'],
'team': ['R&D', 'Accounting', 'Sales', 'Sales', 'IT', 'R&D', 'IT', 'Sales', 'Accounting', 'Accounting', 'Sales', 'IT'],
'start': pd.to_datetime(['20 Oct 2022', '24 Oct 2022', '26 Oct 2022', '31 Oct 2022', '3 Nov 2022', '7 Nov 2022', '10 Nov 2022', '14 Nov 2022', '18 Nov 2022', '23 Nov 2022', '28 Nov 2022', '30 Nov 2022']),
'end': pd.to_datetime(['31 Oct 2022', '28 Oct 2022', '31 Oct 2022', '8 Nov 2022', '9 Nov 2022', '18 Nov 2022', '17 Nov 2022', '22 Nov 2022', '23 Nov 2022', '1 Dec 2022', '5 Dec 2022', '5 Dec 2022']),
'completion_frac': [1, 1, 1, 1, 1, 0.95, 0.7, 0.35, 0.1, 0, 0, 0]})
print(df)
Salida:
task team start end completion_frac
0 A R&D 2022-10-20 2022-10-31 1.00
1 B Accounting 2022-10-24 2022-10-28 1.00
2 C Sales 2022-10-26 2022-10-31 1.00
3 D Sales 2022-10-31 2022-11-08 1.00
4 E IT 2022-11-03 2022-11-09 1.00
5 F R&D 2022-11-07 2022-11-18 0.95
6 G IT 2022-11-10 2022-11-17 0.70
7 H Sales 2022-11-14 2022-11-22 0.35
8 I Accounting 2022-11-18 2022-11-23 0.10
9 J Accounting 2022-11-23 2022-12-01 0.00
10 K Sales 2022-11-28 2022-12-05 0.00
11 L IT 2022-11-30 2022-12-05 0.00
Para facilitar la construcción del diagrama de Gantt con estos datos, calcularemos cuatro columnas adicionales que representen lo siguiente:
- Cuántos días han pasado/pasarán desde el inicio del proyecto hasta la fecha de inicio de cada tarea:
df['days_to_start'] = (df['start'] - df['start'].min()).dt.days
- Cuántos días han pasado/pasarán desde el inicio del proyecto hasta la fecha de fin de cada tarea:
df['days_to_end'] = (df['end'] - df['start'].min()).dt.days
- La duración de cada tarea, incluyendo tanto la fecha de inicio como la de fin:
df['task_duration'] = df['days_to_end'] - df['days_to_start'] + 1 # para incluir también la fecha de fin
- El estado de finalización de cada tarea traducido de fracción a número de días asignados a esa tarea:
df['completion_days'] = df['completion_frac'] * df['task_duration']
Comprobemos el resultado:
print(df)
Salida:
task team start end completion_frac days_to_start \
0 A R&D 2022-10-20 2022-10-31 1.00 0
1 B Accounting 2022-10-24 2022-10-28 1.00 4
2 C Sales 2022-10-26 2022-10-31 1.00 6
3 D Sales 2022-10-31 2022-11-08 1.00 11
4 E IT 2022-11-03 2022-11-09 1.00 14
5 F R&D 2022-11-07 2022-11-18 0.95 18
6 G IT 2022-11-10 2022-11-17 0.70 21
7 H Sales 2022-11-14 2022-11-22 0.35 25
8 I Accounting 2022-11-18 2022-11-23 0.10 29
9 J Accounting 2022-11-23 2022-12-01 0.00 34
10 K Sales 2022-11-28 2022-12-05 0.00 39
11 L IT 2022-11-30 2022-12-05 0.00 41
days_to_end task_duration completion_days
0 11 12 12.00
1 8 5 5.00
2 11 6 6.00
3 19 9 9.00
4 20 7 7.00
5 29 12 11.40
6 28 8 5.60
7 33 9 3.15
8 34 6 0.60
9 42 9 0.00
10 46 8 0.00
11 46 6 0.00
Ahora ya podemos generar un diagrama de Gantt básico en matplotlib:
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'])
plt.show()
Salida:

Este gráfico necesita varios ajustes para poder extraer el máximo de información:
- Añadir un título significativo y etiquetas del eje x (fechas)
- Invertir el eje y para ver las tareas en orden cronológico de arriba a abajo
- Añadir una cuadrícula
- Colorear las tareas por equipo
- Mostrar una leyenda
- Añadir el estado de finalización de las tareas
Aplicaremos todas estas mejoras en breve. Antes, veamos otra forma de construir un diagrama de Gantt básico.
Usando broken_barh()
Cuando algunas tareas incluyen dos o más subtareas distribuidas en el tiempo, conviene usar otro método de matplotlib: broken_barh().
Para generar datos de ejemplo y probar este enfoque, haremos lo siguiente:
- Tomar de nuestro dataframe inicial solo las tareas asignadas al equipo de Sales (por ahora, ignoramos la columna
completion_frac). - Renombrar las columnas
startyendcomostart_1yend_1respectivamente (por comodidad). - Añadir más subtareas a algunas de las tareas disponibles.
- Para cada subtarea, calcular las tres columnas mostradas arriba que representan:
- Cuántos días han pasado/pasarán desde el inicio del proyecto hasta la fecha de inicio de cada subtarea.
- Cuántos días han pasado/pasarán desde el inicio del proyecto hasta la fecha de fin de cada subtarea.
- La duración de cada subtarea, incluyendo tanto la fecha de inicio como la de fin.
# 1
df2 = df[df['team']=='Sales'][['task', 'team', 'start', 'end']]
# 2
df2.rename(columns={'start': 'start_1', 'end': 'end_1'}, inplace=True)
df2.reset_index(drop=True, inplace=True)
# 3
df2['start_2'] = pd.to_datetime([None, '10 Nov 2022', '25 Nov 2022', None])
df2['end_2'] = pd.to_datetime([None, '14 Nov 2022', '28 Nov 2022', None])
df2['start_3'] = pd.to_datetime([None, None, '1 Dec 2022', None])
df2['end_3'] = pd.to_datetime([None, None, '5 Dec 2022', None])
# 4
for i in [1, 2, 3]:
suffix = '_' + str(i)
df2['days_to_start' + suffix] = (df2['start' + suffix] - df2['start_1'].min()).dt.days
df2['days_to_end' + suffix] = (df2['end' + suffix] - df2['start_1'].min()).dt.days
df2['task_duration' + suffix] = df2['days_to_end' + suffix] - df2['days_to_start' + suffix] + 1
print(df2)
Salida:
task team start_1 end_1 start_2 end_2 start_3 \
0 C Sales 2022-10-26 2022-10-31 NaT NaT NaT
1 D Sales 2022-10-31 2022-11-08 2022-11-10 2022-11-14 NaT
2 H Sales 2022-11-14 2022-11-22 2022-11-25 2022-11-28 2022-12-01
3 K Sales 2022-11-28 2022-12-05 NaT NaT NaT
end_3 days_to_start_1 days_to_end_1 task_duration_1 \
0 NaT 0 5 6
1 NaT 5 13 9
2 2022-12-05 19 27 9
3 NaT 33 40 8
days_to_start_2 days_to_end_2 task_duration_2 days_to_start_3 \
0 NaN NaN NaN NaN
1 15.0 19.0 5.0 NaN
2 30.0 33.0 4.0 36.0
3 NaN NaN NaN NaN
days_to_end_3 task_duration_3
0 NaN NaN
1 NaN NaN
2 40.0 5.0
3 NaN NaN
Observa que ahora la tarea D tiene dos subtareas, la tarea H tiene tres, y C y K tienen una cada una. Por tanto, D y H son las tareas para las que usaremos el método broken_barh(). Como la sintaxis de este método es algo menos intuitiva que la de barh(), repasemos sus parámetros principales y su formato. Los parámetros obligatorios de broken_barh() son:
xranges: una secuencia de tuplas con el formato (xmin,xwidth) para indicar el inicio y la extensión de cada barra. Aquí, cada barra representa una subtarea. Este parámetro muestra la fecha de inicio y la duración de cada subtarea.yrange: una tupla con el formato (ymin,yheight) para indicar la posición en y y la altura de cada barra.
Ahora, tracemos nuestro diagrama de Gantt "fragmentado". El algoritmo es:
- Crear una figura con subplots.
- Iterar por las filas del dataframe y comprobar si la tarea tiene una, dos o tres subtareas. En función de ello:
- Una subtarea: dibuja una barra con
barh(), como antes. - Dos subtareas: dibuja dos barras con
broken_barh(). - Tres subtareas: dibuja tres barras con
broken_barh(). - Añadir los ajustes básicos: configurar y etiquetar los ticks del eje y.
# 1
fig, ax = plt.subplots()
# 2
for index, row in df2.iterrows():
if row['start_2'] is None:
ax.barh(y=df2['task'], width=df2['task_duration_1'], left=df2['days_to_start_1'])
elif row['start_2'] is not None and row['start_3'] is None:
ax.broken_barh(xranges=[(row['days_to_start_1'], row['task_duration_1']), (row['days_to_start_2'], row['task_duration_2'])], yrange=(index + 1, 0.5))
else:
ax.broken_barh(xranges=[(row['days_to_start_1'], row['task_duration_1']), (row['days_to_start_2'], row['task_duration_2']), (row['days_to_start_3'], row['task_duration_3'])], yrange=(index + 1, 0.5))
# 3
ax.set_yticks([1.25, 2.25, 3.25, 4.25])
ax.set_yticklabels(df2['task'])
plt.show()
Salida:

En el código anterior, también podríamos usar broken_barh() para trazar las barras de las tareas con una sola subtarea. En ese caso, sustituye esta línea:
ax.barh(y=df2['task'], width=df2['task_duration_1'], left=df2['days_to_start_1'])
por esta otra:
ax.broken_barh(xranges=[(row['days_to_start_1'], row['task_duration_1'])], yrange=(index + 1, 0.5))
Así el código queda más uniforme y legible.
Cómo personalizar un diagrama de Gantt en Matplotlib
Volvamos a nuestro dataframe inicial:
print(df)
Salida:
task team start end completion_frac days_to_start \
0 A R&D 2022-10-20 2022-10-31 1.00 0
1 B Accounting 2022-10-24 2022-10-28 1.00 4
2 C Sales 2022-10-26 2022-10-31 1.00 6
3 D Sales 2022-10-31 2022-11-08 1.00 11
4 E IT 2022-11-03 2022-11-09 1.00 14
5 F R&D 2022-11-07 2022-11-18 0.95 18
6 G IT 2022-11-10 2022-11-17 0.70 21
7 H Sales 2022-11-14 2022-11-22 0.35 25
8 I Accounting 2022-11-18 2022-11-23 0.10 29
9 J Accounting 2022-11-23 2022-12-01 0.00 34
10 K Sales 2022-11-28 2022-12-05 0.00 39
11 L IT 2022-11-30 2022-12-05 0.00 41
days_to_end task_duration completion_days
0 11 12 12.00
1 8 5 5.00
2 11 6 6.00
3 19 9 9.00
4 20 7 7.00
5 29 12 11.40
6 28 8 5.60
7 33 9 3.15
8 34 6 0.60
9 42 9 0.00
10 46 8 0.00
11 46 6 0.00
y a nuestro primer diagrama de Gantt:
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'])
plt.show()
Salida:

En concreto, queremos añadir algunas personalizaciones para que el gráfico aporte más información.
Si quieres profundizar en la personalización de gráficos con matplotlib, consulta estos recursos: Matplotlib Tutorial: Python Plotting y Matplotlib Cheat Sheet: Plotting in Python.
Ajustes básicos del diagrama de Gantt
Incluyen:
- Añadir un título al gráfico
- Ajustar y personalizar los ejes
- Añadir una cuadrícula
Añadir un título
Tener un título con sentido es imprescindible para cualquier gráfico, incluido un diagrama de Gantt. Supongamos que nuestro proyecto se llama Project X:
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.show()
Salida:

Ajustar y personalizar los ejes y añadir una cuadrícula
En este paso, partiremos del código anterior y haremos lo siguiente:
- Crear una figura con subplots para poder trabajar con el objeto
ax. - Invertir el eje y para ver las tareas en orden cronológico de arriba a abajo.
- Determinar las ubicaciones óptimas de los ticks del eje x.
- Digamos que queremos situar los ticks del eje x cada lunes, teniendo en cuenta que Project X empezó un jueves.
- Optamos solo por ticks principales para evitar sobrecargar el gráfico.
- Definir las etiquetas adecuadas para los ticks del eje x y su formato.
- De nuevo, tenemos en cuenta que Project X empezó un jueves y solo debemos etiquetar los lunes.
- Como todo el proyecto transcurre en 2022, no necesitamos indicar el año en cada etiqueta.
- Es importante que el número de ticks coincida con el de etiquetas.
- Añadir los ticks y sus etiquetas.
- Añadir una cuadrícula vertical.
# 1
fig, ax = plt.subplots()
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'] + 1)
plt.title('Project Management Schedule of Project X', fontsize=15)
# 2
plt.gca().invert_yaxis()
# 3
xticks = np.arange(5, df['days_to_end'].max() + 2, 7)
# 4
xticklabels = pd.date_range(start=df['start'].min() + dt.timedelta(days=4), end=df['end'].max()).strftime("%d/%m")
# 5
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
# 6
ax.xaxis.grid(True, alpha=0.5)
plt.show()
Salida:

Si necesitas más práctica para leer y escribir código en Python con soltura, nuestro curso Intermediate Python es un buen punto de partida.
Colorear tareas por equipo
Ahora, asignaremos un color a cada barra (cada tarea) según el equipo responsable. Para ello haremos lo siguiente:
- Crear un diccionario con los nombres de los equipos como claves y colores base de matplotlib como valores.
- Crear una figura con subplots.
- Iterar por las filas del dataframe, crear una barra por fila y aplicarle el color correspondiente al equipo.
- Añadir los ajustes básicos que aplicamos antes.
# 1
team_colors = {'R&D': 'c', 'Accounting': 'm', 'Sales': 'y', 'IT': 'b'}
# 2
fig, ax = plt.subplots()
# 3
for index, row in df.iterrows():
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
# 4
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
plt.show()
Salida:

Añadir una leyenda
Al gráfico anterior le falta una leyenda que muestre qué color corresponde a cada equipo. Para solucionarlo, crearemos patches, objetos 2D de matplotlib rellenos con un color predefinido. Usaremos la clase matplotlib.patches.Patch para generar una lista de patches, uno por equipo, con su color:
patches = []
for team in team_colors:
patches.append(matplotlib.patches.Patch(color=team_colors[team]))
Ahora ya podemos añadir la leyenda al gráfico:
fig, ax = plt.subplots()
for index, row in df.iterrows():
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
# Añadir una leyenda
ax.legend(handles=patches, labels=team_colors.keys(), fontsize=11)
plt.show()
Salida:

Este ejemplo de diagrama de Gantt ya es mucho más informativo.
Encontrarás más técnicas para mejorar la estética y la legibilidad de tus gráficos en el curso Improving Your Data Visualizations in Python.
Añadir el estado de finalización
Hasta ahora no hemos usado la columna completion_days que calculamos al principio. La utilizaremos ahora para que el diagrama de Gantt muestre en qué fase de finalización está cada tarea.
Técnicamente, añadiremos otra barra encima de cada barra existente. La barra inferior representará la duración total asignada a cada tarea en días, y la superior mostrará el estado de finalización de esa tarea, también en días asignados.
Para cada tarea, ambas barras tendrán el mismo color del equipo correspondiente, pero la inferior será más clara que la superior para distinguirlas bien.
Dicho de otro modo, si un equipo está rindiendo por encima en una tarea, la combinación de ambas barras reflejará como si ya hubiera trabajado más días de los previstos (lo que indica que probablemente la tarea se completará antes de la fecha límite).
En el caso contrario, si un equipo va por detrás en una tarea, la combinación de barras mostrará como si hubiera trabajado menos días de los previstos (lo que sugiere que la tarea se completará después de la fecha límite).
fig, ax = plt.subplots()
for index, row in df.iterrows():
# Barra inferior: duración total de la tarea
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']], alpha=0.4)
# Barra superior: estado de finalización
plt.barh(y=row['task'], width=row['completion_days'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
ax.legend(handles=patches, labels=team_colors.keys(), fontsize=11)
plt.show()
Salida:

En el gráfico vemos que los equipos no avanzan al mismo ritmo, algo habitual en proyectos reales. Para obtener más información, supongamos que la fecha actual es el 17 de noviembre de 2022 y marquemos esa fecha en el gráfico:
fig, ax = plt.subplots()
for index, row in df.iterrows():
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']], alpha=0.4)
plt.barh(y=row['task'], width=row['completion_days'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
ax.legend(handles=patches, labels=team_colors.keys(), fontsize=11)
# Marcar la fecha actual en el gráfico
ax.axvline(x=29, color='r', linestyle='dashed')
ax.text(x=29.5, y=11.5, s='17/11', color='r')
plt.show()
Salida:

Podemos extraer estas conclusiones sobre el estado actual de Project X:
- El equipo de R&D va por delante en la tarea F y probablemente la completará antes de la fecha límite.
- El equipo de IT lleva retraso en la tarea G y probablemente necesite apoyo.
- El equipo de Sales avanza según lo previsto en la tarea H.
- El equipo de Accounting empezó la tarea I antes de lo planificado.
Estas observaciones pueden ayudar a los líderes de equipo y a la persona responsable del proyecto a ajustar el calendario y asignar recursos a tiempo para evitar retrasos en las entregas.
Otros ajustes posibles
Podemos añadir más detalles a nuestro diagrama de Gantt en matplotlib. Por ejemplo:
- Añadir el porcentaje de finalización en cada barra.
- Subdividir algunas tareas en subtareas (ya vimos cómo hacerlo técnicamente antes).
- Anotar tareas relacionadas.
- Marcar hitos y fechas límite clave.
- Resaltar fines de semana y festivos.
- Ordenar las barras por equipo o tipo de tarea.
- Añadir ticks menores en el eje x para aumentar el nivel de detalle.
- Mejorar el formato y el diseño general del gráfico.
Eso sí, es importante mantener el diagrama limpio y evitar sobrecargarlo con demasiados detalles. Como en cualquier visualización, hay que equilibrar la cantidad de información con la legibilidad del gráfico, así como la claridad del código que lo genera.
Empieza a crear tu propio diagrama de Gantt en Python con Matplotlib
En resumen, en este tutorial hemos visto:
- qué es un diagrama de Gantt
- qué información muestra
- cuándo se usa
- las distintas formas y matices para crearlo con matplotlib
- cómo hacer un diagrama de Gantt en Python con matplotlib
- cómo ajustarlo para que sea más informativo
- y cómo interpretar el resultado final.
Además, revisamos varios ejemplos de diagramas de Gantt y el proceso paso a paso para construirlos.
Si quieres un enfoque más integral para aprender a crear gráficos atractivos e informativos en Python, echa un vistazo al Data Visualization with Python skill track.


