利用Python绘制数据的瀑布图的教程


Posted in Python onApril 07, 2015

介绍

对于绘制某些类型的数据来说,瀑布图是一种十分有用的工具。不足为奇的是,我们可以使用Pandas和matplotlib创建一个可重复的瀑布图。

在往下进行之前,我想先告诉大家我指代的是哪种类型的图表。我将建立一个维基百科文章中描述的2D瀑布图。

这种图表的一个典型的用处是显示开始值和结束值之间起“桥梁”作用的+和-的值。因为这个原因,财务人员有时会将其称为一个桥梁。跟我之前所采用的其他例子相似,这种类型的绘图在Excel中不容易生成,当然肯定有生成它的方法,但是不容易记住。

关于瀑布图需要记住的关键点是:它本质上是一个堆叠在一起的条形图,不过特殊的一点是,它有一个空白底栏,所以顶部栏会“悬浮”在空中。那么,让我们开始吧。
创建图表

首先,执行标准的输入,并确保IPython能显示matplot图。
 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
 
%matplotlib inline

设置我们想画出瀑布图的数据,并将其加载到数据帧(DataFrame)中。

数据需要以你的起始值开始,但是你需要给出最终的总数。我们将在下面计算它。
 

index = ['sales','returns','credit fees','rebates','late charges','shipping']
data = {'amount': [350000,-30000,-7500,-25000,95000,-7000]}
trans = pd.DataFrame(data=data,index=index)

我使用了IPython中便捷的display函数来更简单地控制我要显示的内容。
 

from IPython.display import display
display(trans)

利用Python绘制数据的瀑布图的教程

瀑布图的最大技巧是计算出底部堆叠条形图的内容。有关这一点,我从stackoverflow上的讨论中学到很多。

首先,我们得到累积和。
 

display(trans.amount.cumsum())
sales      350000
returns     320000
credit fees   312500
rebates     287500
late charges  382500
shipping    375500
Name: amount, dtype: int64

这看起来不错,但我们需要将一个地方的数据转移到右边。
 

blank=trans.amount.cumsum().shift(1).fillna(0)
display(blank)
 
sales        0
returns     350000
credit fees   320000
rebates     312500
late charges  287500
shipping    382500
Name: amount, dtype: float64

我们需要向trans和blank数据帧中添加一个净总量。
 

total = trans.sum().amount
trans.loc["net"] = total
blank.loc["net"] = total
display(trans)
display(blank)

利用Python绘制数据的瀑布图的教程

sales        0
returns     350000
credit fees   320000
rebates     312500
late charges  287500
shipping    382500
net       375500
Name: amount, dtype: float64

创建我们用来显示变化的步骤。

step = blank.reset_index(drop=True).repeat(3).shift(-1)
step[1::3] = np.nan
display(step)
 
0     0
0    NaN
0  350000
1  350000
1    NaN
1  320000
2  320000
2    NaN
2  312500
3  312500
3    NaN
3  287500
4  287500
4    NaN
4  382500
5  382500
5    NaN
5  375500
6  375500
6    NaN
6    NaN
Name: amount, dtype: float64

对于“net”行,为了不使堆叠加倍,我们需要确保blank值为0。
 

blank.loc["net"] = 0

然后,将其画图,看一下什么样子。
 

my_plot = trans.plot(kind='bar', stacked=True, bottom=blank,legend=None, title="2014 Sales Waterfall")
my_plot.plot(step.index, step.values,'k')

利用Python绘制数据的瀑布图的教程

看起来相当不错,但是让我们试着格式化Y轴,以使其更具有可读性。为此,我们使用FuncFormatter和一些Python2.7+的语法来截断小数并向格式中添加一个逗号。
 

def money(x, pos):
  'The two args are the value and tick position'
  return "${:,.0f}".format(x)
 
from matplotlib.ticker import FuncFormatter
formatter = FuncFormatter(money)

然后,将其组合在一起。
 

my_plot = trans.plot(kind='bar', stacked=True, bottom=blank,legend=None, title="2014 Sales Waterfall")
my_plot.plot(step.index, step.values,'k')
my_plot.set_xlabel("Transaction Types")
my_plot.yaxis.set_major_formatter(formatter)

利用Python绘制数据的瀑布图的教程

完整脚本

基本图形能够正常工作,但是我想添加一些标签,并做一些小的格式修改。下面是我最终的脚本:
 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
 
#Use python 2.7+ syntax to format currency
def money(x, pos):
  'The two args are the value and tick position'
  return "${:,.0f}".format(x)
formatter = FuncFormatter(money)
 
#Data to plot. Do not include a total, it will be calculated
index = ['sales','returns','credit fees','rebates','late charges','shipping']
data = {'amount': [350000,-30000,-7500,-25000,95000,-7000]}
 
#Store data and create a blank series to use for the waterfall
trans = pd.DataFrame(data=data,index=index)
blank = trans.amount.cumsum().shift(1).fillna(0)
 
#Get the net total number for the final element in the waterfall
total = trans.sum().amount
trans.loc["net"]= total
blank.loc["net"] = total
 
#The steps graphically show the levels as well as used for label placement
step = blank.reset_index(drop=True).repeat(3).shift(-1)
step[1::3] = np.nan
 
#When plotting the last element, we want to show the full bar,
#Set the blank to 0
blank.loc["net"] = 0
 
#Plot and label
my_plot = trans.plot(kind='bar', stacked=True, bottom=blank,legend=None, figsize=(10, 5), title="2014 Sales Waterfall")
my_plot.plot(step.index, step.values,'k')
my_plot.set_xlabel("Transaction Types")
 
#Format the axis for dollars
my_plot.yaxis.set_major_formatter(formatter)
 
#Get the y-axis position for the labels
y_height = trans.amount.cumsum().shift(1).fillna(0)
 
#Get an offset so labels don't sit right on top of the bar
max = trans.max()
neg_offset = max / 25
pos_offset = max / 50
plot_offset = int(max / 15)
 
#Start label loop
loop = 0
for index, row in trans.iterrows():
  # For the last item in the list, we don't want to double count
  if row['amount'] == total:
    y = y_height[loop]
  else:
    y = y_height[loop] + row['amount']
  # Determine if we want a neg or pos offset
  if row['amount'] > 0:
    y += pos_offset
  else:
    y -= neg_offset
  my_plot.annotate("{:,.0f}".format(row['amount']),(loop,y),ha="center")
  loop+=1
 
#Scale up the y axis so there is room for the labels
my_plot.set_ylim(0,blank.max()+int(plot_offset))
#Rotate the labels
my_plot.set_xticklabels(trans.index,rotation=0)
my_plot.get_figure().savefig("waterfall.png",dpi=200,bbox_inches='tight')

运行该脚本将生成下面这个漂亮的图表:

利用Python绘制数据的瀑布图的教程

最后的想法

如果你之前不熟悉瀑布图,希望这个示例能够向你展示它到底是多么有用。我想,可能一些人会觉得对于一个图表来说需要这么多的脚本代码有点糟糕。在某些方面,我同意这种想法。如果你仅仅只是做一个瀑布图,而以后不会再碰它,那么你还是继续用Excel中的方法吧。

然而,如果瀑布图真的很有用,并且你需要将它复制给100个客户,将会怎么样呢?接下来你将要怎么做呢?此时使用Excel将会是一个挑战,而使用本文中的脚本来创建100个不同的表格将相当容易。再次说明,这一程序的真正价值在于,当你需要扩展这个解决方案时,它能够便于你创建一个易于复制的程序。

我真的很喜欢学习更多Pandas、matplotlib和IPothon的知识。我很高兴这种方法能够帮到你,并希望其他人也可以从中学习到一些知识,并将这一课所学应用到他们的日常工作中。

Python 相关文章推荐
Python模拟三级菜单效果
Sep 11 Python
python 处理dataframe中的时间字段方法
Apr 10 Python
Python 中的Selenium异常处理实例代码
May 03 Python
python 判断参数为Nonetype类型或空的实例
Oct 30 Python
详解python中递归函数
Apr 16 Python
Python Pandas数据结构简单介绍
Jul 03 Python
Python While循环语句实例演示及原理解析
Jan 03 Python
pycharm运行程序时看不到任何结果显示的解决
Feb 21 Python
解决flask接口返回的内容中文乱码的问题
Apr 03 Python
在pycharm中使用pipenv创建虚拟环境和安装django的详细教程
Nov 30 Python
python中count函数知识点浅析
Dec 17 Python
使用python实现学生信息管理系统
Feb 25 Python
浅析Python中的多进程与多线程的使用
Apr 07 #Python
Python多线程编程(八):使用Event实现线程间通信
Apr 05 #Python
Python多线程编程(七):使用Condition实现复杂同步
Apr 05 #Python
Python多线程编程(六):可重入锁RLock
Apr 05 #Python
Python多线程编程(五):死锁的形成
Apr 05 #Python
Python多线程编程(四):使用Lock互斥锁
Apr 05 #Python
Python多线程编程(三):threading.Thread类的重要函数和方法
Apr 05 #Python
You might like
PHP提取字符串中的图片地址[正则表达式]
2011/11/12 PHP
PHP读取文件内容的五种方式
2015/12/28 PHP
Laravel中的Blade模板引擎示例详解
2017/10/10 PHP
php命令行模式代码实例详解
2021/02/26 PHP
JavaScript执行顺序详细介绍
2013/12/04 Javascript
JS操作CSS随机改变网页背景实现思路
2014/03/10 Javascript
jQuery超赞的评分插件(8款)
2015/08/20 Javascript
jQuery简单倒计时效果完整示例
2016/09/20 Javascript
IE8兼容Jquery.validate.js的问题
2016/12/01 Javascript
ECMAScript6 新特性范例大全
2017/03/24 Javascript
js实现网页的两个input标签内的数值加减(示例代码)
2017/08/15 Javascript
jQuery实现的简单动态添加、删除表格功能示例
2017/09/21 jQuery
3种vue组件的书写形式
2017/11/29 Javascript
webpack-dev-server自动更新页面方法
2018/02/22 Javascript
js canvas实现写字动画效果
2018/11/30 Javascript
layui对工具条进行选择性的显示方法
2019/09/19 Javascript
使用JS来动态操作css的几种方法
2019/12/18 Javascript
vue-resource post数据时碰到Django csrf问题的解决
2020/03/13 Javascript
jQuery实现简单三级联动效果
2020/09/05 jQuery
[52:08]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#2Fnatic VS OG第一局
2016/03/05 DOTA
[02:28]DOTA2 2017国际邀请赛小组赛回顾
2017/08/09 DOTA
pytyon 带有重复的全排列
2013/08/13 Python
编写Python脚本使得web页面上的代码高亮显示
2015/04/24 Python
Python中定时任务框架APScheduler的快速入门指南
2017/07/06 Python
Python 实现淘宝秒杀的示例代码
2018/01/02 Python
详解python 模拟豆瓣登录(豆瓣6.0)
2019/04/18 Python
python实现中文文本分句的例子
2019/07/15 Python
浅谈OpenCV中的新函数connectedComponentsWithStats用法
2020/07/05 Python
python 实现朴素贝叶斯算法的示例
2020/09/30 Python
Canvas绘制浮动球效果的示例
2017/12/29 HTML / CSS
台湾母婴用品限时团购:妈咪爱
2018/08/03 全球购物
Perfume’s Club中文官网:西班牙美妆在线零售品牌
2020/08/24 全球购物
实习自我鉴定
2013/12/15 职场文书
茶叶店创业计划书范文
2014/01/19 职场文书
财务部会计岗位职责
2015/02/03 职场文书
vue-cli3.x配置全局的scss的时候报错问题及解决
2022/04/30 Vue.js