Running and Quitting
Overview
Teaching: 20 min
Exercises: 0 minQuestions
How can I run Python programs?
Objectives
Launch the JupyterLab server.
Create a new Python script.
Create a Jupyter notebook.
Shutdown the JupyterLab server.
Understand the difference between a Python script and a Jupyter notebook.
Create Markdown cells in a notebook.
Create and run Python cells in a notebook.
Many software developers will often use an integrated development environment (IDE) or a text editor to create and edit their Python programs which can be executed through the IDE or command line directly. While this is a common approach, we are going to use the Jupyter Notebook via JupyterLab for the remainder of this workshop.
This has several advantages:
- You can easily type, edit, and copy and paste blocks of code.
- Tab complete allows you to easily access the names of things you are using and learn more about them.
- It allows you to annotate your code with links, different sized text, bullets, etc. to make it more accessible to you and your collaborators.
- It allows you to display figures next to the code that produces them to tell a complete story of the analysis.
Each notebook contains one or more cells that contain code, text, or images.
Getting Started with JupyterLab
JupyterLab is an application with a web-based user interface from Project Jupyter that enables one to work with documents and activities such as Jupyter notebooks, text editors, terminals, and even custom components in a flexible, integrated, and extensible manner. JupyterLab requires a reasonably up-to-date browser (ideally a current version of Chrome, Safari, or Firefox); Internet Explorer versions 9 and below are not supported.
JupyterLab is included as part of the Anaconda Python distribution. If you have not already installed the Anaconda Python distribution, see the setup instructions for installation instructions.
Even though JupyterLab is a web-based application, JupyterLab runs locally on your machine and does not require an internet connection.
- The JupyterLab server sends messages to your web browser.
- The JupyterLab server does the work and the web browser renders the result.
- You will type code into the browser and see the result when the web page talks to the JupyterLab server.
JupyterLab? What about Jupyter notebooks?
JupyterLab is the next stage in the evolution of the Jupyter Notebook. If you have prior experience working with Jupyter notebooks, then you will have a good idea of what to expect from JupyterLab.
Experienced users of Jupyter notebooks interested in a more detailed discussion of the similarities and differences between the JupyterLab and Jupyter notebook user interfaces can find more information in the JupyterLab user interface documentation.
Starting JupyterLab
You can start the JupyterLab server through the command line or through an application called
Anaconda Navigator
. Anaconda Navigator is included as part of the Anaconda Python distribution.
macOS - Command Line
To start the JupyterLab server you will need to access the command line through the Terminal. There are two ways to open Terminal on Mac.
- In your Applications folder, open Utilities and double-click on Terminal
- Press Command + spacebar to launch Spotlight. Type
Terminal
and then double-click the search result or hit Enter
After you have launched Terminal, type the command to launch the JupyterLab server.
$ jupyter lab
Windows Users - Command Line
To start the JupyterLab server you will need to access the Anaconda Prompt.
Press Windows Logo Key and search for Anaconda Prompt
, click the result or press enter.
After you have launched the Anaconda Prompt, type the command:
$ jupyter lab
Anaconda Navigator
To start a JupyterLab server from Anaconda Navigator you must first start Anaconda Navigator (click for detailed instructions on macOS, Windows, and Linux). You can search for Anaconda Navigator via Spotlight on macOS (Command + spacebar), the Windows search function (Windows Logo Key) or opening a terminal shell and executing the anaconda-navigator
executable from the command line.
After you have launched Anaconda Navigator, click the Launch
button under JupyterLab. You may need
to scroll down to find it.
Here is a screenshot of an Anaconda Navigator page similar to the one that should open on either macOS or Windows.
And here is a screenshot of a JupyterLab landing page that should be similar to the one that opens in your default web browser after starting the JupyterLab server on either macOS or Windows.
The JupyterLab Interface
JupyterLab has many features found in traditional integrated development environments (IDEs) but is focused on providing flexible building blocks for interactive, exploratory computing.
The JupyterLab Interface consists of the Menu Bar, a collapsable Left Side Bar, and the Main Work Area which contains tabs of documents and activities.
Menu Bar
The Menu Bar at the top of JupyterLab has the top-level menus that expose various actions available in JupyterLab along with their keyboard shortcuts (where applicable). The following menus are included by default.
- File: Actions related to files and directories such as New, Open, Close, Save, etc. The File menu also includes the Shut Down action used to shutdown the JupyterLab server.
- Edit: Actions related to editing documents and other activities such as Undo, Cut, Copy, Paste, etc.
- View: Actions that alter the appearance of JupyterLab.
- Run: Actions for running code in different activities such as notebooks and code consoles (discussed below).
- Kernel: Actions for managing kernels. Kernels in Jupyter will be explained in more detail below.
- Tabs: A list of the open documents and activities in the main work area.
- Settings: Common JupyterLab settings can be configured using this menu. There is also an Advanced Settings Editor option in the dropdown menu that provides more fine-grained control of JupyterLab settings and configuration options.
- Help: A list of JupyterLab and kernel help links.
Kernels
The JupyterLab docs define kernels as “separate processes started by the server that run your code in different programming languages and environments.” When we open a Jupyter Notebook, that starts a kernel - a process - that is going to run the code. In this lesson, we’ll be using the Jupyter ipython kernel which lets us run Python 3 code interactively.
Using other Jupyter kernels for other programming languages would let us write and execute code in other programming languages in the same JupyterLab interface, like R, Java, Julia, Ruby, JavaScript, Fortran, etc.
A screenshot of the default Menu Bar is provided below.
Left Sidebar
The left sidebar contains a number of commonly used tabs, such as a file browser (showing the contents of the directory where the JupyterLab server was launched), a list of running kernels and terminals, the command palette, and a list of open tabs in the main work area. A screenshot of the default Left Side Bar is provided below.
The left sidebar can be collapsed or expanded by selecting “Show Left Sidebar” in the View menu or by clicking on the active sidebar tab.
Main Work Area
The main work area in JupyterLab enables you to arrange documents (notebooks, text files, etc.) and other activities (terminals, code consoles, etc.) into panels of tabs that can be resized or subdivided. A screenshot of the default Main Work Area is provided below.
Drag a tab to the center of a tab panel to move the tab to the panel. Subdivide a tab panel by dragging a tab to the left, right, top, or bottom of the panel. The work area has a single current activity. The tab for the current activity is marked with a colored top border (blue by default).
Creating a Python script
- To start writing a new Python program click the Text File icon under the Other header in the Launcher tab of the Main Work Area.
- You can also create a new plain text file by selecting the New -> Text File from the File menu in the Menu Bar.
- To convert this plain text file to a Python program, select the Save File As action from the File menu in the Menu Bar and give your new text file a name that ends with the
.py
extension.- The
.py
extension lets everyone (including the operating system) know that this text file is a Python program. - This is convention, not a requirement.
- The
Creating a Jupyter Notebook
To open a new notebook click the Python 3 icon under the Notebook header in the Launcher tab in the main work area. You can also create a new notebook by selecting New -> Notebook from the File menu in the Menu Bar.
Additional notes on Jupyter notebooks.
- Notebook files have the extension
.ipynb
to distinguish them from plain-text Python programs. - Notebooks can be exported as Python scripts that can be run from the command line.
Below is a screenshot of a Jupyter notebook running inside JupyterLab. If you are interested in more details, then see the official notebook documentation.
How It’s Stored
- The notebook file is stored in a format called JSON.
- Just like a webpage, what’s saved looks different from what you see in your browser.
- But this format allows Jupyter to mix source code, text, and images, all in one file.
Arranging Documents into Panels of Tabs
In the JupyterLab Main Work Area you can arrange documents into panels of tabs. Here is an example from the official documentation.
First, create a text file, Python console, and terminal window and arrange them into three panels in the main work area. Next, create a notebook, terminal window, and text file and arrange them into three panels in the main work area. Finally, create your own combination of panels and tabs. What combination of panels and tabs do you think will be most useful for your workflow?
Solution
After creating the necessary tabs, you can drag one of the tabs to the center of a panel to move the tab to the panel; next you can subdivide a tab panel by dragging a tab to the left, right, top, or bottom of the panel.
Code vs. Text
Jupyter mixes code and text in different types of blocks, called cells. We often use the term “code” to mean “the source code of software written in a language such as Python”. A “code cell” in a Notebook is a cell that contains software; a “text cell” is one that contains ordinary prose written for human beings.
The Notebook has Command and Edit modes.
- If you press Esc and Return alternately, the outer border of your code cell will change from gray to blue.
- These are the Command (gray) and Edit (blue) modes of your notebook.
- Command mode allows you to edit notebook-level features, and Edit mode changes the content of cells.
- When in Command mode (esc/gray),
- The b key will make a new cell below the currently selected cell.
- The a key will make one above.
- The x key will delete the current cell.
- The z key will undo your last cell operation (which could be a deletion, creation, etc).
- All actions can be done using the menus, but there are lots of keyboard shortcuts to speed things up.
Command Vs. Edit
In the Jupyter notebook page are you currently in Command or Edit mode?
Switch between the modes. Use the shortcuts to generate a new cell. Use the shortcuts to delete a cell. Use the shortcuts to undo the last cell operation you performed.Solution
Command mode has a grey border and Edit mode has a blue border. Use Esc and Return to switch between modes. You need to be in Command mode (Press Esc if your cell is blue). Type b or a. You need to be in Command mode (Press Esc if your cell is blue). Type x. You need to be in Command mode (Press Esc if your cell is blue). Type z.
Use the keyboard and mouse to select and edit cells.
- Pressing the Return key turns the border blue and engages Edit mode, which allows you to type within the cell.
- Because we want to be able to write many lines of code in a single cell, pressing the Return key when in Edit mode (blue) moves the cursor to the next line in the cell just like in a text editor.
- We need some other way to tell the Notebook we want to run what’s in the cell.
- Pressing Shift+Return together will execute the contents of the cell.
- Notice that the Return and Shift keys on the right of the keyboard are right next to each other.
The Notebook will turn Markdown into pretty-printed documentation.
- Notebooks can also render Markdown.
- A simple plain-text format for writing lists, links, and other things that might go into a web page.
- Equivalently, a subset of HTML that looks like what you’d send in an old-fashioned email.
- Turn the current cell into a Markdown cell by entering the Command mode (Esc/gray) and press the M key.
In [ ]:
will disappear to show it is no longer a code cell and you will be able to write in Markdown.- Turn the current cell into a Code cell by entering the Command mode (Esc/gray) and press the y key.
Markdown does most of what HTML does.
* Use asterisks
* to create
* bullet lists.
- Use asterisks
- to create
- bullet lists.
1. Use numbers
1. to create
1. numbered lists.
- Use numbers
- to create
- numbered lists.
* You can use indents
* To create sublists
* of the same type
* Or sublists
1. Of different
1. types
- You can use indents
- To create sublists
- of the same type
- Or sublists
- Of different
- types
# A Level-1 Heading
A Level-1 Heading
## A Level-2 Heading (etc.)
A Level-2 Heading (etc.)
Line breaks
don't matter.
But blank lines
create new paragraphs.
Line breaks don’t matter.
But blank lines create new paragraphs.
[Create links](http://software-carpentry.org) with `[...](...)`.
Or use [named links][data_carpentry].
[data_carpentry]: http://datacarpentry.org
Create links with [...](...)
.
Or use named links.
Creating Lists in Markdown
Create a nested list in a Markdown cell in a notebook that looks like this:
- Get funding.
- Do work.
- Design experiment.
- Collect data.
- Analyze.
- Write up.
- Publish.
Solution
This challenge integrates both the numbered list and bullet list. Note that the bullet list is indented 2 spaces so that it is inline with the items of the numbered list.
1. Get funding. 2. Do work. * Design experiment. * Collect data. * Analyze. 3. Write up. 4. Publish.
More Math
What is displayed when a Python cell in a notebook that contains several calculations is executed? For example, what happens when this cell is executed?
7 * 3 2 + 1
Solution
Python returns the output of the last calculation.
3
Change an Existing Cell from Code to Markdown
What happens if you write some Python in a code cell and then you switch it to a Markdown cell? For example, put the following in a code cell:
x = 6 * 7 + 12 print(x)
And then run it with Shift+Return to be sure that it works as a code cell. Now go back to the cell and use Esc then m to switch the cell to Markdown and “run” it with Shift+Return. What happened and how might this be useful?
Solution
The Python code gets treated like Markdown text. The lines appear as if they are part of one contiguous paragraph. This could be useful to temporarily turn on and off cells in notebooks that get used for multiple purposes.
x = 6 * 7 + 12 print(x)
Equations
Standard Markdown (such as we’re using for these notes) won’t render equations, but the Notebook will. Create a new Markdown cell and enter the following:
$\sum_{i=1}^{N} 2^{-i} \approx 1$
(It’s probably easier to copy and paste.) What does it display? What do you think the underscore,
_
, circumflex,^
, and dollar sign,$
, do?Solution
The notebook shows the equation as it would be rendered from LaTeX equation syntax. The dollar sign,
$
, is used to tell Markdown that the text in between is a LaTeX equation. If you’re not familiar with LaTeX, underscore,_
, is used for subscripts and circumflex,^
, is used for superscripts. A pair of curly braces,{
and}
, is used to group text together so that the statementi=1
becomes the subscript andN
becomes the superscript. Similarly,-i
is in curly braces to make the whole statement the superscript for2
.\sum
and\approx
are LaTeX commands for “sum over” and “approximate” symbols.
Closing JupyterLab
- From the Menu Bar select the “File” menu and then choose “Shut Down” at the bottom of the dropdown menu. You will be prompted to confirm that you wish to shutdown the JupyterLab server (don’t forget to save your work!). Click “Shut Down” to shutdown the JupyterLab server.
- To restart the JupyterLab server you will need to re-run the following command from a shell.
$ jupyter lab
Closing JupyterLab
Practice closing and restarting the JupyterLab server.
Key Points
Python scripts are plain text files.
Use the Jupyter Notebook for editing and running Python.
The Notebook has Command and Edit modes.
Use the keyboard and mouse to select and edit cells.
The Notebook will turn Markdown into pretty-printed documentation.
Markdown does most of what HTML does.
Variables and Assignment
Overview
Teaching: 15 min
Exercises: 10 minQuestions
How can I store data in programs?
Objectives
Write programs that assign scalar values to variables and perform calculations with those values.
Correctly trace value changes in programs that use scalar assignment.
Use variables to store values.
- Variables are names for values.
- In Python the
=
symbol assigns the value on the right to the name on the left. - The variable is created when a value is assigned to it.
-
Here, Python assigns an age to a variable
age
and a name in quotes to a variablefirst_name
.age = 42 first_name = 'Ahmed'
- Variable names
- can only contain letters, digits, and underscore
_
(typically used to separate words in long variable names) - cannot start with a digit
- are case sensitive (age, Age and AGE are three different variables)
- can only contain letters, digits, and underscore
- Variable names that start with underscores like
__alistairs_real_age
have a special meaning so we won’t do that until we understand the convention.
Use print
to display values.
- Python has a built-in function called
print
that prints things as text. - Call the function (i.e., tell Python to run it) by using its name.
- Provide values to the function (i.e., the things to print) in parentheses.
- To add a string to the printout, wrap the string in single or double quotes.
- The values passed to the function are called arguments
print(first_name, 'is', age, 'years old')
Ahmed is 42 years old
print
automatically puts a single space between items to separate them.- And wraps around to a new line at the end.
Variables must be created before they are used.
- If a variable doesn’t exist yet, or if the name has been mis-spelled, Python reports an error. (Unlike some languages, which “guess” a default value.)
print(last_name)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-c1fbb4e96102> in <module>()
----> 1 print(last_name)
NameError: name 'last_name' is not defined
- The last line of an error message is usually the most informative.
- We will look at error messages in detail later.
Variables Persist Between Cells
Be aware that it is the order of execution of cells that is important in a Jupyter notebook, not the order in which they appear. Python will remember all the code that was run previously, including any variables you have defined, irrespective of the order in the notebook. Therefore if you define variables lower down the notebook and then (re)run cells further up, those defined further down will still be present. As an example, create two cells with the following content, in this order:
print(myval)
myval = 1
If you execute this in order, the first cell will give an error. However, if you run the first cell after the second cell it will print out
1
. To prevent confusion, it can be helpful to use theKernel
->Restart & Run All
option which clears the interpreter and runs everything from a clean slate going top to bottom.
Variables can be used in calculations.
- We can use variables in calculations just as if they were values.
- Remember, we assigned the value
42
toage
a few lines ago.
- Remember, we assigned the value
age = age + 3
print('Age in three years:', age)
Age in three years: 45
Use an index to get a single character from a string.
- The characters (individual letters, numbers, and so on) in a string are
ordered. For example, the string
'AB'
is not the same as'BA'
. Because of this ordering, we can treat the string as a list of characters. - Each position in the string (first, second, etc.) is given a number. This number is called an index or sometimes a subscript.
- Indices are numbered from 0.
- Use the position’s index in square brackets to get the character at that position.
atom_name = 'helium'
print(atom_name[0])
h
Use a slice to get a substring.
- A part of a string is called a substring. A substring can be as short as a single character.
- An item in a list is called an element. Whenever we treat a string as if it were a list, the string’s elements are its individual characters.
- A slice is a part of a string (or, more generally, a part of any list-like thing).
- We take a slice with the notation
[start:stop]
, wherestart
is the integer index of the first element we want andstop
is the integer index of the element just after the last element we want. - The difference between
stop
andstart
is the slice’s length. - Taking a slice does not change the contents of the original string. Instead, taking a slice returns a copy of part of the original string.
atom_name = 'sodium'
print(atom_name[0:3])
sod
Use the built-in function len
to find the length of a string.
print(len('helium'))
6
- Nested functions are evaluated from the inside out, like in mathematics.
Python is case-sensitive.
- Python thinks that upper- and lower-case letters are different,
so
Name
andname
are different variables. - There are conventions for using upper-case letters at the start of variable names so we will use lower-case letters for now.
Use meaningful variable names.
- Python doesn’t care what you call variables as long as they obey the rules (alphanumeric characters and the underscore).
flabadab = 42
ewr_422_yY = 'Ahmed'
print(ewr_422_yY, 'is', flabadab, 'years old')
- Use meaningful variable names to help other people understand what the program does.
- The most important “other person” is your future self.
Predicting Values
What is the final value of
position
in the program below? (Try to predict the value without running the program, then check your prediction.)initial = 'left' position = initial initial = 'right' print(position)
Solution
left
The
initial
variable is assigned the value'left'
. In the second line, theposition
variable also receives the string value'left'
. In third line, theinitial
variable is given the value'right'
, but theposition
variable retains its string value of'left'
.
Challenge
If you assign
a = 123
, what happens if you try to get the second digit ofa
viaa[1]
?Solution
Numbers are not strings or sequences and Python will raise an error if you try to perform an index operation on a number. In the next lesson on types and type conversion we will learn more about types and how to convert between different types. If you want the Nth digit of a number you can convert it into a string using the
str
built-in function and then perform an index operation on that string.a = 123 print(a[1])
TypeError: 'int' object is not subscriptable
a = str(123) print(a[1])
2
Choosing a Name
Which is a better variable name,
m
,min
, orminutes
? Why? Hint: think about which code you would rather inherit from someone who is leaving the lab:
ts = m * 60 + s
tot_sec = min * 60 + sec
total_seconds = minutes * 60 + seconds
Solution
minutes
is better becausemin
might mean something like “minimum” (and actually is an existing built-in function in Python that we will cover later).
Slicing practice
What does the following program print?
atom_name = 'carbon' print('atom_name[1:3] is:', atom_name[1:3])
Solution
atom_name[1:3] is: ar
Slicing concepts
Given the following string:
species_name = "Acacia buxifolia"
What would these expressions return?
species_name[2:8]
species_name[11:]
(without a value after the colon)species_name[:4]
(without a value before the colon)species_name[:]
(just a colon)species_name[11:-3]
species_name[-5:-3]
- What happens when you choose a
stop
value which is out of range? (i.e., tryspecies_name[0:20]
orspecies_name[:103]
)Solutions
species_name[2:8]
returns the substring'acia b'
species_name[11:]
returns the substring'folia'
, from position 11 until the endspecies_name[:4]
returns the substring'Acac'
, from the start up to but not including position 4species_name[:]
returns the entire string'Acacia buxifolia'
species_name[11:-3]
returns the substring'fo'
, from the 11th position to the third last positionspecies_name[-5:-3]
also returns the substring'fo'
, from the fifth last position to the third last- If a part of the slice is out of range, the operation does not fail.
species_name[0:20]
gives the same result asspecies_name[0:]
, andspecies_name[:103]
gives the same result asspecies_name[:]
Key Points
Use variables to store values.
Use
Variables persist between cells.
Variables must be created before they are used.
Variables can be used in calculations.
Use an index to get a single character from a string.
Use a slice to get a substring.
Use the built-in function
len
to find the length of a string.Python is case-sensitive.
Use meaningful variable names.
Data Types and Type Conversion
Overview
Teaching: 10 min
Exercises: 10 minQuestions
What kinds of data do programs store?
How can I convert one type to another?
Objectives
Explain key differences between integers and floating point numbers.
Explain key differences between numbers and character strings.
Use built-in functions to convert between integers, floating point numbers, and strings.
Every value has a type.
- Every value in a program has a specific type.
- Integer (
int
): represents positive or negative whole numbers like 3 or -512. - Floating point number (
float
): represents real numbers like 3.14159 or -2.5. - Character string (usually called “string”,
str
): text.- Written in either single quotes or double quotes (as long as they match).
- The quote marks aren’t printed when the string is displayed.
Use the built-in function type
to find the type of a value.
- Use the built-in function
type
to find out what type a value has. - Works on variables as well.
- But remember: the value has the type — the variable is just a label.
print(type(52))
<class 'int'>
fitness = 'average'
print(type(fitness))
<class 'str'>
In a notebook you can use the %whos
command to find out information about variables which are set in the session.
%whos
Types control what operations (or methods) can be performed on a given value.
- A value’s type determines what the program can do to it.
print(5 - 3)
2
print('hello' - 'h')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-67f5626a1e07> in <module>()
----> 1 print('hello' - 'h')
TypeError: unsupported operand type(s) for -: 'str' and 'str'
You can use the “+” and “*” operators on strings.
- “Adding” character strings concatenates them.
full_name = 'Ahmed' + ' ' + 'Walsh'
print(full_name)
Ahmed Walsh
- Multiplying a character string by an integer N creates a new string that consists of that character string repeated N times.
- Since multiplication is repeated addition.
separator = '=' * 10
print(separator)
==========
Strings have a length (but numbers don’t).
- The built-in function
len
counts the number of characters in a string.
print(len(full_name))
11
- But numbers don’t have a length (not even zero).
print(len(52))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-f769e8e8097d> in <module>()
----> 1 print(len(52))
TypeError: object of type 'int' has no len()
Must convert numbers to strings or vice versa when operating on them.
- Cannot add numbers and strings.
print(1 + '2')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-fe4f54a023c6> in <module>()
----> 1 print(1 + '2')
TypeError: unsupported operand type(s) for +: 'int' and 'str'
- Not allowed because it’s ambiguous: should
1 + '2'
be3
or'12'
? - Some types can be converted to other types by using the type name as a function.
print(1 + int('2'))
print(str(1) + '2')
3
12
Can mix integers and floats freely in operations.
- Integers and floating-point numbers can be mixed in arithmetic.
- Python 3 automatically converts integers to floats as needed.
print('half is', 1 / 2.0)
print('three squared is', 3.0 ** 2)
half is 0.5
three squared is 9.0
Variables only change value when something is assigned to them.
- If we make one cell in a spreadsheet depend on another, and update the latter, the former updates automatically.
- This does not happen in programming languages.
variable_one = 1
variable_two = 5 * variable_one
variable_one = 2
print('first is', variable_one, 'and second is', variable_two)
first is 2 and second is 5
- The computer reads the value of
variable_one
when doing the multiplication, creates a new value, and assigns it tovariable_two
. - Afterwards, the value of
variable_two
is set to the new value and not dependent onvariable_one
so its value does not automatically change whenvariable_one
changes.
Fractions
What type of value is 3.4? How can you find out?
Solution
It is a floating-point number (often abbreviated “float”). It is possible to find out by using the built-in function
type()
.print(type(3.4))
<class 'float'>
Automatic Type Conversion
What type of value is 3.25 + 4?
Solution
It is a float: integers are automatically converted to floats as necessary.
result = 3.25 + 4 print(result, 'is', type(result))
7.25 is <class 'float'>
Division Types
In Python 3, the
//
operator performs integer (whole-number) floor division, the/
operator performs floating-point division, and the%
(or modulo) operator calculates and returns the remainder from integer division:print('5 // 3:', 5 // 3) print('5 / 3:', 5 / 3) print('5 % 3:', 5 % 3)
5 // 3: 1 5 / 3: 1.6666666666666667 5 % 3: 2
If
num_subjects
is the number of subjects taking part in a study, andnum_per_survey
is the number that can take part in a single survey, write an expression that calculates the number of surveys needed to reach everyone once.Solution
We want the minimum number of surveys that reaches everyone once, which is the rounded up value of
num_subjects/ num_per_survey
. This is equivalent to performing a floor division with//
and adding 1. Before the division we need to subtract 1 from the number of subjects to deal with the case wherenum_subjects
is evenly divisible bynum_per_survey
.num_subjects = 600 num_per_survey = 42 num_surveys = (num_subjects - 1) // num_per_survey + 1 print(num_subjects, 'subjects,', num_per_survey, 'per survey:', num_surveys)
600 subjects, 42 per survey: 15
Strings to Numbers
Where reasonable,
float()
will convert a string to a floating point number, andint()
will convert a floating point number to an integer:print("string to float:", float("3.4")) print("float to int:", int(3.4))
string to float: 3.4 float to int: 3
If the conversion doesn’t make sense, however, an error message will occur.
print("string to float:", float("Hello world!"))
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-5-df3b790bf0a2> in <module> ----> 1 print("string to float:", float("Hello world!")) ValueError: could not convert string to float: 'Hello world!'
Given this information, what do you expect the following program to do?
What does it actually do?
Why do you think it does that?
print("fractional string to int:", int("3.4"))
Solution
What do you expect this program to do? It would not be so unreasonable to expect the Python 3
int
command to convert the string “3.4” to 3.4 and an additional type conversion to 3. After all, Python 3 performs a lot of other magic - isn’t that part of its charm?int("3.4")
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-2-ec6729dfccdc> in <module> ----> 1 int("3.4") ValueError: invalid literal for int() with base 10: '3.4'
However, Python 3 throws an error. Why? To be consistent, possibly. If you ask Python to perform two consecutive typecasts, you must convert it explicitly in code.
int(float("3.4"))
3
Arithmetic with Different Types
Which of the following will return the floating point number
2.0
? Note: there may be more than one right answer.first = 1.0 second = "1" third = "1.1"
first + float(second)
float(second) + float(third)
first + int(third)
first + int(float(third))
int(first) + int(float(third))
2.0 * second
Solution
Answer: 1 and 4
Key Points
Every value has a type.
Use the built-in function
type
to find the type of a value.Types control what operations can be done on values.
Strings can be added and multiplied.
Strings have a length (but numbers don’t).
Must convert numbers to strings or vice versa when operating on them.
Can mix integers and floats freely in operations.
Variables only change value when something is assigned to them.
Built-in Functions and Help
Overview
Teaching: 15 min
Exercises: 10 minQuestions
How can I use built-in functions?
How can I find out what they do?
What kind of errors can occur in programs?
Objectives
Explain the purpose of functions.
Correctly call built-in Python functions.
Correctly nest calls to built-in functions.
Use help to display documentation for built-in functions.
Correctly describe situations in which SyntaxError and NameError occur.
Use comments to add documentation to programs.
# This sentence isn't executed by Python.
adjustment = 0.5 # Neither is this - anything after '#' is ignored.
A function may take zero or more arguments.
- We have seen some functions already — now let’s take a closer look.
- An argument is a value passed into a function.
len
takes exactly one.int
,str
, andfloat
create a new value from an existing one.print
takes zero or more.print
with no arguments prints a blank line.- Must always use parentheses, even if they’re empty, so that Python knows a function is being called.
print('before')
print()
print('after')
before
after
Every function returns something.
- Every function call produces some result.
- If the function doesn’t have a useful result to return,
it usually returns the special value
None
.None
is a Python object that stands in anytime there is no value.
result = print('example')
print('result of print is', result)
example
result of print is None
Commonly-used built-in functions include max
, min
, and round
.
- Use
max
to find the largest value of one or more values. - Use
min
to find the smallest. - Both work on character strings as well as numbers.
- “Larger” and “smaller” use (0-9, A-Z, a-z) to compare letters.
max_value = max(1, 2, 3)
print('maximum value is:', max_value)
min_value = min('a', 'A', '0')
print('minimum value is:', min_value)
maximum value is: 3
minimum value is: 0
Functions may only work for certain (combinations of) arguments.
max
andmin
must be given at least one argument.- “Largest of the empty set” is a meaningless question.
- And they must be given things that can meaningfully be compared.
print(max(1, 'a'))
TypeError Traceback (most recent call last)
<ipython-input-52-3f049acf3762> in <module>
----> 1 print(max(1, 'a'))
TypeError: '>' not supported between instances of 'str' and 'int'
Functions may have default values for some arguments.
round
will round off a floating-point number.- By default, rounds to zero decimal places.
round(3.712)
4
- We can specify the number of decimal places we want.
round(3.712, 1)
3.7
Use the built-in function help
to get help for a function.
- Every built-in function has documentation available.
help(round)
Help on built-in function round in module builtins:
round(number, ndigits=None)
Round a number to a given precision in decimal digits.
The return value is an integer if ndigits is omitted or None. Otherwise
the return value has the same type as the number. ndigits may be negative.
The Jupyter Notebook has two ways to get help.
- Option 1: Place the cursor near where the function is invoked in a cell
(i.e., the function name or its parameters),
- Hold down Shift, and press Tab.
- Do this several times to expand the information returned.
- Option 2: Type the function name in a cell with a question mark after it. Then run the cell.
Functions attached to objects are called methods
- Methods have parentheses like functions, but come after the variable.
- Some methods are used for internal Python operations, and are marked with double underlines.
my_string = 'Hello world!' # creation of a string object
print(len(my_string)) # the len function takes a string as an argument and returns the length of the string
print(my_string.swapcase()) # calling the swapcase method on the my_string object
print(my_string.__len__()) # calling the internal __len__ method on the my_string object, used by len(my_string)
12
hELLO WORLD!
12
- You might even see them chained together. They operate left to right.
print(my_string.isupper()) # Not all the letters are uppercase
print(my_string.upper()) # This capitalizes all the letters
print(my_string.upper().isupper()) # Now all the letters are uppercase
False
HELLO WORLD
True
Python reports a syntax error when it can’t understand the source of a program.
- Won’t even try to run the program if it can’t be parsed.
# Forgot to close the quote marks around the string.
name = 'Feng
File "<ipython-input-56-f42768451d55>", line 2
name = 'Feng
^
SyntaxError: EOL while scanning string literal
# An extra '=' in the assignment.
age = = 52
File "<ipython-input-57-ccc3df3cf902>", line 2
age = = 52
^
SyntaxError: invalid syntax
- Look more closely at the error message:
print("hello world"
File "<ipython-input-6-d1cc229bf815>", line 1
print ("hello world"
^
SyntaxError: unexpected EOF while parsing
- The message indicates a problem on first line of the input (“line 1”).
- In this case the “ipython-input” section of the file name tells us that we are working with input into IPython, the Python interpreter used by the Jupyter Notebook.
- The
-6-
part of the filename indicates that the error occurred in cell 6 of our Notebook. - Next is the problematic line of code,
indicating the problem with a
^
pointer.
Python reports a runtime error when something goes wrong while a program is executing.
age = 53
remaining = 100 - aege # mis-spelled 'age'
NameError Traceback (most recent call last)
<ipython-input-59-1214fb6c55fc> in <module>
1 age = 53
----> 2 remaining = 100 - aege # mis-spelled 'age'
NameError: name 'aege' is not defined
- Fix syntax errors by reading the source and runtime errors by tracing execution.
What Happens When
- Explain in simple terms the order of operations in the following program: when does the addition happen, when does the subtraction happen, when is each function called, etc.
- What is the final value of
radiance
?radiance = 1.0 radiance = max(2.1, 2.0 + min(radiance, 1.1 * radiance - 0.5))
Solution
- Order of operations:
1.1 * radiance = 1.1
1.1 - 0.5 = 0.6
min(radiance, 0.6) = 0.6
2.0 + 0.6 = 2.6
max(2.1, 2.6) = 2.6
- At the end,
radiance = 2.6
Spot the Difference
- Predict what each of the
- Does
max(len(rich), poor)
run or produce an error message? If it runs, does its result make any sense?easy_string = "abc" print(max(easy_string)) rich = "gold" poor = "tin" print(max(rich, poor)) print(max(len(rich), len(poor)))
Solution
print(max(easy_string))
c
print(max(rich, poor))
tin
print(max(len(rich), len(poor)))
4
max(len(rich), poor)
throws a TypeError. This turns intomax(4, 'tin')
and as we discussed earlier a string and integer cannot meaningfully be compared.TypeError Traceback (most recent call last) <ipython-input-65-bc82ad05177a> in <module> ----> 1 max(len(rich), poor) TypeError: '>' not supported between instances of 'str' and 'int'
Why Not?
Why is it that
max
andmin
do not returnNone
when they are called with no arguments?Solution
max
andmin
return TypeErrors in this case because the correct number of parameters was not supplied. If it just returnedNone
, the error would be much harder to trace as it would likely be stored into a variable and used later in the program, only to likely throw a runtime error.
Last Character of a String
If Python starts counting from zero, and
len
returns the number of characters in a string, what index expression will get the last character in the stringname
? (Note: we will see a simpler way to do this in a later episode.)Solution
name[len(name) - 1]
Explore the Python docs!
The official Python documentation is arguably the most complete source of information about the language. It is available in different languages and contains a lot of useful resources. The Built-in Functions page contains a catalogue of all of these functions, including the ones that we’ve covered in this lesson. Some of these are more advanced and unnecessary at the moment, but others are very simple and useful.
Key Points
Use comments to add documentation to programs.
A function may take zero or more arguments.
Commonly-used built-in functions include
max
,min
, andround
.Functions may only work for certain (combinations of) arguments.
Functions may have default values for some arguments.
Use the built-in function
help
to get help for a function.The Jupyter Notebook has two ways to get help.
Every function returns something.
Python reports a syntax error when it can’t understand the source of a program.
Python reports a runtime error when something goes wrong while a program is executing.
Fix syntax errors by reading the source code, and runtime errors by tracing the program’s execution.
Break
Overview
Teaching: 0 min
Exercises: 0 minQuestions
Objectives
Key Points
Lists
Overview
Teaching: 10 min
Exercises: 10 minQuestions
How can I store multiple values?
Objectives
Explain why programs need collections of values.
Write programs that create flat lists, index them, slice them, and modify them through assignment and method calls.
A list stores many values in a single structure.
- Doing calculations with a hundred variables called
pressure_001
,pressure_002
, etc., would be at least as slow as doing them by hand. - Use a list to store many values together.
- Contained within square brackets
[...]
. - Values separated by commas
,
.
- Contained within square brackets
- Use
len
to find out how many values are in a list.
pressures = [0.273, 0.275, 0.277, 0.275, 0.276]
print('pressures:', pressures)
print('length:', len(pressures))
pressures: [0.273, 0.275, 0.277, 0.275, 0.276]
length: 5
Use an item’s index to fetch it from a list.
- Just like strings.
print('zeroth item of pressures:', pressures[0])
print('fourth item of pressures:', pressures[4])
zeroth item of pressures: 0.273
fourth item of pressures: 0.276
Lists’ values can be replaced by assigning to them.
- Use an index expression on the left of assignment to replace a value.
pressures[0] = 0.265
print('pressures is now:', pressures)
pressures is now: [0.265, 0.275, 0.277, 0.275, 0.276]
Appending items to a list lengthens it.
- Use
list_name.append
to add items to the end of a list.
primes = [2, 3, 5]
print('primes is initially:', primes)
primes.append(7)
print('primes has become:', primes)
primes is initially: [2, 3, 5]
primes has become: [2, 3, 5, 7]
append
is a method of lists.- Like a function, but tied to a particular object.
- Use
object_name.method_name
to call methods.- Deliberately resembles the way we refer to things in a library.
- We will meet other methods of lists as we go along.
- Use
help(list)
for a preview.
- Use
extend
is similar toappend
, but it allows you to combine two lists. For example:
teen_primes = [11, 13, 17, 19]
middle_aged_primes = [37, 41, 43, 47]
print('primes is currently:', primes)
primes.extend(teen_primes)
print('primes has now become:', primes)
primes.append(middle_aged_primes)
print('primes has finally become:', primes)
primes is currently: [2, 3, 5, 7]
primes has now become: [2, 3, 5, 7, 11, 13, 17, 19]
primes has finally become: [2, 3, 5, 7, 11, 13, 17, 19, [37, 41, 43, 47]]
Note that while extend
maintains the “flat” structure of the list, appending a list to a list makes the result
two-dimensional - the last element in primes
is a list, not an integer.
Use del
to remove items from a list entirely.
- We use
del list_name[index]
to remove an element from a list (in the example, 9 is not a prime number) and thus shorten it. del
is not a function or a method, but a statement in the language.
primes = [2, 3, 5, 7, 9]
print('primes before removing last item:', primes)
del primes[4]
print('primes after removing last item:', primes)
primes before removing last item: [2, 3, 5, 7, 9]
primes after removing last item: [2, 3, 5, 7]
The empty list contains no values.
- Use
[]
on its own to represent a list that doesn’t contain any values.- “The zero of lists.”
- Helpful as a starting point for collecting values (which we will see in episode 12).
Lists may contain values of different types.
- A single list may contain numbers, strings, and anything else.
goals = [1, 'Create lists.', 2, 'Extract items from lists.', 3, 'Modify lists.']
Character strings can be indexed like lists.
- Get single characters from a character string using indexes in square brackets.
element = 'carbon'
print('zeroth character:', element[0])
print('third character:', element[3])
zeroth character: c
third character: b
Character strings are immutable.
- Cannot change the characters in a string after it has been created.
- Immutable: can’t be changed after creation.
- In contrast, lists are mutable: they can be modified in place.
- Python considers the string to be a single value with parts, not a collection of values.
element[0] = 'C'
TypeError: 'str' object does not support item assignment
- Lists and character strings are both collections.
Indexing beyond the end of the collection is an error.
- Python reports an
IndexError
if we attempt to access a value that doesn’t exist.- This is a kind of runtime error.
- Cannot be detected as the code is parsed because the index might be calculated based on data.
print('99th element of element is:', element[99])
IndexError: string index out of range
Fill in the Blanks
Fill in the blanks so that the program below produces the output shown.
values = ____ values.____(1) values.____(3) values.____(5) print('first time:', values) values = values[____] print('second time:', values)
first time: [1, 3, 5] second time: [3, 5]
Solution
values = [] values.append(1) values.append(3) values.append(5) print('first time:', values) values = values[1:] print('second time:', values)
How Large is a Slice?
If
start
andstop
are both non-negative integers, how long is the listvalues[start:stop]
?Solution
The list
values[start:stop]
has up tostop - start
elements. For example,values[1:4]
has the 3 elementsvalues[1]
,values[2]
, andvalues[3]
. Why ‘up to’? As we saw in episode 2, ifstop
is greater than the total length of the listvalues
, we will still get a list back but it will be shorter than expected.
From Strings to Lists and Back
Given this:
print('string to list:', list('tin')) print('list to string:', ''.join(['g', 'o', 'l', 'd']))
string to list: ['t', 'i', 'n'] list to string: gold
- What does
list('some string')
do?- What does
'-'.join(['x', 'y', 'z'])
generate?Solution
list('some string')
converts a string into a list containing all of its characters.join
returns a string that is the concatenation of each string element in the list and adds the separator between each element in the list. This results inx-y-z
. The separator between the elements is the string that provides this method.
Working With the End
What does the following program print?
element = 'helium' print(element[-1])
- How does Python interpret a negative index?
- If a list or string has N elements, what is the most negative index that can safely be used with it, and what location does that index represent?
- If
values
is a list, what doesdel values[-1]
do?- How can you display all elements but the last one without changing
values
? (Hint: you will need to combine slicing and negative indexing.)Solution
The program prints
m
.
- Python interprets a negative index as starting from the end (as opposed to starting from the beginning). The last element is
-1
.- The last index that can safely be used with a list of N elements is element
-N
, which represents the first element.del values[-1]
removes the last element from the list.values[:-1]
Stepping Through a List
What does the following program print?
element = 'fluorine' print(element[::2]) print(element[::-1])
- If we write a slice as
low:high:stride
, what doesstride
do?- What expression would select all of the even-numbered items from a collection?
Solution
The program prints
furn eniroulf
stride
is the step size of the slice.- The slice
1::2
selects all even-numbered items from a collection: it starts with element1
(which is the second element, since indexing starts at0
), goes on until the end (since noend
is given), and uses a step size of2
(i.e., selects every second element).
Sort and Sorted
What do these two programs print? In simple terms, explain the difference between
sorted(letters)
andletters.sort()
.# Program A letters = list('gold') result = sorted(letters) print('letters is', letters, 'and result is', result)
# Program B letters = list('gold') result = letters.sort() print('letters is', letters, 'and result is', result)
Solution
Program A prints
letters is ['g', 'o', 'l', 'd'] and result is ['d', 'g', 'l', 'o']
Program B prints
letters is ['d', 'g', 'l', 'o'] and result is None
sorted(letters)
returns a sorted copy of the listletters
(the original listletters
remains unchanged), whileletters.sort()
sorts the listletters
in-place and does not return anything.
Copying (or Not)
What do these two programs print? In simple terms, explain the difference between
new = old
andnew = old[:]
.# Program A old = list('gold') new = old # simple assignment new[0] = 'D' print('new is', new, 'and old is', old)
# Program B old = list('gold') new = old[:] # assigning a slice new[0] = 'D' print('new is', new, 'and old is', old)
Solution
Program A prints
new is ['D', 'o', 'l', 'd'] and old is ['D', 'o', 'l', 'd']
Program B prints
new is ['D', 'o', 'l', 'd'] and old is ['g', 'o', 'l', 'd']
new = old
makesnew
a reference to the listold
;new
andold
point towards the same object.
new = old[:]
however creates a new list objectnew
containing all elements from the listold
;new
andold
are different objects.
Key Points
A list stores many values in a single structure.
Use an item’s index to fetch it from a list.
Lists’ values can be replaced by assigning to them.
Appending items to a list lengthens it.
Use
del
to remove items from a list entirely.The empty list contains no values.
Lists may contain values of different types.
Character strings can be indexed like lists.
Character strings are immutable.
Indexing beyond the end of the collection is an error.
Libraries
Overview
Teaching: 10 min
Exercises: 10 minQuestions
How can I use software that other people have written?
How can I find out what that software does?
Objectives
Explain what software libraries are and why programmers create and use them.
Write programs that import and use modules from Python’s standard library.
Find and read documentation for the standard library interactively (in the interpreter) and online.
Most of the power of a programming language is in its libraries.
- A library is a collection of files (called modules) that contains
functions for use by other programs.
- May also contain data values (e.g., numerical constants) and other things.
- Library’s contents are supposed to be related, but there’s no way to enforce that.
- The Python standard library is an extensive suite of modules that comes with Python itself.
- Many additional libraries are available from PyPI (the Python Package Index).
- We will see later how to write new libraries.
Libraries and modules
A library is a collection of modules, but the terms are often used interchangeably, especially since many libraries only consist of a single module, so don’t worry if you mix them.
A program must import a library module before using it.
- Use
import
to load a library module into a program’s memory. - Then refer to things from the module as
module_name.thing_name
.- Python uses
.
to mean “part of”.
- Python uses
- Using
math
, one of the modules in the standard library:
import math
print('pi is', math.pi)
print('cos(pi) is', math.cos(math.pi))
pi is 3.141592653589793
cos(pi) is -1.0
- Have to refer to each item with the module’s name.
math.cos(pi)
won’t work: the reference topi
doesn’t somehow “inherit” the function’s reference tomath
.
Use help
to learn about the contents of a library module.
- Works just like help for a function.
help(math)
Help on module math:
NAME
math
MODULE REFERENCE
http://docs.python.org/3/library/math
The following documentation is automatically generated from the Python
source files. It may be incomplete, incorrect or include features that
are considered implementation detail and may vary between Python
implementations. When in doubt, consult the module reference at the
location listed above.
DESCRIPTION
This module is always available. It provides access to the
mathematical functions defined by the C standard.
FUNCTIONS
acos(x, /)
Return the arc cosine (measured in radians) of x.
⋮ ⋮ ⋮
Import specific items from a library module to shorten programs.
- Use
from ... import ...
to load only specific items from a library module. - Then refer to them directly without library name as prefix.
from math import cos, pi
print('cos(pi) is', cos(pi))
cos(pi) is -1.0
Create an alias for a library module when importing it to shorten programs.
- Use
import ... as ...
to give a library a short alias while importing it. - Then refer to items in the library using that shortened name.
import math as m
print('cos(pi) is', m.cos(m.pi))
cos(pi) is -1.0
- Commonly used for libraries that are frequently used or have long names.
- E.g., the
matplotlib
plotting library is often aliased asmpl
.
- E.g., the
- But can make programs harder to understand, since readers must learn your program’s aliases.
Exploring the Math Module
- What function from the
math
module can you use to calculate a square root without usingsqrt
?- Since the library contains this function, why does
sqrt
exist?Solution
- Using
help(math)
we see that we’ve gotpow(x,y)
in addition tosqrt(x)
, so we could usepow(x, 0.5)
to find a square root.The
sqrt(x)
function is arguably more readable thanpow(x, 0.5)
when implementing equations. Readability is a cornerstone of good programming, so it makes sense to provide a special function for this specific common case.Also, the design of Python’s
math
library has its origin in the C standard, which includes bothsqrt(x)
andpow(x,y)
, so a little bit of the history of programming is showing in Python’s function names.
Locating the Right Module
You want to select a random character from a string:
bases = 'ACTTGCTTGAC'
- Which standard library module could help you?
- Which function would you select from that module? Are there alternatives?
- Try to write a program that uses the function.
Solution
The random module seems like it could help.
The string has 11 characters, each having a positional index from 0 to 10. You could use the
random.randrange
orrandom.randint
functions to get a random integer between 0 and 10, and then select thebases
character at that index:from random import randrange random_index = randrange(len(bases)) print(bases[random_index])
or more compactly:
from random import randrange print(bases[randrange(len(bases))])
Perhaps you found the
random.sample
function? It allows for slightly less typing but might be a bit harder to understand just by reading:from random import sample print(sample(bases, 1)[0])
Note that this function returns a list of values.
The simplest and shortest solution is the
random.choice
function that does exactly what we want:from random import choice print(choice(bases))
When Is Help Available?
When a colleague of yours types
help(math)
, Python reports an error:NameError: name 'math' is not defined
What has your colleague forgotten to do?
Solution
Importing the math module (
import math
)
Importing With Aliases
- Fill in the blanks so that the program below prints
90.0
.- Rewrite the program so that it uses
import
withoutas
.- Which form do you find easier to read?
import math as m angle = ____.degrees(____.pi / 2) print(____)
Solution
import math as m angle = m.degrees(m.pi / 2) print(angle)
can be written as
import math angle = math.degrees(math.pi / 2) print(angle)
Since you just wrote the code and are familiar with it, you might actually find the first version easier to read. But when trying to read a huge piece of code written by someone else, or when getting back to your own huge piece of code after several months, non-abbreviated names are often easier, except where there are clear abbreviation conventions.
Importing Specific Items
- Fill in the blanks so that the program below prints
90.0
.- Do you find this version easier to read than preceding ones?
- Why wouldn’t programmers always use this form of
import
?____ math import ____, ____ angle = degrees(pi / 2) print(angle)
Solution
from math import degrees, pi angle = degrees(pi / 2) print(angle)
Most likely you find this version easier to read since it’s less dense. The main reason not to use this form of import is to avoid name clashes. For instance, you wouldn’t import
degrees
this way if you also wanted to use the namedegrees
for a variable or function of your own. Or if you were to also import a function nameddegrees
from another library.
Reading Error Messages
- Read the code below and try to identify what the errors are without running it.
- Run the code, and read the error message. What type of error is it?
from math import log log(0)
Solution
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-1-d72e1d780bab> in <module> 1 from math import log ----> 2 log(0) ValueError: math domain error
- The logarithm of
x
is only defined forx > 0
, so 0 is outside the domain of the function.- You get an error of type
ValueError
, indicating that the function received an inappropriate argument value. The additional message “math domain error” makes it clearer what the problem is.
Key Points
Most of the power of a programming language is in its libraries.
A program must import a library module in order to use it.
Use
help
to learn about the contents of a library module.Import specific items from a library to shorten programs.
Create an alias for a library when importing it to shorten programs.
Working with NumPy
Overview
Teaching: 10 min
Exercises: 10 minQuestions
What is NumPy and how do I use it?
Objectives
Import the NumPy library.
Create a NumPy array.
Apply functions to NumPy arrays.
Working with the NumPy library
- NumPy is a widely-used Python library for numerical operations.
- NumPy uses the
ndarray
type for storing data. - Load it with
import numpy as np
. The aliasnp
is commonly used for NumPy.
import numpy as np
primes = np.array([2, 3, 5, 7, 11])
print(primes)
[2 3 5 7 11]
The NumPy array looks similar to a list, but let’s take a closer look:
print(type(primes))
print(len(primes))
print(primes.shape)
print(primes.dtype)
<class 'numpy.ndarray'>
5
(5,)
int64
- The
shape
attribute contains information about the dimensions of the array. - All values in the array must have the same
dtype
(data type).
Array functions
NumPy provides many functions, including its own versions of min
and max
:
print(np.min(primes))
print(np.max(primes))
print(np.mean(primes))
2
11
5.6
A NumPy array will have many methods available, including min
, max
and mean
:
print(primes.min())
print(primes.max())
print(primes.mean())
2
11
5.6
NumPy functions can operate on all elements in an array. For example, what happens if we try to run the math.sin
function on multiple items?
import math
sequence = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
math.sin(sequence)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_76280/1284448365.py in <module>
2
3 sequence = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
----> 4 math.sin(sequence)
TypeError: must be real number, not list
The math.sin
function can only process a single value.
The NumPy sin
function can process multiple values:
sequence = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
np.sin(sequence)
array([ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 ,
-0.95892427, -0.2794155 , 0.6569866 , 0.98935825, 0.41211849])
The sequence
list is converted to a NumPy ndarray
during this process.
Multi dimensional arrays
NumPy arrays can have multiple dimensions:
values = np.array([[0, 7, 2], [4, 4, 5]])
print(values)
print(values.shape)
[[0 7 2]
[4 4 5]]
(2, 3)
The values
array is two dimensional, with 2 rows and 3 columns.
Values in NumPy arrays with multiple dimension have multiple indexes. The index of the value 5
in the array is [1, 2]
. The row or y index comes first, followed by the column or x index:
print(values[1, 2])
5
Finding the median value
If we can find the mean value of the values array with:
print(values.mean())
Can we find the median value in a similar way? If not, is there another way to find the median value?
Solution
print(np.median(values))
4.0
The ndarray type does not have a median method, so
values.median()
does not work. However, the numpy library does include the median function, which can be applied to an array.
Applying functions along an axis
What is the difference between these commands and the results they return?
print(values.max()) print(values.max(axis=0)) print(values.max(axis=1))
Solution
7 [4 7 5] [7 5]
The first command returns the maximum value from the whole array. The second command returns the maximum value from each column (
axis=0
). The third command returns the maximum value from each row (axis=1
).
Data types
What is the data type of the
values
array, and how could the array be created with a different data type, e.g.np.float32
?Solution
values = np.array([[0, 7, 2], [4, 4, 5]]) print(values.dtype) print(values) values = np.array([[0, 7, 2], [4, 4, 5]], dtype=np.float32) print(values.dtype) print(values)
int64 [[0 7 2] [4 4 5]] float32 [[0. 7. 2.] [4. 4. 5.]]
The
dtype
argument can be used to specify the data type when creating a NumPy array.
NaN values
If we create an array containing a NaN (not a number) value, how do we find the maximum value?
results = np.array([0.3, 7.2, np.nan, 4.5, 9.7])
Solution
print(results.max()) print(np.nanmax(results))
nan 9.7
NumPy includes functions, such as
nanmax
, which will ignore any NaN values in the input.
Key Points
NumPy provides many funtions for working with numerical data.
The NumPy
ndarray
can be used to store numerical data with multiple dimensions.The NumPy functions enable efficient processing of values in a
ndarray
.
Plotting
Overview
Teaching: 20 min
Exercises: 10 minQuestions
How can I plot my data?
Objectives
Use
matplotlib
to create various plots
matplotlib
is the most widely used scientific plotting library in Python.
- Commonly use a sub-library called
matplotlib.pyplot
. - The Jupyter Notebook will render plots inline by default.
import matplotlib.pyplot as plt
- Simple plots are then (fairly) simple to create.
time = [0, 1, 2, 3]
position = [0, 100, 200, 300]
plt.plot(time, position)
plt.xlabel('Time (hr)')
plt.ylabel('Position (km)')
Display All Open Figures
In our Jupyter Notebook example, running the cell should generate the figure directly below the code. The figure is also included in the Notebook document for future viewing. However, other Python environments like an interactive Python session started from a terminal or a Python script executed via the command line require an additional command to display the figure.
Instruct
matplotlib
to show a figure:plt.show()
This command can also be used within a Notebook - for instance, to display multiple figures if several are created by a single cell.
Plotting data from NumPy arrays
Let’s generate some data using NumPy:
x = np.arange(0, 10, 0.1)
sin_x = np.sin(x)
cos_x = np.cos(x)
The np.arange
function will generate an array of number starting at 0
and stopping before 10
, with an interval of 0.1
.
We can plot the value of sin(x)
and cos(x)
on the same axes:
plt.plot(x, sin_x)
plt.plot(x, cos_x)
We can set the colour of the lines using the c
option to plot()
, and we can add a legend to indicate which values belong to which series:
plt.plot(x, sin_x, c='teal', label='sin(x)')
plt.plot(x, cos_x, c='peru', label='cos(x)')
plt.legend(loc='lower left')
Adding a Legend
Often when plotting multiple datasets on the same figure it is desirable to have a legend describing the data.
This can be done in
matplotlib
in two stages:
- Provide a label for each dataset in the figure:
plt.plot(x, sin_x, label='sin(x)') plt.plot(x, cos_x, label='cos(x)')
- Instruct
matplotlib
to create the legend.plt.legend()
By default matplotlib will attempt to place the legend in a suitable position. If you would rather specify a position this can be done with the
loc=
argument, e.g to place the legend in the upper left corner of the plot, specifyloc='upper left'
Matplotlib is capable of making many type of plots. We can create a scatter plot of the sin(x)
values:
plt.figure(figsize=(10, 8))
plt.scatter(x, sin_x, c=x, s=x*3)
plt.xlabel('x', fontsize=16)
plt.ylabel('sin(x)', fontsize=16)
plt.title('sine plot', fontsize=18)
plt.tick_params(labelsize=14)
plt.colorbar()
plt.savefig('sin.png')
Each plotting function in Matplotlib has its own set of argmuents. The documentation for the scatter()
function can be found here.
The plt.figure(figsize=(10, 8))
command is used to create a figure of the specified size. The default units for Matplotlib figures are inches. In this instance the figure size is adjusted, to avoid any axes labels being cut off when saving the plot to a file, to 10 inches wide by 8 inches high.
The c=x
option sets the colour value of the scatter points based on the value of x
. The s=x*3
options sets the size of the scatter points based on the values of x
multiplied by 3.
A title is added to the plot using plt.title()
. The font size is set using the fontsize
argument for the title, x axis label and y axis label. To set the font size for the tick labels, the plt._tick_params()
function is used, where the size is set using the labelsize
option.
A colour scale is added using the function plt.colorbar()
.
Saving your plot to a file
If you are satisfied with the plot you see you may want to save it to a file, perhaps to include it in a publication. There is a function in the matplotlib.pyplot module that accomplishes this: savefig. Calling this function, e.g. with
plt.savefig('my_figure.png')
will save the current figure to the file
my_figure.png
. The file format will automatically be deduced from the file name extension (other formats are pdf, ps, eps and svg).Note that functions in
plt
refer to a global figure variable and after a figure has been displayed to the screen (e.g. withplt.show
) matplotlib will make this variable refer to a new empty figure. Therefore, make sure you callplt.savefig
before the plot is displayed to the screen, otherwise you may find a file with an empty plot.
Creating a figure containing multiple plots
In the above examples, Matplotlib is automatically creating the figure and axes for each plot, but there are various ways in which these elements can be manually created where required.
For example, the plt.subplots()
function can be used to create a figure
which contains multiple sets of axes.
If we wished to create a figure containing two plots, we could use the command:
fig, ax = plt.subplots(nrows=2, ncols=1)
This function returns two values, the figure, which we have stored as fig
and the axes, which we have stored as ax
.
If we print()
the ax
variable, which should see that this is a list
containing the axes we have requested:
fig, ax = plt.subplots(nrows=2, ncols=1)
print(ax)
[<Axes: > <Axes: >]
We can access each set of axes from the ax
variable, and create a plot
within.
When plotting this way, we access the plotting functions (e.g. plot()
,
scatter()
) as a method of the axes.
We could create a figure containing subplots of sin(x)
and cos(x)
using
the following method:
# create the figure and axes:
fig, ax = plt.subplots(nrows=2, ncols=1)
# access the first set of axes:
ax0 = ax[0]
# plot sin(x) in the first axes:
ax0.plot(x, sin_x)
# set the plot title:
ax0.set_title('sin(x)')
# access the second set of axes:
ax1 = ax[1]
# plot cos(x) in the second axes:
ax1.plot(x, cos_x)
# set the plot title:
ax1.set_title('cos(x)')
# set the figure title:
fig.suptitle('plots of sin(x) and y(x)')
# save the figure:
fig.savefig('sin_and_cos_plots.png')
Plotting 2d data
Matplotlib has various options available for plotting 2d data, such as:
pcolormesh
, pseudocolor plot with a non-regular rectangular grid.contour
, contour lines.contourf
, filled contours.
To test some of these, we will first use NumPy to generate some 2d data.
# create the x and y values, from -10 to 10, with a 0.1 increment, using the
# numpy arange function:
x = np.arange(-10, 10.1, 0.1)
y = np.arange(-10, 10.1, 0.1)
# create x and y coordinate grids using the numpy meshgrid function:
grid_x, grid_y = np.meshgrid(x, y)
Here, we use the NumPy meshgrid
function, which is a versatile NumPy function used to create coordinate grids from one-dimensional coordinate arrays. It is widely used in mathematical computations, plotting, and simulations, where grid-like data is essential.
This example provides a demonstration of how the meshgrid
function works, and the output which it creates:
x_coords, y_coords = np.meshgrid([1, 2, 3], [6, 7, 8, 9])
print(x_coords)
print(y_coords)
[[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]]
[[6 6 6]
[7 7 7]
[8 8 8]
[9 9 9]]
We can again use the NumPy sin
function to generate our 2d data:
# generate 2d values for plotting using the numpy sin function:
z = np.sin(grid_x * grid_y)
Once we have some 2d data, a quick way to take a look at the data is using Matplotlib’s imshow
function:
plt.imshow(z)
The imshow
function allows us to take a quick look at the data, but does not include the values for the x or y axes.
If we use Matplotlib’s contourf
function, we can include the x and y values in our plot:
# Create filled contour plot using Matplob's contourf function:
plt.contourf(x, y, z)
# Add a colour bar:
plt.colorbar()
Making your plots accessible
Whenever you are generating plots to go into a paper or a presentation, there are a few things you can do to make sure that everyone can understand your plots.
- Always make sure your text is large enough to read. Use the
fontsize
parameter inxlabel
,ylabel
,title
, andlegend
, andtick_params
withlabelsize
to increase the text size of the numbers on your axes.- Similarly, you should make your graph elements easy to see. Use
s
to increase the size of your scatterplot markers andlinewidth
to increase the sizes of your plot lines.- Using color (and nothing else) to distinguish between different plot elements will make your plots unreadable to anyone who is colorblind, or who happens to have a black-and-white office printer. For lines, the
linestyle
parameter lets you use different types of lines. For scatterplots,marker
lets you change the shape of your points. If you’re unsure about your colors, you can use Coblis or Color Oracle to simulate what your plots would look like to those with colorblindness.
Colour scales
Try and recreate the scatter plot for
cos(x)
, and see if you can change the colour scale to ‘jet’.More information about Matplotlib colour maps can be found here.
Solution
plt.figure(figsize=(10, 8)) plt.scatter(x, cos_x, c=x, s=x*3, cmap='jet') plt.xlabel('x', fontsize=16) plt.ylabel('sin(x)', fontsize=16) plt.title('Cosine plot', fontsize=18) plt.tick_params(labelsize=14) plt.colorbar() plt.savefig('cos.png')
More colour scales
See if you can create a plot of the 2d
z
data, using thepcolormesh
function.Select a suitable diverging colour map for the plot .
Solution
plt.figure(figsize=(10, 8)) plt.pcolormesh(x, y, z, cmap='RdBu') plt.colorbar() plt.savefig('2d_sin.png')
Key Points
matplotlib
is the most widely used scientific plotting library in Python.Many styles of plot are available: see the Python Graph Gallery for more options.
Can plot many sets of data together.
Reading Tabular Data with Pandas
Overview
Teaching: 10 min
Exercises: 10 minQuestions
How can I read tabular data?
Objectives
Import the Pandas library.
Use Pandas to load a simple CSV data set.
Get some basic information about a Pandas DataFrame.
Plot the data in a Pandas DataFrame.
Use the Pandas library to do statistics on tabular data.
- Pandas is a widely-used Python library for statistics, particularly on tabular data.
- Borrows many features from R’s dataframes.
- A 2-dimensional table whose columns have names and potentially have different data types.
- Load it with
import pandas as pd
. The alias pd is commonly used for Pandas. - Read a Comma Separated Values (CSV) data file with
pd.read_csv
.- Argument is the name of the file to be read.
- Assign result to a variable to store the data that was read.
We are going to read some temperature data, collected by the NCAS weather station in Leeds.
import pandas as pd
data = pd.read_csv('data/temperature_2022-07.csv')
print(data)
Date Max Temperature Average Temperature Min Temperature
0 2022-07-01 19.3 15.2 12.6
1 2022-07-02 20.3 16.3 13.3
2 2022-07-03 20.4 15.7 11.7
...
28 2022-07-29 22.7 17.6 14.2
29 2022-07-30 20.7 18.2 16.2
30 2022-07-31 22.7 18.6 15.8
- The columns in a DataFrame are the observed variables, and the rows are the observations.
- Pandas uses backslash
\
to show wrapped lines when output is too wide to fit the screen.
File Not Found
Our lessons store their data files in a
data
sub-directory, which is why the path to the file isdata/temperature_2022-07.csv
. If you forget to includedata/
, or if you include it but your copy of the file is somewhere else, you will get a runtime error that ends with a line like this:FileNotFoundError: [Errno 2] No such file or directory: 'data/temperature_2022-07.csv`
Use index_col
to specify that a column’s values should be used as row headings.
- Row headings are numbers (0 and 1 in this case).
- Really want to index by date.
- Pass the name of the column to
read_csv
as itsindex_col
parameter to do this.
data = pd.read_csv('data/temperature_2022-07.csv', index_col='Date')
print(data)
Max Temperature Average Temperature Min Temperature
Date
2022-07-01 19.3 15.2 12.6
2022-07-02 20.3 16.3 13.3
2022-07-03 20.4 15.7 11.7
...
2022-07-29 22.7 17.6 14.2
2022-07-30 20.7 18.2 16.2
2022-07-31 22.7 18.6 15.8
Use the DataFrame.info()
method to find out more about a DataFrame.
data.info()
<class 'pandas.core.frame.DataFrame'>
Index: 31 entries, 2022-07-01 to 2022-07-31
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Max Temperature 31 non-null float64
1 Average Temperature 31 non-null float64
2 Min Temperature 31 non-null float64
dtypes: float64(3)
memory usage: 992.0+ bytes
- This is a
DataFrame
- 31 rows
- Three columns, ‘Max Temperature’, ‘Average Temperature, and ‘Min Temperature’
- Uses 992 bytes of memory.
The DataFrame.columns
variable stores information about the DataFrame’s columns.
- Note that this is data, not a method. (It doesn’t have parentheses.)
- Like
math.pi
. - So do not use
()
to try to call it.
- Like
- Called a member variable, or just member.
print(data.columns)
Index(['Max Temperature', 'Average Temperature', 'Min Temperature'], dtype='object')
Use DataFrame.T
to transpose a DataFrame.
- Sometimes want to treat columns as rows and vice versa.
- Transpose (written
.T
) doesn’t copy the data, just changes the program’s view of it. - Like
columns
, it is a member variable.
print(data.T)
Date 2022-07-01 2022-07-02 2022-07-03 2022-07-04 \
Max Temperature 19.3 20.3 20.4 18.2
Average Temperature 15.2 16.3 15.7 15.3
Min Temperature 12.6 13.3 11.7 13.5
Date 2022-07-05 2022-07-06 2022-07-07 2022-07-08 \
Max Temperature 20.7 19.9 24.5 23.7
Average Temperature 16.2 16.8 18.3 18.8
Min Temperature 12.2 14.6 14.1 14.3
Date 2022-07-09 2022-07-10 ... 2022-07-22 2022-07-23 \
Max Temperature 24.5 28.3 ... 17.1 21.3
Average Temperature 19.1 21.0 ... 15.7 18.2
Min Temperature 14.8 13.6 ... 14.6 13.6
Date 2022-07-24 2022-07-25 2022-07-26 2022-07-27 \
Max Temperature 23.9 22.5 19.7 23.2
Average Temperature 19.9 17.4 14.9 17.4
Min Temperature 17.8 13.3 12.6 12.1
Date 2022-07-28 2022-07-29 2022-07-30 2022-07-31
Max Temperature 20.7 22.7 20.7 22.7
Average Temperature 16.6 17.6 18.2 18.6
Min Temperature 13.5 14.2 16.2 15.8
[3 rows x 31 columns]
Use DataFrame.describe()
to get summary statistics about data.
DataFrame.describe()
gets the summary statistics of only the columns that have numerical data.
All other columns are ignored, unless you use the argument include='all'
.
print(data.describe())
Max Temperature Average Temperature Min Temperature
count 31.000000 31.000000 31.000000
mean 23.567742 18.816129 14.745161
std 5.056441 3.553880 2.533093
min 17.100000 14.900000 11.600000
25% 20.350000 16.500000 13.350000
50% 22.500000 18.200000 14.200000
75% 24.700000 19.500000 15.600000
max 39.300000 29.100000 21.700000
Accessing values by column
To access the values in a particular column, the column name can be accessed from the DataFrame in a similar way to accessing values in a list by index. To access the Average Temperature
values:
print(data['Average Temperature'])
Date
2022-07-01 15.2
2022-07-02 16.3
2022-07-03 15.7
2022-07-04 15.3
2022-07-05 16.2
2022-07-06 16.8
2022-07-07 18.3
2022-07-08 18.8
2022-07-09 19.1
2022-07-10 21.0
2022-07-11 23.6
2022-07-12 22.6
2022-07-13 18.3
2022-07-14 16.5
2022-07-15 16.5
2022-07-16 18.6
2022-07-17 23.4
2022-07-18 28.7
2022-07-19 29.1
2022-07-20 21.7
2022-07-21 17.1
2022-07-22 15.7
2022-07-23 18.2
2022-07-24 19.9
2022-07-25 17.4
2022-07-26 14.9
2022-07-27 17.4
2022-07-28 16.6
2022-07-29 17.6
2022-07-30 18.2
2022-07-31 18.6
Name: Average Temperature, dtype: float64
The index column, which is Date
in this example can not be accessed in this way, but is instead accessed using the index
property of the DataFrame:
print(data.index)
Index(['2022-07-01', '2022-07-02', '2022-07-03', '2022-07-04', '2022-07-05',
'2022-07-06', '2022-07-07', '2022-07-08', '2022-07-09', '2022-07-10',
'2022-07-11', '2022-07-12', '2022-07-13', '2022-07-14', '2022-07-15',
'2022-07-16', '2022-07-17', '2022-07-18', '2022-07-19', '2022-07-20',
'2022-07-21', '2022-07-22', '2022-07-23', '2022-07-24', '2022-07-25',
'2022-07-26', '2022-07-27', '2022-07-28', '2022-07-29', '2022-07-30',
'2022-07-31'],
dtype='object', name='Date')
Plotting a DataFrame
First, we will re-read the CSV file, telling Pandas to parse the ‘Date’ values to convert them in to Pandas Timestamp
objects:
data = pd.read_csv('data/temperature_2022-07.csv', index_col='Date', parse_dates=['Date'])
data.head()
What does the data.head()
function do? What do you think data.tail()
might do?
Pandas makes quick plotting of data very simple:
data.plot()
A specific column can be plotted, by using the y
argument:
plt.style.use('ggplot')
data.plot(y='Average Temperature')
plt.ylabel('temperature (°C)')
plt.xlabel('date')
Note how we have changes the style of the plot using plt.style.use('ggplot')
.
Running plt.style.use('default')
will switch back to using the default style.
More information about Matplotlib styles can be found here.
Plot types
The DataFrame
plot()
method can produce different kinds of plots, which can be specified using thekind=
argument.The Pandas documentation describes the available options.
How could you create a box plot of the
data
values?Solution
data.plot(kind='box')
Reading Other Data
Read the data in
temperature_2022-08.csv
(which should be in the same directory astemperature_2022-07.csv
) into a variable calledmore_data
, display its summary statistics, and plot the values.Solution
To read in a CSV, we use
pd.read_csv
and pass the filename'data/temperature_2022-08.csv'
to it. The summary statistics can be displayed with theDataFrame.describe()
method.more_data.plot()
will plot the values.more_data = pd.read_csv('data/temperature_2022-08.csv', index_col='Date', parse_dates=['Date']) print(more_data.describe()) more_data.plot()
Writing Data
As well as the
read_csv
function for reading data from a file, Pandas provides ato_csv
function to write DataFrames to files. Applying what you’ve learned about reading from files, write one of your DataFrames to a file calledprocessed.csv
. You can usehelp
to get information on how to useto_csv
.Solution
In order to write the DataFrame
more_data
to a file calledprocessed.csv
, execute the following command:more_data.to_csv('processed.csv')
For help on
to_csv
, you could execute, for example:help(more_data.to_csv)
Note that
help(to_csv)
throws an error! This is a subtlety and is due to the fact thatto_csv
is NOT a function in and of itself and the actual call ismore_data.to_csv
.
Key Points
Use the Pandas library to get basic statistics out of tabular data.
Use
index_col
to specify that a column’s values should be used as row headings.Use
DataFrame.info
to find out more about a dataframe.The
DataFrame.columns
variable stores information about the dataframe’s columns.Use
DataFrame.T
to transpose a dataframe.Use
DataFrame.describe
to get summary statistics about data.Use
DataFrame.plot
to plot the data.
End Of First Session
Overview
Teaching: 45 min
Exercises: 0 minQuestions
Objectives
You may also wish to consider:
- What sort of packages might you use in Python and why would you use them?
- What limitations or problems might you run into when thinking about how to apply what we’ve learned to your own projects or data?
Key Points
For Loops
Overview
Teaching: 15 min
Exercises: 15 minQuestions
How can I make a program do many things?
Objectives
Explain what for loops are normally used for.
Trace the execution of a simple (unnested) loop and correctly state the values of variables in each iteration.
Write for loops that use the Accumulator pattern to aggregate values.
A for loop executes commands once for each value in a collection.
- Doing calculations on the values in a list one by one
is as painful as working with
pressure_001
,pressure_002
, etc. - A for loop tells Python to execute some statements once for each value in a list, a character string, or some other collection.
- “for each thing in this group, do these operations”
for number in [2, 3, 5]:
print(number)
- This
for
loop is equivalent to:
print(2)
print(3)
print(5)
- And the
for
loop’s output is:
2
3
5
A for
loop is made up of a collection, a loop variable, and a body.
for number in [2, 3, 5]:
print(number)
- The collection,
[2, 3, 5]
, is what the loop is being run on. - The body,
print(number)
, specifies what to do for each value in the collection. - The loop variable,
number
, is what changes for each iteration of the loop.- The “current thing”.
The first line of the for
loop must end with a colon, and the body must be indented.
- The colon at the end of the first line signals the start of a block of statements.
- Python uses indentation rather than
{}
orbegin
/end
to show nesting.- Any consistent indentation is legal, but almost everyone uses four spaces.
for number in [2, 3, 5]:
print(number)
IndentationError: expected an indented block
- Indentation is always meaningful in Python.
firstName = "Jon"
lastName = "Smith"
File "<ipython-input-7-f65f2962bf9c>", line 2
lastName = "Smith"
^
IndentationError: unexpected indent
- This error can be fixed by removing the extra spaces at the beginning of the second line.
Loop variables can be called anything.
- As with all variables, loop variables are:
- Created on demand.
- Meaningless: their names can be anything at all.
for kitten in [2, 3, 5]:
print(kitten)
The body of a loop can contain many statements.
- But no loop should be more than a few lines long.
- Hard for human beings to keep larger chunks of code in mind.
primes = [2, 3, 5]
for p in primes:
squared = p ** 2
cubed = p ** 3
print(p, squared, cubed)
2 4 8
3 9 27
5 25 125
Use range
to iterate over a sequence of numbers.
- The built-in function
range
produces a sequence of numbers.- Not a list: the numbers are produced on demand to make looping over large ranges more efficient.
range(N)
is the numbers 0..N-1- Exactly the legal indices of a list or character string of length N
print('a range is not a list: range(0, 3)')
for number in range(0, 3):
print(number)
a range is not a list: range(0, 3)
0
1
2
The Accumulator pattern turns many values into one.
- A common pattern in programs is to:
- Initialize an accumulator variable to zero, the empty string, or the empty list.
- Update the variable with values from a collection.
# Sum the first 10 integers.
total = 0
for number in range(10):
total = total + (number + 1)
print(total)
55
- Read
total = total + (number + 1)
as:- Add 1 to the current value of the loop variable
number
. - Add that to the current value of the accumulator variable
total
. - Assign that to
total
, replacing the current value.
- Add 1 to the current value of the loop variable
- We have to add
number + 1
becauserange
produces 0..9, not 1..10.
Classifying Errors
Is an indentation error a syntax error or a runtime error?
Solution
An IndentationError is a syntax error. Programs with syntax errors cannot be started. A program with a runtime error will start but an error will be thrown under certain conditions.
Practice Accumulating
Fill in the blanks in each of the programs below to produce the indicated result.
# Total length of the strings in the list: ["red", "green", "blue"] => 12 total = 0 for word in ["red", "green", "blue"]: ____ = ____ + len(word) print(total)
Solution
total = 0 for word in ["red", "green", "blue"]: total = total + len(word) print(total)
# List of word lengths: ["red", "green", "blue"] => [3, 5, 4] lengths = ____ for word in ["red", "green", "blue"]: lengths.____(____) print(lengths)
Solution
lengths = [] for word in ["red", "green", "blue"]: lengths.append(len(word)) print(lengths)
# Concatenate all words: ["red", "green", "blue"] => "redgreenblue" words = ["red", "green", "blue"] result = ____ for ____ in ____: ____ print(result)
Solution
words = ["red", "green", "blue"] result = "" for word in words: result = result + word print(result)
Identifying Variable Name Errors
- Read the code below and try to identify what the errors are without running it.
- Run the code and read the error message. What type of
NameError
do you think this is? Is it a string with no quotes, a misspelled variable, or a variable that should have been defined but was not?- Fix the error.
- Repeat steps 2 and 3, until you have fixed all the errors.
for number in range(10): # use a if the number is a multiple of 3, otherwise use b if (Number % 3) == 0: message = message + a else: message = message + "b" print(message)
Solution
- Python variable names are case sensitive:
number
andNumber
refer to different variables.- The variable
message
needs to be initialized as an empty string.- We want to add the string
"a"
tomessage
, not the undefined variablea
.message = "" for number in range(10): # use a if the number is a multiple of 3, otherwise use b if (number % 3) == 0: message = message + "a" else: message = message + "b" print(message)
Identifying Item Errors
- Read the code below and try to identify what the errors are without running it.
- Run the code, and read the error message. What type of error is it?
- Fix the error.
seasons = ['Spring', 'Summer', 'Fall', 'Winter'] print('My favorite season is ', seasons[4])
Solution
This list has 4 elements and the index to access the last element in the list is
3
.seasons = ['Spring', 'Summer', 'Fall', 'Winter'] print('My favorite season is ', seasons[3])
Key Points
A for loop executes commands once for each value in a collection.
A
for
loop is made up of a collection, a loop variable, and a body.The first line of the
for
loop must end with a colon, and the body must be indented.Indentation is always meaningful in Python.
Loop variables can be called anything (but it is strongly advised to have a meaningful name to the looping variable).
The body of a loop can contain many statements.
Use
range
to iterate over a sequence of numbers.The Accumulator pattern turns many values into one.
Conditionals
Overview
Teaching: 15 min
Exercises: 15 minQuestions
How can programs do different things for different data?
Objectives
Correctly write programs that use if and else statements and simple Boolean expressions (without logical operators).
Trace the execution of unnested conditionals and conditionals inside loops.
Use if
statements to control whether or not a block of code is executed.
- An
if
statement (more properly called a conditional statement) controls whether some block of code is executed or not. - Structure is similar to a
for
statement:- First line opens with
if
and ends with a colon - Body containing one or more statements is indented (usually by 4 spaces)
- First line opens with
mass = 3.54
if mass > 3.0:
print(mass, 'is large')
mass = 2.07
if mass > 3.0:
print (mass, 'is large')
3.54 is large
Conditionals are often used inside loops.
- Not much point using a conditional when we know the value (as above).
- But useful when we have a collection to process.
masses = [3.54, 2.07, 9.22, 1.86, 1.71]
for m in masses:
if m > 3.0:
print(m, 'is large')
3.54 is large
9.22 is large
Use else
to execute a block of code when an if
condition is not true.
else
can be used following anif
.- Allows us to specify an alternative to execute when the
if
branch isn’t taken.
masses = [3.54, 2.07, 9.22, 1.86, 1.71]
for m in masses:
if m > 3.0:
print(m, 'is large')
else:
print(m, 'is small')
3.54 is large
2.07 is small
9.22 is large
1.86 is small
1.71 is small
Use elif
to specify additional tests.
- May want to provide several alternative choices, each with its own test.
- Use
elif
(short for “else if”) and a condition to specify these. - Always associated with an
if
. - Must come before the
else
(which is the “catch all”).
masses = [3.54, 2.07, 9.22, 1.86, 1.71]
for m in masses:
if m > 9.0:
print(m, 'is HUGE')
elif m > 3.0:
print(m, 'is large')
else:
print(m, 'is small')
3.54 is large
2.07 is small
9.22 is HUGE
1.86 is small
1.71 is small
Conditions are tested once, in order.
- Python steps through the branches of the conditional in order, testing each in turn.
- So ordering matters.
grade = 85
if grade >= 70:
print('grade is C')
elif grade >= 80:
print('grade is B')
elif grade >= 90:
print('grade is A')
grade is C
- Does not automatically go back and re-evaluate if values change.
velocity = 10.0
if velocity > 20.0:
print('moving too fast')
else:
print('adjusting velocity')
velocity = 50.0
adjusting velocity
- Often use conditionals in a loop to “evolve” the values of variables.
velocity = 10.0
for i in range(5): # execute the loop 5 times
print(i, ':', velocity)
if velocity > 20.0:
print('moving too fast')
velocity = velocity - 5.0
else:
print('moving too slow')
velocity = velocity + 10.0
print('final velocity:', velocity)
0 : 10.0
moving too slow
1 : 20.0
moving too slow
2 : 30.0
moving too fast
3 : 25.0
moving too fast
4 : 20.0
moving too slow
final velocity: 30.0
- The program must have a
print
statement outside the body of the loop to show the final value ofvelocity
, since its value is updated by the last iteration of the loop.
Compound Relations Using
and
,or
, and ParenthesesOften, you want some combination of things to be true. You can combine relations within a conditional using
and
andor
. Continuing the example above, suppose you havemass = [ 3.54, 2.07, 9.22, 1.86, 1.71] velocity = [10.00, 20.00, 30.00, 25.00, 20.00] i = 0 for i in range(5): if mass[i] > 5 and velocity[i] > 20: print("Fast heavy object. Duck!") elif mass[i] > 2 and mass[i] <= 5 and velocity[i] <= 20: print("Normal traffic") elif mass[i] <= 2 and velocity[i] <= 20: print("Slow light object. Ignore it") else: print("Whoa! Something is up with the data. Check it")
Just like with arithmetic, you can and should use parentheses whenever there is possible ambiguity. A good general rule is to always use parentheses when mixing
and
andor
in the same condition. That is, instead of:if mass[i] <= 2 or mass[i] >= 5 and velocity[i] > 20:
write one of these:
if (mass[i] <= 2 or mass[i] >= 5) and velocity[i] > 20: if mass[i] <= 2 or (mass[i] >= 5 and velocity[i] > 20):
so it is perfectly clear to a reader (and to Python) what you really mean.
Tracing Execution
What does this program print?
pressure = 71.9 if pressure > 50.0: pressure = 25.0 elif pressure <= 50.0: pressure = 0.0 print(pressure)
Solution
25.0
Trimming Values
Fill in the blanks so that this program creates a new list containing zeroes where the original list’s values were negative and ones where the original list’s values were positive.
original = [-1.5, 0.2, 0.4, 0.0, -1.3, 0.4] result = ____ for value in original: if ____: result.append(0) else: ____ print(result)
[0, 1, 1, 1, 0, 1]
Solution
original = [-1.5, 0.2, 0.4, 0.0, -1.3, 0.4] result = [] for value in original: if value < 0.0: result.append(0) else: result.append(1) print(result)
Processing Small Files
Modify this program so that it only processes files with fewer than 50 records.
import glob import pandas as pd for filename in glob.glob('data/*.csv'): contents = pd.read_csv(filename) ____: print(filename, len(contents))
Solution
import glob import pandas as pd for filename in glob.glob('data/*.csv'): contents = pd.read_csv(filename) if len(contents) < 50: print(filename, len(contents))
Initializing
Modify this program so that it finds the largest and smallest values in the list no matter what the range of values originally is.
values = [...some test data...] smallest, largest = None, None for v in values: if ____: smallest, largest = v, v ____: smallest = min(____, v) largest = max(____, v) print(smallest, largest)
What are the advantages and disadvantages of using this method to find the range of the data?
Solution
values = [-2,1,65,78,-54,-24,100] smallest, largest = None, None for v in values: if smallest is None and largest is None: smallest, largest = v, v else: smallest = min(smallest, v) largest = max(largest, v) print(smallest, largest)
If you wrote
== None
instead ofis None
, that works too, but Python programmers always writeis None
because of the special wayNone
works in the language.It can be argued that an advantage of using this method would be to make the code more readable. However, a disadvantage is that this code is not efficient because within each iteration of the
for
loop statement, there are two more loops that run over two numbers each (themin
andmax
functions). It would be more efficient to iterate over each number just once:values = [-2,1,65,78,-54,-24,100] smallest, largest = None, None for v in values: if smallest is None or v < smallest: smallest = v if largest is None or v > largest: largest = v print(smallest, largest)
Now we have one loop, but four comparison tests. There are two ways we could improve it further: either use fewer comparisons in each iteration, or use two loops that each contain only one comparison test. The simplest solution is often the best:
values = [-2,1,65,78,-54,-24,100] smallest = min(values) largest = max(values) print(smallest, largest)
Key Points
Use
if
statements to control whether or not a block of code is executed.Conditionals are often used inside loops.
Use
else
to execute a block of code when anif
condition is not true.Use
elif
to specify additional tests.Conditions are tested once, in order.
Writing Functions
Overview
Teaching: 15 min
Exercises: 15 minQuestions
How can I create my own functions?
Objectives
Explain and identify the difference between function definition and function call.
Write a function that takes a small, fixed number of arguments and produces a single result.
Break programs down into functions to make them easier to understand.
- Human beings can only keep a few items in working memory at a time.
- Understand larger/more complicated ideas by understanding and combining pieces.
- Components in a machine.
- Lemmas when proving theorems.
- Functions serve the same purpose in programs.
- Encapsulate complexity so that we can treat it as a single “thing”.
- Also enables re-use.
- Write one time, use many times.
Define a function using def
with a name, parameters, and a block of code.
- Begin the definition of a new function with
def
. - Followed by the name of the function.
- Must obey the same rules as variable names.
- Then parameters in parentheses.
- Empty parentheses if the function doesn’t take any inputs.
- We will discuss this in detail in a moment.
- Then a colon.
- Then an indented block of code.
def print_greeting():
print('Hello!')
Defining a function does not run it.
- Defining a function does not run it.
- Like assigning a value to a variable.
- Must call the function to execute the code it contains.
print_greeting()
Hello!
Arguments in a function call are matched to its defined parameters.
- Functions are most useful when they can operate on different data.
- Specify parameters when defining a function.
- These become variables when the function is executed.
- Are assigned the arguments in the call (i.e., the values passed to the function).
- If you don’t name the arguments when using them in the call, the arguments will be matched to parameters in the order the parameters are defined in the function.
def print_date(year, month, day):
joined = str(year) + '/' + str(month) + '/' + str(day)
print(joined)
print_date(1871, 3, 19)
1871/3/19
Or, we can name the arguments when we call the function, which allows us to specify them in any order and adds clarity to the call site; otherwise as one is reading the code they might forget if the second argument is the month or the day for example.
print_date(month=3, day=19, year=1871)
1871/3/19
- Via Twitter:
()
contains the ingredients for the function while the body contains the recipe.
Functions may return a result to their caller using return
.
- Use
return ...
to give a value back to the caller. - May occur anywhere in the function.
- But functions are easier to understand if
return
occurs:- At the start to handle special cases.
- At the very end, with a final result.
def average(values):
if len(values) == 0:
return None
return sum(values) / len(values)
a = average([1, 3, 4])
print('average of values:', a)
average of values: 2.6666666666666665
print('average of empty list:', average([]))
average of empty list: None
- Remember: every function returns something.
- A function that doesn’t explicitly
return
a value automatically returnsNone
.
result = print_date(1871, 3, 19)
print('result of call is:', result)
1871/3/19
result of call is: None
Adding helpful information
Helpful information can be added to a function using a docstring.
After the def
line of a function, textual information explaining what the function does can be added using a multi line comment.
Multi line comments start and end with three quotation marks, """
:
def average(values):
"""
Return the average of a set of values
"""
if len(values) == 0:
return None
return sum(values) / len(values)
help(average)
Help on function average in module __main__:
average(values)
Return the average of a set of values
Identifying Syntax Errors
- Read the code below and try to identify what the errors are without running it.
- Run the code and read the error message. Is it a
SyntaxError
or anIndentationError
?- Fix the error.
- Repeat steps 2 and 3 until you have fixed all the errors.
def another_function print("Syntax errors are annoying.") print("But at least python tells us about them!") print("So they are usually not too hard to fix.")
Solution
def another_function(): print("Syntax errors are annoying.") print("But at least Python tells us about them!") print("So they are usually not too hard to fix.")
Definition and Use
What does the following program print?
def report(pressure): print('pressure is', pressure) print('calling', report, 22.5)
Solution
calling <function report at 0x7fd128ff1bf8> 22.5
A function call always needs parenthesis, otherwise you get memory address of the function object. So, if we wanted to call the function named report, and give it the value 22.5 to report on, we could have our function call as follows
print("calling") report(22.5)
calling pressure is 22.5
Order of Operations
What’s wrong in this example?
result = print_time(11, 37, 59) def print_time(hour, minute, second): time_string = str(hour) + ':' + str(minute) + ':' + str(second) print(time_string)
After fixing the problem above, explain why running this example code:
result = print_time(11, 37, 59) print('result of call is:', result)
gives this output:
11:37:59 result of call is: None
Why is the result of the call
None
?Solution
The problem with the example is that the function
print_time()
is defined after the call to the function is made. Python doesn’t know how to resolve the nameprint_time
since it hasn’t been defined yet and will raise aNameError
e.g.,NameError: name 'print_time' is not defined
The first line of output
11:37:59
is printed by the first line of code,result = print_time(11, 37, 59)
that binds the value returned by invokingprint_time
to the variableresult
. The second line is from the second print call to print the contents of theresult
variable.
print_time()
does not explicitlyreturn
a value, so it automatically returnsNone
.
Encapsulation
Fill in the blanks to create a function that takes a single filename as an argument, loads the data in the file named by the argument, and returns the minimum value in that data.
import pandas as pd def min_in_data(____): data = ____ return ____
Solution
import pandas as pd def min_in_data(filename): data = pd.read_csv(filename) return data.min()
Find the First
Fill in the blanks to create a function that takes a list of numbers as an argument and returns the first negative value in the list. What does your function do if the list is empty? What if the list has no negative numbers?
def first_negative(values): for v in ____: if ____: return ____
Solution
def first_negative(values): for v in values: if v < 0: return v
If an empty list or a list with all positive values is passed to this function, it returns
None
:my_list = [] print(first_negative(my_list))
None
Calling by Name
Earlier we saw this function:
def print_date(year, month, day): joined = str(year) + '/' + str(month) + '/' + str(day) print(joined)
We saw that we can call the function using named arguments, like this:
print_date(day=1, month=2, year=2003)
- What does
print_date(day=1, month=2, year=2003)
print?- When have you seen a function call like this before?
- When and why is it useful to call functions this way?
Solution
2003/2/1
- We saw examples of using named arguments when working with the pandas library. For example, when reading in a dataset using
data = pd.read_csv('data/temperature_2022-07.csv', index_col='Date')
the last argumentindex_col
is a named argument.- Using named arguments can make code more readable since one can see from the function call what name the different arguments have inside the function. It can also reduce the chances of passing arguments in the wrong order, since by using named arguments the order doesn’t matter.
Key Points
Break programs down into functions to make them easier to understand.
Define a function using
def
with a name, parameters, and a block of code.Defining a function does not run it.
Arguments in a function call are matched to its defined parameters.
Functions may return a result to their caller using
return
.
Break
Overview
Teaching: 0 min
Exercises: 0 minQuestions
Objectives
Key Points
Variable Scope
Overview
Teaching: 5 min
Exercises: 5 minQuestions
How do function calls actually work?
How can I determine where errors occurred?
Objectives
Identify local and global variables.
Identify parameters as local variables.
Read a traceback and determine the file, function, and line number on which the error occurred, the type of error, and the error message.
The scope of a variable is the part of a program that can ‘see’ that variable.
- There are only so many sensible names for variables.
- People using functions shouldn’t have to worry about what variable names the author of the function used.
- People writing functions shouldn’t have to worry about what variable names the function’s caller uses.
- The part of a program in which a variable is visible is called its scope.
pressure = 103.9
def adjust(t):
temperature = t * 1.43 / pressure
return temperature
pressure
is a global variable.- Defined outside any particular function.
- Visible everywhere.
t
andtemperature
are local variables inadjust
.- Defined in the function.
- Not visible in the main program.
- Remember: a function parameter is a variable that is automatically assigned a value when the function is called.
print('adjusted:', adjust(0.9))
print('temperature after call:', temperature)
adjusted: 0.01238691049085659
Traceback (most recent call last):
File "/Users/swcarpentry/foo.py", line 8, in <module>
print('temperature after call:', temperature)
NameError: name 'temperature' is not defined
Reading Error Messages
Read the traceback below, and identify the following:
- How many levels does the traceback have?
- What is the file name where the error occurred?
- What is the function name where the error occurred?
- On which line number in this function did the error occur?
- What is the type of error?
- What is the error message?
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-2-e4c4cbafeeb5> in <module>() 1 import errors_02 ----> 2 errors_02.print_friday_message() /Users/ghopper/thesis/code/errors_02.py in print_friday_message() 13 14 def print_friday_message(): ---> 15 print_message("Friday") /Users/ghopper/thesis/code/errors_02.py in print_message(day) 9 "sunday": "Aw, the weekend is almost over." 10 } ---> 11 print(messages[day]) 12 13 KeyError: 'Friday'
Solution
- Three levels.
errors_02.py
print_message
- Line 11
KeyError
. These errors occur when we are trying to look up a key that does not exist (usually in a data structure such as a dictionary). We can find more information about theKeyError
and other built-in exceptions in the Python docs.KeyError: 'Friday'
Key Points
The scope of a variable is the part of a program that can ‘see’ that variable.
Dictionaries
Overview
Teaching: 20 min
Exercises: 15 minQuestions
How can I store key-value data?
Objectives
Store data in a Python dictionary
Extract data from a Python dictionary
Python provides a data type called a dictionary, which is similar to a list in that it is a collection of objects.
Dictionaries and lists share the following characteristics:
- Both are mutable.
- Both are dynamic. They can grow and shrink as needed.
- Both can be nested. A list can contain another list. A dictionary can contain another dictionary. A dictionary can also contain a list, and vice versa.
Dictionaries differ from lists primarily in how elements are accessed:
- List elements are accessed by their position in the list, via indexing.
- Dictionary elements are accessed via keys.
This section aims to provide a good sense of when a dictionary is the appropriate data type to use, and how to do so.
Creating a dictionary
We can define a dictionary by enclosing a comma-separated list of key-value pairs in curly braces ({}
). A colon (:
) separates each key from its associated value:
person = {
'name': 'Ahmed',
'age': 42
}
Once we have defined a dictionary, we can inspect its type and values:
print(type(person))
print(person)
<class 'dict'>
{'name': 'Ahmed', 'age': 42}
A value is retrieved from a dictionary by specifying its corresponding key in square brackets ([]
):
print(person['name'])
print(person['age'])
Ahmed
42
Updating a dictionary
Let’s create a dictionary, containing the details for multiple individuals, where the details for each individual are also stored as dictionaries:
people = {
'Ahmed': {
'age': 42
},
'Cheryl': {
'age': 33
}
}
print(people)
{'Ahmed': {'age': 42}, 'Cheryl': {'age': 33}}
We can add a new entry to a dictionary by defining a new key:
people['Susan'] = {'age': 25}
print(people)
{'Ahmed': {'age': 42}, 'Cheryl': {'age': 33}, 'Susan': {'age': 25}}
An existing value in a dictionary can be updated by redefining the value associated with an existing key:
people['Cheryl']['age'] = 34
print(people)
{'Ahmed': {'age': 42}, 'Cheryl': {'age': 34}, 'Susan': {'age': 25}}
The available keys in a dictionary can be accessed using the dictionary’s .keys()
method:
print(people.keys())
dict_keys(['Ahmed', 'Cheryl', 'Susan'])
Using a dictionary to store data
In our data/
directory, we have three files containing temperature data for June, July and August 2022:
data/temperature_2022-06.csv
data/temperature_2022-07.csv
data/temperature_2022-08.csv
We can create a dictionary to store this information:
temp_files = {
'June': 'data/temperature_2022-06.csv',
'July': 'data/temperature_2022-07.csv',
'August': 'data/temperature_2022-08.csv'
}
print(temp_files)
{'June': 'data/temperature_2022-06.csv', 'July': 'data/temperature_2022-07.csv', 'August': 'data/temperature_2022-08.csv'}
If we would like to create a single figure containing three plots, one for each month, we can do this by accessing values from the dictionary:
# Create the subplot axes, with a single row and three columns:
fig, axs = plt.subplots(nrows=1, ncols=3)
# Set the figure size:
fig.set_figwidth(24)
fig.set_figheight(8)
# Plot June data
# access the axes for this plot:
ax = axs[0]
# Set the month name:
month = 'June'
# Read the data for this month using Pandas:
data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date'])
# Plot the data using the Pandas DataFrame plot function:
data.plot(ax=ax)
# Set the plot title:
ax.set_title(month)
# Plot July data
# access the axes for this plot:
ax = axs[1]
# Set the month name:
month = 'July'
# Read the data for this month using Pandas:
data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date'])
# Plot the data using the Pandas DataFrame plot function:
data.plot(ax=ax)
# Set the plot title:
ax.set_title(month)
# Plot August data
# access the axes for this plot:
ax = axs[2]
# Set the month name:
month = 'August'
# Read the data for this month using Pandas:
data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date'])
# Plot the data using the Pandas DataFrame plot function:
data.plot(ax=ax)
# Set the plot title:
ax.set_title(month)
The three plots in the figure do not have the same y axis limits, which makes it difficult to compare the values.
We can fix this by setting the y axis limits, using the set_ylim
function:
# Create the subplot axes, with a single row and three columns:
fig, axs = plt.subplots(nrows=1, ncols=3)
# Set the figure size:
fig.set_figwidth(24)
fig.set_figheight(8)
# Plot June data
# access the axes for this plot:
ax = axs[0]
# Set the month name:
month = 'June'
# Read the data for this month using Pandas:
data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date'])
# Plot the data using the Pandas DataFrame plot function:
data.plot(ax=ax)
# Set the y axis limits:
ax.set_ylim((0, 40))
# Set the plot title:
ax.set_title(month)
# Plot July data
# access the axes for this plot:
ax = axs[1]
# Set the month name:
month = 'July'
# Read the data for this month using Pandas:
data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date'])
# Plot the data using the Pandas DataFrame plot function:
data.plot(ax=ax)
# Set the y axis limits:
ax.set_ylim((0, 40))
# Set the plot title:
ax.set_title(month)
# Plot August data
# access the axes for this plot:
ax = axs[2]
# Set the month name:
month = 'August'
# Read the data for this month using Pandas:
data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date'])
# Plot the data using the Pandas DataFrame plot function:
data.plot(ax=ax)
# Set the y axis limits:
ax.set_ylim((0, 40))
# Set the plot title:
ax.set_title(month)
Plotting in a loop
Can you recreate the figure above by looping through the values in the dictionary. You may wish to use the
enumerate
function.Solution
# Create the subplot axes, with a single row and three columns: fig, axs = plt.subplots(nrows=1, ncols=3) # Set the figure size: fig.set_figwidth(24) fig.set_figheight(8) # Loop through dictionary keys, using the enumerate function: for index, month in enumerate(temp_files.keys()): # Access the axes for this plot: ax = axs[index] # Set the month name from the dictoinary key name: month = month # Read the data for this month using Pandas: data = pd.read_csv(temp_files[month], index_col='Date', parse_dates=['Date']) # Plot the data using the Pandas DataFrame plot function: data.plot(ax=ax) # Set the y axis limits: ax.set_ylim((0, 40)) # Set the plot title: ax.set_title(month)
Key Points
Python dictionaries are one of the most versatile and efficient data types in Python.
Working With Geospatial data
Overview
Teaching: 0 min
Exercises: 0 minQuestions
How can I read in and plot geospatial data?
Objectives
Read in data from NetCDF files
Plot data on a map
Installing additional libraries with conda
The conda
command line tool is used to manage packages and environments within an Anaconda installation.
From a terminal, or Anaconda prompt, the libraries we will be using can be installed with:
conda install -y -c conda-forge iris cartopy
After installing these packages, it may be necessary to open a new terminal and launch a new Jupyter Lab session for things to work correctly.
- Iris is a powerful, format-agnostic, community-driven Python package for analysing and visualising Earth science data.
- Cartopy is designed for geospatial data processing in order to produce maps and other geospatial data analyses.
Reading NetCDF data with Iris
Reading in a NetCDF file using Iris is done using the load()
or load_cube()
functions.
We have been provided some NetCDF files containing ERA5 global temperature data from the ECMWF.
The file data/era5_mean_temp_1981-2010.nc
contains the global mean temperature data for 1981 to 2010. The file data/era5_mean_annual_temp_2018-2022.nc
contains global annual mean temperatures for the years 2018 to 2022.
The files we will be looking at contain a single variable, so we can load data from our first file with the load_cube()
function:
import iris
hist_temp = iris.load_cube('data/era5_mean_temp_1981-2010.nc')
print(hist_temp)
2 metre temperature / (K) (latitude: 181; longitude: 360)
Dimension coordinates:
latitude x -
longitude - x
Scalar coordinates:
expver 1
time 1995-12-17 00:00:00, bound=(1981-01-01 00:00:00, 2010-12-01 00:00:00)
Cell methods:
0 time: mean
Attributes:
Conventions 'CF-1.7'
history '2023-12-10 09:47:57 GMT by grib_to_netcdf-2.25.1: /opt/ecmwf/mars-client/bin/grib_to_netcdf.bin ...'
The hist_temp
object is an iris cube. This object contains the
variable data as well as related metadata.
print(type(hist_temp))
<class 'iris.cube.Cube'>
Loading data from files containing multiple variables
When using the iris
load()
function it will produce a list of cubes, one for each variable in the file, which may look similar to this:cubes = iris.load('data/20191201.nc') print(cubes)
0: 2 metre temperature / (K) (time: 4; latitude: 256; longitude: 512) 1: air_pressure_at_mean_sea_level / (Pa) (time: 4; latitude: 256; longitude: 512)
We can see there are two variables in the file, 2 metre temperature and air_pressure_at_mean_sea_level.
The variable
cubes
is an Iris cube list, and the variables in the list can be accessed by the index value:print(cubes[0])
2 metre temperature / (K) (time: 4; latitude: 256; longitude: 512) Dimension coordinates: time x - - latitude - x - longitude ...
We can load a single variable from a file, by passing the variable name to Iris.
Variables in NetCDF files can contain multiple versions of the name. The name to use when loading a particular variable with Iris will be the same as we saw when looking at the
cubes
variable, so to load the 2m temperature variable, we use the name 2 metre temperature:temp = iris.load_cube('data/20191201.nc', '2 metre temperature') print(temp)
2 metre temperature / (K) (time: 4; latitude: 256; longitude: 512) Dimension coordinates: time x - - latitude - x - longitude ...
The data values for an Iris cube can be found in the data
property:
print(hist_temp.data)
print(type(hist_temp.data))
[[258.93490373 258.93490373 258.93490373 ... 258.93490373 258.93490373
258.93490373]
[259.1283197 259.13209851 259.13575203 ... 259.12782856 259.12798893
259.12817938]
[259.32918805 259.33184424 259.33421477 ... 259.31327094 259.31432339
259.32189605]
...
[228.46974617 228.43380738 228.39738245 ... 228.53184095 228.5038557
228.48709162]
[228.06460655 228.0546333 228.0449006 ... 228.10377788 228.09062722
228.07752166]
[227.69386212 227.69386212 227.69386212 ... 227.69386212 227.69386212
227.69386212]]
<class 'numpy.ma.core.MaskedArray'>
We can see that Iris stores data in a Numpy array. The type of array used to store the data is a MaskedArray
, which means that values can be masked out, for example if there was only data for values over land, the points which were in ocean areas may be masked out.
As the data is a Numpy array, we can find out some more information about the shape of the array, and the values it contains:
print(hist_temp.data.shape)
print(hist_temp.data.min())
print(hist_temp.data.max())
print(hist_temp.data.mean())
(181, 360)
219.82352664773725
306.46696949834524
278.2130725081754
The time, latitude and longitude information for the data can be accessed using the coord
property of the cube:
print(hist_temp.coord('time'))
print(hist_temp.coord('latitude'))
print(hist_temp.coord('longitude'))
DimCoord : time / (hours since 1900-01-01 00:00:00.0, standard calendar)
points: [1995-12-17 00:00:00]
bounds: [[1981-01-01 00:00:00, 2010-12-01 00:00:00]]
shape: (1,) bounds(1, 2)
dtype: int32
standard_name: 'time'
long_name: 'time'
var_name: 'time'
DimCoord : latitude / (degrees)
points: [ 90., 89., ..., -89., -90.]
shape: (181,)
...
We would like to plot the data to see what it looks like, so will extract the values we need:
lons = hist_temp.coord('longitude').points
lats = hist_temp.coord('latitude').points
hist_temp_data = hist_temp.data
print(lons.shape)
print(lats.shape)
print(hist_temp_data.shape)
(360,)
(181,)
(181, 360)
We have 360 longitude values, 181 latitude values and 360*181 temperature values.
There are various ways to plot 2d data, and we will use the pcolormesh
function to plot the temperature data:
plt.pcolormesh(lons, lats, hist_temp_data, cmap='coolwarm')
plt.colorbar()
Using Cartopy to plot data on a map
Now we have loaded some geospatial data, we can use the Cartopy package to plot the data on a map.
Cartopy can plot data in various projections, we will create some axes for our plot using the PlateCarree, and add the costlines to the plot:
import cartopy
map_projection = cartopy.crs.PlateCarree()
map_axes = plt.axes(projection=map_projection)
map_axes.add_feature(cartopy.feature.COASTLINE)
map_axes.gridlines(draw_labels=True)
There are several other basic features which can be added to a map using Cartopy. These include:
- COASTLINE
- BORDERS
- LAKES
- LAND
- OCEAN
- RIVERS
We have added gridlines to the map with map_ax.gridlines()
function.
Next we will add our temperature data to the map. When working with Matplotlib axes as we are here, the plotting functions become a method of the axes, so rather than plt.pcolormesh()
, we will use map_axes.pcolormesh()
:
# Set map projection:
map_projection = cartopy.crs.PlateCarree()
# Create the plot axes:
map_axes = plt.axes(projection=map_projection)
# Add gridlines to the map:
map_axes.gridlines(draw_labels=True)
# Plot the temperature data:
temp_plot = map_axes.pcolormesh(lons, lats, hist_temp_data, cmap='coolwarm')
# Add coastlines to the map:
map_axes.add_feature(cartopy.feature.COASTLINE)
# Add a colour scale:
cbar = plt.colorbar(temp_plot, orientation='horizontal', fraction=0.05)
# Set the colour bar label:
cbar.set_label('temperature (K)')
# Set the plot title:
map_axes.set_title('Temperature 1981-2010')
There is now quite a lot going on to create the plot, and we can see how adding comments helps to keep track of what is being done.
- We first set the projection for the plot (
map_projection =
) - The axes for the plot are created using
plot.axes()
- Coastlines are added to the plot using
map_axes.add_feature()
- Gridlines are added using
map_ax.gridlines()
- The temperature data is plotted using
map_axes.pcolormesh()
and the plot object is stored astemp_plot
- A colour scale is added using
plt.colorbar()
to which we pass thetemp_plot
object. The colour scale object is stored ascbar
. - The label for the colour scale is set with
cbar.set_label()
. - Finally, we set the plot title using
map_axes.set_title()
Comparing historical data with recent annual data
We have annual temperature data for the years 2018-2022 in the file data/era5_mean_annual_temp_2018-2022.nc
which we would like to complare with the historical data. We can load this data using iris:
ann_temp = iris.load_cube('data/era5_mean_annual_temp_2018-2022.nc')
print(ann_temp)
2 metre temperature / (K) (time: 5; latitude: 181; longitude: 360)
Dimension coordinates:
time x - -
latitude - x -
longitude - - x
Scalar coordinates:
expver 1
Cell methods:
0 time: mean
Attributes:
Conventions 'CF-1.7'
history '2023-12-10 09:47:57 GMT by grib_to_netcdf-2.25.1: /opt/ecmwf/mars-client/bin/grib_to_netcdf.bin ...'
DimCoord : time / (hours since 1900-01-01 00:00:00.0, standard calendar)
points: [
2018-06-17 00:00:00, 2019-06-17 00:00:00, 2020-06-16 12:00:00,
2021-06-17 00:00:00, 2022-06-17 00:00:00]
bounds: [
[2018-01-01 00:00:00, 2018-12-01 00:00:00],
[2019-01-01 00:00:00, 2019-12-01 00:00:00],
[2020-01-01 00:00:00, 2020-12-01 00:00:00],
[2021-01-01 00:00:00, 2021-12-01 00:00:00],
[2022-01-01 00:00:00, 2022-12-01 00:00:00]]
shape: (5,) bounds(5, 2)
dtype: int32
standard_name: 'time'
long_name: 'time'
var_name: 'time'
We can see that this annual data has an additional time dimension, i.e. there are 360*181 gridded temperature values at each time step.
print('shape of annual temp data:', ann_temp.data.shape)
print('shape of data for first time step:', ann_temp.data[0].shape)
shape of annual temp data: (5, 181, 360)
shape of data for first time step: (181, 360)
We would like to compare the temperature for each year in the ann_temp
data to the historical data.
To avoided having to repeat code, we will create a function to plot the data:
def plot_temp_diff(ann_data, hist_data, year):
"""
plot the difference between the annual data and the historical data
"""
# Calculate the temperature difference:
temp_diff = ann_data - hist_data
# Set map projection:
map_projection = cartopy.crs.PlateCarree()
# Create the plot axes:
map_axes = plt.axes(projection=map_projection)
# Add gridlines to the map:
map_axes.gridlines(draw_labels=True)
# Plot the temperature data:
temp_plot = map_axes.pcolormesh(lons, lats, temp_diff, cmap='coolwarm')
# Add coastlines to the map:
map_axes.add_feature(cartopy.feature.COASTLINE)
# Add a colour scale:
cbar = plt.colorbar(temp_plot, orientation='horizontal', fraction=0.05)
# Set the colour bar label:
cbar.set_label('temperature difference')
# Set the plot title:
map_axes.set_title(f'Temperature difference {year}, 1981-2010')
# Display the plot:
plt.show()
Once we have created the function, we can use this to plot the difference in the temperature data for a single year.
The data for 2018 is the first time step in the annual data, so to plot the difference between the historical data and the data for 2018:
plot_temp_diff(ann_temp.data[0], hist_temp_data, 2018)
To plot the differences for all years, we can loop through the data.
We can do this using the built in enumerate
function, which loops through a collection of items, and at each step of the loop provides the index and value, for example:
fruits = ['apples', 'bananas', 'raspberries']
colours = ['green', 'yellow', 'red']
for index, fruit in enumerate(fruits):
colour = colours[index]
print(fruit, 'are', colour)
apples are green
bananas are yellow
raspberries are red
We will define a range of years for which we have data, and use the enumerate
function to loop through this list, and plot the corresponding data.
# get a range of years for which we have data, 2018 to 2022:
years = range(2018, 2023)
# loop through the years using enumerate:
for index, year in enumerate(years):
# plot the difference between the historical data and the annual data
# for this year:
plot_temp_diff(ann_temp.data[index], hist_temp_data, year)
Setting colour bounds
It is difficult to compare the plots, as the colour bounds have automatically been set based on the data values, and are different for each plot.
The colour bounds for the
pcolormesh
plot can withvmin
andvmax
arguments. Update theplot_temp_diff
function, setting suitable values, and recreate the plots.Solution
Suitable bounds for the colour values may be -4 to 4, and the
pcolormesh
line in the function could be updated to:temp_plot = map_axes.pcolormesh(lons, lats, temp_diff, cmap='coolwarm', vmin=-4, vmax=4)
This should produce plots which all have the same colour bounds
Different plot types
Different plot types may be suitable for different types of data. For our temperature data, we could try creating a filled contour plot, using the
contourf
function.Try updating the
plot_temp_diff
function to use thecontourf
function, rather thanpcolormesh
.Rather than
vmin
andvmax
, the colour bounds for a contour plot are set with thelevels
argument. For examplelevels=np.arange(-4, 5, 1)
Solution
To create filled contour plots, the
pcolormesh
line in the function could be replaced with:temp_plot = map_axes.contourf(lons, lats, temp_diff, cmap='coolwarm', levels=np.arange(-4, 5, 1))
Using a different map projection
How would you update the
plot_temp_diff
function to use theOrthographic
projection?You will need to add the following argument to the
pcolormesh()
orcontourf()
command, so that the data points are correctly projected:transform=cartopy.crs.PlateCarree()
Solution
The setting of the
map_projection
variable can be updated:map_projection = cartopy.crs.Orthographic()
Then the plotting
transform
argument can be added to the plotting function, which ispcolormesh
in this example, but the same could be added tocontourf
temp_plot = map_axes.pcolormesh(lons, lats, temp_diff, cmap='coolwarm', vmin=-4, vmax=4, transform=cartopy.crs.PlateCarree())
Key Points
Iris reads data in to an Iris Cube object
Cartopy can be used to plot data on a map
Programming Style
Overview
Teaching: 10 min
Exercises: 0 minQuestions
How can I make my programs more readable?
How do most programmers format their code?
How can programs check their own operation?
Objectives
Provide sound justifications for basic rules of coding style.
Refactor one-page programs to make them more readable and justify the changes.
Use Python community coding standards (PEP-8).
Coding style
A consistent coding style helps others (including our future selves) read and understand code more easily. Code is read much more often than it is written, and as the Zen of Python states, “Readability counts”. Python proposed a standard style through one of its first Python Enhancement Proposals (PEP), PEP8.
Some points worth highlighting:
- document your code and ensure that assumptions, internal algorithms, expected inputs, expected outputs, etc., are clear
- use clear, semantically meaningful variable names
- use white-space, not tabs, to indent lines (tabs can cause problems across different text editors, operating systems, and version control systems)
Follow standard Python style in your code.
- PEP8:
a style guide for Python that discusses topics such as how to name variables,
how to indent your code,
how to structure your
import
statements, etc. Adhering to PEP8 makes it easier for other Python developers to read and understand your code, and to understand what their contributions should look like. - To check your code for compliance with PEP8, you can use the pycodestyle application and tools like the black code formatter can automatically format your code to conform to PEP8 and pycodestyle (a Jupyter notebook formatter also exists nb_black).
- Some groups and organizations follow different style guidelines besides PEP8. For example, the Google style guide on Python makes slightly different recommendations. Google wrote an application that can help you format your code in either their style or PEP8 called yapf.
- With respect to coding style, the key is consistency. Choose a style for your project be it PEP8, the Google style, or something else and do your best to ensure that you and anyone else you are collaborating with sticks to it. Consistency within a project is often more impactful than the particular style used. A consistent style will make your software easier to read and understand for others and for your future self.
Use assertions to check for internal errors.
Assertions are a simple but powerful method for making sure that the context in which your code is executing is as you expect.
def calc_bulk_density(mass, volume):
'''Return dry bulk density = powder mass / powder volume.'''
assert volume > 0
return mass / volume
If the assertion is False
, the Python interpreter raises an AssertionError
runtime exception. The source code for the expression that failed will be displayed as part of the error message. To ignore assertions in your code run the interpreter with the ‘-O’ (optimize) switch. Assertions should contain only simple checks and never change the state of the program. For example, an assertion should never contain an assignment.
Use docstrings to provide builtin help.
If the first thing in a function is a character string that is not assigned directly to a variable, Python attaches it to the function, accessible via the builtin help function. This string that provides documentation is also known as a docstring.
def average(values):
"Return average of values, or None if no values are supplied."
if len(values) == 0:
return None
return sum(values) / len(values)
help(average)
Help on function average in module __main__:
average(values)
Return average of values, or None if no values are supplied.
Multiline Strings
Often use multiline strings for documentation. These start and end with three quote characters (either single or double) and end with three matching characters.
"""This string spans multiple lines. Blank lines are allowed."""
Document This
Turn the comment in the following function into a docstring and check that
help
displays it properly.def middle(a, b, c): # Return the middle value of three. # Assumes the values can actually be compared. values = [a, b, c] values.sort() return values[1]
Solution
def middle(a, b, c): """Return the middle value of three. Assumes the values can actually be compared.""" values = [a, b, c] values.sort() return values[1]
Key Points
Follow standard Python style in your code.
Use docstrings to provide builtin help.
Wrap-Up
Overview
Teaching: 10 min
Exercises: 0 minQuestions
What have we learned?
What else is out there and where do I find it?
Objectives
Name and locate scientific Python community sites for software, workshops, and help.
Leslie Lamport once said, “Writing is nature’s way of showing you how sloppy your thinking is.” The same is true of programming: many things that seem obvious when we’re thinking about them turn out to be anything but when we have to explain them precisely.
Different ways to interact with Python
We have been interacting in notebooks, using Jupyter Lab. There are various other ways to interact with Python.
Notebooks
Notebooks can be accessed via the Jupyter Lab interface, or the ‘classic’ notebook interface can be launched with:
jupyter notebook
From the Jupyter Lab notebook interface, a notebook can be saved to a .py
text file by selecting File > Save and Export Notebook As > Executable Script.
From the classic notebook interface, select File > Download as > Python (.py).
Command Line
From a terminal / Anaconda prompt window, the Python interpeter can be accessed by running python
. Commands can be entered at the prompt and results can be printed to the terminal output.
From a terminal / Anaconda prompt window, .py
files can run with:
python name_of_file.py
Spyder
Spyder is a popular graphical development environment for working with Python, and has similar features to R Studio and Matlab.
When installing the full version of Anaconda, Spyder is included in the installation, and a shortcut to launch the software in the Windows Start Menu. The program can also be launched from a terminal / Anaconda prompt, by running:
spyder
To install spyder using the conda
command:
conda install -c conda-forge spyder
Python supports a large and diverse community across academia and industry.
-
The Python 3 documentation covers the core language and the standard library.
-
PyCon is the largest annual conference for the Python community.
-
SciPy is a rich collection of scientific utilities. It is also the name of a series of annual conferences.
-
Jupyter is the home of Project Jupyter.
-
Pandas is the home of the Pandas data library.
-
Stack Overflow’s general Python section can be helpful, as well as the sections on NumPy, SciPy, and Pandas.
Key Points
Python supports a large and diverse community across academia and industry.