import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import datetime as dt技术支持
接下来,创建一些示例数据:
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)技术支持
输出:
task team start end completion_frac
0 A R&D 2022-10-202022-10-311.001 B Accounting 2022-10-242022-10-281.002 C Sales 2022-10-262022-10-311.003 D Sales 2022-10-312022-11-081.004 E IT 2022-11-032022-11-091.005 F R&D 2022-11-072022-11-180.956 G IT 2022-11-102022-11-170.707 H Sales 2022-11-142022-11-220.358 I Accounting 2022-11-182022-11-230.109 J Accounting 2022-11-232022-12-010.0010 K Sales 2022-11-282022-12-050.0011 L IT 2022-11-302022-12-050.00技术支持
task team start end completion_frac days_to_start \
0 A R&D 2022-10-202022-10-311.0001 B Accounting 2022-10-242022-10-281.0042 C Sales 2022-10-262022-10-311.0063 D Sales 2022-10-312022-11-081.00114 E IT 2022-11-032022-11-091.00145 F R&D 2022-11-072022-11-180.95186 G IT 2022-11-102022-11-170.70217 H Sales 2022-11-142022-11-220.35258 I Accounting 2022-11-182022-11-230.10299 J Accounting 2022-11-232022-12-010.003410 K Sales 2022-11-282022-12-050.003911 L IT 2022-11-302022-12-050.0041
days_to_end task_duration completion_days
0111212.001855.0021166.0031999.0042077.005291211.4062885.6073393.1583460.6094290.00104680.00114660.00技术支持
# 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])# 4for 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]+1print(df2)技术支持
输出:
task team start_1 end_1 start_2 end_2 start_3 \
0 C Sales 2022-10-262022-10-31 NaT NaT NaT
1 D Sales 2022-10-312022-11-082022-11-102022-11-14 NaT
2 H Sales 2022-11-142022-11-222022-11-252022-11-282022-12-013 K Sales 2022-11-282022-12-05 NaT NaT NaT
end_3 days_to_start_1 days_to_end_1 task_duration_1 \
0 NaT 0561 NaT 513922022-12-05192793 NaT 33408
days_to_start_2 days_to_end_2 task_duration_2 days_to_start_3 \
0 NaN NaN NaN NaN
115.019.05.0 NaN
230.033.04.036.03 NaN NaN NaN NaN
days_to_end_3 task_duration_3
0 NaN NaN
1 NaN NaN
240.05.03 NaN NaN 技术支持
请注意,现在任务 D 有两个子任务,任务 H 有三个子任务,而任务 C 和 K 各有一个子任务。因此,我们将对 D 和 H 使用 broken_barh() 方法。由于该方法的语法比 barh() 略不直观,我们先看看它的主要参数及格式。broken_barh() 的必填参数有:
task team start end completion_frac days_to_start \
0 A R&D 2022-10-202022-10-311.0001 B Accounting 2022-10-242022-10-281.0042 C Sales 2022-10-262022-10-311.0063 D Sales 2022-10-312022-11-081.00114 E IT 2022-11-032022-11-091.00145 F R&D 2022-11-072022-11-180.95186 G IT 2022-11-102022-11-170.70217 H Sales 2022-11-142022-11-220.35258 I Accounting 2022-11-182022-11-230.10299 J Accounting 2022-11-232022-12-010.003410 K Sales 2022-11-282022-12-050.003911 L IT 2022-11-302022-12-050.0041
days_to_end task_duration completion_days
0111212.001855.0021166.0031999.0042077.005291211.4062885.6073393.1583460.6094290.00104680.00114660.00技术支持
fig, ax = plt.subplots()for index, row in df.iterrows():# Adding a lower bar - for the overall task duration
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start']+1, color=team_colors[row['team']], alpha=0.4)# Adding an upper bar - for the status of completion
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()技术支持
在这份循序渐进的指南中,我们将讨论什么是甘特图、这类可视化为何以及何时有用、如何用 matplotlib 在 Python 中制作甘特图,以及如何进一步自定义。过程中,我们还会在 matplotlib 中构建一些甘特图示例。
什么是甘特图,它为何有用?
甘特图是一种条形图,用于展示完整的项目规划与管理进度。它通常显示项目任务名称、任务的开始与结束日期、任务所属团队、任务完成状态(以百分比或分数表示)、各团队绩效以及项目的重要里程碑等信息。
在甘特图中,任务名称沿 y 轴排列,通常按其开始日期自上而下按时间顺序排序。x 轴用于显示时间区间,每个条形的宽度对应于该任务的实施时长。
由于甘特图本质上是特定形式的水平条形图,我们可以使用 Python 的多种数据可视化库来创建它,包括 matplotlib、Plotly、Bokeh 和 Altair。本教程将重点介绍在最为流行的库 – matplotlib 中制作甘特图。
如果您想探索用其他方式在 Python 中创建甘特图和其他类型的图表,欢迎查看课程 Introduction to Data Visualization with Plotly in Python。如果您需要巩固或提升 matplotlib 技能,这些课程会很有帮助:Introduction to Data Visualization with Matplotlib 和 Python for MATLAB Users。
如何在 Python 中用 Matplotlib 制作甘特图
使用 barh()
先从使用
matplotlib.pyplot的barh()方法构建一个基础的 matplotlib 甘特图开始。首先,我们需要导入必要的库:
接下来,创建一些示例数据:
输出:
为便于基于此数据构建甘特图,我们需要再计算四列,以表示以下信息:
检查结果:
输出:
现在,我们可以在 matplotlib 中生成一个基础甘特图:
输出:
上述图表仍需做一些调整,才能最大化地传达信息:
我们很快就会实现这些改进。不过在此之前,先来看如何绘制基础甘特图。
使用
broken_barh()当某些任务包含在一段时间内分散的两个或更多子任务时,应使用另一个 matplotlib 方法 –
broken_barh()。为便于尝试这种方式,我们先准备一些示例数据:
completion_frac列)。start和end两列分别重命名为start_1和end_1(便于处理)。输出:
请注意,现在任务 D 有两个子任务,任务 H 有三个子任务,而任务 C 和 K 各有一个子任务。因此,我们将对 D 和 H 使用
broken_barh()方法。由于该方法的语法比barh()略不直观,我们先看看它的主要参数及格式。broken_barh()的必填参数有:xranges– 一个形如 (xmin,xwidth) 的元组序列,用于表示每个条形的起点和延伸长度。这里,每个条形代表一个子任务。因此,该参数用于显示每个子任务的开始日期和持续时间。yrange– 一个形如 (ymin,yheight) 的元组,用于表示每个条形的 y 位置和高度。现在,我们来绘制“分段”的甘特图。步骤如下:
barh()方法绘制一个条形,如前所示。broken_barh()方法绘制两个条形。broken_barh()方法绘制三个条形。输出:
在上述代码中,对于只有一个子任务的任务,我们其实也可以用
broken_barh()来绘制条形。在这种情况下,需要将这行代码:替换为:
这种做法还能让代码更加统一、易读。
如何在 Matplotlib 中自定义甘特图
让我们回到最初的数据框:
输出:
以及第一个甘特图:
输出:
我们希望对图表进行一些自定义,使其更具洞察力。
想要更深入地了解在 matplotlib 中自定义图表,请参考以下资源:Matplotlib Tutorial: Python Plotting 和 Matplotlib Cheat Sheet: Plotting in Python。
基础甘特图调整
包括:
添加图表标题
任何图表(包括甘特图)都需要一个恰当的标题。假设我们的项目名为 Project X:
输出:
调整并自定义坐标轴并添加网格
在此步骤,我们将在前述代码的基础上,进行以下操作:
ax对象。输出:
如果您需要更多 Python 练习以便更轻松地编写和阅读任何 Python 代码,我们的 Intermediate Python 课程是一个不错的参考。
按团队为任务着色
现在,我们根据任务所属团队为每个条形(代表特定任务)着色。为此我们将:
输出:
添加图例
上面的甘特图缺少一个图例来说明各颜色对应的团队。为了解决这个问题,我们来创建 patches —— 使用预定义颜色填充的 matplotlib 2D 图形对象。我们将使用 matplotlib.patches.Patch 类生成一个补丁列表,每个团队一个,并按对应颜色填充:
现在,我们就可以为图表添加图例了:
输出:
现在的甘特图看起来信息量更大了。
关于提升图表美观度和可读性的更多技巧,请参阅课程 Improving Your Data Visualizations in Python。
添加完成状态
到目前为止,我们还没有使用在本教程开头计算的
completion_days column。现在,我们将用它来显示每个任务的完成阶段,使甘特图更具洞察力。技术上,我们会在每个既有条形的上方再叠加一个条形。下方条形表示每个任务分配天数内的总体持续时间,上方条形表示该任务在分配天数内的完成状态。
对每个任务,这两条条形都会使用对应团队的相同颜色,但下方条形会更浅,以便区分两者。
换言之,如果某个团队在任务上超前,那么这两个条形叠加起来会显示该团队在该任务上已工作的天数多于预期(意味着该任务可能会在截止日期之前完成)。
反之,如果某个团队落后,则两条对应条形叠加起来会显示该团队在该任务上的工作天数少于预期(意味着该任务可能会在截止日期之后完成)。
输出:
从上图可以看到,各团队完成任务的节奏并不一致,这在许多现实项目中很常见。为了从图中获取更多信息,假设当前日期为 2022 年 11 月 17 日,并在图上标记该日期:
输出:
关于 Project X 当前状态,我们可以做出以下观察:
这些观察有助于团队负责人和项目经理相应地调整项目进度,并及时分配必要资源,以避免交付延误。
其他可能的调整
我们还可以为 matplotlib 甘特图添加更多细节,例如:
不过,需要注意的是,保持甘特图的简洁,避免堆砌过多细节同样重要。与任何其他数据可视化一样,我们需要在图表的信息承载能力与其可读性之间保持平衡,同时也要兼顾用于构建图表的代码的可读性。
开始用 Matplotlib 在 Python 中制作您的甘特图
总而言之,在本教程中,我们探讨了:
此外,我们查看了多个甘特图示例以及其构建的分步过程。
如果您希望以更系统的方式学习如何在 Python 中创建有说服力且信息丰富的图表,请查看我们的 Data Visualization with Python 技能路径。