作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级
在之前写了十几篇如何做数据可视化改进的文章后,媳妇终于也是忍不住给我提需求了,你写文章倒是卡卡卡,那就把我的工作表也整一下吧,我想也没想就接了这个需求。
课后讨论:大家认真看完,如果有老婆\女朋友的也试着教会它们,勇于尝试,评论区回复效果,我听到的回答是 你不爱我了?
需求描述
先有给一个PPT的图片(ps: 我用手机拍的),然后说明这是不同阶段参与运动的频率,然后要在柱形图上增加目标值,本科生 90%、研究生95%、博士生 95%
解题思路
初始化数据
虽然没表格,有图像也行那就人工智能写一下数据,还好柱形图就三个。
import matplotlib.pyplot as plt categories = ['本科生', '硕士', '博士'] # 类别:本科生、硕士、博士 current_values = [53.30, 52.43, 15.69] # 当前值 target_values = [90, 95, 95] # 目标值
第一次可视化
希望把目标和现状在图上有区分度,显示为虚线,现状显示为实线
结果把90%和95%的目标画成一条虚线延长线了,虽然目标值跟柱形图对应,但是虚线的长度太大。需求方不满意让我重新做
第二次可视化改进
调整目标值,让目标值对应柱形图的宽度,现状看着稍微好一些了
python代码解读
# 为每个柱形图绘制目标值虚线,并添加数值标签 for i, (rect, target) in enumerate(zip(rects1, target_values)): left_limit = rect.get_x() right_limit = rect.get_x() + rect.get_width() ax.hlines(y=target, xmin=left_limit, xmax=right_limit, color='r', linestyles='--') ax.text((left_limit + right_limit) / 2, target + 1, f'{target}%', ha='center', va='bottom', color='red', fontsize=8)
1. 循环结构:
for i, (rect, target) in enumerate(zip(rects1, target_values)):
- 这行代码通过
enumerate
函数迭代一个通过zip
函数组合的列表。zip(rects1, target_values)
将rects1
(柱形图对象列表)和target_values
(目标值列表)合并成一个元组列表。每次迭代都会同时获取一个柱形(rect
)和对应的目标值(target
),同时enumerate
提供一个索引i
。
2. 计算柱形图的左右边界:
left_limit = rect.get_x() right_limit = rect.get_x() + rect.get_width()
rect.get_x()
获取当前柱形的左边界的 x 坐标。rect.get_width()
返回柱形的宽度。右边界 (right_limit
) 是左边界加上柱形的宽度。
3. 绘制水平虚线:
ax.hlines(y=target, xmin=left_limit, xmax=right_limit, color='r', linestyles='--')
- 使用
ax.hlines()
方法在 y 坐标为target
的位置画一条水平线,这条线从left_limit
(柱形的左边界)到right_limit
(柱形的右边界)。线条颜色设置为红色 ('r'
),样式为虚线 ('--'
)。
4. 添加数值标签:
ax.text((left_limit + right_limit) / 2, target + 1, f'{target}%', ha='center', va='bottom', color='red', fontsize=8)
ax.text()
方法用于在图表上放置文本。文本内容为目标值百分比(f'{target}%'
)。
- 第一个参数是 x 坐标,这里设置为柱形的中心点,即左右边界的平均值 (
(left_limit + right_limit) / 2
)。 - 第二个参数是 y 坐标,设置为目标值加1,即
target + 1
,这样标签会显示在目标线的正上方,避免覆盖线条。 ha='center'
设置水平对齐为中心,确保文本水平居中于指定的 x 坐标。va='bottom'
设置垂直对齐为底部,使文本的底部对齐于指定的 y 坐标。- 文本颜色为红色 (
'red'
),字体大小为 8。
第三次可视化改进
只有一个横向的虚线不太直观,我需要的是两个柱形图,目标用虚线框,现状用实线框,稍微修改一下变成这样的图,顺便把边框和网格线也去掉
python代码解读
# 绘制当前值柱形图 current_rects = ax.bar(categories, current_values, bar_width, alpha=opacity, color='b', label='当前') # 目标值高于当前值时,才进行叠加绘制 for i, (rect, target) in enumerate(zip(current_rects, target_values)): if target > current_values[i]: # 只在目标值高于当前值时绘制叠加部分 ax.bar(categories[i], target - current_values[i], bar_width, bottom=current_values[i], alpha=opacity/2, color='r', hatch='', facecolor='none',edgecolor='r', linestyle='--',label='目标' if i == 0 else "") # 移除顶部和右侧的边框,只保留X轴和Y轴的边框 ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False)
绘制叠加柱形图
ax.bar()
方法用于绘制柱形图。
categories[i]
: 指定柱形图的分类标签。target - current_values[i]
: 柱形图的高度,表示目标值超过当前值的部分。bar_width
: 柱形图的宽度。bottom=current_values[i]
: 设置柱形图的底部开始位置,即在当前值的柱形图上方开始绘制。alpha=opacity/2
: 设置透明度为原透明度的一半,使叠加部分更为透明,以区分当前值和目标值叠加部分。color='r'
: 设置柱形图颜色为红色。hatch=''
: 指定填充样式,这里为空,表示无填充样式。facecolor='none'
: 设置填充颜色为无,使柱体透明,只显示边缘。edgecolor='r'
: 设置边缘颜色为红色。linestyle='--'
: 设置线条样式为虚线。label='目标' if i == 0 else ""
: 为第一个叠加柱形图添加标签“目标”,其余不添加标签,避免图例重复。
第四次可视化改进
我想这次肯定OK了吧,老婆说,哦忘记了我还要加一条去年的目标值直线,我不禁陷入沉思,这大概就是程序员和产品经理的矛盾由来吧,我笑着对老婆说,小问题接着加上了这行代码
# 添加一条表示平均目标值的水平直线 ax.axhline(y=96, color='g', linestyle='-', linewidth=2, label='去年')
再给老婆看的时候,她说不用了这个直线加不加无所谓,接着跟我说我不懂她的业务,这时候我们还是很心平气和的对话
- 我说:把notebook给你看看,后面自己改改很简单
- 她说:你不爱我了?
- 我说:亲爱的,有事随时找我,一下就给你搞定(下课下课,心理再大声说:后面肯定要教会你)
可视化改进前后
完整代码
import matplotlib.pyplot as plt # 初始化数据 categories = ['本科生', '硕士', '博士'] # 类别:本科生、硕士、博士 current_values = [53.30, 52.43, 15.69] # 当前值 target_values = [90, 95, 95] # 目标值 bar_width = 0.35 opacity = 0.8 # 创建一个较大的图表,尺寸可以根据需要调整 fig, ax = plt.subplots(figsize=(10, 6)) # 添加一条表示平均目标值的水平直线 ax.axhline(y=96, color='g', linestyle='-', linewidth=2, label='去年') # 在当前值柱形图上叠加目标值柱形图,使用更浅的颜色和透明度 # 目标值高于当前值时,才进行叠加绘制 for i, (rect, target) in enumerate(zip(current_rects, target_values)): if target > current_values[i]: # 只在目标值高于当前值时绘制叠加部分 ax.bar(categories[i], target - current_values[i], bar_width, bottom=current_values[i], alpha=opacity/2, color='r', hatch='', facecolor='none',edgecolor='r', linestyle='--',label='目标' if i == 0 else "") # 绘制当前值柱形图 current_rects = ax.bar(categories, current_values, bar_width, alpha=opacity, color='b', label='当前') # 添加数值标签到当前值柱形图上 for rect in current_rects: height = rect.get_height() ax.annotate(f'{height}%', xy=(rect.get_x() + rect.get_width() / 2, height), xytext=(0, 3), # 数值距离柱形图顶部的偏移量 textcoords="offset points", ha='center', va='bottom') # 添加数值标签到目标值柱形图上 for i, target in enumerate(target_values): ax.annotate(f'{target}%', xy=(i, target), xytext=(0, 3), # 数值距离柱形图顶部的偏移量 textcoords="offset points", ha='center', va='bottom') # 移除顶部和右侧的边框,只保留X轴和Y轴的边框 ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) # 添加标签和标题 # ax.set_xlabel('学位') ax.set_ylabel('百分比') ax.set_xticks(range(len(categories))) # 调整X轴刻度的位置 ax.set_xticklabels(categories) ax.set_ylim(0, 100) # 设置Y轴的范围,以显示0%到100%的百分比 # 添加图例 ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)) # 以紧凑布局显示图表 plt.tight_layout() plt.show()
欢迎关注微信公众号 数据分析螺丝钉