0% found this document useful (0 votes)
3 views

PythonBook

This document is a comprehensive guide on scientific and mathematical computing using Python, covering essential topics such as installation, programming basics, numerical computing with NumPy, and data visualization with Matplotlib. It includes detailed sections on Python data types, control flow, functions, and additional topics like differential equations and programming tips. The content is designed for courses at the University at Buffalo, focusing on practical applications and good programming practices.

Uploaded by

andih nyaina
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

PythonBook

This document is a comprehensive guide on scientific and mathematical computing using Python, covering essential topics such as installation, programming basics, numerical computing with NumPy, and data visualization with Matplotlib. It includes detailed sections on Python data types, control flow, functions, and additional topics like differential equations and programming tips. The content is designed for courses at the University at Buffalo, focusing on practical applications and good programming practices.

Uploaded by

andih nyaina
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 96

Scientific and Mathematical Computing

Using Python

Adam Cunningham
University at Buffalo
Department of Biostatistics
This work is licensed under the Creative Commons Attribution 4.0 International License.
To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send
a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
Contents

I Getting Started 7

1 Running Python 8
1.1 Install Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2 Jupyter Notebook . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.1 The ”Files” Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.2 The ”Running” Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.3 The ”Clusters” Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.4 The ”Conda” Tab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.5 Starting a New Notebook . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2.6 Magics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.7 Markdown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2.8 LAT
EX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

II Programming Python 15

2 Python Data Types 16


2.1 Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2 Booleans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4 Formatting Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.5 Type Conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.6 Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

3 Containers 26
3.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.2 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.4 Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4 Controlling the Flow 34


4.1 Boolean Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2 If Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.3 Conditional Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

3
4.4 For Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.5 While Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.6 Break and Continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.7 Error Handling with Try-Except . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.8 Reading and Writing Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

5 Packaging and Reusing Code 45


5.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.2 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.3 Comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.4 Generator Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.5 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

III Numerical Computing 52

6 NumPy 53
6.1 Array Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.2 Array Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.3 Array Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.4 Array Indexing and Slicing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6.5 Indexing with Integer Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.6 Indexing with Boolean Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

7 Matplotlib 65
7.1 Basic Plotting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.2 A More Complex Plotting Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.3 Bar Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
7.4 Polar Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.5 Histograms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.6 Pie Charts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.7 Contour Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
7.8 Slope Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.9 Stream Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.10 Multiple Plots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.11 Formatting Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
7.12 Formatting Mathematical Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

8 Differential Equations 78
8.1 First-Order Differential Equations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
8.2 Higher Order Linear Equations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.3 Systems of Equations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

9 Additional Topics 82
9.1 Loading Numerical Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
9.2 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.3 Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
9.4 Random Number Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.5 Sound Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9.6 Linear Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

IV Programming Tips 90

10 Programming Style 91
10.1 Choosing Good Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.2 Choosing Good Function Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
10.3 No “Magic Numbers” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
10.4 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
10.5 Errors and Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

11 Further Reading 94

Index 95

5
Preface

This book covers material used in the courses ”MTH 306: Differential Equations” and
”MTH 337: Introduction to Scientific and Mathematical Computing” taught at the Uni-
versity at Buffalo. The following areas are covered:

• Programming using Python, the scientific computing package NumPy, and the plot-
ting library Matplotlib.
• Python packages available for solving differential equations, random number gen-
eration, linear programming, animation, and loading numerical, image, and sound
data.
• Programming tips relating to good programming style.

Python is a beautiful language. Have fun!

6
I Getting Started

7
1 Running Python

1.1 Install Python

The examples in this book use Python 3.5. It is recommended that you use the Anaconda
distribution, which is available free on Windows, Mac and Linux and contains all the
packages needed (NumPy, SciPy, Matplotlib, Jupyter Notebook).

8
CHAPTER 1. RUNNING PYTHON 1.2. JUPYTER NOTEBOOK

1.2 Jupyter Notebook

The development enviroment used in this book is the Jupyter Notebook. This provides:

• An interactive environment for writing and running code.


• A way to integrate code, text and graphics in a single document.

1.2.1 The ”Files” Tab

On starting Jupyter, a window with several tabs is displayed. The first of these is the
”Files” tab. This provides a view of all the files in the folder where Jupyter Notebook was
started, similar to a folder view in ”Windows Explorer”.

Files can be selected in this window then opened, duplicated, renamed or deleted. The
button can be used to create a new text file, folder, terminal, or Jupyter Notebook.

1.2.2 The ”Running” Tab

The ”Running” tab shows the currently active terminals or notebooks in Jupyter, and can
be used to close them down.

1.2.3 The ”Clusters” Tab

The third tab is the ”Clusters” tab, which we will not consider in this class.

1.2.4 The ”Conda” Tab

The ”Conda” tab is used to manage the add-on packages that are available for Python.
These provide additional functionality not present in the core system.

9
CHAPTER 1. RUNNING PYTHON 1.2. JUPYTER NOTEBOOK

Installing a new package is done by selecting it from the list of available packages in the
left-hand window, then clicking the button to install it. The package will be displayed
in the right-hand window once it has been downloaded and installed.

1.2.5 Starting a New Notebook

A new Jupyter Notebook can be created from the ”Files” tab using the button.
The notebook opens in a web browser, and contains:

• A Title bar, containing the name of the notebook.


• A Menu bar, containing all the functions available in the notebook.
• A Tool bar, for easy access to the most commonly-used functions.
• A list of cells, containing code or text, and the results of executing the code.

Title bar
Menu bar
Tool bar

Cell

The menu bar contains the following functions.

10
CHAPTER 1. RUNNING PYTHON 1.2. JUPYTER NOTEBOOK

Menubar functions
Menu Item Functions Available
File Open, close, copy, save, or rename the current notebook.
Export the notebook to a different format (HTML, PDF etc).
Edit Cut, copy, paste or delete cells.
Split and merge cells.
Move cells up or down.
Find and replace text in the notebook.
View Turn the header, toolbar, or cell toolbars on or off.
Insert Insert new cells into the notebook.
Cell Run cells (i.e. execute the code they contain)
Set the cell type (Code/Markdown)
Turn cell output on or off.
Kernel Interrupt or restart the Python ”kernel”. Interrupting the kernel is
used if code is taking too long to execute. Restarting the kernel
starts Python again in a clean state, without any additional variables
or functions loaded.
Widgets Embed widgets (controls for interactive data)
Help Help with the Notebook, Python, and the most important add-on
packages (NumPy, SciPy, Matplotlib).

The tool bar contain the following functions.

Toolbar functions
Button Function
Save current notebook

Insert a new cell below the currently selected one

Cut/copy/paste a cell

Move the selected cell up/down

Run cell/interrupt kernel/restart kernel

Set the cell type (usually Code or Markdown)

Open the palette of all cell commands

Open the cell toolbar

Publish this notebook to Anaconda.org

Edit/show a presentation based on this notebook

11
CHAPTER 1. RUNNING PYTHON 1.2. JUPYTER NOTEBOOK

Code and text are entered in cells, which can be of different types. The main types we
will use are:

• Code cells, which contain Python code.


– Click on a cell with the mouse to start entering code.
– Enter adds a new line in the cell, without executing the code.
– Shift-Enter (or clicking the ”Play” button in the toolbar, or Cell → Run
Cells in the menubar) executes the code in the cell and moves the cursor to the
next cell.
– Tab brings up help for the function the cursor is currently in.
• Markdown cells contain text formatted using the Markdown language, and mathe-
matical formulas defined using LATEXmath syntax.

The type can be selected by either using the ”Cell Type” pull-down menu in the
toolbar, or Cell → Cell Type in the menubar.

1.2.6 Magics

Magics are instructions that perform specialized tasks. They are entered and executed in
code cells, and prefaced by ”%” for a line magic (which just applies to one line) or ”%%”
for a cell magic (which applies to the whole cell). The main ones we will be using are:

• %matplotlib inline sets up the notebook so that plots are drawn inline (in the
notebook itself).

• %run hfilei executes the Python commands in hfilei.

• %timeit hcodei records the time it takes to run a line of Python code.

• %%timeit records the time it takes to run all the Python code in a cell.

An example of timing code execution using %%timeit is as follows,

%%timeit x = range(10000)
max(x) The line ”x = range(10000)”
is run once but not timed. The
1000 loops, best of 3: 884 µs per ”max(x)” line is timed.
loop

Note that the %%timeit magic must be the first line in the code cell.

12
CHAPTER 1. RUNNING PYTHON 1.2. JUPYTER NOTEBOOK

1.2.7 Markdown

Text can be added to Jupyter Notebooks using Markdown cells. Markdown is a language
that can be used to specify formatted text such as italic and bold text, lists, hyperlinks,
tables and images. Some examples are shown below.

Markdown How it prints

# An h1 header An h1 header
## An h2 header An h2 header
### An h3 header An h3 header

#### An h4 header An h4 header

*italic* italic

**bold** bold

This is a bullet list This is a bullet list


* First item
• First item
* Second item
• Second item
This is an enumerated list This is an enumerated list
1. First item
2. Second item 1. First item
2. Second item
[UB link](https://www.buffalo.edu/) UB link

![Python](PythonImage.jpg "Python")

A horizontal line
A horizontal line
***

13
CHAPTER 1. RUNNING PYTHON 1.2. JUPYTER NOTEBOOK

1.2.8 LATEX

Mathematical expressions in Markdown cells are specified using the typesetting language
LATEX. These expressions are identified using $hformulai$ for an inline formula (displayed
within a line of text), or $$hformulai$$ for a larger formula displayed on a separate line.

Superscripts
$x^2$ x2

Subscripts
$x_1$ x1

Fractions
1
$\frac{1}{2}$ 2

Greek Letters
$\alpha, \beta, \gamma, \ldots, \omega$ α, β, γ, . . . , ω

Series
Pn
$\sum_{i = 1}^n$ i=1

Integrals
Rb
$\int_a^b$ a

Square Roots

$\sqrt{a + b}$ a+b

Overline
$\bar{x}$ x̄

Brackets
$\{1, 2, \ldots, n\}$ {1, 2, . . . , n}

Matrices
" #
1 2
$\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}$
3 4

14
II Programming Python

15
2 Python Data Types

Python is a flexible and powerful high-level language that is well suited to scientific and
mathematical computing. It has been designed with a clear and expressive syntax with a
focus on ensuring that code is readable.

2.1 Numbers

The basic numerical types used in Python are:

• Integers.
• Floats (reals).
• Complex numbers (pairs of floats).

Python will automatically convert numbers from one type to another when appropriate.
For example, adding two integers yields an integer, but adding an integer and a float
yields a float. The main arithmetic operations are +, -, *, /, and **. Operations
are evaluated in standard order - Parentheses, Exponentiation, Multiplication, Division,
Addition, Subtraction. To avoid possible ambiguity, use parentheses to make the order of
evaluation clear.

3 + 2
Addition
5

3 - 2
Subtraction
1

3 * 2
Multiplication
6

3 / 2
Division
1.5

16
CHAPTER 2. PYTHON DATA TYPES 2.1. NUMBERS

3**2
Exponentiation ( not 3^2)
9

Some other useful operations are floor division (//), modulus (%), and absolute value
(abs).

3 // 2 Floor division returns the inte-


gral part of the quotient.
1

12 % 5
Modulus returns the remain-
2 der.

abs(-88)
abs returns the absolute value.
88

Python has a built-in complex number type, and knows the rules of complex arithmetic.

1 + 2j
Generate a complex number (j
(1+2j) is used instead of i).

complex(1, 2)
Another way to generate a
(1+2j) complex number.

(1+2j).real real returns the real part of a


complex number.
1.0

(1+2j).imag imag returns the imaginary


part of a complex number.
2.0

abs(3+4j) abs returns the modulus when


applied to a complex number.
5.0

z = 1 + 2j
w = 3 - 1j Note that a ’1’ is needed in
front of the j.

17
CHAPTER 2. PYTHON DATA TYPES 2.2. BOOLEANS

z + w
Complex addition.
(4+1j)

z * w
Complex multiplication.
(5+5j)

2.2 Booleans

Python also has a Boolean type, which only takes the values True or False. These can
also be used like numbers, where True has the value 1 and False the value 0.

True or False
Logical disjunction
True

True and False


Logical conjunction
False

not True
Logical negation
False

True + 41
True has the numerical value
42 1.

False * 41
False has the numerical
0 value 0.

2.3 Strings

Strings are sequences of characters. They are identified by surrounding quote marks.

• To generate a string, enclose a sequence of characters in either single ('') or double


(””) quotes (Python doesn’t care which).
• A single character in Python is just a one-element string.
• Python strings are immutable - once defined, they can’t be changed. They can of
course still be copied or operated on to create new strings.

18
CHAPTER 2. PYTHON DATA TYPES 2.3. STRINGS

print("abc") print outputs text to the


screen (discarding the quotes).
abc

"abc" + "def" Adding two strings makes a


new string by concatenation.
"abcdef"

"abc"*3 Multiplying a string by an in-


teger repeats the string.
"abcabcabc"

print("I love 'MTH 337'!") Embedding quote marks


within a string.
I love 'MTH 337'!

A ”\” within a string is used to specify special characters such as newlines and tabs.

string1 = "abc\ndef"
print(string1)
The ”\n” character specifies a
abc newline.
def

string2 = "abc\tdef"
print(string2) The ”\t” character specifies a
tab.
abc def

Strings elements are accessed using square brackets, [].

• Indexing obtains characters from the string using a single integer to identify the
position of the character.
• Slicing obtains a substring using start:stop:step to specify characters to select.
• Indexing and slicing are zero-based - the first character is at position 0.
• Indexing and slicing is ”up to but not including” the stop position.
• A ”:” can be used to select all characters either before or after a given position.

Indexing the string ”abcde”

a b c d e

0 1 2 3 4

19
CHAPTER 2. PYTHON DATA TYPES 2.3. STRINGS

"abcde"[1] Indexing the character at index


1 (indices start at 0, not 1).
"b"

"abcde"[-1] Negative indices count back-


wards from the end of the
"e" string.

"abcde"[1:4] Slicing a string from position 1


up to (but not including) posi-
"bcd" tion 4.

"abcde"[2:] Select all characters from posi-


tion 2 to the end of the string.
"cde"

"abcde"[:2] Select all characters from the


start of the string up to (but
"ab" not including) position 2.

"abcde"[::2] Select every second character


from the whole string.
"ace"

"abcdefg"[1:5:2] Select every second character


from positions 1 up to 5.
"bd"

"abcde"[::-1] Reversing a string by reading


"edcba" it backwards.

String methods are commands specific to strings that carry out some action on the string.
They are called by following the string with a ’.’ followed by the method name.

"mth337".upper() upper converts all letters to


uppercase.
"MTH337"

"ABCDE".lower() lower converts all letters to


"abcde" lowercase.

"mth337".capitalize()
capitalize capitalizes the first
"Mth337" letter.

20
CHAPTER 2. PYTHON DATA TYPES 2.4. FORMATTING STRINGS

"abcabcabc".replace("c", "z") replace replaces all occur-


rences of one substring with
"abzabzabz" another substring.

"I love MTH337!".split()


split splits a string into a list
["I", "love", "MTH337!"] of words.

String methods can also be used for counting the number of times a given substring occurs
and for finding where it occurs.

"avada kedavra".count("a") count counts the number of


times an item occurs in a list.
5

"avada kedavra".index("ked") index finds the index where a


substring occurs in a string.
6

2.4 Formatting Strings

Strings can be formatted using the format function. This allows ”replacement fields”
surrounded by curly brackets {} in a string to be replaced by some other data. ”Format
specifications” within replacement fields define how data is to be formatted, including the
field width, padding and number of decimal places.

• Empty replacement fields {} are filled in order with the arguments given.
• Numbers inside replacement fields specify arguments by position, starting with zero.

"{} {}".format("a", "b") ”Replacement fields” {} are


filled in order by format.
"a b"

"1st: {0}, 2nd: {1}".format(3,4) The arguments to format can


also be identified by position,
"1st: 3, 2nd: 4" starting at 0.

Format specifications for the field width and padding are provided after a colon ’:’.

• A field width can be specified using :n where n is an integer specifying the number
of characters in the field.
• If the data has less characters than the field width, the default is to insert extra
spaces on the right i.e. to ”pad” on the right.

21
CHAPTER 2. PYTHON DATA TYPES 2.5. TYPE CONVERSIONS

• To pad on the left, use :>n. To explicitly pad on the right, use :<n. The > and <
are like direction arrows ”sending” the data to the indicated side of the field.
• Data can be centered in a replacement field using :ˆn.

print("{:5} {}".format("a", 2)) Using a field of width 5 with


the default padding.
a 2

print("{:>5} {}".format("a", 2)) Note the left padding in this


example.
a 2

print("{:^5} {}".format("a", 2))


Now centering ’a’ in the field
a 2 of width 5.

Format specifications for floats allow tables of numerical data to be neatly printed and
aligned.

• Integers are referred to using :d.


• Floats are referred to using :f.
• An integer after a decimal point is used to indicate how many decimal places to
display.
• Use :n.mf to indicate a replacement field of width n for a float printed to m decimal
places.

print("{:5.2f}".format(pi)) Print pi in a field of width 5 to


2 decimal places
3.14

print("{:^10.4f}".format(pi)) Padding can be combined with


other options if the padding is
3.1416 specified first.

2.5 Type Conversions

Objects can be explicitly converted from one type to another, as long as the conversion
makes sense. This is called type casting .

• Ints can be cast to floats, and both ints and floats can be cast to complex numbers.
• Complex numbers can’t be converted to ints or floats.

22
CHAPTER 2. PYTHON DATA TYPES 2.5. TYPE CONVERSIONS

• Any object can be cast to a string.


• Strings can be cast to numerical types if the string represents a valid number.

Casting is done using the functions bool, int, float, complex, and str.

bool(1)
Convert integer to boolean.
True

bool(42) Any nonzero value counts as


True.
True

bool(0)
Zero equates to False.
False

bool("")
An empty string is also False.
False

int(2.99) Convert float to integer (the


decimal part is discarded).
2

int("22")
Convert string to int.
22

float("4.567")
Convert string to float.
4.567

complex("1+2j")
Convert string to complex.
(1+2j)

float(10)
Convert integer to float.
10.0

complex(10)
Convert integer to complex
(10+0j) number.

str(True)
Convert boolean to string.
"True"

23
CHAPTER 2. PYTHON DATA TYPES 2.6. VARIABLE NAMES

str(1)
Convert integer 1 to string
"1" ”1”.

str(1.234)
Convert float to string.
"1.234"

2.6 Variable Names

Variable names can be used to refer to objects in Python. They:

• Must start with either a letter or an underscore.


• Are case sensitive. So value, VALUE, and Value all refer to different variables.
• Are assigned a value using ”=”. The variable name goes to the left of the ”=”, and
the value to assign on the right.

x = 5 Assign x the value 5 (note that


print(x) “=” is used for assignment,
not “==”).
5

y = x + 3
print(y) Assign y the value of x + 3.
8

course = "MTH 337"


print(course) course is a string (printed
without quotes).
MTH 337

a, b = 2, 3
print(a, b) Multiple variables can be as-
signed at the same time.
2 3

The values of a and b are


a, b = b, a swapped (note that the right
print(a, b) hand side is evaluated com-
pletely before the assignment
3 2
is done).

24
CHAPTER 2. PYTHON DATA TYPES 2.6. VARIABLE NAMES

z = 3
z += 2
print(z) Same as z = z + 2.

z -= 1
print(z)
Same as z = z - 1.
4

z *= 3
print(z)
Same as z = z * 3.
12

z /= 2
print(z) Same as z = z / 2.
6

z %= 5
print(z) Same as z = z % 5.
1

25
3 Containers

We often need a way to group objects of a similar kind together. Sometimes the order of
the objects is important, as in a mathematical sequence {a0 , a1 , a2 , . . .}, and sometimes
the order is irrelevant, as in the set {’cat’, ’dog’, ’hippogriff’}.
Python provides several types of container that can be used to group objects together.
Containers differ in the following ways:

• Mutable versus immutable. Mutable containers can be modified after they have been
created; immutable containers cannot.
• Ordered versus unordered. The items in an ordered container are stored in a fixed
sequence; those in an unordered container are not.
• Indexable versus non-indexable. The items in an indexed container can be retrieved
using a key; those in a non-indexable container cannot.

3.1 Lists

A list is an ordered sequence of objects, identified by surrounding square brackets, [].

• To generate a list, enclose a sequence of objects (separated by commas) in square


brackets.
• List elements can be of any type, and can be of different types within the same list.
• Lists are mutable - once created, elements can be added, replaced or deleted.

mylist = [1, "a", 6.58]


print(mylist) Use square brackets to create
a list.
[1, "a", 6.58]

len(mylist) len returns the number of ele-


ments in a list.
3

26
CHAPTER 3. CONTAINERS 3.1. LISTS

list1 = [1, 2, 3]
list2 = [4, 5, 6] Adding two lists makes a new
list1 + list2 list by concatenation.
[1, 2, 3, 4, 5, 6]

list1 * 3
Multiplying a list by an integer
[1, 2, 3, 1, 2, 3, 1, 2, 3] repeats the list.

list3 = []
print(list3) list3 is an empty list.
[]

list4 = list()
print(list4) Another way to create an
empty list.
[]

Lists can be indexed and sliced in the same way as strings, using square brackets. Indexing
and slicing can also be used with the ”=” assignment operator to change the elements of
a list.

primes = [2, 3, 5, 7, 11, 13, 17]


primes[1] Access the element at index 1
(indexing starts at 0).
3

primes[3:] List slicing, start at position 3,


through to the end.
[7, 11, 13, 17]

primes[:3] List slicing, start at the begin-


ning, end at position 2.
[2, 3, 5]

primes[2:5] List slicing, start at position 2,


end at position 4.
[5, 7, 11]

primes[::-1]
One way to reverse a list.
[17, 13, 11, 7, 5, 3, 2]

27
CHAPTER 3. CONTAINERS 3.1. LISTS

mylist[1] = "e" List elements can be changed


print(mylist) by assigning a new element at
a given index.
["a", "e", "c"]

List methods exist for changing, adding and deleting elements. These all modify an
existing list.

mylist = ["a", "b"]


mylist.append("c")
append adds an element to
print(mylist)
the end of a list.
["a", "b", "c"]

mylist.extend(["d", "e", "f"])


print(mylist) extend adds elements to a
list.
["a", "b", "c", "d", "e", "f"]

mylist.insert(0, "z")
print(mylist) insert inserts an element at a
given position.
["z", "a", "b", "c", "d", "e", "f"]

mylist.pop() pop removes the element at a


print(mylist) given position and returns it.
The default is to remove the
["z", "a", "b", "c", "d", "e"] last element.

mylist.remove("z")
print(mylist) remove removes the first in-
stance of an item from a list.
["a", "b", "c", "d", "e"]

Methods also exist for counting items in a list and where they occur.

fib = [1, 1, 2, 3, 5, 8, 13]


fib.count(1) count counts the number of
times an item occurs in a list.
2

fib.index(13) index returns the index of the


first occurrence.
6

Lists can be sorted and reversed.

28
CHAPTER 3. CONTAINERS 3.2. TUPLES

letters = ["a", "b", "c"]


letters.reverse() reverse changes an existing
print(letters) list, reversing the order of el-
ements.
["c", "b", "a"]

numbers = [2, 10, 3, 26, 5] sorted returns a sorted list,


print(sorted(numbers)) but does not modify the exist-
ing list.
[2, 3, 5, 10, 26]

numbers.sort()
print(numbers) sort sorts a list in place, mod-
ifying the existing list.
[2, 3, 5, 10, 26]

sorted(numbers, reverse=True) The reverse keyword is used to


sort in descending order.
[26, 10, 5, 3, 2]

The min and max functions find the smallest and largest items in a list.

numbers = [2, 10, 3, 26, 5]


print(min(numbers), max(numbers)) min and max find the small-
est and largest items.
2 26

3.2 Tuples

Tuples are containers like lists, with the difference being that they are immutable - once
defined, elements cannot be changed or added. Tuples are identified by surrounding
standard parentheses, ().

• To generate a tuple, enclose a sequence of objects (separated by commas) in standard


parentheses.
• Tuple indexing and slicing works in the same way as for lists and strings.
• It is an error to try to change a tuple element once the tuple has been created.

Tuples are simpler and more efficient than lists in terms of memory use and performance.
They are often preferred for ”temporary” variables that will not need to be modified. They
can also be used as dictionary keys, which lists cannot.

29
CHAPTER 3. CONTAINERS 3.3. SETS

tuple1 = ("a", "b", "c")


print(tuple1) Create a tuple using standard
parentheses.
("a", "b", "c")

tuple1[2] Tuple elements can be indexed


just like lists or strings.
"c"

tuple1[1:] Slicing works the same way for


tuples as for lists or strings.
("b", "c")

Any comma-separated sequence of values defines a tuple, which can be used to assign
values to multiple variables at a time.

tuple2 = 1, 2, 3
print(tuple2) A comma-separated sequence
of values defines a tuple.
(1, 2, 3)

(x, y) = (10, 20)


print("x =", x)
The variables on the left-hand
print("y =", y)
side are assigned to the values
x = 10 on the right.
y = 20

a, b = (2, 4) The parentheses are not


print(a, b)
strictly necessary, and can be
2 4 discarded.

3.3 Sets

Sets are containers with the same meaning they do in mathematics - unordered collections
of items with no duplicates. Sets are identified by surrounding curly brackets, {}.

• To generate a set, enclose a sequence of objects (separated by commas) in curly


brackets.
• Duplicates will be removed when creating a set or operating on existing sets.
• Sets can be used instead of lists when we know that each element is unique and
immutable (unchanging).

30
CHAPTER 3. CONTAINERS 3.3. SETS

myset = {1, 2, 3}
print(myset) Sets are created using curly
brackets.
set([1, 2, 3])

myset = set([1, 2, 3, 2])


print(myset) Creating a set from a list (note
that duplicates are removed).
set([1, 2, 3])

print(set())
set([]) creates an empty set.
set([])

The standard mathematical operations for sets are all built into Python.

set1 = {1, 2, 3}
set2 = {3, 4, 5} Create 2 sets.

1 in set1
in tests for set membership.
True

set1 | set2
Set union (the union operator
{1, 2, 3, 4, 5} can also be used).

set1 & set2


Set intersection (can also use
{3} the intersection operator).

set1 - set2
Set difference (can also use the
{1, 2} difference operator).

set1 ^ set2 Symmetric difference (can


also use the symmet-
{1, 2, 4, 5} ric difference operator).

set1 <= set2 Test if one set is a subset of


another (can also use the is-
False subset operator).

31
CHAPTER 3. CONTAINERS 3.4. DICTIONARIES

3.4 Dictionaries

Dictionaries are containers where items are accessed by a key. This makes them different
from sequence type objects such as strings, lists, and tuples, where items are accessed by
position.

• To generate a dictionary, enclose a sequence of key:value pairs (separated by


commas) in curly brackets.
• The key can be any immutable object - a number, string, or tuple.
• New dictionary elements can be added, and existing ones can be changed, by using
an assignment statement.
• Order is not preserved in a dictionary, so printing a dictionary will not necessarily
print items in the same order that they were added.

dict1 = {"x":1, "y":2, "z":3}


print(dict1) Note the colon in the
key:value pairs.
{"x": 1, "y": 2, "z": 3}

dict1["y"] Dictionary values are accessed


using the keys
2

dict1["y"] = 10
Dictionary values can be
print(mydict)
changed using the “=”
{"x": 1, "y": 10, "z": 3} assignment operator.

dict1["w"] = 0
New key:value pairs can be
print(mydict)
assigned using the “=” assign-
{"x": 1, "y": 10, "z": 3, "w": 0} ment operator.

dict1.get("a") get returns None if the key


does not exist.

dict1.get("a", 42)
get can also return a default
42 value.

dict2 = {}
print(dict2)
Creating an empty dictionary.
{}

32
CHAPTER 3. CONTAINERS 3.4. DICTIONARIES

dict3 = dict()
print(dict3) Another way to create an
empty dictionary.
{}

It is an error to attempt to access a dictionary using a key that does not exist.
This can be avoided by using the get method, which returns a default value if
the key is not found.

33
4 Controlling the Flow

4.1 Boolean Expressions

Boolean expressions are statements that either evaluate to True or False. An important
use of these expressions is for tests in conditional code that only executes if some condition
is met. Examples of Boolean expressions include the standard comparison operators below.

5 == 5
Check for equality.
True

5 != 5
Check for inequality,
False

3 < 2
Less than.
False

3 <= 3
Less than or equals.
True

"a" < "b" Strings are compared by lexi-


cographic (dictionary) order.
True

Note that any empty container evaluates to False in a Boolean expression. Examples
include empty strings (””), lists ([]), and dictionaries ({}).

4.2 If Statements

Python if statements provide a way to execute a block of code only if some condition is
True.
if statement
if <condition>:
<code to execute when condition True>
<following code>

34
CHAPTER 4. CONTROLLING THE FLOW 4.2. IF STATEMENTS

Note that:

• hconditioni is a Boolean expression, which must evaluate to True or False.


• hconditioni must be followed by a colon, :.
• The block of code to execute if hconditioni is True starts on the next line, and must
be indented.
• The convention in Python is that code blocks are indented with 4 spaces.
• The block of code to execute is finished by de-indenting back to the previous level.

from math import *


if pi > e: The block of code following
print("Pi is bigger than e!") the if statement only executes
if the condition is met.
Pi is bigger than e!

An else statement can be added after an if statement is complete. This will be followed
by the code to execute if the condition is False.

if-else statement
if <condition>:
<code to execute when condition True>
else:
<code to execute when condition False>
<following code>

The following example illustrates an if-else statement.

x, y = 2**3, 3**2
if x < y:
print("x < y") The block of code following
else: the else statement executes if
print("x >= y") the condition is not met.

x < y

Multiple elif statements (short for else-if) can be added to create a series of conditions
that are tested in turn until one succeeds. Each elif must also be followed by a condition
and a colon.

35
CHAPTER 4. CONTROLLING THE FLOW 4.3. CONDITIONAL EXPRESSIONS

elif statement
if <condition 1>:
<code to execute when condition 1 True>
elif <condition 2>:
<code to execute when condition 2 True>
else:
<code to execute if neither condition is True>
<following code>

The following example illustrates a series of conditions being tested.

score = 88
if score >= 90:
print("A")
elif score >= 80:
print("B")
Only the first two conditions
elif score >= 70:
are tested - the rest are
print("C")
skipped since the second con-
elif score >= 60:
dition is True.
print("D")
else:
print("F")

4.3 Conditional Expressions

It often happens that we want to assign a variable name some value if a condition is True,
and another value if a condition is False. Using an if statement, we would have:

if <condition>:
x = <true_value>
else:
x = <false_value>

Python provides an elegant way to do the same thing in a single line using a conditional
expression.

Conditional expression
x = <true_value> if <condition> else <false_value>

An example is given below.

36
CHAPTER 4. CONTROLLING THE FLOW 4.4. FOR LOOPS

x = 22
Note that x % 2 returns the
parity = "odd" if x % 2 else "even"
remainder when x is divided by
print(x, "has", parity, "parity")
2. Any nonzero value evalu-
22 has even parity ates as True.

4.4 For Loops

Python for loops provide a way to iterate (loop) over the items in a list, string, tuple, or
any other iterable object, executing a block of code on each pass through the loop.

for loop
for <iteration variable(s)> in <iterable>:
<code to execute each time>
<following code>

Note that:

• The for statement must be followed by a colon, :.


• One or more iteration variables are bound to the values in hiterablei on successive
passes through the loop.
• The block of code to execute each time through the loop starts on the next line, and
must be indented.
• This block of code is finished by de-indenting back to the previous level.

Sequence objects, such as strings, lists and tuples, can be iterated over as follows.

for i in [2, 4, 6]:


print(i) Iterate over the elements of a
list. The iteration variable i
2 gets bound to each element in
4 turn.
6

for char in "abc":


print(char) Iterate over the characters in a
string. The iteration variable
a char gets bound to each char-
b acter in turn.
c

37
CHAPTER 4. CONTROLLING THE FLOW 4.4. FOR LOOPS

for i, char in enumerate("abc"):


print(i, char) enumerate allows an iteration
variable to be bound to the in-
0 a dex of each item, as well as to
1 b the item itself.
2 c

The range function generates integers in a given range. It is often used inside a for loop
to iterate over some sequence of integers.

• The start, stop and step parameters to range are similar to those used to slice
lists and strings.
• Integers are only generated by range as needed, rather than as a list.

for i in range(3):
print(i) range(n) generates n consec-
utive integers, starting at 0
0
and ending at n - 1.
1
2

teens = range(13, 20) range(start, stop) generates


print(list(teens))
consecutive integers, from
[13, 14, 15, 16, 17, 18, 19] start to stop - 1.

evens = range(0, 9, 2) The third step argument to


print(list(evens)) range specifies the increment
from one integer to the next.
[0, 2, 4, 6, 8]

for i in range(5, 0, -1):


print(i)
range can also count back-
5
wards using a negative step
4
3 size.
2
1

total = 0
for i in range(1, 6):
total += i
Sum the numbers from 1 to 5.
print(total)

15

38
CHAPTER 4. CONTROLLING THE FLOW 4.4. FOR LOOPS

sum(range(1, 6)) Another (simpler) way to sum


the numbers in a given range.
15

Dictionary elements consist of key:value pairs. When iterated over, variables can be bound
to the key, the value, or both.

mydict = {"x":1, "y":2, "z":3}


for key in mydict: Iteration over a dictionary
print(key) binds to the key (note that or-
der is not preserved in a dictio-
y
nary).
x
z

for value in mydict.values():


print(value) Use values to iterate over the
dictionary values rather than
2
the keys.
1
3

for key, value in mydict.items():


print(key, value) Use items to iterate over the
dictionary keys and values to-
y 2
gether.
x 1
z 3

We can also iterate in parallel over multiple lists of equal length by using zip. This
generates a sequence of tuples, with one element of each tuple drawn from each list.

courses = [141, 142, 337]


zip(courses, ranks) generates
ranks = ["good", "better", "best!"]
a sequence of tuples. Each
zipped = zip(courses, ranks)
tuple contains one course and
print(list(zipped))
one rank, with the tuples in
[(141, "good"), (142, "better"), the same order as the list el-
(337, "best")] ements.

for course, rank in zipped:


print(course, rank) Multiple iteration variables can
be bound at each iteration of
141 good
a loop.
142 better
337 best!

39
CHAPTER 4. CONTROLLING THE FLOW 4.5. WHILE LOOPS

4.5 While Loops

Python while loops execute a block of code repeatedly as long as some condition is met.

while loop
while <condition>:
<code to execute repeatedly>
<following code>

Note that for the loop to terminate, the code must change some part of the hconditioni
so that it eventually returns False.

i = 3
while i > 0: The variable i is printed while
print(i) it remains greater than zero.
i -= 1 The code inside the loop must
change the value of i to ensure
3 that the loop eventually termi-
2 nates.
1

4.6 Break and Continue

Sometimes we need to end a loop early, either by ending just the current iteration, or by
quitting the whole loop. The statements break and continue provide a way to do this.

• To end the loop completely and jump to the following code, use the break statement.
• To end the current iteration and skip to the next item in the loop, use the continue
statement. This can often help to avoid nested if-else statements.

vowels = "aeiou"
for char in "jabbawocky":
if char in vowels: The for loop is terminated by
print("First vowel is", char) break once the first vowel is
break found.

First vowel is a

40
CHAPTER 4. CONTROLLING THE FLOW 4.7. ERROR HANDLING WITH TRY-EXCEPT

total = 0
for char in "jabbawocky":
if char in vowels:
Skip over the vowels using
continue
continue, and just count the
total += 1
consonants.
print(total, "consonants found")

7 consonants found

4.7 Error Handling with Try-Except

If an invalid operation is attempted when running code then an error is usually gener-
ated. Examples include dividing by zero, indexing past the end of a sequence, creating a
floating point number that is too large, or adding two arrays of different sizes. In these
circumstances, the code typically breaks at the point of the invalid operation.
A try-except statement can be used to handle errors more deliberately than having the
code break. This allows us to first try to execute some code that we’re not sure will work,
and then execute some other code if it doesn’t.
try-except statement
try:
<code to attempt to execute>
except:
<code to execute only when an error occurs>

Errors typically have a type associated with them, which specifies the kind of error that has
occurred. Examples include ZeroDivisionError, IndexError, OverflowError, and ValueError.
The ”except” part of a try-except statement can be specialized to handle these different
kinds of error.
try-except statement with named error types
try:
<code to attempt to execute>
except ErrorType:
<code to only execute when the named error occurs>

The following example shows how to gracefully handle an error resulting from division by
zero.

41
CHAPTER 4. CONTROLLING THE FLOW 4.8. READING AND WRITING FILES

for i in range(-2,3):
The number 10 is being di-
print(10/i)
vided by a sequence of inte-
-5.0 gers, one of which happens to
-10.0 be zero. Without error han-
Traceback (most recent call last): dling, the code breaks when
File "hstdini", line 2, in hmodulei the zero is encountered and a
ZeroDivisionError: division by zero ”ZeroDivisionError” is raised.

for i in range(-2,3):
try:
print(10/i)
except ZeroDivisionError: Using a try-except statement
print("Can't divide by zero!") to handle the ZeroDivision-
Error allows the loop to run to
-5.0
completion without breaking.
-10.0
Can’t divide by zero!
10.0
5.0

4.8 Reading and Writing Files

Several reports for this class will involve reading and analyzing data that has been stored
in a file. This typically involves three steps:

• Open the file using the open function. This returns a file handle - an object we then
use to access the text that the file contains.
• Process the file, either line-by-line, or as a single text string.
• Close the file. This is done using the close function.

It is possible to read in the entire contents of a file in one go using the functions read
and readlines. However, we may not need to read the entire contents into memory if we
are dealing with a large file and just want to extract some information from the text. In
this case, it is preferable to iterate over the lines of text that the file contains.
The following examples assume that a file named ”firstnames.txt” has been created in
the directory that Python was started in. This file contains the three lines:

”firstnames.txt”
Leonard
Penny
Sheldon

42
CHAPTER 4. CONTROLLING THE FLOW 4.8. READING AND WRITING FILES

input_file = open("firstnames.txt") Open ”firstnames.txt” for


reading using open.

for line in input_file: The lines of an open file


print(line, end='') can be iterated over in a for
loop. Note the use of the
Leonard end keyword in print(line,
Penny end=’’), since each line al-
Sheldon ready ends with a new line.

input_file.close()
Close ”firstnames.txt” using
close.

input_file = open("firstnames.txt")
first_names = input_file.read() read reads in the whole file
input_file.close() as a single string. The new-
first_names lines at the end of each line
are shown as ”\n” characters.
"Leonard\nPenny\nSheldon\n"

print(first_names)
Printing a string causes the
Leonard newline characters ”\n” to be
Penny rendered as new lines.
Sheldon

input_file = open("firstnames.txt")
data = input_file.readlines()
readlines reads in the whole
input_file.close()
file as a list, with each line as
data
a separate string.
["Leonard\n", "Penny\n", "Sheldon\n"]

Files can also be opened for writing using the ”w” option to open.

data = ["Hofstadter", "?", "Cooper"]


output_file = open("names.txt", "w") Write each string in the data
for name in data: list to a separate line in the
output_file.write(name + "\n") file. Note that new lines are
output_file.close() not automatically included, so
they need to be added.

43
CHAPTER 4. CONTROLLING THE FLOW 4.8. READING AND WRITING FILES

input_file = open("names.txt")
last_names = input_file.read()
input_file.close()
print(last_names) Check that the ”names.txt”
file has been written correctly.
Hofstadter
?
Cooper

44
5 Packaging and Reusing Code

5.1 Functions

Functions provide a way to reuse a block of code by giving it a name. The code inside
the function can then be executed by calling the function name. Using functions makes
code more understandable and easier to maintain.

• Complex problems are better understood when broken down into simpler steps. By
putting the code for each step into a well-named function, the overall structure of
the solution is easier to see and understand.
• Duplicating code in two or more places makes code longer and more difficult to
maintain. This is because changes to the code need to be made each place the code
is duplicated, leading to problems if the code is not kept in sync. Putting duplicated
code into a function which is called wherever the code is needed avoids this problem.
• Variables defined and used inside a function only exist inside the function. They
are said to be local variables, as opposed to global variables that can be accessed
everywhere. Local variables allow the same variable name to be used safely in different
parts of a program. This is because modifying the variable inside the function only
changes its value inside the function, without affecting the rest of the program.

Functions can be defined with the option of passing in additional data to be used inside the
function. The variables used to identify this additional data are the function parameters,
and the particular values passed in when the function is called are the function arguments.
The def statement is used to define functions in Python, with the syntax:

def statement
def <name> (<parameters>):
"""documentation string"""
<code>

• Functions take a list of required arguments, identified by position.


• Functions can take keyword arguments, identified by name. These can also be
assigned default values in the function definition to use if no values are passed in.
• Functions can return one or more values using the return statement. Note that
functions do not have to return a value - they could just perform some action
instead. A function stops executing as soon as a return statement is encountered.

45
CHAPTER 5. PACKAGING AND REUSING CODE 5.1. FUNCTIONS

• An optional documentation string can be added at the start of the function (before
the code) to describe what the function does. This string is usually enclosed in triple
quotes.

The arguments to a function can be specified by position, keyword, or some combination


of both. Some examples using just positional arguments are as follows.

def square(x): The function exits as soon


return x**2 as the return statement is
called. The value after the re-
print(square(3)) turn statement is sent back to
the code that called the func-
9 tion.

def multiply(x, y):


"""Return the product xy""" Parameters are bound to the
return x*y function arguments in the or-
der given. The documentation
print(multiply(3, 2)) string is placed after the colon
and before the code.
6

def minmax(data):
return min(data), max(data)
Multiple values are returned as
print(minmax([1, 3, 7, 2, 10])) a tuple.

(1, 10)

Using keyword arguments allows default values to be assigned. This is particularly useful
when a function can be called with many different options, and avoids having to call
functions with a long list of arguments.

• Keyword arguments are specified using key=default in place of a positional argu-


ment.
• Using keyword instead of positional arguments means we don’t need to remember
the order of arguments, and allows the defaults to be used most of the time.
• Positional and keyword arguments can be used in the same function, as long as the
positional arguments come first.

def close_enough(x, y, tolerance=.1)


return abs(x - y) <= tolerance The tolerance argument is 0.1
by default.

46
CHAPTER 5. PACKAGING AND REUSING CODE 5.1. FUNCTIONS

close_enough(1, 1.05)
The default tolerance of 0.1 is
True used in this case.

close_enough(1, 1.05, tolerance=.01) The default tolerance is over-


ridden by the value of 0.01.
False

If the number of arguments is not known in advance, functions can be defined to take a
variable number of positional arguments and/or a variable number of keyword arguments.
We are unlikely to be using these options ourselves, although they occur frequently in the
documentation for Matplotlib.

• The positional arguments are specified as *args and are available as a tuple. In-
dividual positional arguments can then be accessed by indexing into the tuple by
position.
• The keyword arguments are specified as **kwargs and are available as a dictionary.
Individual keyword arguments can then be accessed by indexing into this dictionary
by key.

An example using a variable number of positional arguments is given below.

def poly(x, *coefficients): Defining a function to com-


"Evaluate a polynomial at value x" pute the polynomial
total = 0
a0 + a1 x + a2 x 2 + · · ·
for n, a in enumerate(coefficients):
total += a*(x**n) The *coefficients argument
return total can be a sequence of any
length. The function call here
poly(10, 1, 2, 3) computes
321 1 + 2 · 10 + 3 · 102

We may on occasion need a simple function that will only be used in a single place, and
not want to have to define and name a separate function for this purpose. In this case
we can define an anonymous or lambda function just in the place where it is needed. The
syntax for a lambda function is:

lambda statement
lambda <arguments> : <code>

The lambda statement returns an unnamed function which takes the arguments given
before the colon, and returns the result of executing the code after the colon. Typical
uses for lambda functions are where one function needs to be passed in as an argument
to a different function.

47
CHAPTER 5. PACKAGING AND REUSING CODE 5.2. MODULES

ages = [21, 19, 98] The key argument to sorted


names = ["Bruce", "Sheila", "Adam"] lets us sort the data based on
data = zip(ages, names) the first list. Using a lambda
sorted(data, key=lambda x : x[0]) function avoids having to de-
fine and name a separate func-
[(19, "Sheila"), (21, "Bruce"), (98,
tion for this simple task.
"Adam")]

5.2 Modules

A module is a file containing Python definitions and statements. These allow us to


use code created by other developers, and greatly extend what we can do with Python.
Since many different modules are available, it is possible that the same names are used
by different developers. We therefore need a way to identify which module a particular
variable or function came from.
The import statement is used to make the variables and functions in a module available
for use. We can either:

• Import everything from a module for immediate use.


• Import only certain named variables and functions from a module.
• Import everything from a module, but require that variable and function names be
prefaced by either the module name or some alias.

from math import pi


pi is now a variable name that
print(pi)
we can use, but not the rest of
3.14159265359 the math module.

from math import *


print(e) Everything in the math module
is now available.
2.71828182846

import numpy
print(numpy.arcsin(1)) Everything in numpy can be
used, prefaced by ”numpy”.
1.57079632679

import numpy as np Everything in numpy can be


print(np.cos(0)) used, prefaced by the alias
”np”.
1.0

If we want to know what a module contains, we can use the dir function. This returns a
list of all the variable and function names in the module.

48
CHAPTER 5. PACKAGING AND REUSING CODE 5.3. COMPREHENSIONS

import math
print(dir(math))

[" doc ", " name ", " package ",


"acos", "acosh", "asin", "asinh",
"atan", "atan2", "atanh", "ceil",
The math module contains
"copysign", "cos", "cosh", "degrees",
all the standard mathematical
"e", "erf", "erfc", "exp", "expm1",
functions, as well as the con-
"fabs", "factorial", "floor", "fmod",
stants ”e” and ”pi”.
"frexp", "fsum", "gamma", "hypot",
"isinf", "isnan", "ldexp", "lgamma",
"log", "log10", "log1p", "modf",
"pi", "pow", "radians", "sin",
"sinh", "sqrt", "tan", "tanh",
"trunc"]

5.3 Comprehensions

Often we want to create a container by modifying and filtering the elements of some other
sequence. Comprehensions provide an elegant way to do this, similar to mathematical set-
builder notation. For list comprehensions, the syntax is:

List comprehension
[<expression> for <variables> in <iterable> if <condition>]

The code in hexpressioni is evaluated for each item in the hcontaineri, and the result
becomes an element of the new list. The hconditioni does not have to be present but, if
it is, only elements which satisfy the condition become incorporated into the new list.

[i**2 for i in range(5)]


i**2 is evaluated for every
[0, 1, 4, 9, 16] item i in the list.

[d for d in range(1,7) if 6 % d == 0] Divisors of 6 - only elements


passing the test 6 % d == 0
[1, 2, 3, 6] are included.

We can also use a dictionary comprehension to create a dictionary without needing to


repeatedly add key:value pairs.

Dictionary comprehension
{<key>:<value> for <variables> in <iterable> if <condition>}

The following example creates a dictionary from a sequence of integers.

49
CHAPTER 5. PACKAGING AND REUSING CODE 5.4. GENERATOR EXPRESSIONS

Create a dictionary from a se-


{i:i**2 for i in range(4)}
quence of integers. Note the
{0:0, 1:1, 2:4, 3:9} key:value pairs and surround-
ing curly brackets.

5.4 Generator Expressions

We often want to iterate over the elements of some sequence, but don’t need to save the
sequence. In these cases, a list comprehension is an unnecessary overhead since the list
is created and saved in memory for a single use. A more efficient alternative is to use a
”generator expression”.

• Generator expressions are used like lists inside for loops.


• They don’t create the entire sequence before it is used, so memory doesn’t need to
be allocated to store all the elements.
• Instead, the current element is saved, and the next one is produced only when
requested by the loop.

Generator expressions have almost the same syntax as a list comprehension, but use
parentheses instead of square brackets.

Generator expression
(<expression> for <variables> in <iterable> if <condition>)

An example is given below.

squares = (i**2 for i in range(1, 4))


for s in squares:
print(s) The elements of squares are
generated as needed, not gen-
1 erated and saved in advance.
4
9

5.5 Comments

Comments are text that is included in the code but not executed. They are used to
document and explain what the code is doing. Python allows two forms of comment.

• A hash symbol # means that the rest of the line is a comment, and is not to be
executed.

50
CHAPTER 5. PACKAGING AND REUSING CODE 5.5. COMMENTS

• A documentation string is surrounded by triple quotes ”””. Everything inside the


quotes is ignored.

# This is a single-line comment


No need to comment a com-
ment.

"""This is a documentation string."""

51
III Numerical Computing

52
6 NumPy

NumPy (Numerical Python) is the fundamental package for scientific computing with
Python. It defines a new kind of container - the ndarray (usually just referred to as
an array) - that supports fast and efficient computation. NumPy also defines the basic
routines for accessing and manipulating these arrays.
Arrays have the following properties (among others):

• A shape, which is a tuple of integers. The number of integers is the number of


dimensions in the array, and the integers specify the size of each dimension.
• A dtype (data-type), which specifies the type of the objects stored in the array.

In NumPy, the dimensions of an array are referred to as axes. An example of an array


with dtype int and shape ((4, 5)) is shown below. The first axis has four elements, each
of which is a one-dimensional array with 5 elements.

[[ 0 1 2 3 4 ]
[ 5 6 7 8 9 ]
[ 10 11 12 13 14 ]
[ 15 16 17 18 19 ]]
The main differences between NumPy arrays and Python lists are:

• The objects in a NumPy array must all be of the same type - booleans, integers,
floats, complex numbers or strings.
• The size of an array is fixed at creation, and can’t be changed later.
• Arrays can be multi-dimensional.
• Mathematical operations can be applied directly to arrays. When this is done they
are applied elementwise to the array, generating another array as output. This is
much faster than iterating over a list.
• Indexing for arrays is more powerful than that for lists, and includes indexing using
integer and boolean arrays.
• Slicing an array produces a view of the original array, not a copy. Modifying this
view will change the original array.

NumPy is well documented online, with a standard tutorial and good introductory tutorial
available.

53
CHAPTER 6. NUMPY 6.1. ARRAY CREATION

6.1 Array Creation

NumPy arrays can be created:

• From a list. The elements of the list need to all be of the same type, or of a kind
that can all be cast to the same type. For example, a list consisting of both integers
and floats will generate an array of floats, since the integers can all be converted to
floats.
• According to a given shape. The array will be initialized differently depending on the
function used.
• From another array. The new array will be of the same shape as the existing array,
and could either be a copy, or initialized with some other values.
• As a result of an operation on other arrays. The standard mathematical operators
can all be applied directly to arrays. The result is an array of the same shape where
the operation has been performed separately on corresponding elements.

The following functions are the main ones we use in this class.
array Create an array from a list.
linspace Return an array of evenly spaced numbers over a specified interval.
arange Return an array of evenly spaced integers within a given interval.
empty Return an a new array of a given shape and type, without initializing entries.
zeros Return an a new array of a given shape and type, filled with zeros.
ones Return an a new array of a given shape and type, filled with ones.
empty like Return a new array with the same shape and type as a given array.
zeros like Return an array of zeros with the same shape and type as a given array.
ones like Return an array of ones with the same shape and type as a given array.
copy Return an array copy of the given object.
meshgrid Returns a pair of 2D x and y grid arrays from 1D x and y coordinate arrays.

These functions are illustrated below. The ”np” alias is standard practice when using
NumPy.

import numpy as np
x = np.array([1, 2, 3]) array(object) creates an array
print(x) from a list - note that arrays
are printed without commas.
[1 2 3]

54
CHAPTER 6. NUMPY 6.1. ARRAY CREATION

x = np.array([1, 2, 3], dtype=float) array(object, dtype) creates


print(x) an array of type dtype - the
integers are now cast to floats.
[1. 2. 3.]

x = np.linspace(0, 1, 6) linspace(start, stop, num)


print(x) returns num equally spaced
points, including endpoints.
[0. 0.2 0.4 0.6 0.8 1. ]

x = np.arange(5) arange returns an array of


print(x) evenly spaced values within a
given interval.
[0 1 2 3 4]

The functions empty, zeros and ones all take a shape argument and create an array of
that shape, initialized as appropriate.

x = np.empty((3, 2))
print(x) empty(shape) returns an ar-
ray of shape shape, initially
[[ 6.93946206e-310 6.93946206e-310]
filled with garbage.
[ 6.36598737e-314 6.36598737e-314]
[ 6.36598737e-314 0.00000000e+000]]

x = np.zeros((2, 3))
zeros(shape) returns an array
print(x)
of shape shape filled with ze-
[[ 0. 0. 0.] ros - note the default type is
[ 0. 0. 0.]] float.

x = np.ones((2, 3), dtype=int) ones(shape, dtype) returns an


print(x) array of shape shape filled with
ones - using dtype=int casts
[[ 1 1 1]
the elements to type int.
[ 1 1 1]]

Arrays can be created directly from other arrays using empty like, zeros like, ones like
and copy.

x = np.arange(3, dtype=float)
print(x) Create an array of floats using
arange.
[0. 1. 2.]

55
CHAPTER 6. NUMPY 6.1. ARRAY CREATION

y = np.empty_like(x)
print(y) y has the same shape as x, but
is initially filled with garbage.
[ 0.00000000e+000 6.51913678e+091
6.95022185e-310]

y = np.zeros_like(x)
print(y) y has the same shape as x, but
is initialized with zeros.
[ 0. 0. 0.]

y = np.ones_like(x)
print(y) y has the same shape as x, but
is initialized with ones.
[ 1. 1. 1.]

y = np.copy(x)
print(y) y is a copy of x - changing y
will not change x.
[0. 1. 2.]

The function meshgrid(x, y ) creates two-dimensional arrays from one-dimensional x- and


y-coordinate axes. One array contains the x-coordinates of all the points in the xy-plane
defined by these axes, and the other contains the y-coordinates. An example is shown
below.
Meshgrid

0 1 2 3
0 1 2 3
0 1 2 3
0 1 2 3

X, Y = meshgrid(x, y)

0 1 2
0 0 0 0
1 1 1 1
2 2 2 2

x = np.arange(4)
y = np.arange(3) meshgrid creates 2D x- and
X, Y = np.meshgrid(x, y) y- coordinate arrays from 1D
x- and y- coordinate arrays.

56
CHAPTER 6. NUMPY 6.2. ARRAY PROPERTIES

print(X)
X is a 2D array containing just
[[0 1 2 3] the x-coordinates of points in
[0 1 2 3] the xy plane.
[0 1 2 3]]

print(Y)
Y is a 2D array containing just
[[0 0 0 0] the y-coordinates of points in
[1 1 1 1] the xy plane.
[2 2 2 2]]

The function meshgrid is often used when we want to apply a function of two variables
to points in the x-y plane. The following example uses the arrays X and Y above.

def distance(x, y):


return np.round(sqrt(x**2 + y**2), 3)
The function distance finds
the distance of a point (x, y)
print(distance(X, Y))
from the origin, rounded to 3
[[ 0. 1. 2. 3. ] decimal places by the around
[ 1. 1.414 2.236 3.162] function.
[ 2. 2.236 2.828 3.606]]

6.2 Array Properties

The properties of an array can be accessed as follows.

import numpy as np
x = np.arange(6)
type(x) x is of type numpy.ndarray.

numpy.ndarray

x.dtype
dtype returns the element
dtype("int64") type - a 64-bit integer.

x.shape
x is a 1-dimensional array with
(6,) 6 elements in the first axis.

It is possible to create an array from the elements of an existing array, but with the
properties changed.

57
CHAPTER 6. NUMPY 6.3. ARRAY OPERATIONS

• The number of dimensions and size of each dimension can be changed using reshape
(as long as the total number of elements is unchanged).
• The dtype can be changed using astype.

x = np.arange(6).reshape((2, 3)) reshape creates a view of an


print(x) array with the same number
of elements, but a different
[[0 1 2]
shape.
[3 4 5]]

y = x.astype(float) astype casts the integers in x


print(y) to floats in y. This creates a
new array - modifying it will
[[0. 1. 2.]
not alter the original.
[3. 4. 5.]]

6.3 Array Operations

Array arithmetic is done on an element-by-element basis. Operations are applied to every


element of an array, with each result becoming an element in a new array.

import numpy as np
x = np.arange(4) Create an array of consecutive
print(x) integers using arange.
[0 1 2 3]

print(x + 1) 1 is added to every element of


the array x.
[1 2 3 4]

print(x * 2) Every element of the array x is


multiplied by 2.
[0 2 4 6]

print(x ** 2) Every element of the array x is


squared.
[0 1 4 9]

y = array([3, 2, 5, 1])
Create a second array

58
CHAPTER 6. NUMPY 6.3. ARRAY OPERATIONS

print(x)
print(y)
The elements of x are added
print(x + y)
to the corresponding elements
[0 1 2 3] of y on an element-by-element
[3 2 5 1] basis.
[3 3 7 4]

print(x**y) Exponentiation is done using


corresponding elements of the
[ 0 1 32 3] arrays x and y.

Comparison operators and other Boolean expressions are also applied on an element-by-
element basis. The Boolean expression is applied to every element of the array, with each
result becoming an element in a new boolean array.

x = np.arange(5)
print(x) The Boolean expression is
print(x % 2 == 0) evaluated for each element
separately, resulting in an ar-
[0 1 2 3 4] ray of booleans.
[ True False True False True]

x = np.arange(4)
y = np.array([3, 2, 5, 1])
print(x) The comparison is done on an
print(y) elementwise basis between ele-
print(x < y) ments of arrays x amd y. The
result is an array of booleans.
[0 1 2 3]
[3 2 5 1]
[ True True True False]

NumPy contains vectorized versions of all the basic mathematical functions. Note that
they need to be imported before we can use them. Some examples are given below.

x = np.arange(3)
print(x) Create an array.
[0 1 2]

print(np.sin(x)) sin is applied to each element,


to create a new array.
[ 0. 0.84147098 0.90929743]

59
CHAPTER 6. NUMPY 6.4. ARRAY INDEXING AND SLICING

print(np.exp(x))
exp is the exponential opera-
[ 1. 2.71828183 7.3890561 ] tor.

x = np.random.randint(5, size=(2,3)) random.randint returns an


print(x) array of a given size filled
with randomly selected inte-
[[1 4 3]
gers from a given range.
[2 2 3]]

print(x.min(), x.max()) min and max calculate the


minimum and maximum val-
1 4 ues across the entire array.

print(x.min(axis=0)) The axis argument finds each


print(x.min(axis=1)) minimum along a given axis.
The resulting array is the
[1 2 3] shape of the original array, but
[1 2] with the given axis removed.

print(x.sum()) sum sums all the elements of


an array.
15

print(x.sum(axis=0)) Providing the axis argument


sums along the given axis.
[3 6 6]

6.4 Array Indexing and Slicing

Arrays can be indexed and sliced using square brackets in the same way as lists.

• An index or start:stop:step filter needs to be provided for each axis, separated


by commas.
• Indexing is zero-based, as it is with lists.
• Slicing an array produces a view of the original array, not a copy. Modifying this
view will change the original array.

60
CHAPTER 6. NUMPY 6.5. INDEXING WITH INTEGER ARRAYS

import numpy as np
x = np.arange(20).reshape((4, 5))
print(x) reshape provides a fast way to
create a 2D array from a 1D
[[ 0 1 2 3 4]
array.
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]

print(x[1,2]) Indexing is done into each axis


in order - row 1, column 2.
7

print(x[1]) Row 1 can be selected using an


integer.
[5 6 7 8 9]

print(x[:,1]) Slicing can be used to select


the first element of every axis
[ 1 6 11 16] (i.e. column 1).

print(x[1:3, 1:4]) Slice rows 1 and 2 using 1:3,


then slice columns 1, 2 and 3
[[ 6 7 8]
using 1:4.
[11 12 13]]

6.5 Indexing with Integer Arrays

Although slicing can be used to return a subset of elements from an array, its use is
restricted to either selecting consecutive elements, or to selecting elements that are sep-
arated by the same fixed amount. We sometimes want to select instead some arbitrary
subset of an array, possibly even selecting the same element more than once. Integer array
indexing provides a way to do this.

• The index is a NumPy array of integers.


• Each integer in the index array selects a corresponding element from the target array.
• The result is an array where the first axes are the same shape as the index array.

61
CHAPTER 6. NUMPY 6.5. INDEXING WITH INTEGER ARRAYS

Using an integer array as an index

x 0 1 4 9 16 25 36 49 64

index 2 5

x[index] 4 25

import numpy as np
x = np.arange(9)**2 First create the array using
print(x) arange, then square each el-
ement.
[ 0 1 4 9 16 25 36 49 64]

index = np.array([2, 5]) An array is returned contain-


print(x[index]) ing elements from the first ar-
ray, selected according to the
[4 25] integers in the second array.

The index can be an integer array of any shape, and can include duplicates as shown
below.

The indexing array contains in-


index = np.array([[2, 3], [7, 2]])
tegers that are used to index
print(index)
into the target array. Note
[[2 3] that the same elements (2, in
[7 2]] this case) can be selected more
than once.

When indexing a one-


print(x[index]) dimensional array using an
integer array, the returned
[[ 4 9]
array has the same shape as
[49 4]]
the indexing array.

The target array can also be a multidimensional array. In this case, the elements selected
by the index array will be the array elements of the target.

62
CHAPTER 6. NUMPY 6.6. INDEXING WITH BOOLEAN ARRAYS

colors = np.array([[1., 0., 0.],


The colors array is a two-
[0., 1., 0.],
dimensional array, so the
[0., 0., 1.]])
elements of colors are one-
index = np.array([1, 0, 2, 1, 1, 0])
dimensional arrays (the rows).
print(colors[index])

[[0. 1. 0.] The colors array can be


[1. 0. 0.] thought of as a ”lookup
[0. 0. 1.] table” - integer indexing looks
[0. 1. 0.] up the elements of this table,
[0. 1. 0.] and uses the values found to
[1. 0. 0.]] construct the result.

6.6 Indexing with Boolean Arrays

We can also index using arrays of booleans. In this case, the indexing array acts like
a mask, letting through only the elements in the target array that correspond to True
values in the indexing array.

Using a boolean array as a mask

x 0 1 2 3

mask True True False True

x[mask] 0 1 3

import numpy as np
x = np.arange(4)
mask = np.array([True, True, False, True])
Only the elements with a
print(x)
matching True in the mask
print(x[mask])
are selected.
[0 1 2 3]
[0 1 3]

A common use of this technique is to select the elements of an array that satisfy some
condition. This can be done by first applying the condition to the array to generate an
array of booleans, then using the resulting array as an index. The result is that only the
elements for which the condition holds true are selected.

63
CHAPTER 6. NUMPY 6.6. INDEXING WITH BOOLEAN ARRAYS

Selecting array elements that satisfy a condition

x 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

x % 3 == 0 T F F T F F T F F T F F T F F T F F T F

x[x % 3 == 0] 0 3 6 9 12 15 18

x = np.arange(20) index3 and index5 are


index3 = (x % 3 == 0) boolean arrays containing
index5 = (x % 5 == 0) True elements for the inte-
gers that are divisible by 3 and
5 respectively.

print(x[index3]) Select just the elements of x


that are divisible by 3.
[ 0 3 6 9 12 15 18]

print(x[index5]) Select just the elements of x


that are divisible by 5.
[ 0 5 10 15]

The function logical or per-


print(x[np.logical_or(index3, index5)]) forms an elementwise ”or”.
The result is the integers di-
[ 0 3 5 6 9 10 12 15 18]
visible by either 3 or 5.

The logical operators not, or and and do not get applied elementwise when
applied to NumPy arrays. The functions logical not, logical or and logi-
cal and need to be used instead.

64
7 Matplotlib

Matplotlib is a 2D plotting library for Python. It can be used to generate graphs, his-
tograms, bar charts, contour plots, polar plots, scatter plots, and many other kinds of
mathematical graphics. The ”pyplot” interface provides a MATLAB-like interface for sim-
ple plotting, and is the main one we will be using in class. The online reference provides
a full description of the available functions. A good tutorial is also available online.
The following commands are the main ones used for creating and formatting graphs.
plot Plot lines and/or markers.
scatter Scatter plot of x vs y.
show Display a figure.
title Set a title for the graph.
xlabel/ylabel Set labels for the x and y axes.
xlim/ylim Get or set the range of x and y values to be displayed.
xticks/yticks Get or set the locations and labels for the tick marks on the x and y axes.
subplot Plot multiple graphs in one figure.
figure Create a new figure.
fill between Fill the area between two curves.
legend Put a legend on the graph.

Colors, line styles, and marker styles can all be set to create customized graphs. These
are usually specified as strings, with the most frequently used options as follows.

Style options Colors


”-” solid line ”b” blue
”- -” dashed line ”g” green
”.” point marker ”r” red
”o” circle marker ”c” cyan
”s” square marker ”m” magenta
”+” plus marker ”y” yellow
”x” x marker ”k” black
”D” diamond marker ”w” white

65
CHAPTER 7. MATPLOTLIB 7.1. BASIC PLOTTING

7.1 Basic Plotting

Functions can be graphed using a call to plot(x, y ), followed by a call to show. Note
that:

• The x parameter contains the x-coordinates of the points to plot, and the y parameter
contains the y-coordinates.
• We need to import the required NumPy and Matplotlib functions for array manipu-
lation and plotting.
• If using Jupyter Notebook, calling the IPython magic %matplotlib inline calls show
automatically when the cell is executed.
• The default plotting behavior is to connect the points with a blue line.

The following example plots the exponential function in the range [0, 5].

Plotting an Exponential Function


import numpy as np # NumPy functions available with "np." prefix
import matplotlib.pyplot as plt # Import plotting functions from Matplotlib

x = np.linspace(0, 5) # Create array of equally spaced values


plt.plot(x, np.exp(x)) # Plot the exponential function
plt.show() # Finally, show the figure

160

140

120

100

80

60

40

20

0
0 1 2 3 4 5

66
CHAPTER 7. MATPLOTLIB 7.2. A MORE COMPLEX PLOTTING EXAMPLE

7.2 A More Complex Plotting Example

A range of options are available for customizing plots. These are illustrated in the example
below, which plots a sine and cosine curve on the same graph. Note that:

• The third argument to plot can be used to set colors, line types and marker types.
• Multiple plots can be drawn on one figure, followed by a single call to show.

Plotting Two Graphs on a Single Figure


import numpy as np # Imports linspace, sin, cos
import matplotlib.pyplot as plt # Import plotting functions
x = np.linspace(0, 2*np.pi, 50) # Plot 50 points on the x-axis
plt.figure(figsize=(10,7)) # Set the size of the figure
plt.plot(x, np.sin(x), label='sine') # Default style is a blue line
plt.scatter(x, np.cos(x), c='r', label='cosine') # Use 'r' for red circles
plt.xlabel('theta') # Label the x-axis
plt.xlim(0, 2*np.pi) # Limit x-axis to this range
ticks = [i*np.pi/2 for i in range(5)] # Locations of ticks on x-axis
labels = [r'$0$',r'$\pi/2$', r'$\pi$', # Labels for the x-axis ticks
r'$3\pi/2$', r'$2\pi$'] # - these are LaTeX strings
plt.xticks(ticks, labels, size='large') # Add the x-ticks and labels
plt.title('Sine and Cosine') # Add a title
plt.legend() # Legend uses the plot labels
plt.show() # Finally, show the figure

title
1.0 Sine and Cosine
sine
cosine

0.5
legend

0.0
default style

0.5 'ro' style

xlim
1.0
0 π/2 π 3π/2 2π
xticks theta xlabel

67
CHAPTER 7. MATPLOTLIB 7.3. BAR PLOTS

7.3 Bar Plots

The function bar is used to create bar plots.


• Bars are described by their height, width, and position of the left and bottom edges.
• The width argument can be used to make bars thinner or thicker.
• The face color and edge color of the bars can be specified independently.
The following example shows a bar plot with the face color set to ”c” (cyan) and edge
color set to ”b” (blue). Labels are positioned at the centers of the bars.

Bar Plot
import matplotlib.pyplot as plt # Import plotting functions

grades = ['A', 'B', 'C', 'D', 'F'] # Used to label the bars
freqs = [30, 35, 20, 10, 5] # Bar heights are frequencies
width = 0.8 # Relative width of each bar
ticks = [width/2 + i for i in range(5)] # Ticks in center of the bars
plt.bar(range(5), freqs, fc='c', ec='b') # fc/ec are face/edge colors
plt.xticks(ticks, grades) # Place labels for the bars
plt.ylim(0, 40) # Set the space at the top
plt.title('Grade distribution') # Add a title
plt.xlabel('Grade') # Add a label for the x-axis
plt.ylabel('Frequency (%)') # Add a label for the y-axis
plt.show() # Finally, show the figure

title
40 Grade distribution

35

30

25
Frequency (%)

fc = 'c'
20 ylim
width = .8
15 ec = 'b'
ylabel
10

0
A B C D F
xticks Grade xlabel

68
CHAPTER 7. MATPLOTLIB 7.4. POLAR PLOTS

7.4 Polar Plots

The function polar is used to create polar plots. These plot radius against angle in polar
coordinates.
• The first argument to polar is an array of angles, and the second argument the
corresponding radii.
• Colors, line types and marker types are specified in the same way as plot.
• Polar can be called multiple times, followed by a single call to show.
The following example shows a polar plot with the marker style to ”d” (diamond) and the
color set to ”m” (magenta).

Plotting in Polar Coordinates


import numpy as np # Import linspace, cos, pi
import matplotlib.pyplot as plt # Import plotting functions

theta = np.linspace(0, 2*np.pi, 100) # Create array of equally spaced values


r = 2 + np.cos(5*theta) # Generate radius as a function of angle
plt.polar(theta, r, marker='d', ls='None', color='m') # Diamond marker 'd'
plt.title('Polar plot') # Add the title
plt.show() # Finally, show the figure

Polar
90°plot

135° 45°

3.0
2.5
2.0
1.5
1.0
0.5
180° 0°

225° 315°

270°

69
CHAPTER 7. MATPLOTLIB 7.5. HISTOGRAMS

7.5 Histograms

The function hist is used to plot histograms. These group numerical data into ”bins”,
usually of equal width, in order to show how the data is distributed.
• Each bin covers a range of values, with the height of each bin indicating the number
of points falling in that range.
• The first argument is an array or sequence of arrays.
• The bins argument specifies the number of bins to use.
• The range argument specifies the range of values to include.
The following example plots a histogram of 1000 samples drawn from a uniform probability
distribution over [0, 1).

Plotting a Histogram
import numpy as np # Make random.rand available
import matplotlib.pyplot as plt # Import plotting functions

x = np.random.rand(1000) # 1000 random values in [0, 1)


plt.hist(x, bins=20, range=(0,1), fc='g') # Create histogram with 20 bins
plt.title('Uniform distribution') # Add a title
plt.xlabel('Value') # Add a label for the x-axis
plt.ylabel('Frequency') # Add a label for the y-axis
plt.show() # Finally, show the figure

title
70 Uniform distribution
bins = 20 fc = 'g'
60

50

40
Frequency

30

ylabel 20

10
range = (0, 1)
0
0.0 0.2 0.4 0.6 0.8 1.0
Value xlabel

70
CHAPTER 7. MATPLOTLIB 7.6. PIE CHARTS

7.6 Pie Charts

The function pie is used to create pie charts. These are a type of graph in which a circle
is divided into wedges that each represent a proportion of the whole.

• The first argument to pie is a sequence of values used for the wedge sizes.
• The labels argument is a sequence of strings providing the labels for each wedge.
• The shadow argument is a boolean specifying whether to draw a shadow beneath
the pie.

The following example shows a pie chart with shadow set to True .

Plotting a Pie Chart


import matplotlib.pyplot as plt # Import plotting functions

percentages = [55, 25, 10, 5, 5] # Wedge sizes


labels = ['A', 'B', 'C', 'D', 'F'] # List of labels for the wedges
plt.axes(aspect=1) # Aspect ratio = 1 for a true circle
plt.pie(percentages, labels=labels, shadow=True)
plt.title('MTH 337 Grade Distribution') # Add a title
plt.show() # Finally, show the figure

title
MTH 337 Grade Distribution
A
labels

shadow=True D

C
B

71
CHAPTER 7. MATPLOTLIB 7.7. CONTOUR PLOTS

7.7 Contour Plots

The functions contour and contourf are used for contour plots and filled contour plots
respectively. These are projections of a graph surface onto a plane, with the contours
showing the level curves of the graph.
• The first two arguments are one dimensional arrays representing the x- and y-
cooordinates of the points to plot.
• The third coordinate is a two dimensional array representing the z-coordinates.
• Contour levels are automatically set, although they can be customized.
• A colorbar can be added to display the level curves.
The following examples are of a filled and unfilled contour plot of the two-dimensional
2 2
Gaussian function, f (x, y) = e−(x +y ) .

Filled and Unfilled Contour Plots


import numpy as np # Imports linspace, meshgrid, exp
import matplotlib.pyplot as plt # Import plotting functions

x = np.linspace(-2,2) # Locations of x-coordinates


y = np.linspace(-2,2) # Locations of y-coordinates
XX, YY = np.meshgrid(x, y) # meshgrid returns two 2D arrays
z = np.exp(-(XX**2 + YY**2)) # z is a 2D Gaussian
plt.figure(figsize=(14,5)) # Set the figure dimensions
plt.subplot('121') # First subplot, 1 row, 2 columns
plt.contour(x, y, z) # Contour plot
plt.title('Contour plot') # Title added to first subplot
plt.colorbar() # Color bar added to first subplot
plt.subplot('122') # Second subplot
plt.contourf(x, y, z) # Filled contour plot
plt.title('Filled contour plot') # Title added to second subplot
plt.colorbar() # Color bar added to second subplot
plt.show() # Finally, show the figure

2.0 Contour plot 0.90 2.0 Filled contour plot 1.05


1.5 1.5 0.90
0.75
1.0 1.0
0.75
0.5 0.60 0.5
0.60
0.0 0.0
0.45 0.45
0.5 0.5
0.30
1.0 1.0
0.30
1.5 1.5 0.15

2.0 0.15 2.0 0.00


2.0 1.5 1.0 0.5 0.0 0.5 1.0 1.5 2.0 2.0 1.5 1.0 0.5 0.0 0.5 1.0 1.5 2.0

72
CHAPTER 7. MATPLOTLIB 7.8. SLOPE FIELDS

7.8 Slope Fields

The function quiver plots a two dimensional field of arrows, also known as a slope field.
• If four or more arguments are present, the first two arguments are one dimensional
arrays representing the x- and y-cooordinates of the arrows.
• The next two required arguments are two dimensional arrays representing the x- and
y-components of the arrow vectors.
• An optional argument can be used to change the individual colors of the arrows.
Keyword arguments allow the shape and appearance of the arrows to be customized.
The following example is of a quiver plot of the system of ordinary differential equations,
dx dy
= 1 − y2, =y−x
dt dt
Plotting a Slope Field
import numpy as np # Imports linspace, meshgrid
import matplotlib.pyplot as plt # Import plotting functions

x = np.linspace(-3.5, 3.5, 29) # Locations of x-coordinates


y = np.linspace(-2, 2, 17) # Locations of y-coordinates
xx, yy = np.meshgrid(x, y) # meshgrid returns two 2D arrays
u = 1 - yy**2 # dx/dt = 1 - y**2
v = yy - xx # dy/dt = y - x
plt.quiver(x, y, u, v, color='b') # Plot the slope field
plt.xlabel('x') # Add a label for the x-axis
plt.ylabel('y') # Add a label for the y-axis
plt.title('Quiver plot') # Add a title
plt.show() # Finally, show the figure

Quiver plot
2.0
1.5
1.0
0.5
0.0
y

0.5
1.0
1.5
2.0
3 2 1 0 1 2 3
x

73
CHAPTER 7. MATPLOTLIB 7.9. STREAM PLOTS

7.9 Stream Plots

The function streamplot plots the streamlines of a vector flow. This is similar to a slope
field, but with the arrows of the slope field connected into continuous curves.
• The first two arguments are one dimensional arrays representing the x- and y-
cooordinates of the vectors.
• The next two arguments are two dimensional arrays representing the x- and y-
components of the vectors.
• Keyword arguments allow the density, color, and thickness of the streamlines to be
customized.
The following example is of a stream plot of the same system of ordinary differential
equations used in the previous slope field example.
Plotting a Stream Plot
import numpy as np # Imports linspace, meshgrid
import matplotlib.pyplot as plt # Import plotting functions

x = np.linspace(-3.5, 3.5, 15) # Locations of x-coordinates


y = np.linspace(-2, 2, 9) # Locations of y-coordinates
xx, yy = np.meshgrid(x, y) # meshgrid returns two 2D arrays
u = 1 - yy**2 # dx/dt = 1 - y**2
v = yy - xx # dy/dt = y - x
plt.streamplot(x, y, u, v) # Plot the streamplot
plt.xlabel('x') # Add a label for the x-axis
plt.ylabel('y') # Add a label for the y-axis
plt.title('Stream plot') # Add a title
plt.show() # Finally, show the figure

Stream plot
2.0
1.5
1.0
0.5
0.0
y

0.5
1.0
1.5
2.0
3 2 1 0 1 2 3
x

74
CHAPTER 7. MATPLOTLIB 7.10. MULTIPLE PLOTS

7.10 Multiple Plots

The function subplot is used to plot multiple graphs on a single figure. This divides a
figure into a grid of rows and columns, with plotting done in the currently active subplot.

• Calls to subplot specify the number of rows, number of columns, and subplot number.
• Subplots are numbered from left to right, row by row, starting with 1 in the top left.
• All plotting is done in the location specified in the most recent call to subplot.
• If there are less than 10 rows, columns and subplots, subplot can be called with a
string argument. For example, subplot(2, 3, 4) is the same as subplot(”234”).

The example below uses 2 rows and 3 columns. The ”subplot” calls displayed on the
figure show which call corresponds to each grid location.

Displaying Multiple Plots with subplot


import matplotlib.pyplot as plt # Import plotting functions

fig=plt.figure(figsize=(8,5)) # Set the figure dimensions


nrows=2 # Number of rows
ncols=3 # Number of columns
for i in range(nrows*ncols):
plt.subplot(nrows,ncols,i+1) # Subplot numbering starts at 1

5 5 5
4 4 4
3 3 3
2 subplot(2,3,1) 2 subplot(2,3,2) 2 subplot(2,3,3)
1 1 1
0 0 0
0 2 4 6 8 10 0 2 4 6 8 10 0 2 4 6 8 10
5 5 5
4 4 4
3 3 3
2 subplot(2,3,4) 2 subplot(2,3,5) 2 subplot(2,3,6)
1 1 1
0 0 0
0 2 4 6 8 10 0 2 4 6 8 10 0 2 4 6 8 10

75
CHAPTER 7. MATPLOTLIB 7.11. FORMATTING TEXT

7.11 Formatting Text

The function text is used to add a text string to a plot at a given position.

• The first three positional arguments specify the x-position, y-position, and text string.
• The fontsize argument specifies the size of the font to use.
• The fontstyle argument specifies the style of font to use (’normal’, ’italic’ etc).
• The fontweight argument specifies how heavy the font should be (’normal’, ’bold’).
• The family argument specifies the font family to use (’serif’, ’sans-serif’ etc).
• The color argument specifies the color of the text.

These options can be combined together (for example, to specify text that is bold, red,
italic and 14-point). The example below illustrates the use of these options.

Formatting Text
import matplotlib.pyplot as plt # Import plotting functions
sizes = [10,12,14,16,18]
for sizepos, size in enumerate(sizes):
plt.text(0, sizepos, "Font size = {}".format(size), fontsize=size)

styles = ['normal', 'italic', 'oblique']


for stylepos, style in enumerate(styles):
plt.text(1, stylepos, "Style = {}".format(style), fontstyle=style)

families = ['serif', 'sans-serif', 'monospace']


for familypos, family in enumerate(families):
plt.text(2, familypos, "Family = {}".format(family), family=family)

weights = ['normal', 'bold']


for weightpos, weight in enumerate(weights):
plt.text(3, weightpos, "Weight = {}".format(weight), fontweight=weight)

colors = ['r', 'g', 'b', 'y', 'c']


for colorpos, color in enumerate(colors):
plt.text(4, colorpos, "Color = {}".format(color), color=color)

Font size = 18 Color = c


Font size = 16 Color = y
Font size = 14 Style = oblique Family = monospace Color = b
Font size = 12 Style = italic Family = sans-serif Weight = bold Color = g
Font size = 10 Style = normal Family = serif Weight = normal Color = r

76
CHAPTER 7. MATPLOTLIB 7.12. FORMATTING MATHEMATICAL EXPRESSIONS

7.12 Formatting Mathematical Expressions

LATEX provides a way to format mathematical expressions in Matplotlib graphs in a similar


way to Jupyter Notebook Markdown cells.

• Mathematical expressions are identified using r”$hformulai$” .


• The syntax for hformulai is the same as that described in section 1.4.3 on LATEX.
• These expressions can be used anywhere a string is used, such as titles, axis and tick
labels, and legends.

The example below illustrates several examples of mathematical expressions using LATEX.
Formatting Mathematical Expressions with LATEX
import numpy as np # Import arange, cumsum, pi
import matplotlib.pyplot as plt # Import plotting functions

x = np.arange(1,10)
y1 = np.cumsum(1/x) # cumsum calculates the cumulative sum
y2 = np.cumsum(1/(x**2))
width = 0.4
plt.bar(x, y1, width=width, fc='c',label=r'$\sum_{i=1}^n \frac{1}{i}$')
plt.bar(x+width, y2, width=width, fc='y', label=r'$\sum_{i=1}^n \frac{1}{i^2}$')
ticks = x + width # Shift the x-ticks to center on the bars
xlabels = [str(val) for val in x] # Labels must be strings
plt.xticks(ticks, xlabels)
ticks = [1, np.pi**2/6]
ylabels = [r'$1$', r'$\pi^2/6$'] # Note that \pi renders as a symbol
plt.yticks(ticks, ylabels)
plt.xlabel(r'$n$')
plt.legend(loc='upper left')
plt.title('Partial sum of p-series for p = 1, 2')

Partial sum of p-series for p = 1, 2


n
X
1
i = 1i
X n
1
2
i = 1i

π 2 /6

1 2 3 4 5 6 7 8 9
n

77
8 Differential Equations

Differential equations of the form:


dy
= f unc(y, t, . . .)
dt
can be solved in Python using scipy.integrate.odeint. The value of y can be a vector,
which allows systems of equations and higher-order equations to all be solved by this one
function.

• The first func argument is a function which computes the derivative of y at t.


• The second y0 argument specifies the initial state.
• The third t argument is an array of time points at which the solution is to be
evaluated.
• The keyword args is a tuple of additional arguments to be passed to func.

The value returned by odeint is an array of solutions to the differential equation for each
time in the array t. The first value in the solution is the initial value y0.
To apply odeint to an n-th order differential equation, we need to first solve for the n-th
derivative:
dn x dn−1 x
 
dx
= f t, x, , . . . , n−1
dtn dt dt
and then convert to a first-order system as follows:
dx d2 x dn−1 x
 
y = x, , 2 , . . . , n−1
dt dt dt

So:
dx d2 x dn x dx d2 x dn−1 x
    
d dx
y= , ,..., n = , , . . . , f t, x, , . . . , n−1
dt dt dt2 dt dt dt2 dt dt
The function defined by func then needs to accept the array y as an argument and return
an array of derivatives corresponding to the elements of y.

78
CHAPTER 8. DIFFERENTIAL EQUATIONS
8.1. FIRST-ORDER DIFFERENTIAL EQUATIONS

8.1 First-Order Differential Equations

The following example solves the logistic growth equation:


dP
= kP (M − P )
dt
where P is the population at time t, k controls the rate of growth, and M is the maximum
population size.
Solving a First-Order Differential Equation Using odeint
import numpy as np # Import linspace
import matplotlib.pyplot as plt # Import plotting functions
from scipy.integrate import odeint

def logistic(P, t, k, M): # Calculates derivative values


dPdt = k*P*(M - P)
return dPdt

P0 = 1 # Initial value for P


t = np.linspace(0, 10) # Array of time values
k = 0.1 # Controls growth rate
M = 10 # Maximum population size
P = odeint(logistic, P0, t, args=(k, M)) # Call odeint to solve the ODE
plt.plot(t, P) # Plot the solution against time
plt.xlabel('time') # Add a label for the x-axis
plt.ylabel('Population') # Add a label for the y-axis
plt.title('Logistic Population Growth') # Add a title
plt.show() # Finally, show the figure

Logistic Population Growth


10

8
Population

0 2 4 6 8 10
time

79
CHAPTER 8. DIFFERENTIAL EQUATIONS 8.2. HIGHER ORDER LINEAR EQUATIONS

8.2 Higher Order Linear Equations

The following example solves the damped harmonic oscillator equation:


d2 x dx
m 2
+ c + kx = 0
dt dt
where x is the position of a body at time t, m is the mass of the body, c is the damping
constant, and k is the spring constant. Because this is a second-order equation, to use
odeint we define the vector y = [x, dx/dt] = [x, v]. The derivative of y is then given by:
   
dy dx dv cv + kx
= , = v, −
dt dt dt m

Solving a Second-Order Differential Equation Using odeint


import numpy as np # Import linspace
import matplotlib.pyplot as plt # Import plotting functions
from scipy.integrate import odeint

def damped(y, t, m, c, k): # Calculates derivative values


x, v = y
a = -(c*v + k*x)/m
dydt = np.array([v, a])
return dydt

y0 = np.array([1, 0]) # Initial position and velocity


t = np.linspace(0, 5, 200) # Array of time values
m, c, k = 0.5, 1, 50 # Mass, damping, spring constants
y = odeint(damped, y0, t, args=(m, c, k)) # Call odeint to solve the ODE
plt.plot(t, y[:, 0]) # Plot the solution against time
plt.xlabel('time') # Add a label for the x-axis
plt.ylabel('x') # Add a label for the y-axis
plt.title('Damped Harmonic Motion') # Add a title
plt.show() # Finally, show the figure

Damped Harmonic Motion


1.00
0.75
0.50
0.25
x

0.00
0.25
0.50
0.75
0 1 2 3 4 5
time

80
CHAPTER 8. DIFFERENTIAL EQUATIONS 8.3. SYSTEMS OF EQUATIONS

8.3 Systems of Equations

The following example solves a system of equations known as a predator-prey model. The
state is defined by the population of predators y and their prey x, coupled by the pair of
equations:
dx dy
= x(a − py) = y(−b + qx)
dt dt
The constants a, p, b and q control rates of growth and decline due to interactions between
the species, and to natural rates of birth and death.
Solving a System of Equations Using odeint
import numpy as np # Import linspace
import matplotlib.pyplot as plt # Import plotting functions
from scipy.integrate import odeint

def predator_prey(pp, t, a, p, b, q): # Calculates derivative values


x, y = pp
dxdt = x*(a - p*y)
dydt = y*(-b + q*x)
dpp_dt = np.array([dxdt, dydt])
return dpp_dt

pp0 = np.array([70, 30]) # Initial populations


t = np.linspace(0, 80, 200) # Array of time values
args = (0.2, 0.005, 0.5, 0.01) # Growth and decline rates
pp = odeint(predator_prey, pp0, t, args=args) # Call odeint to solve the system
plt.plot(t, pp[:, 0], 'k', label='prey') # Plot the prey against time
plt.plot(t, pp[:, 1], 'b', label='predators') # Plot the predators against time
plt.xlabel('time') # Add a label for the x-axis
plt.ylabel('x, y') # Add a label for the y-axis
plt.title('Predator-Prey Model') # Add a title
plt.legend() # Add a legend
plt.show() # Finally, show the figure

Predator-Prey Model
70

60

50
x, y

40

30
prey
20 predators
0 10 20 30 40 50 60 70 80
time

81
9 Additional Topics

9.1 Loading Numerical Files

We often need to load files containing numerical data into a NumPy array for further
processing and display. Such data files typically consist of:

• Header information. This describes what the data represents and how it is formatted.
• A set of rows of numerical data. Each row contains the same number of values,
separated by some string such as a comma or tab.

The NumPy function numpy.loadtxt can be used to load such data. This returns a NumPy
array, where each row corresponds to a line in the data file. The first argument to this
function is the data file name. Some of the optional keyword arguments are shown below.

• dtype. This is the data type of values in the array, which are floats by default.
• delimiter. This is the string used to separate values in each row. By default, any
whitespace such as spaces or tabs are considered delimiters.
• skiprows. This is the number of rows to ignore at the start of the file before reading
in data. It is usually used to skip over the header information, and defaults to 0.

The example shown below uses a file called “weather.dat”, which contains the following:

Day High-Temp Low-Temp


1 77 56
2 79 62

from numpy import loadtxt


Import the loadtxt function.

data = loadtxt("weather.dat", dtype=int,


Load the ”weather.dat” file,
skiprows=1)
skipping the first row, and cre-
print(data)
ating a 2 × 3 array of integers.
[[ 1 77 56] For floats, the dtype argument
[ 2 79 62]] would not be used.

82
CHAPTER 9. ADDITIONAL TOPICS 9.2. IMAGES

9.2 Images

Matplotlib provides functions for saving, reading, and displaying images. These images
are either 2- or 3-dimensional NumPy arrays. In both cases, the first two axes of the array
correspond to the rows and columns of the image. The third axis corresponds to the color
of the pixel at each (column, row) coordinate.

• For a 2D array, the array values are floats in the range [0, 1]. These represent the
luminance (brightness) of a grayscale image from black (0) to white (1).
• For a 3D array, the third axis can have either 3 or 4 elements. In both cases, the
first three elements correspond to the red, green, and blue components of the pixel
color. These can be either floats in the range [0, 1], or 8-bit integers of type ’uint8’.
A fourth element corresponds to an “alpha” value representing transparency.

The main functions we use are:


imread Read an image file into an array.
imsave Save an image to file.
imshow Display an image array.
The following example creates an image as a 3D NumPy array of floats. The red, green
and blue color components of the image are then set directly using array slicing.

Image Files: Creating a NumPy Image Array


import numpy as np # Import zeros
import matplotlib.pyplot as plt # Import imsave, imread, imshow

img = np.zeros((100, 100, 3)) # Create an image array of 100 rows and columns.
img[:60,:60,0] = 1. # Set the top-left corner to red.
img[40:,40:,1] = 1. # Set the lower-right corner to green.
img[20:80,20:80,2] = 1. # Set the center square to blue.

plt.imsave("squares.png", img) # Save the img array to the "squares.png" file


img2 = plt.imread("squares.png") # Read the file back to the img2 array
plt.imshow(img2) # Display the image

83
CHAPTER 9. ADDITIONAL TOPICS 9.3. ANIMATION

9.3 Animation

An animation consists of a sequence of frames which are displayed one after the other.
Animation using Matplotlib essentially involves updating the data associated with some
drawn object or objects (such as points or lines), and redrawing these objects. Producing
an animation therefore involves the following steps:

• Set up the variables and data structures relating to the animation.


• Draw the first frame.
• Repeatedly update the frame with new data.

Animations are generated using FuncAnimation from the matplotlib.animation module.


This takes the following required arguments:

• fig. This is the figure in which the animation is to be drawn. It can be obtained
using either the Matplotlib figure or subplots functions.
• func. This specifies the function to call to perform a single step of the animation. It
should take a single argument which is the frame number (an integer). The frame
number is used to update the values of drawn objects such as points or lines. If the
blit keyword argument is True, this function should return a tuple of the modified
objects that need to be redrawn.

FuncAnimation also takes the following keyword arguments.

• frames. An integer specifying the number of frames to generate.


• init func. This specifies the function which is called once at the start to draw the
background that is common to all frames. If the blit keyword argument is True, this
function should also return a tuple of the modified objects that need to be redrawn.
• interval. This argument specifies the time (in ms) to wait between drawing successive
frames.
• blit. If True, the animation only redraws the parts of the plot which have changed.
This can help ensure that successive frames are displayed quickly.
• repeat. If True (the default), the animation will repeat from the beginning once it
is finished.

The following example for Jupyter Notebook animates a point circling the origin with
constant angular velocity. The animate function is defined to update the position of the
point based on the frame number.

84
CHAPTER 9. ADDITIONAL TOPICS 9.3. ANIMATION

Animation: A Point Circling the Origin


%matplotlib # Not %matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation

omega = .02 # Angular velocity


fig, ax = plt.subplots(figsize=(4,4)) # Get the figure & axes for the plot
ax.set_aspect('equal') # Make the axes have the same scale
point, = plt.plot([], [], 'ro', ms=10) # "point" is the object drawn by plot
plt.xlim(-1.5,1.5) # - note that "plot" returns a tuple
plt.ylim(-1.5,1.5) # Set limits for the entire animation

# Initialization function. This is called once to plot the background.


def init():
point.set_data([], [])
return point, # Return a tuple of the modified objects

# Animation function. This is called once per animation step.


# The integer i is the frame number.
def animate(i):
x = np.cos(i*omega)
y = np.sin(i*omega)
point.set_data(x, y) # Update the x, y coordinates of the point
return point, # Return a tuple of the modified objects

# Start the animator with a call to "FuncAnimation"


animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=20)

Some frames from this animation are shown below.

Note that in Jupyter Notebook the IPython magic we need to use is %matplotlib rather
than %matplotlib inline. Inline graphs in Jupyter Notebook are static, meaning that
once drawn, they cannot be updated. Using %matplotlib generates graphs in a separate
window, where the updated data can be displayed.

85
CHAPTER 9. ADDITIONAL TOPICS 9.4. RANDOM NUMBER GENERATION

9.4 Random Number Generation

NumPy provides a library of functions for random number generation in the numpy.random
module. These return either a sample, or an array of samples of a given size, drawn from
a given probability distribution. The main functions we use are:
random.rand Samples are drawn from a uniform distribution over [0, 1).
random.randint Samples are integers drawn from a given range.
random.randn Samples are drawn from the “standard normal” distribution.
random.normal Samples are drawn from a normal (Gaussian) distribution.
random.choice Samples are drawn from a given list or 1D array.
The following examples illustrate the use of these functions.

import numpy as np Use random.rand to gener-


print(np.random.rand()) ate a single number uniformly
drawn from the interval [0, 1).
0.723812203628

print(np.random.rand(3)) Use random.rand to generate


an array of 3 random numbers
[ 0.74654564 0.58764797 0.15557362] drawn from [0, 1).

print(np.random.rand(2, 3)) Use random.rand to generate


a 2 × 3 array of random num-
[[ 0.65382707 0.71701863 0.5738609]
bers drawn from [0, 1).
[ 0.22064692 0.57487732 0.5710538]]

print(np.random.randint(7)) Use random.randint to gen-


erate an integer drawn from
3 {0, ..., 6}.

print(np.random.randint(5,9,size=(2,4))) Use random.randint to gen-


erate a 2 × 4 array of integers
[[5 5 5 8]
drawn from {5, 6, 7, 8}.
[7 8 7 6]]

Use random.randn to gener-


print(np.random.randn(3))
ate an array of samples drawn
[ 0.47481788 -0.7690172 0.42338774] from the ”standard” normal
distribution.

Use random.normal to gen-


print(np.random.normal(100, 15)) erate a sample drawn from a
normal distribution with µ =
111.676554337
100, σ = 15.

86
CHAPTER 9. ADDITIONAL TOPICS 9.5. SOUND FILES

9.5 Sound Files

Sound is a vibration that propagates through a medium such as air as a wave of pressure
and displacement. Recording devices such as microphones convert this wave to an elec-
trical signal. This signal is then sampled at regular intervals and converted to a sequence
of numbers, which correspond to the wave amplitude at given times.
The WAV file format is a standard for storing such audio data without compression. WAV
files contain two main pieces of information:

• The rate at which the wave has been sampled, usually 44,100 times per second.
• The audio data, usually with 16 bits used per sample. This allows 216 = 65,536
different amplitude levels to be represented.

The module scipy.io.wavfile provides functions to read and write such files.
scipy.io.wavfile.read Read a WAV file, returning the sample rate and the data.
scipy.io.wavfile.write Write a NumPy array as a WAV file.

The following example creates and saves a WAV file with a single frequency at middle C,
then plots the first 1000 samples of the data.

WAV File: Middle C


import numpy as np # Import linspace, sin
import matplotlib.pyplot as plt # Import plotting functions
from scipy.io import wavfile

rate = 44100 # Number of samples/second


end = 10 # The file is 10 seconds long
time = np.linspace(0, end, rate*end+1) # Time intervals are 1/rate
freq = 261.625565 # Frequency of "middle C"
data = np.sin(2*np.pi*freq*time) # Generate the sine wave
wavfile.write("middleC.wav", rate, data) # Write the array to a WAV file
plt.plot(time[:1000], data[:1000]) # Plot the first 1000 samples
plt.show() # Finally, show the figure

87
CHAPTER 9. ADDITIONAL TOPICS 9.6. LINEAR PROGRAMMING

9.6 Linear Programming

Linear programming problems are a special class of optimization problem. They involve
finding the maximum (or minimum) of some linear objective function f (x) of a vector of
variables x = (x1 , x2 , . . . , xn ), subject to a set of linear equality and inequality constraints.
Since the objective function and constraints are linear, we can represent the problem as:

Maximize cT x, where the vector c contains the coefficients of the objective function,
subject to Aub ∗ x ≤ bub , where Aub is a matrix and bub a vector,
and Aeq ∗ x = beq , where Aeq is a matrix and beq a vector.

An example of such a problem would be: x = {x1 , x2 }. Maximize f (x) = 2x1 + 3x2
subject to the inequality constraints (i) 0 ≤ x1 ≤ 80, (ii) x2 ≥ 0, (iii) x1 + x2 ≤ 100, and
(iv) x1 + 2x2 ≤ 160.
This example is graphed below, showing the level curves of f (x).

The function scipy.optimize.linprog implements the ”simplex algorithm” we discuss in class


to solve this problem. The arguments to this function are the values c, Aub , bub , Aeq and

88
CHAPTER 9. ADDITIONAL TOPICS 9.6. LINEAR PROGRAMMING

beq given above. An optional bounds argument represents the range of permissible values
that the variables can take, with None used to indicate no limit.
Applying linprog to this problem is done as shown below.

Linear Programming: Finding the Maximum Value


import numpy as np
from scipy.optimize import linprog

c = np.array([-2, -3]) # Negative coefficients of f(x)


A_ub = np.array([[1, 1], [1, 2]]) # Matrix of the inequality coefficients
b_ub = np.array([100, 160]) # Vector of the inequality upper bounds
bounds = [(0, 80), (0, None)] # Each tuple is a (lower, upper) bound
result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds)
print(result.x) # The result "x" field holds the solution

This yields the correct solution for x1 and x2 , as seen in the graph above:

[ 40. 60.]

Note that linprog finds the minimum of f (x). To find the maximum, the
negative of the c coefficient values needs to be used instead.

89
IV Programming Tips

90
10 Programming Style

This chapter contains some tips on how to make programs easier to read and understand.
Programs are written first and foremost to be understood by human beings, not by com-
puters. Ideally, it should be possible a year from now for you to pick up the code that
you’re writing today and still understand what you were doing and why.

10.1 Choosing Good Variable Names

Good variable names make reading and debugging a program much easier. Well chosen
names are easy to decipher, and make the intent clear without additional comments.

• A variable name should fully and accurately describe the data it represents. As an
example, date may be ambiguous whereas current_date is not. A good technique
is to state in words what the variable represents, and use that for the name.
• Names that are too short don’t convey enough meaning. For example, using d for
date or cd for current date is almost meaningless. Research shows that programs
with variable names that are about 9 to 15 characters long are easiest to understand
and debug.
• Variable names should be problem-oriented, refering to the problem domain, not how
the problem is being solved. For example, planet_velocity refers to the problem,
but vector_3d refers to how this information is being represented.
• Loop indices are often given short, simple names such as i, j and k. This is okay
here, since these variables are just used in the loop, then thrown away.
• If loops are nested, longer index names such as row and column can help avoid
confusion.
• Boolean variables should have names that imply either True or False. For example,
prime_found implies that either a prime has been found, or it hasn’t.
• Boolean variables should be positive. For example, use prime_found rather than
prime_not_found, since negative names are difficult to read (particularly if they are
negated).
• Named constants should be in uppercase and refer to what the constant represents
rather than the value it has. For example, if you want to use the same color blue
for the font in every title, then define the color in one place as TITLE_FONT_COLOR
rather than FONT_BLUE. If you later decide to have red rather than blue titles, just
redefine TITLE_FONT_COLOR and it keeps the same meaning.

91
CHAPTER 10. PROGRAMMING STYLE 10.2. CHOOSING GOOD FUNCTION NAMES

10.2 Choosing Good Function Names

The recommended style for naming functions in Python is to use all lowercase letters,
separated by underscores as necessary. As with variable names, good function names can
help make the intent of the code much easier to decipher.

• For procedures (functions that do something and don’t return a value), use a verb
followed by an object. An example would be plot_prime_distribution.
• For functions that return values, use a description of what the returned value repre-
sents. An example would be miles_to_kilometers.
• Don’t use generic names such as calculate_stuff or numbered functions such
as function1. These don’t tell you what the function does, and make the code
difficult to follow.
• Describe everything that the function does, and make the function name as long as
is necessary to do so. If the function name is too long, it may be a sign that the
function itself is trying to do too much. In this case, the solution is to use shorter
functions which perform just one task.

10.3 No “Magic Numbers”

Magic numbers are numbers such as 168 or 9.81 that appear in a program without
explanation. The problem with such numbers is that the meaning is unclear from just
reading the number itself.

• Numbers should be replaced with named constants which are defined in one place,
close to the start of your code file.
• Named constants make code more readable. It’s a lot easier to understand what
HOURS_PER_WEEK is referring to than the number 168.
• If a number needs to change, named constants allow this change to be done in one
place easily and reliably.

10.4 Comments

It’s not necessary to comment every line of code, and ”obvious” comments which just
repeat what the code does should be avoided. For example, the endline comment in the
following code is redundant and does nothing to explain what the code is for.
x += 1 # Add 1 to x

Good comments serve two main purposes:

• ”Intent” comments explain the purpose of the code. They operate at the level of the
problem (why the code was written) - rather than at the programming-language level
(how the code operates). Intent is often one of the hardest things to understand
when reading code written by another programmer.

92
CHAPTER 10. PROGRAMMING STYLE 10.5. ERRORS AND DEBUGGING

• ”Summary” comments distill several lines of code into one or two sentences. These
can be scanned faster than the code itself to quickly understand what the code is
doing. For example, suppose you are creating several different graphs for a report. A
summary comment before each set of plotting commands can describe which figure
in the report the code is producing.

Endline comments are those at the end of a line, after the code. They are best avoided
for a number of reasons.

• Endline comments are short by necessity as they need to fit into the space remaining
on a line. This means that they tend to be cryptic and uninformative.
• Endline comments are difficult to keep aligned (particularly as the code changes),
and if they’re not aligned they become messy and interfere with the visual structure
of the code.

A final note is to get in the habit of documenting code files. At the top of every file,
include a block comment describing the contents of the file, the author, and the date the
file was created. An example would be:

Sample File Header


# MTH 337: Intro to Scientific and Mathematical Computing, Spring 2017
# Report 1: Primitive Pythagorean Triples
# Created by Adam Cunningham 1/30/2017

10.5 Errors and Debugging

The following suggestions may help to reduce errors.

• Test each function completely as you go.


• In the initial stages of learning Python, test each few lines of code before moving on
to the next.
• Add ”print” statements inside a function to print out the intermediate values of a
calculation. This can be used to check that a function is working as required, and
can always be commented out afterwards.

In the event of an error being generated, IPython will typically give as much information
as possible about the error. If this information is not sufficient, the %debug magic will
start the IPython debugger. This lets the current values of variables inside a function be
examined, and allows code to be stepped through one line at a time.

93
11 Further Reading

The following books may prove useful for further study or reference.

• L. Felipe Martins. IPython Notebook Essentials. Packt Publishing Ltd, Birmingham.


2014.
A fairly short introduction to using NumPy and Matplotlib in Jupyter Notebooks.
This is not a Python tutorial, although there is a brief review of Python in the
appendix.
• Steve McConnell. Code Complete: A Practical Handbook of Software Construction,
Second Edition. Microsoft Press. 2004.
A general guide to code writing and software construction, this book focuses on
questions of software design rather than any specific language. More useful to an
intermediate-level programmer who wants to improve their skills. No references to
Python.
• Bruce E. Shapiro. Scientific Computation: Python Hacking for Math Junkies. Sher-
wood Forest Books, Los Angeles. 2015.
A tutorial for Python, NumPy and Matplotlib that also covers many of the same
scientific and mathematical topics as this class.
• John M. Stewart. Python for Scientists. Cambridge University Press, Cambridge.
2014.
A good introduction to Python, NumPy, Matplotlib and three-dimensional graph-
ics. Extensive treatment of numerical solutions to ordinary, stochastic, and partial
differential equations.

94
Index

%%timeit magic, 12 elif, 35, 36


LATEX, 14, 77 else, 35
empty, 55
abs, 17 empty like, 55
and, 64 enumerate, 38
animate, 84 exp, 60
animation, 84 extend, 28
append, 28
arange, 55, 58, 62 filled contour plots, 72
around, 57 floats, 16
array creation, 54 for, 37, 38, 40, 43, 50
astype, 58 format, 21
function names, 92
bar, 68 functions, 45
bar plots, 68
Boolean expressions, 34 generator expressions, 50
Boolean type, 18
break, 40 hist, 70
histograms, 70
capitalize, 20
close, 42, 43 if, 34–36
comments, 50, 92 if-else, 35
complex numbers, 16 imag, 17
conditional expressions, 36 import, 48
container, 26 index, 21, 28
continue, 40, 41 insert, 28
contour, 72 integers, 16
contour plots, 72 intersection, 31
contourf, 72 IPython magics, 12
copy, 55 items, 39
count, 21, 28
Jupyter Notebook, 9
def, 45
dictionaries, 32 lambda, 47
dictionary comprehensions, 49 len, 26
difference, 31 line styles, 65
dir, 48 linear programming, 88
dtype, 53 linprog, 89
dtype, 57 list comprehensions, 49

95
INDEX INDEX

list methods, 28 readlines, 42, 43


loadtxt, 82 real, 17
logical and, 64 remove, 28
logical not, 64 replace, 21
logical or, 64 reshape, 58, 61
lower, 20 return, 46
magic numbers, 92 scipy.integrate.odeint, 78
Markdown, 13 scipy.io.wavfile, 87
marker styles, 65 sets, 30
math, 49 shape, 53
Matplotlib, 65 show, 66, 67
max, 29, 60 sin, 59
meshgrid, 56, 57 slope field, 73
min, 29, 60 sorted, 48
modules, 48 sound, 87
split, 21
not, 64
streamplot, 74
NumPy, 53
string indexing, 19
odeint, 78, 80 string methods, 20
ones, 55 string slicing, 19
ones like, 55 strings, 18
open, 42, 43 subplot, 75
or, 64 sum, 60
symmetric difference, 31
pie, 71
pie chart, 71 text, 76
plot, 69 try-except, 41, 42
polar, 69 tuples, 29
polar plots, 69 type casting, 22
pop, 28
print, 19 union, 31
programming style, 91 upper, 20

quiver, 73 values, 39
variable names, 24, 91
random numbers, 86
random.normal, 86 WAV files, 87
random.rand, 86 while, 40
random.randint, 60, 86
random.randn, 86 zeros, 55
range, 38 zeros like, 55
read, 42, 43 zip, 39

96

You might also like