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, 圖片的意義可以表達近萬字 (“A picture's meaning can express ten thousand words”). 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.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.”57 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.
A complete user’s guide for PyLab is at the Web site matplotlib.net/users/index. 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. Let’s start with a simple example that uses pylab.plot to produce two plots. Executing import pylab pylab.figure(1) #create figure 1 pylab.plot([1,2,3,4], [1,7,3,5]) #draw on figure 1 pylab.show() #show figure on screen will cause a window to appear on your computer monitor. Its exact appearance may depend on the operating system on your machine, but it will look similar to the following: 57 http://www.com/products/matlab/description1.html?s_cid=ML_b1008_desintro 142 Chapter 11.
Plotting and More About Classes The bar at the top 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. 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. The final line of code, pylab.show(), causes the window to appear on the computer screen.58 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.
The bar at the bottom of the window contains a number of push buttons. The rightmost button is used to write the plot to a file.59 The next button to the left is used to adjust the appearance of the plot in the window. The next four buttons are used for panning and zooming. And the button on the left is used to restore the figure to its original appearance after you are done playing with pan and zoom.
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. The file extension .png indicates that the file is in the Portable Networks Graphics format. This is a public domain standard for representing images.
58 In some operating systems, pylab. This is unfortunate. The usual workaround is to ensure that pylab.show() is the last line of code to be executed. 59 For those of you too young to know, the icon represents a “floppy disk.” Floppy disks were first introduced by IBM in 1971.
They were 8 inches in diameter and held all of 80,000 bytes. Unlike later floppy disks, they actually were floppy. The original IBM PC had a single 160Kbyte 5.5-inch floppy disk drive. For most of the 1970s and 1980s, floppy disks were the primary storage device for personal computers.
The transition to rigid enclosures (as represented in the icon that launched this digression) started in the mid-1980s (with the Macintosh), which didn’t stop people from continuing to call them floppy disks. Plotting and More About Classes 143 The code 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 below. 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 range(len([5, 6, 10, 3])), which is why they range from 0 to 3 in this case. Contents of Figure-Jane.png Contents of Figure-Addie.png PyLab has a notion of “current figure.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. This explains why the figure written to the file Figure-Addie.png was the second figure created.
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) produces the plot on the left below. Plotting and More About Classes 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 the code the lines pylab.ylabel('Value of Principal ($)') we get the plot above and on the right. For every plotted curve, there is an optional argument that is a format string indicating the color and line type of the plot.60 The letters and symbols of the format string are derived from those used in MATLAB, and are composed of a color indicator followed by a line-style indicator.
The default format string is 'b-', which produces a solid blue line. To plot the above with red circles, one would replace the call pylab.plot(values) by pylab.plot(values, 'ro'), which produces the plot on the right. For a complete list of color and line-style indicators, see http://matplotlib.net/api/pyplot_api. 60 In order to keep the price down, we chose to publish this book in black and white.
That posed a dilemma: should we discuss how to use color in plots or not? We concluded that color is too important to ignore. If you want to see what the plots look like in color, run the code. Plotting and More About Classes 145 It’s 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., 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.ylabel('Value of Principal ($)') produces the intentionally bizarre-looking plot 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.
So, for example, you can set the default line width to 6 points61 by executing the code pylab. 61 The point is a measure used in typography. It is equal to 1/72 of an inch, which is 0. Plotting and More About Classes The default values used in most of the examples in this book were set with the code #set line width pylab.linewidth'] = 4 #set font size for titles pylab.titlesize'] = 20 #set font size for labels on axes pylab.labelsize'] = 20 #set size of numbers on x-axis pylab.labelsize'] = 16 #set size of numbers on y-axis pylab.labelsize'] = 16 #set size of ticks on x-axis pylab.size'] = 7 #set size of ticks on y-axis pylab.size'] = 7 #set size of markers pylab.markersize'] = 10 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. For a complete discussion of how to customize settings, see http://matplotlib.net/users/customizing.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.1 enhances class Mortgage by adding methods that make it convenient to produce such plots. (The function findPayment, which is used in Mortgage, is defined in Figure 8.) The methods plotPayments and plotBalance are simple one-liners, but they do use a form of pylab.plot that we have not yet seen.
When a figure contains multiple plots, it is useful to produce a key that identifies what each plot is intended to represent.1, each invocation of pylab.plot uses the label keyword argument to associate a string with the plot produced by that invocation. (This and other keyword arguments must follow any format strings.) A key can then be added to the figure by calling the function pylab.legend, as shown in Figure 11. 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.62 62 It is an approximation because it does not perform a net present value calculation to take into account the time value of cash. Plotting and More About Classes 147 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.months = months self.payment = findPayment(loan, self.rate, months) self.legend = None #description of mortgage def makePayment(self): """Make a payment""" self.payment) reduction = self.owed[-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.paid[1:], style, label = self.legend) def plotBalance(self, style): pylab.owed, 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.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[i]) #Equity acquired through payments is amount of original loan # paid to date, which is amount of loan minus what is still owed equityAcquired = pylab.loan]*len(self.owed)) equityAcquired = equityAcquired - pylab.owed) net = pylab.array(totPd) - equityAcquired pylab.plot(net, style, label = self.1 Class Mortgage with plotting methods The expression pylab.owed) in 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 148 Chapter 11.
Plotting and More About Classes type, array, which PyLab inherits from NumPy.63 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. Consider, for example, the code a1 = pylab.