教你用Python建立瀑布圖

介紹python

對於繪製某些類型的數據來講,瀑布圖是一種十分有用的工具。不足爲奇的是,咱們可使用Pandas和matplotlib建立一個可重複的瀑布圖。函數

在往下進行以前,我想先告訴你們我指代的是哪一種類型的圖表。我將創建一個維基百科文章中描述的2D瀑布圖。工具

這種圖表的一個典型的用處是顯示開始值和結束值之間起「橋樑」做用的+和-的值。由於這個緣由,財務人員有時會將其稱爲一個橋樑。跟我以前所採用的其餘例子類似,這種類型的繪圖在Excel中不容易生成,固然確定有生成它的方法,可是不容易記住。oop

關於瀑布圖須要記住的關鍵點是:它本質上是一個堆疊在一塊兒的條形圖,不過特殊的一點是,它有一個空白底欄,因此頂部欄會「懸浮」在空中。那麼,讓咱們開始吧。學習

建立圖表

首先,執行標準的輸入,並確保IPython能顯示matplot圖。spa

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

設置咱們想畫出瀑布圖的數據,並將其加載到數據幀(DataFrame)中。code

數據須要以你的起始值開始,可是你須要給出最終的總數。咱們將在下面計算它。orm

1
2
3
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函數來更簡單地控制我要顯示的內容。blog

1
2
from IPython.display import display
display(trans)

瀑布圖的最大技巧是計算出底部堆疊條形圖的內容。有關這一點,我從stackoverflow上的討論中學到不少。ip

首先,咱們獲得累積和。

1
2
3
4
5
6
7
8
display(trans.amount.cumsum())
sales           350000
returns         320000
credit fees     312500
rebates         287500
late charges    382500
shipping        375500
Name: amount, dtype: int64

這看起來不錯,但咱們須要將一個地方的數據轉移到右邊。

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

咱們須要向trans和blank數據幀中添加一個淨總量。

1
2
3
4
5
total = trans. sum ().amount
trans.loc[ "net" ] = total
blank.loc[ "net" ] = total
display(trans)
display(blank)

1
2
3
4
5
6
7
8
sales                0
returns         350000
credit fees     320000
rebates         312500
late charges    287500
shipping        382500
net             375500
Name: amount, dtype: float64

建立咱們用來顯示變化的步驟。

1
2
3
step = blank.reset_index(drop = True ).repeat( 3 ).shift( - 1 )
step[ 1 :: 3 ] = np.nan
display(step)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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。

1
blank.loc[ "net" ] = 0

而後,將其畫圖,看一下什麼樣子。

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

看起來至關不錯,可是讓咱們試着格式化Y軸,以使其更具備可讀性。爲此,咱們使用FuncFormatter和一些Python2.7+的語法來截斷小數並向格式中添加一個逗號。

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

而後,將其組合在一塊兒。

1
2
3
4
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)

完整腳本

基本圖形可以正常工做,可是我想添加一些標籤,並作一些小的格式修改。下面是我最終的腳本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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' )

運行該腳本將生成下面這個漂亮的圖表:

最後的想法

若是你以前不熟悉瀑布圖,但願這個示例可以向你展現它究竟是多麼有用。我想,可能一些人會以爲對於一個圖表來講須要這麼多的腳本代碼有點糟糕。在某些方面,我贊成這種想法。若是你僅僅只是作一個瀑布圖,而之後不會再碰它,那麼你仍是繼續用Excel中的方法吧。

然而,若是瀑布圖真的頗有用,而且你須要將它複製給100個客戶,將會怎麼樣呢?接下來你將要怎麼作呢?此時使用Excel將會是一個挑戰,而使用本文中的腳原本建立100個不一樣的表格將至關容易。再次說明,這一程序的真正價值在於,當你須要擴展這個解決方案時,它可以便於你建立一個易於複製的程序。

我真的很喜歡學習更多Pandas、matplotlib和IPothon的知識。我很高興這種方法可以幫到你,並但願其餘人也能夠從中學習到一些知識,並將這一課所學應用到他們的平常工做中。

關於做者: PyPer

相關文章
相關標籤/搜索