利用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之从if开始语句的征程
Sep 14 Python
使用SAE部署Python运行环境的教程
May 05 Python
详解Python Socket网络编程
Jan 05 Python
Python中在脚本中引用其他文件函数的实现方法
Jun 23 Python
python 处理string到hex脚本的方法
Oct 26 Python
python内置数据类型之列表操作
Nov 12 Python
Numpy截取指定范围内的数据方法
Nov 14 Python
使用python画社交网络图实例代码
Jul 10 Python
Python socket 套接字实现通信详解
Aug 27 Python
vscode 配置 python3开发环境的方法
Sep 19 Python
详解django使用include无法跳转的解决方法
Mar 19 Python
python用tkinter实现一个gui的翻译工具
Oct 26 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
让你同时上传 1000 个文件 (二)
2006/10/09 PHP
php 获取一个月第一天与最后一天的代码
2010/05/16 PHP
php smarty截取中文字符乱码问题?gb2312/utf-8
2011/11/07 PHP
php图片添加水印例子
2016/07/20 PHP
laravel自定义分页效果
2017/07/23 PHP
为jquery.ui.dialog 增加“自动记住关闭时的位置”的功能
2009/11/24 Javascript
如何确保JavaScript的执行顺序 之jQuery.html并非万能钥匙
2011/03/03 Javascript
jQuery中判断一个元素是否为另一个元素的子元素(或者其本身)
2012/03/21 Javascript
javascript使用正则控制input输入框允许输入的值方法大全
2014/06/19 Javascript
ECMAScript6新增值比较函数Object.is
2015/06/12 Javascript
js实现的二级横向菜单条实例
2015/08/22 Javascript
JavaScript ParseFloat()方法
2015/12/18 Javascript
javascript的 {} 语句块详解
2016/02/27 Javascript
JS实现中国公民身份证号码有效性验证
2017/02/20 Javascript
vue watch自动检测数据变化实时渲染的方法
2018/01/16 Javascript
vue地区选择组件教程详解
2018/05/04 Javascript
在Vue项目中,防止页面被缩放和放大示例
2019/10/28 Javascript
在Python程序中操作文件之isatty()方法的使用教程
2015/05/24 Python
Python语言实现获取主机名根据端口杀死进程
2016/03/31 Python
python负载均衡的简单实现方法
2018/02/04 Python
python tkinter界面居中显示的方法
2018/10/11 Python
对Python3 序列解包详解
2019/02/16 Python
python字典嵌套字典的情况下找到某个key的value详解
2019/07/10 Python
HTML5标签嵌套规则详解【必看】
2016/04/26 HTML / CSS
松下电器美国官方商店:Panasonic美国
2016/10/14 全球购物
美国狗旅行和户外用品领先供应商:kurgo
2020/08/18 全球购物
怎么处理XML的中文问题
2015/03/26 面试题
什么是数据库锁?Oracle中都有哪些类型的锁?
2015/08/21 面试题
会计专业应届生求职信
2013/11/24 职场文书
车间副主任岗位职责
2013/12/24 职场文书
酒店经理职责
2014/01/30 职场文书
大跃进口号
2014/06/16 职场文书
介绍信模板
2015/01/31 职场文书
库房管理员岗位职责
2015/02/12 职场文书
老干部座谈会主持词
2015/07/03 职场文书
《家庭教育》读后感3篇
2019/12/18 职场文书