• No results found

Example: mean angle

Below we give a more involved example that uses the math module inside a function definition. This function will be used later on in this book, where it is used to calculate the average angle value from a list of input angles. Later, in context, it will be used to look at the angles of chemical bonds that come from alternative molecular shapes (conformations) to give the average bond angle. Because angles are a circular measure (i.e. 360° equals 0°) we cannot take the simple mean value of the numbers. Instead we find the average sine and cosine of the angles, which oscillate between plus and minus one and so don’t have a problem when angles go past a full turn or become negative. Both the sine and cosine are required because each function on its own is not unique to a single angle, e.g. the sine of 45° equals the sine of 135°. Accordingly, we use the averages of both sine and cosine to generate the average angle via a special inverse tangent function, and naturally the mathematical operations come from the math module:

from math import sin, cos, degrees, radians, atan2 def meanAngle(angles, inDegrees=True):

sumSin = 0.0 for angle in angles: if inDegrees: angle = radians(angle) sumCos += cos(angle) sumSin += sin(angle) N = len(angles) meanAngle = atan2(sumSin/N, sumCos/N) if inDegrees: meanAngle = degrees(meanAngle) return meanAngle The inverse tangent function used is specifically atan2; while a normal inverse tangent (atan) operates on a single number and gives an angle between −90° and +90°, this function uses both sine and cosine values to calculate the inverse tangent and also which quadrant of the circle it lies in. Thus the result is an angle between −180° and +180°. Finally, note how all calculations are done in radians, so we must explicitly convert the input and output when working with values specified in units of degrees, signalled by the input argument inDegrees being True.

Rounding

The math module also has a couple of handy functions that convert floating point numbers to integral floating point numbers, i.e. round up or down to the nearest whole number, but give a result that is still floating point. The function floor(x) converts x to the largest whole number (integral) value less than or equal to x, and similarly ceil(x) converts x to the smallest integral value greater than or equal to x:

math.floor(5.25) # 5.0 math.ceil(5.25) # 6.0

Even though these functions remove any fractional part, after the decimal point, in both cases the data type returned is floating point. Hence these could be converted to actual Python integers using the int() function: int(math.floor(5.25)) # 5 int(math.ceil(5.25)) # 6 Note that int(x) is the integer part of x and so int(math.floor(x)) is not the same as int(x) when x is negative: int(math.floor(-5.25)) # -6; the integer less than the value int(-5.25) # -5; the integer part of the value The more usual kind of rounding, to the nearest whole number or decimal place, would be done with the inbuilt round() function; this is not in the math module. round(8.49) # 8.0 ; rounded down round(8.51) # 9.0 ; rounded up

round(3.141592, 1) # 3.1 ; to one decimal place round(3.141592, 3) # 3.142 ; to three decimal places round(9621, -2) # 9600.0 ; to nearest hundred

As illustrated above, the second argument in round() specifies how many decimal places the rounding is done to, and this number can be negative to round off at positions above 1.0; to the nearest ten, hundred, thousand etc. Note that even rounded numbers will still be subject to floating point errors.

In Python 3 the behaviour of round() changed slightly. Consider the case when the second argument takes its default value, 0. In Python 2, the function rounds to the number with the larger magnitude if the fractional part of the argument is 0.5, but in Python 3 this only happens if that number is even, otherwise it rounds to the number with the smaller magnitude. round(7.5) # 8.0 in both Python 2 and Python 3 round(8.5) # 9.0 in Python 2, 8.0 in Python 3 Thus in Python 3 you always get an even number in this circumstance.

Plotting

To go along with mathematical operations it is naturally often handy to plot the numerical values as a graph or chart. To illustrate this we will use the popular Matplotlib module which is often included with SciPy and NumPy packages. Naturally, the following examples assume that we have installed the Matplotlib, otherwise you will get an error from the import command. From the matplotlib module we will import pyplot, which has lots of helpful functions that can be used to display information as charts and graphs.

from matplotlib import pyplot

To use pyplot we first create some example values in a list and then call the .plot() function, passing the data in as an argument. This will create a line graph from the values list, but doesn’t immediately display anything on screen. values = [x*x for x in range(10)] pyplot.plot(values) To actually display the line graph on screen we call the .show() function, which should cause a pop-up window to appear. pyplot.show()

In order to create a graph with multiple data lines we can repeatedly use plot() with different data lists, which will all be added to the same graph, before finally invoking show().

valuesA = [x*x for x in range(1,10)] valuesB = [100.0/x for x in range(1,10)] pyplot.plot(valuesA)

pyplot.show()

Note that once we call show() the current graph data is completely cleared, i.e. we will get a blank plot if we repeat show() immediately after. So far the graphs have all been plotted by supplying height (y axis) information for the values, which are plotted in order. However, by passing two data lists to each plot we can also specify the values for the x axis:

xVals = range(21,30)

yVals = [100.0/x for x in range(1,10)] pyplot.plot(xVals, yVals)

pyplot.show()

As standard the plot() function will use relatively sensible defaults to determine the look of the graphs. However, there are many extra options that can be specified to control the drawing of the lines, axes, legends etc. Here we create a thicker purple line by setting the color and linewidth attributes. Also, we give the line plot a textual label which will appear if legend() is used. For the axes we control the range of displayed values for the y axis with ylim(), here making it wider than the default, which would only go up to the extremes of the data range, and manually specify the values for the tick marks on the axis using yticks() (naturally xlim() and xticks() are also available). pyplot.plot(xVals, yVals, color='purple', linewidth=3.0, label='DataName') pyplot.legend() pyplot.ylim(0, 101) pyplot.yticks([0, 25, 50, 75, 100]) As an alternative to simply showing the graph on screen we could also write an image out to a file. Accordingly we use savefig(), though note that we do this before we call show(), otherwise the latter would clear the current graph of data. Here we export to PNG format by using a file name with a ‘.png’ extension and state that we require 72 dots per inch output resolution:

pyplot.savefig(“TestGraph.png”, dpi=72) pyplot.show()

As well as simple line graphs Matplotlib can easily create many other types of data display. For example, here we create a scatter plot by using scatter() (rather than plot()), where we set the style for the marker symbol and its size (here s=40). Note that, for illustrative purposes, the valsB list is generated by using random.gauss() to take random samples from a normal distribution with a mean of 0.0 and standard deviation 1.0. from random import gauss valsA = range(100) valsB = [gauss(0.0, 1.0) for x in valsA] pyplot.scatter(valsA, valsB, s=40, marker='*') pyplot.show() We could also show the data as a bar chart, where valsB will represent the heights of

the bars:

pyplot.bar(valsA, valsB, color='green') pyplot.show()

Although we could use bar() to show histogram data, i.e. where initial values have been grouped into different regional ranges (bins), we could instead let the hist() function do the work of binning values (counting the number of data points in each range). Here, we create a histogram of the random (normally distributed) sample of points by stating we want the data grouped into 20 bin regions between the limits of −2.0 and 2.0:

pyplot.hist(valsB, bins=20, range=(-2.0, 2.0)) pyplot.show()

As a final example, the pie() function is, as you might guess, handy for making pie charts and naturally this has options to specify the colours and textual labels for each of the segments: sizes = [83, 8, 4, 5] labels = ['Arthropoda', 'Mollusca', 'Cordata', 'Others'] colors = ['#B00000', '#D0D000', '#008000', '#4040FF'] pyplot.pie(sizes, labels=labels, colors=colors) pyplot.show()

There’s a lot more functionality and graph types in pyplot (contour plots, colour matrices, box-and-whisker plots etc.) that we don’t have room to cover here. However, Matplotlib is well documented at the Matplotlib website, which we link at http://www.cambridge.org/pythonforbiology.

Figure 9.1. Example graphs and charts generated using Matplotlib. The examples

relate to the named line graph, scatter plot, histogram and pie chart functions that are available in the ‘pyplot’ module, e.g. via pyplot.hist(data).