第11 章 绘图以及类的进一步扩展

文本通常是交流信息的最好方式,但有些时候,中国谚语“一图胜千言”也是事实。但多数
程序仍然依赖文本输出与用户进行交流。原因何在?因为在多数编程语言中,提供可视化数据太
难了。幸运的是,在Python中非常简单。
11.1 使用PyLab 绘图
PyLab是一个Python标准库模块,提供了MATLAB的很多功能。MATLAB是“一种高级的技
术计算语言和交互环境,可以用于算法开发、数据可视化、数据分析和数值计算”。①在本书后面
的章节中,我们还会介绍一些关于PyLab的更高级的内容,但在本章,我们重点介绍其绘制数据
图形的功能。PyLab绘图能力的完整用户指南参见matplotlib.sourceforge.net/users/index.html。
还有很多网页也提供了非常精彩的教程。本书目的不是提供用户指南或完整教程,相反,本
章只提供若干绘图示例,并解释如何用代码生成这些图形。其他示例将会在后面的章节中介绍。
我们先从一个简单的例子开始,使用pylab.plot生成两张图。运行以下代码:

import pylab
pylab.figure(1) #创建图1
pylab.plot([1,2,3,4], [1,7,3,5]) #在图1上绘图
pylab.show() #在屏幕上显示


会在显示器上打开一个窗口,窗口的具体外观取决于你的Python环境,但会与图11-1(使用
Anaconda生成)非常相似。运行这段代码时,如果你使用的是多数PyLab安装版本的默认参数设
置,那图中的线就可能不会像图11-1那么粗。我们使用的线宽和字号不是标准默认值,这样图形
会在印刷成书的时候好看一些。在后面的小节将介绍如何设置图形参数。

图11-1 一个简单的绘图

图形最上方的标题栏中有窗口名称,本例中是Figure 1。
窗口中间部分是调用pylab.plot生成的图形。pylab.plot中的两个参数必须是同样长度的
序列。第一个参数指定了图中所有点的X轴坐标,第二个参数指定Y轴坐标。两个参数一起提供
一个序列,其中包括4个坐标对:[(1, 1), (2, 7), (3, 3), (4, 5)]。这些点依次绘制在图
中,绘制每个点时,都用一条直线与前面的点相连。
最后一行代码pylab.show()会使窗口显示在计算机屏幕中。①在某些Python环境中,如果没
有这行代码,图形依然会生成,但是不会显示。这种做法乍一听很傻,但其实不是,因为我们完
全可以选择将图形直接保存在文件中(我们之后会这样做),而不是显示在屏幕上。
窗口上方工具栏中有几个按钮。最右侧的按钮会弹出一个窗口,里面有一些可以用来调整图
形设置的选项。左侧与之相邻的按钮用来将图形保存到文件。②再往左的按钮用来调整窗口中图
形的外观。再往左的两个按钮用来缩放和平移。看上去像箭头的两个按钮用来查看以前的视图(类
似网页浏览器中的“前进”和“后退”按钮)。当你使用其他按钮对图形进行一番操作后,可以
使用最左边的按钮将图形还原为初始状态。
我们可以生成多个图形并将其保存到文件中。文件可以使用任何名称,只要你喜欢,但扩展
名会是.png,表示文件的格式是可移植网络图形(Portable Network Graphics)。这是一种表示图
形的公共领域标准。

以下代码:

pylab.figure(1) #创建图1
pylab.plot([1,2,3,4], [1,2,3,4]) #在图1上绘图
pylab.figure(2) #创建图2
pylab.plot([1,4,2,3], [5,6,7,8]) #在图2上绘图
pylab.savefig('Figure-Addie') #保存图2
pylab.figure(1) #回到图1
pylab.plot([5,6,10,3]) #继续在图1上绘图
pylab.savefig('Figure-Jane') #保存图1

会生成图11-2中的两幅图形,并将其保存到文件Figure-Jane.png和Figure-Addie.png中。
 

图11-2 Figure-Jane.png(左)和Figure-Addie.png(右)

我们注意到,最后一个pylab.plot只使用了一个参数,这个参数提供了Y值,相应的X值默
认为由range(len([5, 6, 10, 3]))产生的序列。在本例子中,就是0~3的整数。
Pylab中有个概念叫作“当前图”。运行pylab.figure(x)可以将当前图设置为第x个图形,随
后的绘图函数调用都会作用在这个图上,直到再一次调用pylab.figure。所以写入文件
Figure-Addie.png的图是第二张图。
再看另一个例子,以下代码:
 

interestRate = 0.05
years = 20
values = []
for i in range(years + 1):
values.append(principal)
principal += principal*interestRate
pylab.plot(values)

会生成图11-3中左侧的图。

图11-3 绘制复利增长

通过代码可以推断,这幅图展示了一笔投资的增长,初始投资额为10 000美元,年利率为5%。
但是,只凭这幅图可不容易推理出这些信息。这很糟糕,所有图形都应该有意义明确的标题,所
有坐标轴都应该有标注。
如果在上述代码的后面添加以下代码:

pylab.title('5% Growth, Compounded Annually')
pylab.xlabel('Years of Compounding')
pylab.ylabel('Value of Principal ($)')

可以得到图11-3中右侧的图。
对于每条绘制的曲线,都有一个可选的参数,这个参数是一个格式化的字符串,表示图形中
曲线的颜色和线型。①格式化字符串中的字母和符号都来自MATLAB,由一个颜色标识符和一个线
型标识符组成,线型标识符是可选的。格式化字符串的默认值是'b-',表示一条蓝色实线。如果
想以黑色圆点绘制本金增长情况,应该使用pylab.plot(values,'ko')替换pylab.plot (values),
这样就可以生成图11-4。如果想查看完整的颜色和线型标识符列表,参见http://matplotlib.org/api/
pyplot_api.html#matplotlib.pyplot.plot。

图11-4 另一复利增长图

调用函数时,使用关键字参数还可以改变图形中的字体大小和线条宽度。例如,以下代码:

principal = 10000 #初始投资
interestRate = 0.05
years = 20
values = []
for i in range(years + 1):
values.append(principal)
principal += principal*interestRate
pylab.plot(values, linewidth = 30)
pylab.title('5% Growth, Compounded Annually',
fontsize = 'xx-large')
pylab.xlabel('Years of Compounding', fontsize = 'x-small')
pylab.ylabel('Value of Principal ($)')

通过设置关键字参数,有意生成了一幅风格怪异的图,如图11-5所示。

图11-5 风格怪异的图

我们也可以修改绘图时的默认值,这个操作称为“rc设置”(rc来源于Unix运行时配置文件的
扩展名.rc)。这些默认值保存在一个类似字典的变量中,可以使用pylab.rcParams访问。举例
来说,你可以通过运行以下代码,将默认线宽设置为6点①。

pylab.rcParams['lines.linewidth'] = 6.


rcParams中有很多设置项目,完整的列表参见http://matplotlib.org/users/customizing.html。如
果你不想花费精力对这些参数进行单独设置,可以使用一个预定义的样式表,具体介绍参见
http://matplotlib.org/users/style_sheets.html#style-sheets。
在本书后面的例子中,最常用的rc设置通过以下代码完成:

#设置线宽
pylab.rcParams['lines.linewidth'] = 4
#设置标题字体大小
pylab.rcParams['axes.titlesize'] = 20
#设置坐标轴标签字体大小
pylab.rcParams['axes.labelsize'] = 20
#设置X轴数字大小
pylab.rcParams['xtick.labelsize'] = 16
#设置Y轴数字大小
pylab.rcParams['ytick.labelsize'] = 16
#设置X轴刻度大小
pylab.rcParams['xtick.major.size'] = 7
#设置Y轴刻度大小
pylab.rcParams['ytick.major.size'] = 7
#设置标记点大小,例如,表示点的圆圈大小
pylab.rcParams['lines.markersize'] = 10
#显示图例时,设置图例中标记点的数量
pylab.rcParams['legend.numpoints'] = 1

如果你在彩色显示器上查看图形,那么完全可以不进行这些自定义设置。我们这样做目的是
使图形在缩小和转换为黑白图形之后更好看。
11.2 进阶示例:绘制抵押贷款
在第8章中,我们开发了一个有层次结构的抵押贷款类,并以此为例介绍了子类的用法。当
时我们留下了一个伏笔:“程序应该能够生成一些图形,以显示抵押贷款随着时间发生的变化。”
图11-6给出了一个Mortgage类的增强版本,向类添加了一些方法,可以轻松生成前面所说的那些
图形。(8.4节介绍过函数findPayment,定义如图8-9所示。)
新Mortgage类中比较重要方法的是plotTotPd和plotNet。plotTotPd方法简单绘制已支付
贷款的累积总额,plotNet方法使用支付的现金总额减去因付清部分贷款而获得的本金,绘制抵
押贷款随着时间变化的总成本近似值。②

class Mortgage(object):
"""建立不同种类抵押贷款的抽象类"""
def __init__(self, loan, annRate, months):
self.loan = loan
self.rate = annRate/12.0
self.months = months
self.paid = [0.0]
self.outstanding = [loan]
self.payment = findPayment(loan, self.rate, months)
self.legend = None #description of mortgage
def makePayment(self):
self.paid.append(self.payment)
reduction = self.payment - self.outstanding[-1]*self.rate
self.outstanding.append(self.outstanding[-1] - reduction)
def getTotalPaid(self):
return sum(self.paid)
def __str__(self):
return self.legend
def plotPayments(self, style):
pylab.plot(self.paid[1:], style, label = self.legend)
def plotBalance(self, style):
pylab.plot(self.outstanding, style, label = self.legend)
def plotTotPd(self, style):
totPd = [self.paid[0]]
for i in range(1, len(self.paid)):
totPd.append(totPd[-1] + self.paid[i])
pylab.plot(totPd, style, label = self.legend)
def plotNet(self, style):
totPd = [self.paid[0]]
for i in range(1, len(self.paid)):
totPd.append(totPd[-1] + self.paid[i])
equityAcquired = pylab.array([self.loan] * \
len(self.outstanding))
equityAcquired = equityAcquired - \
pylab.array(self.outstanding)
net = pylab.array(totPd) – equityAcquired
pylab.plot(net, style, label = self.legend)

图11-6 带有绘图方法的Mortgage类

在函数plotNet中,表达式pylab.array(self.outstanding)执行了一个类型转换。到现在
为止,调用PyLab中的绘图函数时,都要求我们使用list类型作为参数。但这只是表面现象,实
际上PyLab会将列表参数转换为另一类型,即从numpy继承的array类型。①调用pylab.array只是
将这个过程显性化了。数组提供了很多列表中没有的便捷操作方式,特别地,使用数组和算术操
作符可以组成表达式。PyLab中有很多方法可以创建数组,但最常用的方式是先创建一个列表,
然后进行转换。如下面的代码:

a1 = pylab.array([1, 2, 4])
print('a1 =', a1)
a2 = a1*2
print('a2 =', a2)
print('a1 + 3 =', a1 + 3)
print('3 - a1 =', 3 - a1)
print('a1 - a2 =', a1 - a2)
print('a1*a2 =', a1*a2)

表达式a1*2将a1中的每个元素都乘以常数2。表达式a1 + 3将a1中的每个元素都加上整数3。
表达式a1 - a2将a1中的每个元素都减去a2中对应的元素(如果两个数组长度不同,就会发生错
误)。表达式a1*a2将a1中的每个元素都乘以a2中对应的元素。运行上面代码,会输出以下结果:

a1 = [1 2 4]
a2 = [2 4 8]
a1 + 3 = [4 5 7]
3 - a1 = [ 2 1 -1]
a1 - a2 = [-1 -2 -4]
a1*a2 = [ 2 8 32]

图11-7重新实现了图8-10中的三个Mortgage子类。每个子类都使用自己特有的__init__方法
覆盖了Mortgage类中的__init__方法。TwoRate子类还覆盖了Mortgage类的makePayment方法。

class Fixed(Mortgage):
def __init__(self, loan, r, months):
Mortgage.__init__(self, loan, r, months)
self.legend = 'Fixed, ' + str(r*100) + '%'
class FixedWithPts(Mortgage):
def __init__(self, loan, r, months, pts):
Mortgage.__init__(self, loan, r, months)
self.pts = pts
self.paid = [loan*(pts/100.0)]
self.legend = 'Fixed, ' + str(r*100) + '%, '\
+ str(pts) + ' points'
class TwoRate(Mortgage):
def __init__(self, loan, r, months, teaserRate, teaserMonths):
Mortgage.__init__(self, loan, teaserRate, months)
self.teaserMonths = teaserMonths
self.teaserRate = teaserRate
self.nextRate = r/12.0
self.legend = str(teaserRate*100)\
+ '% for ' + str(self.teaserMonths)\
+ ' months, then ' + str(r*100) + '%'
def makePayment(self):
if len(self.paid) == self.teaserMonths + 1:
self.rate = self.nextRate
self.payment = findPayment(self.outstanding[-1],
self.rate,
self.months - self.teaserMonths)
Mortgage.makePayment(self)

图11-7 Mortgage子类

图11-8和图11-9中的函数可以生成图形,我们可以通过这些图形对不同类型的抵押贷款有更
深入的了解。

图11-8中的compareMortgage函数创建了一个包含不同类型抵押贷款的列表,并模拟了每种
贷款的一系列还款,就像图8-11一样。然后调用图11-9中的plotMortgage函数,生成图形。

def compareMortgages(amt, years, fixedRate, pts, ptsRate,
varRate1, varRate2, varMonths):
totMonths = years*12
fixed1 = Fixed(amt, fixedRate, totMonths)
fixed2 = FixedWithPts(amt, ptsRate, totMonths, pts)
twoRate = TwoRate(amt, varRate2, totMonths, varRate1, varMonths)
morts = [fixed1, fixed2, twoRate]
for m in range(totMonths):
for mort in morts:
mort.makePayment()
plotMortgages(morts, amt)

图11-8 比较各种抵押贷款

图11-9中的plotMortgage函数使用Mortgage类中的绘图方法生成图形,图形包含三种抵押
贷款的信息。plotMortgage中的循环使用索引i从列表morts和styles中选择元素,保证不同类
型的抵押贷款以同样的方式表示到不同的图形。举例来说,因为morts中的第三个元素表示浮动
利率的抵押贷款,styles中的第三个元素是k:,所以浮动利率的抵押贷款总是使用黑色点线绘
制。内部函数labelPlot为每张图生成合适的标题和坐标轴标注。调用pylab.figure可以保证将
标题和坐标轴标注与相应的图形关联起来。

def plotMortgages(morts, amt):
def labelPlot(figure, title, xLabel, yLabel):
pylab.figure(figure)
pylab.title(title)
pylab.xlabel(xLabel)
pylab.ylabel(yLabel)
pylab.legend(loc = 'best')
styles = ['k-', 'k-.', 'k:']
#给图编号赋名
payments, cost, balance, netCost = 0, 1, 2, 3
for i in range(len(morts)):
pylab.figure(payments)
morts[i].plotPayments(styles[i])
pylab.figure(cost)
morts[i].plotTotPd(styles[i])
pylab.figure(balance)
morts[i].plotBalance(styles[i])
pylab.figure(netCost)
morts[i].plotNet(styles[i])
labelPlot(payments, 'Monthly Payments of $' + str(amt) +
' Mortgages', 'Months', 'Monthly Payments')
labelPlot(cost, 'Cash Outlay of $' + str(amt) +
'Mortgages', 'Months', 'Total Payments')
labelPlot(balance, 'Balance Remaining of $' + str(amt) +
'Mortgages', 'Months', 'Remaining Loan Balance of $')
labelPlot(netCost, 'Net Cost of $' + str(amt) + ' Mortgages',
'Months', 'Payments - Equity $')

图11-9 绘制抵押贷款图形

以下函数调用:

compareMortgages(amt=200000, years=30, fixedRate=0.07,
pts = 3.25, ptsRate=0.05,
varRate1=0.045, varRate2=0.095, varMonths=48)

可以生成一系列图形(图11-10~图11-12),对8.4节中讨论的抵押贷款进行更加形象的阐释。
图11-10中的图形是通过调用plotPayments方法生成的,它简单地绘制了每种抵押贷款的每
月还款额随时间发生的变化。因为调用pylab.legend时使用关键字参数loc进行了设置,当loc
设置为best时,程序会自动选择图例位置,所以图例出现在图中现在的位置。这幅图清楚地显示
了每月还款额随着时间发生的变化,但没有对每种抵押贷款的相对成本提供太多的信息。

图11-10 不同类型抵押贷款月还款额

图11-11中的图形是通过调用plotTotPd方法生成的,它绘制了每月月初发生的累积成本的变
化,从而反映出每种抵押贷款的成本信息。左侧是完整的图形,右图是左图放大后的一部分。

图11-11 不同类型抵押贷款成本随时间的变化

图11-12的图形展示了每种贷款的剩余债务(左图)和总成本净值(右图)。

图11-12 不同类型抵押贷款的余额与净成本

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

___Y1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值