数据可视化(1):使用Pylab进行净现值曲线分析

之前我们学习python的爬虫,在数据科学中,爬虫是获取数据的重要工具。Python的作用并不止步于此,我们还可以通过python进行数据分析。比如我们现在有两个项目

  • 项目A: 初始投入1500,之后6年每年获得收益450
  • 项目B: 初始投入1200,到第六年年底一次性获得3000

我们投资需要用的钱是借来的,借来是有利率的,

我们要通过python来对这两项投资进行收益分析(净现值曲线),通过作图来进行判断,根据利率的变化来选择不同的项目

工具准备

作为工业级通用编程语言,python之所以能在数据领域有一席之地,与matplotlib,numpy,Scipy的存在有很大的关系,而我们这次使用的是Pylab,一个类似于matlab的集成平台,它整合了:

  • numpy:扩展科学计算程式库,支持高阶纬度阵列,矩阵运算,以及大量函数程式。底层纯C开发,性能极其强悍
  • scipy :科学计算程式库,主要用于数学计算,与numpy一样,底层纯C开发
  • matplotlib: 标准的绘图库,使用它我们可以绘制精美的图表
  • ipython:
  • jupyter (ipython notebook):

本文章我们只使用numpy和matplotlib来做基础的绘图操作,后面主要使用matplotlib,使用到了numpy的部分有标注

import pylab as plt

数据准备

根据之前的项目的细节,我们分别使用两个列表来表示项目资金的流动

项目A, 初始投入1500,之后6年每年获得收益450,我们得到

1
proj_A = [-1500,450,450,450,450,450,450]

项目B, 初始投入1200,到第六年年底一次性获得3000

1
proj_B = [-1200,0,0,0,0,0,3000]

接下来我们设置利率变化区间

1
2
3
cost_of_capital_rate =[i/100 for i in range(0,21)]
#返回如下列表
[0.0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.2]

接下来我们生成利率区间下对应的NPV,刚好numpy为我们提供了npv的函数,格式为npv(利率,现金流量列表)

1
2
proj_A_npv = [plt.npv(i,proj_A) for i in cost_of_capital_rate]
proj_B_npv = [plt.npv(i,proj_B) for i in cost_of_capital_rate]

在这里我们使用了列表解析式直接生成,如果这样不习惯我们可以改写成:

1
2
3
4
5
6
7
8
proj_A_npv = []
proj_B_npv = []

for i in cost_of_capital:
npv_A = plt.npv(i,proj_A)
npv_B = plt.npv(i,proj_B)
proj_A_npv.append()
proj_B_npv.append()

我们来看一下proj_A_npv里面是什么:

1
[1200.0, 1107.9644135606968, 1020.6439008106788, 937.73614974518443, 858.96158553585792, 784.06143027035068, 712.79594670242523, 644.94284689384756, 580.29584878253513, 518.66336560391937, 459.86731475800104, 403.74203418221509, 350.13329558504614, 298.89740503913117, 249.90038244412847, 203.01721226533152, 158.13115874789341, 115.13313951123695, 73.921152054113463, 34.399748257616636, -3.5204475308640326]

这些数字就是与我们之前不同利率相对应的NPV,proj_B_npv也是一样

到这里我们的数据就都准备好了,进入下一步,绘图。

作图

新的图表

1
plt.figure('A / B NPV Profile')

重叠与清除

在我们开启了一个新的figure后,我们的所有操作都会叠加到这张图上。比如画了一条曲线后我们可以再添加一条曲线。如果我们不使用清除操作的话,在下次使用该figure时很可能和之前的曲线再次叠加也就是四条曲线。这样干扰了我们正常做图。所以我们每次声明新图表之后最好添加一个清除,日后方便重复使用

plt.clf()

绘制图表

1
2
plt.plot(cost_of_capital_rate,proj_A_npv,'#92e3d7',label = 'Project A',linewidth = 2.0)
plt.plot(cost_of_capital_rate,proj_B_npv,'#f4ab84',label = 'Project B',linewidth = 2.0)

前面两个分别是横坐标纵坐标的变量,我们使用利率为横坐标,npv为纵坐标,后面的参数分别是

  • ‘#92e3d7’ 这一处代表线条的颜色,我们可以使用rgb或者其他的参数,这里使用了hex
  • label = ‘Project A’ 这里表示该条线在图例中的标签
  • linewidth 线条的粗细

到这里我们就已经完成了图片的绘制。我们可以通过show()方法来显示图片。

修改细节

尽管我们已经完成了基本的绘图工作,我们的图还是有一些粗糙,并不能让读者更直观地理解数据,这时我们需要修改细节

上限

我们对y轴的上限进行设置,从0-2000,

1
plt.ylim(0,2000)

2000这个值是怎么来的,很简单,试出来的。在我们之前绘制的片是有刻度和上限的,但是这个刻度和上限有时候并不能够最好地展示我们的折线图,当我们看到这个折线与y轴交汇最高点接近并低于2000时,我们使用2000作为上限可以最好地展示我们的图片

刻度

现在我们要修改我们的刻度,默认刻度间距有点大,这时候我们并不能很好地对数据进行判断。

这里我们使用了numpy的arange功能,快速生成一个刻度列表,使用方法是

1
2
3
4
numpy.arange(起点,终点(不包含),间隔)
>>>plt.arange(0, 2000+1, 400)
生成了以下列表
[0,400,800,1200,1600,2000]

接着我们分别使用yticks,xticks来修改x,y轴坐标

1
2
plt.yticks(plt.arange(0, 2000+1, 400))
plt.xticks(plt.arange(0,0.26+0.01,0.02))

图例

为了让读者更好地分辨两条曲线,我们在这里添加图例

plt.legend()

如果我们要标记图例的位置为右上方,我们添加参数loc

plt.legend(loc = 'upper right')

###标题

最后我们添加标题

plt.title('Project A/B NPV Analysis')

好了,使用show()方法来显示我们的NPV Profile吧

plt.show()

结果如下:

交点

根据上面的图我们可以看出在交点前后NPV最大的公司是不同的,但是我们并不能够知道这个点所代表的利率是多少,所以我们需要将它在图上进行标记。我们先求出这个点的坐标。什么?求出来还要标吗?是的,因为图上的线是点连接成的,有一定偏差;而且这份图不一定只是自己看的,需要求出坐标画图。

交点坐标

在求出交点坐标之前,我们需要知道这个交点背后的意义是什么。在该点的利率下,项目A和项目B的净现值(NPV)是持平的。求出两个项目现金流量的差,然后我们使用这个差求出该点的IRR(IRR是NPV为0时的利率)作为横坐标。最后我们通过该点求出项目A或者B的NPV作为纵坐标。

先对AB求差:

1
2
3
4
5
6
i = 0
intersection = []
for i in len(proj_A):
intersection.append(proj_A[i] - proj_B[i])

#得到[ -300, 450, 450, 450, 450, 450, -2550]

有没有更简单的?有

1
intersection = [proj_A[i] - proj_B[i] for i in len(proj_A)]

有没有更简单更快速的?当然有,这里我们用numpy的array来代替python的list,然后直接进行加减运算,

1
2
a= plt.array(proj_A)#将proj_A从list转换到array
b= plt.array(proj_B)

这时候我们可以看到a是这个样子

1
array([-1500,   450,   450,   450,   450,   450,   450])

然后我们只需

intersection = a-b

就直接得到了我们想要的结果,这么做另一个好处是当我们使用numpy时,速度快了不止一个数量级,具备了大型运算的能力

接下来我们使用numpy的irr和npv函数即可求出x,y坐标

1
2
intersection_irr = plt.irr(intersection)
intersection_npv =plt.npv(intersection_irr,a)

文字标记

matplotlib提供了annotate函数来帮助我们对图进行标记

1
plt.annotate("intersection = %.4f"%intersection_irr,xy=(intersection_irr,intersection_npv),xytext=(intersection_irr+0.02,intersection_npv+100),arrowprops=dict(facecolor='black', shrink=0.05))
  • 开头的’intersection = %.4f’%intersection_irr 是我们想在图片上显示的内容%.4f’%intersection_irr表示取intersection_irr小数点后四位
  • xy 参数接收交点的横纵坐标,是箭头指向的位置
  • xytext 是文字所处的坐标,注意修改时要考虑刻度
  • arrowprops 是我们箭头的属性,shrink = 0.05表示箭头长度只有95%,保留和文字之间的空隙

结果和分析

好了,这次再来看我们的折线图

从上图我们可以得出

1. A / B 的盈利利率区间,盈利范围

2. 在利率为11.01%时,A/B净现值相等,低于该点B项目收益高,高于该点A项目收益高

这么看着好麻烦,我们为什么要使用python而不是EXCEL这样更方便的工具?

这次只是一个小的例子。在数据科学中,我们往往需要处理成千上万的数据,如果使用excel的话,性能可能会差强人意或者会很卡(比如五千家公司十年来每天的股票收盘价格。。。另外python可以通过api接口,爬虫或者数据库直接对接从而获取大量数据,非常方便

完整代码

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
import pylab as plt

proj_A = [-1500,450,450,450,450,450,450]
proj_B = [-1200,0,0,0,0,0,3000]

cost_of_capital_rate =[i/100 for i in range(0,21)]
proj_A_npv = [plt.npv(i,proj_A) for i in cost_of_capital_rate]
proj_B_npv = [plt.npv(i,proj_B) for i in cost_of_capital_rate]

plt.figure('A / B NPV Profile')

plt.clf()

plt.plot(cost_of_capital_rate,proj_A_npv,'#92e3d7',label = 'Project A',linewidth = 2.0)
plt.plot(cost_of_capital_rate,proj_B_npv,'#f4ab84',label = 'Project B',linewidth = 2.0)


plt.ylim(0,2000)
plt.yticks(plt.arange(0, 2000+1, 400))
plt.xticks(plt.arange(0,0.24+0.01,0.02))


plt.legend(loc = 'upper right')

plt.title('Project A/B NPV Analysis')

a= plt.array(proj_A)
b= plt.array(proj_B)
intersection = a-b
intersection_irr = plt.irr(intersection)
intersection_npv =plt.npv(intersection_irr,a)

plt.annotate("intersection = %.4f"%intersection_irr,xy=(intersection_irr,intersection_npv),xytext=(intersection_irr+0.02,intersection_npv+100),arrowprops=dict(facecolor='black', shrink=0.05))

plt.show()