11 PLOTTING AND MORE ABOUT CLASSES
Often text is the best way to communicate information, but sometimes there is a lot of truth to the Chinese proverb, 圖片的意義可以表達近萬字. Yet most programs rely on textual output to communicate with their users. Why? Because in many programming languages presenting visual data is too hard. Fortunately, it is simple to do in Python.
11.1 Plotting Using PyLab
PyLab is a Python standard library module that provides many of the facilities of MATLAB, “a high- level technical computing language and interactive environment for algorithm development, data visualization, data analysis, and numeric computation.” Later in the book we will look at some of the more advanced features of PyLab, but in this chapter we focus on some of its facilities for plotting data. Other plotting facilities are introduced in later chapters. A complete user’s guide to the plotting capabilities of PyLab is at the Web site: User’s Guide — Matplotlib 2.0.0 documentation
There are also a number of Web sites that provide excellent tutorials. We will not try to provide a user’s guide or a complete tutorial here. Instead, in this chapter we will merely provide a few example plots and explain the code that generated them. Other examples appear in later chapters.
1.A simple example
(1) Simple
Let’s start with a simple example that uses pylab.plot to produce two plots. Executing:
import pylab pylab.figure(1) pylab.plot([1,2,3,4,5],[1,7,3,5,12]) pylab.show()
will cause a window to appear on your computer monitor. Its exact appearance may depend on your Python environment, but it will look similar to Figure 11.1 (which was produced using Anaconda). If you run this code with the default parameter settings of most installations of PyLab, the line will probably not be as thick as the line in Figure 11.1. We have used nonstandard default values for line width and font sizes so that the figures will reproduce better in black and white. We discuss how this is done later in this section.
Figure 11.1 A simple plot
1. Sequences of the same length
The bar at the top of the plot contains the name of the window, in this case “Figure 1.” The middle section of the window contains the plot generated by the invocation of pylab.plot. The two parameters of pylab.plot must be sequences of the same length. The first specifies the x-coordinates of the points to be plotted, and the second specifies the y-coordinates. Together, they provide a sequence of four <x, y> coordinate pairs,[(1,1), (2,7), (3,3), (4,5)]
. These are plotted in order. As each point is plotted, a line is drawn connecting it to the previous point.
2. The problem of pylab.show()
The final line of code, pylab.show(), causes the window to appear on the computer screen. In some Python environments, if that line were not present, the figure would still have been produced, but it would not have been displayed. This is not as silly as it at first sounds, since one might well choose to write a figure directly to a file, as we will do later, rather than display it on the screen.
3. Introduction of the Buttons:
The bar at the top of the window contains a number of push buttons. The rightmost button pops up a window with options that can be used to adjust various aspects of the figure. The next button to the left is used to write the plot to a file. The button to the left of that is used to adjust the appearance of the plot in the window. The next two buttons are used for zooming and panning. The two buttons that look like arrows are used to see previous views (like the forward and backward arrows of a Web browser). And the button on the extreme left is used to restore the figure to its original appearance after you are done playing with other buttons.
(2) The multiple figures
It is possible to produce multiple figures and to write them to files. These files can have any name you like, but they will all have the file extension .png. The file extension .png indicates that the file is in the Portable Networks Graphics format. This is a public domain standard for representing images.
multiple figures
pylab.figure(1) #create figure 1 pylab.plot([1,2,3,4], [1,2,3,4]) #draw on figure 1 pylab.figure(2) #create figure 2 pylab.plot([1,4,2,3], [5,6,7,8]) #draw on figure 2 pylab.savefig('Figure-Addie') #save figure 2 pylab.figure(1) #go back to working on figure 1 pylab.plot([5,6,10,3]) #draw again on figure 1 pylab.savefig('Figure-Jane') #save figure 1
produces and saves to files named Figure-Jane.png and Figure- Addie.png the two plots in Figure 11.2.
1. One argument
Observe that the last call to pylab.plot is passed only one argument. This argument supplies the y values. ==The corresponding x values default to the sequence yielded by range(len([5, 6, 10, 3])), which is why they range from 0 to 3 in this case.
2. “Current figure.”
PyLab has a notion of “current figure.” Executing pylab.figure(x) sets the current figure to the figure numbered x. Subsequently executed calls of plotting functions implicitly refer to that figure until another invocation of pylab.figure occurs. This explains why the figure written to the file Figure-Addie.png was the second figure created.
Figure 11.2 Contents of Figure-Jane.png (left) and Figure- Addie.png (right)
2. Compound interest
(1) Simple
values.append(principal) 這里看到List的神奇之處,如若手工操作熄云,多么的繁瑣涮俄。
******Let’s look at another example. The code:**
principal = 10000 #initial investment interestRate = 0.05 years = 20 values = [] for i in range(years + 1): values.append(principal) principal += principal*interestRate pylab.plot(values) pylab.savefig('FigurePlottingCompoundGrowth')
produces the plot on the left in Figure 11.3.
(2) informative Titles
If we look at the code, we can deduce that this is a plot showing the growth of an initial investment of $10,000 at an annually compounded interest rate of 5%. However, this cannot be easily inferred by looking only at the plot itself. That’s a bad thing. All plots should have informative titles, and all axes should be labeled. If we add to the end of our code the lines
The xlabel,ylabel,title
pylab.title('5% Growth, Compounded Annually') pylab.xlabel('Years of Compounding') pylab.ylabel('Value of Principal ($)')
we get the plot on the right in Figure 11.3.
Figure 11.3
(3) Optional argument
For every plotted curve, there is an optional argument that is a format string indicating the color and line type of the plot. The letters and symbols of the format string are derived from those used in MATLAB, and are composed of a color indicator followed by an optional line-style indicator. The default format string is 'b-', which produces a solid blue line. To plot the the growth in principal with black circles, one would replace the call
pylab.plot(values) by pylab.plot(values, 'ko', )
which produces the plot in Figure 11.4. For a complete list of color and line-style indicators, see
Figure 11.4 Another plot of compound growth
(4) Change the type size and line width
It is also possible to change the type size and line width used in plots. This can be done using keyword arguments in individual calls to functions. E.g., the code:
principal = 10000 #initial investment 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 ($)')
produces the intentionally bizarre-looking plot in Figure 11.5.
Figure 11.5 Strange-looking plot
3. Change the default values:“rc settings.”
It is also possible to change the default values, which are known as “rc settings.” (The name “rc” is derived from the .rc file extension used for runtime configuration files in Unix.) These values are stored in a dictionary-like variable that can be accessed via the name pylab.rcParams
. So, for example, you can set the default line width to 6 points by executing the code
pylab.rcParams['lines.linewidth'] = 6.
There are an enormous number rcParams settings. A complete list can be found at
If you don’t want to worry about customizing individual parameters,there are pre-defined style sheets. A description of these can be found at
Customizing plots with style sheets — Matplotlib 1.5.3 documentation
The values used in most of the remaining examples in this book were set with the code
#set line width
pylab.rcParams['lines.linewidth'] = 4
#set font size for titles
pylab.rcParams['axes.titlesize'] = 20
#set font size for labels on axes
pylab.rcParams['axes.labelsize'] = 20
#set size of numbers on x-axis
pylab.rcParams['xtick.labelsize'] = 16
#set size of numbers on y-axis
pylab.rcParams['ytick.labelsize'] = 16
#set size of ticks on x-axis
pylab.rcParams['xtick.major.size'] = 7
#set size of ticks on y-axis
pylab.rcParams['ytick.major.size'] = 7
#set size of markers, e.g., circles representing points
pylab.rcParams['lines.markersize'] = 10
#set number of times marker is shown when displaying legend
pylab.rcParams['legend.numpoints'] = 1
If you are viewing plots on a color display, you will have little reason to customize these settings. We customized the settings we used so that it would be easier to read the plots when we shrank them and converted them to black and white.
11.2 Plotting Mortgages, an Extended Example
In Chapter 8, we worked our way through a hierarchy of mortgages as way of illustrating the use of subclassing. We concluded that chapter by observing that “our program should be producing plots designed to show how the mortgage behaves over time.” Figure 11.6 enhances class Mortgage by adding methods that make it convenient to produce such plots. (The function findPayment, which appears in Figure 8.9, is discussed in Section 8.4.)
Figure 11.6 Class Mortgage with plotting methods
print('class Mortgage') class Mortgage(object): """Abstract class for building different kinds of mortgages""" def __init__(self, loan, annRate, months): """Create a new mortgage""" 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): """Make a payment""" self.paid.append(self.payment) reduction = self.payment - self.outstanding[-1]*self.rate self.outstanding.append(self.outstanding[-1] - reduction) def getTotalPaid(self): """Return the total amount paid so far""" 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): """Plot the cumulative total of the payments made""" 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): """Plot an approximation to the total cost of the mortgage over time by plotting the cash expended minus the equity acquired by paying off part of the loan""" totPd = [self.paid[0]] for i in range(1, len(self.paid)): totPd.append(totPd[-1] + self.paid[i]) #Equity acquired through payments is amount of original loan #paid to date, which is amount of loan minus what is still outstanding 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)
The nontrivial methods in class Mortgage are plotTotPd and plotNet. The method plotTotPd simply plots the cumulative total of the payments made. The method plotNet plots an approximation to the total cost of the mortgage over time by plotting the cash expended minus the equity acquired by paying off part of the loan.
The expression pylab.array(self.outstanding) in the function plotNet performs a type conversion. Thus far, we have been calling the plotting functions of PyLab with arguments of type list. Under the covers, PyLab has been converting these lists to a different type, array, which PyLab inherits from numpy. The invocation pylab.array makes this explicit. There are a number of convenient ways to manipulate arrays that are not readily available for lists. In particular, expressions can be formed using arrays and arithmetic operators. There are a number of ways to create arrays in PyLab, but the most common way is to first create a list, and then convert it. Consider the code:
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)
The expression a1*2
multiplies each element of a1
by the constant 2
. The expression a1 + 3
adds the integer3
to each element of a1
. The expression a1 - a2
subtracts each element of a2
from the corresponding element of a1
(if the arrays had been of different length, an error would have occurred). The expression a1*a2
multiplies each element of a1 by the corresponding element of a2. When the above code is run it prints:
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]
Figure 11.7 repeats the three subclasses of Mortgage from Figure 8.10. Each has a distinct __init__
method that overrides the__init__
method in Mortgage. The subclass TwoRate also overrides the makePayment method of Mortgage.
Figure 11.7 Subclasses of Mortgage
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)
Figure 11.8 and Figure 11.9 contain functions that can be used to generate plots intended to provide insight about the different kinds of mortgages.
The function compareMortgages, Figure 11.8, creates a list of different kinds of mortgages, and simulates making a series of payments on each, as it did in Figure 8.11. It then calls plotMortgages, Figure 11.9, to produce the plots.
Figure 11.8 Compare mortgages
print('compareMortgages-Figure 11.8 Compare mortgages') 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)
The function plotMortgages in Figure 11.9 uses the plotting methods in Mortgage to produce plots containing information about each of three kinds of mortgages. The loop in plotMortgages uses the index i to select elements from the lists morts and styles in a way that ensures that different kinds of mortgages are represented in a consistent way across figures. For example, since the third element in morts is a variable-rate mortgage and the third element in styles is 'k:', the variable-rate mortgage is always plotted using a black dotted line. The local function labelPlot is used to generate appropriate titles and axis labels for each plot. The calls of pylab.figure
ensure that titles and labels are associated with the appropriate plot.
The call:
compareMortgages(amt=200000, years=30, fixedRate=0.07, pts = 3.25, ptsRate=0.05, varRate1=0.045, varRate2=0.095, varMonths=48)
produces plots (Figure 11.10 - Figure 11.12) that shed some light on the mortgages discussed in Section 8.4.
Figure 11.9 Generate mortgage plots
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:'] #Give names to figure numbers 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]) pylab.figure(payments) 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 $')
The plot in Figure 11.10, which was produced by invocations of plotPayments, simply plots each payment of each mortgage against time. The box containing the key appears where it does because of the value supplied to the keyword argument loc used in the call to pylab.legend. When loc is bound to 'best' the location is chosen automatically. This plot makes it clear how the monthly payments vary (or don’t) over time, but doesn’t shed much light on the relative costs of each kind of mortgage.
Figure 11.10 Monthly payments of different kinds of mortgages
運行報錯:
Traceback (most recent call last): File "11.PlottingAndMoreAboutClasses.py", line 237, in <module> compareMortgages(amt=200000, years=30, fixedRate=0.07, pts=3.25, ptsRate=0.05, varRate1=0.045, varRate2=0.095, varMonths=48) File "11.PlottingAndMoreAboutClasses.py", line 199, in compareMortgages plotMortgages(morts, amt) File "11.PlottingAndMoreAboutClasses.py", line 220, in plotMortgages morts[i].plotNet(styles[i]) File "11.PlottingAndMoreAboutClasses.py", line 145, in plotNet net = pylab.array(totPd) - equityAcquired ValueError: operands could not be broadcast together with shapes (2,) (361,) Gaowei:F3.MIT_Python Gaowei$
The plots in Figure 11.11 were produced by invocations of plotTotPd. They shed some light on the cost of each kind of mortgage by plotting the cumulative costs that have been incurred at the start of each month. The entire plot is on the left, and an enlargement of the left part of the plot is on the right.
Figure 11.11 Cost over time of different kinds of mortgages
沒有運行
The plots in Figure 11.12 show the remaining debt (on the left) and the total net cost of having the mortgage (on the right).
Figure 11.12 Balance remaining and net cost for different kinds of mortgages
沒有運行