From 53dd769a0223f0b156fe5298de245624bce2964a Mon Sep 17 00:00:00 2001 From: Gary Steele <g.a.steele@tudelft.nl> Date: Thu, 20 Aug 2020 23:18:21 +0200 Subject: [PATCH] ok, 27 minutes of clicking, but now all the appropriate cells have nbgrader readonly tages... --- Notebook 1/Notebook 1 Python Basics.ipynb | 586 ++++++++++++-- Notebook 1/Notebook 1 Python Basics.md | 113 ++- Notebook 2/Notebook 2 Functions.ipynb | 653 +++++++++++++-- Notebook 2/Notebook 2 Functions.md | 134 ++- .../Notebook 3 Program Flow Control.ipynb | 665 +++++++++++++-- Notebook 3/Notebook 3 Program Flow Control.md | 137 +++- ...ok 4 Scientific Computing with Numpy.ipynb | 746 +++++++++++++++-- ...ebook 4 Scientific Computing with Numpy.md | 148 +++- Notebook 5/Notebook 5 Data in Python.ipynb | 760 ++++++++++++++++-- Notebook 5/Notebook 5 Data in Python.md | 153 +++- 10 files changed, 3706 insertions(+), 389 deletions(-) diff --git a/Notebook 1/Notebook 1 Python Basics.ipynb b/Notebook 1/Notebook 1 Python Basics.ipynb index 22855cf..c3095d5 100644 --- a/Notebook 1/Notebook 1 Python Basics.ipynb +++ b/Notebook 1/Notebook 1 Python Basics.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e68e92ac1082dd04", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Python Basics\n", "\n", @@ -45,7 +54,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ee6ac0827c6a6783", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To send this command to the python kernel, there are several options. First, select the cell (so that it is either blue or green), and then:\n", "\n", @@ -75,7 +93,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8698bcb51f5ce066", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In the above, the text in the code cell are all python commands. In addition, if you start a line in a code cell with a `#`, python will ignore this line of text. This is use to add **comments** to your code. It is good programming practice to use comments to explain what the code is doing:" ] @@ -92,7 +119,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4919a733b4210f11", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.1** Print your own string to the command line. Can you use special characters as well?" ] @@ -112,7 +148,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-74c3c19c58006da4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## The Python kernel has a memory\n", "\n", @@ -130,7 +175,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84c1e3509c93658b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In python, the `=` symbol represents the **assignment operator**: it is an instruction to **assign** the value of `5` to the variable `a`. If variable `a` already exists, it will be over-written with the new value (in fact, `a` is a python object, something that we will explain in the optional notebook in more detail). If variable `a` does not yet exist, then python will create a new variable for you automatically.\n", "\n", @@ -150,7 +204,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-99b80d998fcc74ff", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Besides numerical values variables can also be strings, which are sequences of characters. You make a string by putting the text between quotes.\n", "\n", @@ -168,7 +231,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a4382a92b6e9aeec", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.2** Combine multiple strings and numerical values in a single `print` statement using the `,` separator." ] @@ -186,7 +258,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cbf03d005dae5d5d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.3** Change the value of `a` to 7 by executing the following cell, and then re-run the **above** cell containing the command `print(a)` (the one with output `5`). What value gets printed now in that cell? " ] @@ -203,7 +284,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2b713ce46a7bca48", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "As you can see in notebooks that the location of your code doesn’t matter, but the order in which you execute them does!!\n", "\n", @@ -224,7 +314,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-16240c968680ffa4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Sometimes, if you execute a lot of cells, or maybe even re-execute a cell after changing its contents, you might lose track of what variables are defined in the memory of your python kernel. For this, there is a convenient built-in \"magic\" command called `%whos` that can list for you all the variables that have been defined in your kernel, along with their values:" ] @@ -241,7 +340,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d61c3a57fbd93ed6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "_(Some notes about `%whos`: `%whos` is not a \"native\" command of the python language, but instead a \"built-in\" command that has been added by the creators of Jupyter. Because of this, you cannot use it outside of Jupyter / iPython...)_\n", "\n", @@ -269,14 +377,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bea496517f99df02", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this case the variable named is displayed, its value, but also its type. Type defines the format in which a variable is stored in memory. In this case `int` stands for integer and `float` stands for floating point number, which is the usual way in which real numbers are stored in a computer. We will learn more about Python variable types below." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-80d1461986f4d365", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Starting and stopping the kernel\n", "\n", @@ -317,7 +443,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6c0352859b13ed82", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You will notice that while a cell is running, the text beside it shows `In [*]:`. The `*` indicates that the cell is being executed, and will change to a number when the cell is finished. You will also see that the small circle beside the `Python 3` text on the right side of the Jupyter menu bar at the top of the page will become solid. Unless you have a lot of patience, you should probably stop the kernel, using the \"Stop\" button, or the menu item \"Kernel / Interrupt\".\n", "\n", @@ -339,7 +474,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fd74d0d65cf02f0a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Python variable types\n", "\n", @@ -361,7 +505,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6f2bfadc72282277", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In the second column, you can see the **type** that python chose for the variables we created. `int` corresponds to integer numbers, `float` corresponds to floating-point numbers. You can see that for variable `c`, python had to choose a `float` type (because 15.5 is not an integer), but for `a` and `b`, it chose integer types. \n", "\n", @@ -392,7 +545,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8ac075dfca80d989", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Because 5/2 = 2.5, Python decided to change the type of variable `a` from `int` to `float` after the assignment operation `a = a/2`. \n", "\n", @@ -410,7 +572,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9f4aa0b51687698a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The notation `1.5e-8` is a notation used in python to indicate the number $1.5 \\times 10^{-8}$.\n", "\n", @@ -437,7 +608,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-212eb128ab19078d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The notation `1j` is special, in particular because there is **no space** between the number `1` and the `j`. This is how Python knows that you are telling it to make a complex number (and not just referring to a variable named `j`...). The number in front of the `j` can be any floating point number: for example," ] @@ -453,7 +633,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-770f5a2d267d39b4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In addition to the mathematical variable types listed above, there are also other types of variables in Python. A common one you may encounter is the \"string\" variable type `str`, which is used for pieces of text. To tell Python you want to make a string, you enclose the text of your string in either single forward quotes `'` or double forward quotes `\"`:" ] @@ -481,7 +670,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6e76757478040b18", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also make multiline strings using three single quotes:" ] @@ -503,7 +701,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-31e4b55de81cef07", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note here that I have used a backslash: this a way to split Python code across multiple lines. \n", "\n", @@ -526,7 +733,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1fa7be3de8eb7f5a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "There is one more useful variable type we will introduce here: the \"boolean\" type `bool`. Boolean variable can have two values: `True` and `False`. You type them in directly as `True` and `False` with no quotes (you will see them turn green). " ] @@ -553,7 +769,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-12ff71b21d9d01e3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We will use boolean types much more later when we look at program control flow, but a simple example using the `if` statement is given below: \n", "\n", @@ -580,7 +805,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7f2acb9e44b581d8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can try changing the value of `g` above to `False` and see what happens if you run the above code cell again.\n", "\n", @@ -589,14 +823,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4016c53a455f3656", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.6** Discover which numbers can be used as `True` and `False` in Python by changing the value of `g` above and re-running the cells." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c1323d47dae023f1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Converting variables between different types\n", "\n", @@ -623,7 +875,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e7f8cf2c018ef4fd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that when converting an `float` to an `int`, python does not round off the value, but instead drops all the numbers off after the decimal point (it \"trucates\" it). If we want to convert to an integer and round it off, we can use the `round()` function:" ] @@ -650,7 +911,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f3f5149b2d627e26", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This works for conversions between many types. Sometimes, you have will lose information in this process: for example, converting a `float` to an `int`, we lose all the numbers after the decimal point. In this example, Python makes a guess at what you probably want to do, and decides to round off the floating point number to the nearest integer. \n", "\n", @@ -668,7 +938,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-582c3f589fac8746", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "A very useful feature is that Python can convert numbers into strings:" ] @@ -690,7 +969,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-18152675870ab0f1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "That is actually what happens when you use the `print()` commands with a numeric value.\n", "\n", @@ -728,7 +1016,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ce079e7a0832a0f5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.7** Define a list of parameters with as many types as possible, i.e. all the examples you see above and maybe a few more. Use `%whos` to see how they look inside the computers' memory. Try to change their format and rerun the `%whos` command." ] @@ -751,7 +1048,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d453c295a6954f9d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Python can do math\n", "\n", @@ -786,7 +1092,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-74f5aea3a72d2f71", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.8** Discover what the following Python operators do by performing some math with them: `*`, `-`, `/`, `**`, `//`, `%`. Print the value of the mathematical operation to the command line in susequent cells." ] @@ -859,7 +1174,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8cd6e8f83013a575", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Another handy built-in function is `abs()`:" ] @@ -878,7 +1202,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ec6f5beb19edae73", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can find the full list of built-in math commands on the python documentation webpage:\n", "\n", @@ -887,7 +1220,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9873288c26d190e4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Sending data to python using input()\n", "\n", @@ -913,7 +1255,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84fd218d798174b7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Even if we type a number into the input box, it will always return a string variable of type `str`:" ] @@ -929,7 +1280,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8f2b377eabcbec2b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If we want to use our input as a number, we have to convert it to a number, for example, by using the `float()` function:" ] @@ -948,7 +1308,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aa5bc8c7aabb059d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also specify text for the label of the input box: " ] @@ -964,7 +1333,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-39dc108b097dadf4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.9** Use the `input` function to get parameters of integer, float and string format into the computer." ] @@ -988,7 +1366,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-53972ca5ec4efa32", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Tab completion in Jupyter Notebooks\n", "\n", @@ -1014,7 +1401,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-df0c3eda1082e7ed", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now click on the following code cell, go the end of the lines in this cell and try pushing `Tab`:" ] @@ -1030,11 +1426,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-26572cb3a032030b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Handy! Jupyter did the typing for me! \n", "\n", - "If multiple things match, you will get a drop-down box and can select the one you want. So press \\texttt{Tab} : after" + "If multiple things match, you will get a drop-down box and can select the one you want. So press `Tab` : after" ] }, { @@ -1048,14 +1453,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cc31b788b063face", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also keep on typing: if you just type `a` after you hit tab and then hit tab again, it will finish the typing for you." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ea392babfac0516", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.10** Use tab completion on the initial letters of a few of the commands that have been presented. Along the way you will discover many more Python commands!" ] @@ -1073,7 +1496,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f4fabb371d6b9224", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Understanding Python Errors\n", "\n", @@ -1096,7 +1528,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4d9a7d42147dd830", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "After your code cell, you will see some colored text called a \"Traceback\". This \"Traceback\" is the way that python tries to tell you where the error is. \n", "\n", @@ -1133,7 +1574,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5deb43b858deacc0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Another common type of error is a `SyntaxError`, which means you have typed something that python does not understand:" ] @@ -1151,7 +1601,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b33dea484458dc18", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also get errors if you try to use operators that do not work with the data type you have. For example, if you try to \"divide\" two strings:" ] @@ -1167,7 +1626,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b40abf4de47d6118", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here, you get a `TypeError`: the division operator is a perfectly fine syntax, it just does not work with strings. \n", "\n", @@ -1183,7 +1651,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-737d6eb17c2a2f4f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 1.11** Run the following code and try to understand what is going wrong by reading the error message." ] @@ -1221,6 +1698,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, diff --git a/Notebook 1/Notebook 1 Python Basics.md b/Notebook 1/Notebook 1 Python Basics.md index 20d549f..1fb47a2 100644 --- a/Notebook 1/Notebook 1 Python Basics.md +++ b/Notebook 1/Notebook 1 Python Basics.md @@ -13,6 +13,7 @@ jupyter: name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e68e92ac1082dd04"} --> # Python Basics In this course, you will work using a platform called "Jupyter Notebooks". Jupyter notebooks are a way to combine formatted text (like the text you are reading now), Python code (which you will use below), and the result of your code and calculations all in one place. Go through these notebooks and run the examples. Try to understand their functioning and, if necessary, add code (such as print statements or variable overviews) to make it clear for you. In addition, there are exercises and practice cells where you can program for yourself. Don't be afraid to start coding yourself, writing code and making mistakes is the best way to learn Python. @@ -41,11 +42,13 @@ Jupyter notebooks are a way to interact with the python kernel. Notebooks are di The selected cell is surrounded by a box. If you type "enter" in a text cell you can start editing the cell. If you push "Run" above, or type "Shift-Enter", the code will be "run". If it is a code cell, it will run a command (see below). If it is a markdown cell, it will "compile" the markdown text language into formatted (HTML) text. You can give commands to this kernel by typing commands using the python language into the code cells of the notebook. Here, you can find an example of a code cell that contains a simple python command `print`, which prints a text string to the command line. +<!-- #endregion --> ```python print("Hello world") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ee6ac0827c6a6783"} --> To send this command to the python kernel, there are several options. First, select the cell (so that it is either blue or green), and then: 1. Click on the **Run** button above in the toolbar. This will execute the cell and move you to the next cell. @@ -59,6 +62,7 @@ When you run the cell, the code will be sent to the python kernel, which will tr After you have run the code cell, a number will appear beside your code cell. This number tell you in which order that piece of code was sent to the kernel. Because the kernel has a "memory", as you will see in the next section, this number can be useful so that you remember in which order the code cells in your notebook were executed. In the example above, the code cell contained only a single line of code, but if you want, you can include as many lines as you want in your code cell: +<!-- #endregion --> ```python print("Hello") @@ -66,14 +70,18 @@ print("world") print("Goodbye") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8698bcb51f5ce066"} --> In the above, the text in the code cell are all python commands. In addition, if you start a line in a code cell with a `#`, python will ignore this line of text. This is use to add **comments** to your code. It is good programming practice to use comments to explain what the code is doing: +<!-- #endregion --> ```python # This will print out a message print("This is a message") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4919a733b4210f11"} --> **Exercise 1.1** Print your own string to the command line. Can you use special characters as well? +<!-- #endregion --> ```python ### BEGIN SOLUTION @@ -81,31 +89,39 @@ print(" df# ") ### END SOLUTION ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-74c3c19c58006da4"} --> ## The Python kernel has a memory In addition to asking python to do things for you, like the "Hello world" example above, you can also have python remember things for you. To do this, you can use the following syntax: +<!-- #endregion --> ```python a = 5 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84c1e3509c93658b"} --> In python, the `=` symbol represents the **assignment operator**: it is an instruction to **assign** the value of `5` to the variable `a`. If variable `a` already exists, it will be over-written with the new value (in fact, `a` is a python object, something that we will explain in the optional notebook in more detail). If variable `a` does not yet exist, then python will create a new variable for you automatically. For you, the cell above will create a "variable" named `a` in memory of the python kernel that has the value of 5. We can check this by printing the value of a: +<!-- #endregion --> ```python print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-99b80d998fcc74ff"} --> Besides numerical values variables can also be strings, which are sequences of characters. You make a string by putting the text between quotes. Note that we can also add a message if we add a string and a numerical value in the `print()` statement by combining things with commas: +<!-- #endregion --> ```python print("The value of a is",a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a4382a92b6e9aeec"} --> **Exercise 1.2** Combine multiple strings and numerical values in a single `print` statement using the `,` separator. +<!-- #endregion --> ```python # Your code here @@ -113,16 +129,20 @@ word = 4 print("print this word:", word) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cbf03d005dae5d5d"} --> **Exercise 1.3** Change the value of `a` to 7 by executing the following cell, and then re-run the **above** cell containing the command `print(a)` (the one with output `5`). What value gets printed now in that cell? +<!-- #endregion --> ```python a = 7 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2b713ce46a7bca48"} --> As you can see in notebooks that the location of your code doesn’t matter, but the order in which you execute them does!! We can also use variables to set the values of other variables: +<!-- #endregion --> ```python b = 0 @@ -131,16 +151,20 @@ b = a print(b) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-16240c968680ffa4"} --> Sometimes, if you execute a lot of cells, or maybe even re-execute a cell after changing its contents, you might lose track of what variables are defined in the memory of your python kernel. For this, there is a convenient built-in "magic" command called `%whos` that can list for you all the variables that have been defined in your kernel, along with their values: +<!-- #endregion --> ```python a=5 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d61c3a57fbd93ed6"} --> _(Some notes about `%whos`: `%whos` is not a "native" command of the python language, but instead a "built-in" command that has been added by the creators of Jupyter. Because of this, you cannot use it outside of Jupyter / iPython...)_ If we define some new variables, they will also appear in the list of defined variables if you execute `%whos`: +<!-- #endregion --> ```python c = 10 @@ -151,9 +175,11 @@ d = 15.5 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bea496517f99df02"} --> In this case the variable named is displayed, its value, but also its type. Type defines the format in which a variable is stored in memory. In this case `int` stands for integer and `float` stands for floating point number, which is the usual way in which real numbers are stored in a computer. We will learn more about Python variable types below. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-80d1461986f4d365"} --> ## Starting and stopping the kernel When you open a notebook for the first time, a new kernel will be started for you, which will have nothing in your memory. @@ -177,15 +203,18 @@ For this, there is both a menubar "Kernel" at the top, along with two useful but <img src="resource/asnlib/public/Notebook_1_restartkernelmenu.png" width=60%></img> To see this in action, you can execute the following cell, which will do nothing other than wait for 10 minutes: +<!-- #endregion --> ```python from time import sleep sleep(10*60) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6c0352859b13ed82"} --> You will notice that while a cell is running, the text beside it shows `In [*]:`. The `*` indicates that the cell is being executed, and will change to a number when the cell is finished. You will also see that the small circle beside the `Python 3` text on the right side of the Jupyter menu bar at the top of the page will become solid. Unless you have a lot of patience, you should probably stop the kernel, using the "Stop" button, or the menu item "Kernel / Interrupt". **Exercise 1.4** List the stored variables using the `%whos` command. Subsequently, restart the kernel. What variables are stored in the memory of the kernel before and after the restart? +<!-- #endregion --> ```python # Your code here @@ -193,21 +222,25 @@ print(a) %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fd74d0d65cf02f0a"} --> ## Python variable types As we saw above, in Python, variable have a property that is called their "type". When you use the assignment operator `=` to assign a value to a variable, python will automatically pick a variable type it thinks fits best, even changing the type of an existing variable if it thinks it is a good idea. You have, in fact, already seen information about the types of variables in the `%whos` command again: +<!-- #endregion --> ```python %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6f2bfadc72282277"} --> In the second column, you can see the **type** that python chose for the variables we created. `int` corresponds to integer numbers, `float` corresponds to floating-point numbers. You can see that for variable `c`, python had to choose a `float` type (because 15.5 is not an integer), but for `a` and `b`, it chose integer types. _(In general, Python tries to choose a variable type that makes calculations the fastest and uses as little memory as possible.)_ If you assign a new value to a variable, it can change the variables type: +<!-- #endregion --> ```python a = a/2 @@ -217,17 +250,21 @@ a = a/2 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8ac075dfca80d989"} --> Because 5/2 = 2.5, Python decided to change the type of variable `a` from `int` to `float` after the assignment operation `a = a/2`. When you are using floating point numbers, you can also use an "exponential" notation to specify very big or very small numbers: +<!-- #endregion --> ```python c = 1.5e-8 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9f4aa0b51687698a"} --> The notation `1.5e-8` is a notation used in python to indicate the number $1.5 \times 10^{-8}$. A third type of mathematical variable type that you may use in physics is a complex number. In python, you can indicate a complex number by using `1j`, which is the python notation for the complex number $i$: +<!-- #endregion --> ```python d = 1+1j @@ -237,13 +274,17 @@ d = 1+1j %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-212eb128ab19078d"} --> The notation `1j` is special, in particular because there is **no space** between the number `1` and the `j`. This is how Python knows that you are telling it to make a complex number (and not just referring to a variable named `j`...). The number in front of the `j` can be any floating point number: for example, +<!-- #endregion --> ```python 0.5*d ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-770f5a2d267d39b4"} --> In addition to the mathematical variable types listed above, there are also other types of variables in Python. A common one you may encounter is the "string" variable type `str`, which is used for pieces of text. To tell Python you want to make a string, you enclose the text of your string in either single forward quotes `'` or double forward quotes `"`: +<!-- #endregion --> ```python e = "This is a string" @@ -256,7 +297,9 @@ print(e) print(f) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6e76757478040b18"} --> You can also make multiline strings using three single quotes: +<!-- #endregion --> ```python multi = \ @@ -268,18 +311,22 @@ multiple lines. print(multi) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-31e4b55de81cef07"} --> Note here that I have used a backslash: this a way to split Python code across multiple lines. Although it's not obvious, Python can also do "operations" on strings, the `+` mathematical opeartors we saw above also works with strings. **Exercise 1.5** Discover what the `+` operator does to a string, i.e. print the output of the sum of two strings. +<!-- #endregion --> ```python # Your code here print(e+" "+f) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1fa7be3de8eb7f5a"} --> There is one more useful variable type we will introduce here: the "boolean" type `bool`. Boolean variable can have two values: `True` and `False`. You type them in directly as `True` and `False` with no quotes (you will see them turn green). +<!-- #endregion --> ```python g = 0 @@ -289,9 +336,11 @@ g = 0 %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-12ff71b21d9d01e3"} --> We will use boolean types much more later when we look at program control flow, but a simple example using the `if` statement is given below: No panic if you don't yet understand the if statement, there will be another entire notebook dedicated to them. This is just an example of why boolean variables exist. +<!-- #endregion --> ```python if True: @@ -304,17 +353,21 @@ if not g: print("g is not true!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7f2acb9e44b581d8"} --> You can try changing the value of `g` above to `False` and see what happens if you run the above code cell again. Also, useful to know: numbers (both `int` and `float`) can also be used in True / False statements! Python will interpret any number that is not zero as `True` and any number that is zero as `False`. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4016c53a455f3656"} --> **Exercise 1.6** Discover which numbers can be used as `True` and `False` in Python by changing the value of `g` above and re-running the cells. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c1323d47dae023f1"} --> ## Converting variables between different types We can also convert a value from one type to another by using functions with the same name as the type that we want to convert them to. Some examples: +<!-- #endregion --> ```python float(5) @@ -324,7 +377,9 @@ float(5) int(7.63) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e7f8cf2c018ef4fd"} --> Note that when converting an `float` to an `int`, python does not round off the value, but instead drops all the numbers off after the decimal point (it "trucates" it). If we want to convert to an integer and round it off, we can use the `round()` function: +<!-- #endregion --> ```python b = round(7.63) @@ -336,15 +391,19 @@ type(b) print(b+0.4) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f3f5149b2d627e26"} --> This works for conversions between many types. Sometimes, you have will lose information in this process: for example, converting a `float` to an `int`, we lose all the numbers after the decimal point. In this example, Python makes a guess at what you probably want to do, and decides to round off the floating point number to the nearest integer. Sometimes, Python can't decide what to do, and so it triggers an error: +<!-- #endregion --> ```python float(1+1j) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-582c3f589fac8746"} --> A very useful feature is that Python can convert numbers into strings: +<!-- #endregion --> ```python a = 7.54 @@ -354,9 +413,11 @@ print(b) %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-18152675870ab0f1"} --> That is actually what happens when you use the `print()` commands with a numeric value. But also very useful is that as long as your string is easily convertable to a number, python can do this for you too! +<!-- #endregion --> ```python float('5.74') @@ -370,7 +431,9 @@ int('774') complex('5+3j') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ce079e7a0832a0f5"} --> **Exercise 1.7** Define a list of parameters with as many types as possible, i.e. all the examples you see above and maybe a few more. Use `%whos` to see how they look inside the computers' memory. Try to change their format and rerun the `%whos` command. +<!-- #endregion --> ```python # Your parameters list @@ -381,9 +444,11 @@ b= %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d453c295a6954f9d"} --> ## Python can do math Python has a set of math functions that are directly built in to the language. You can use python as a calculator! +<!-- #endregion --> ```python 1+1 @@ -396,7 +461,9 @@ a = 5 print(a+1) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-74f5aea3a72d2f71"} --> **Exercise 1.8** Discover what the following Python operators do by performing some math with them: `*`, `-`, `/`, `**`, `//`, `%`. Print the value of the mathematical operation to the command line in susequent cells. +<!-- #endregion --> ```python # Try out * @@ -429,7 +496,9 @@ print(a+1) 4%2 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8cd6e8f83013a575"} --> Another handy built-in function is `abs()`: +<!-- #endregion --> ```python print(abs(10)) @@ -438,11 +507,13 @@ print(abs(1j)) print(abs(1+1j)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ec6f5beb19edae73"} --> You can find the full list of built-in math commands on the python documentation webpage: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9873288c26d190e4"} --> ## Sending data to python using input() So far, we have seen examples of "output": Python telling us stuff, like in the first "Hello world" example above. @@ -450,6 +521,7 @@ So far, we have seen examples of "output": Python telling us stuff, like in the And we have seen example of "code": us giving instructions to python to do things. In addition, we can also send stuff to Python. Often in physics, we do this by having Python read data files, which we will cover later, but we can also send information to python using the `input()` command: +<!-- #endregion --> ```python a = input() @@ -459,13 +531,17 @@ print("The input was:") print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84fd218d798174b7"} --> Even if we type a number into the input box, it will always return a string variable of type `str`: +<!-- #endregion --> ```python type(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8f2b377eabcbec2b"} --> If we want to use our input as a number, we have to convert it to a number, for example, by using the `float()` function: +<!-- #endregion --> ```python a = input() @@ -474,13 +550,17 @@ print("\nThe value of a is:", a) print("a has the type:", type(a)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-aa5bc8c7aabb059d"} --> You can also specify text for the label of the input box: +<!-- #endregion --> ```python a = input("Enter a number: ") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-39dc108b097dadf4"} --> **Exercise 1.9** Use the `input` function to get parameters of integer, float and string format into the computer. +<!-- #endregion --> ```python # Your code here @@ -492,7 +572,7 @@ b = int(b) print(b, type(b)) ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-53972ca5ec4efa32"} --> ## Tab completion in Jupyter Notebooks @@ -510,41 +590,51 @@ this_is_my_very_long_variable_name = 5 this_is_another_ones = 6 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-df0c3eda1082e7ed"} --> Now click on the following code cell, go the end of the lines in this cell and try pushing `Tab`: +<!-- #endregion --> ```python this_is_another_ones ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-26572cb3a032030b"} --> Handy! Jupyter did the typing for me! -If multiple things match, you will get a drop-down box and can select the one you want. So press \texttt{Tab} : after +If multiple things match, you will get a drop-down box and can select the one you want. So press `Tab` : after +<!-- #endregion --> ```python this_is_my_very_long_variable_name ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cc31b788b063face"} --> You can also keep on typing: if you just type `a` after you hit tab and then hit tab again, it will finish the typing for you. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ea392babfac0516"} --> **Exercise 1.10** Use tab completion on the initial letters of a few of the commands that have been presented. Along the way you will discover many more Python commands! +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f4fabb371d6b9224"} --> ## Understanding Python Errors Sometimes, the code you type into a code cell will not work. In this case, Python will not execute your code, but instead print out an error message. In this section, we will take a look at these error messages and learn how to understand them. Let's write some code that will give an error. For example, this is a typo in the name of the `print()` command: +<!-- #endregion --> ```python a = 5 printt(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4d9a7d42147dd830"} --> After your code cell, you will see some colored text called a "Traceback". This "Traceback" is the way that python tries to tell you where the error is. Let's take a look at the traceback: @@ -567,24 +657,29 @@ A `NameError` means that python tried to find a function or variable that you ha At the very end of the traceback, Python tries to explain what the problem was: in this case, it is telling you that there is no function named `printt`. You will also get a `NameError` if you try to use a variable that doesn't exist: +<!-- #endregion --> ```python print(non_existant_variable) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5deb43b858deacc0"} --> Another common type of error is a `SyntaxError`, which means you have typed something that python does not understand: +<!-- #endregion --> ```python a = a $ 5 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b33dea484458dc18"} --> You can also get errors if you try to use operators that do not work with the data type you have. For example, if you try to "divide" two strings: +<!-- #endregion --> ```python "You cannot " / "divide strings" ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b40abf4de47d6118"} --> Here, you get a `TypeError`: the division operator is a perfectly fine syntax, it just does not work with strings. @@ -597,7 +692,9 @@ Sometimes, you can learn more about what the error means by reading these docume In last resort, you can also always try a internet search: googling the error message can help, and there are also lots of useful posts on <a href=https://stackexchange.com>stack exchange</a> (which you will also often find by google). <!-- #endregion --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-737d6eb17c2a2f4f"} --> **Exercise 1.11** Run the following code and try to understand what is going wrong by reading the error message. +<!-- #endregion --> ```python a=10 diff --git a/Notebook 2/Notebook 2 Functions.ipynb b/Notebook 2/Notebook 2 Functions.ipynb index b93a1be..1e9894c 100644 --- a/Notebook 2/Notebook 2 Functions.ipynb +++ b/Notebook 2/Notebook 2 Functions.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-98b92489332760b6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Functions in Python\n", "\n", @@ -63,7 +72,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fb10310a1ec1a391", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To save a lot of typing, one can define a simple function to do this work for us. To define a function, you use the following syntax:\n", "\n", @@ -95,7 +113,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c16ac57175c4a00e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Once you have defined your function, you can execute it by using the code `function_name()`. \n", "\n", @@ -137,14 +164,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a12ce8709fcf3b9f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this example, it may not be such a big deal, but you can imagine that as the code in your function becomes more and more complicated, it will save you a lot of time. Also, imagine that I wanted to change the wording of the sentence I print: in the case with the function, I would only have to do this once, while in the example without function, I would have to manually change this at 5 different places. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cc004d60a3c3b17c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.1** Write your own function that contains two lines of code. The first line should make a new variable `var2` that is `var` converted to an integer. The second line of your code should print the value of `var2`. \n", "\n", @@ -166,7 +211,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1be65f1712abb44d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions with input variables\n", "\n", @@ -204,7 +258,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-34acac6dd514c884", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "How does this work? \n", "\n", @@ -215,7 +278,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-53fd3c06a26175a3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.2** Copy your code from exercise 2.1 (but then with just normal indentation using tabs) into the cell below and change it such that it uses a function with input parameters to achieve the same task." ] @@ -233,7 +305,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-581873fe20420dc6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions with multiple inputs \n", "\n", @@ -270,16 +351,18 @@ "print_status3(a, 2*a)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-78e05b5cbe11902b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.3** Make a new function `print_status4()` that takes three variables as arguments and prints out messages telling the user the values of each of them (as above, but with three input variables). Test it to make sure it works. " ] @@ -297,7 +380,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fbf7e40e800af968", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions that return a value\n", "\n", @@ -319,7 +411,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3da704c82b41c669", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To \"capture\" the value returned by the function, you can assign it to a varible like this:" ] @@ -336,7 +437,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c4eb4137a0421531", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also just directly \"use\" the result of the function if you want:" ] @@ -352,7 +462,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9710d6768449ded8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that as soon as python sees the `return` command, it stops running the function, so any code after it will not be executed:" ] @@ -373,14 +492,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aef6b10798831fa6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you want to send back more than one result to the user of your function, you can separate the results with commas when you use the `return` command. How do you make use of these two variables that you send back? You will explore this in this exercise:" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c20b50841a685607", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.4** **(a)** Write a function that takes two real numbers as input and returns the sum and product of the two numbers. In your function, try to send *both* of the calculated numbers back as a return value. We have not taught you that yet so you will have to look it up: I recommend trying a google search for \"python function return two variables\". " ] @@ -400,7 +537,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-35103493f788b1d5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** Now USE your function to calculate the sum and product of `a` and `b`, \"capturing\" the sum and product in variables `s` and `p`:" ] @@ -422,7 +568,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-af1bcb8c3a7ded79", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Importing functions from libraries\n", "\n", @@ -458,7 +613,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ebea512349a5084", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can see it has been imported by using the `%whos` command: " ] @@ -476,7 +640,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6d7e43494d90fc23", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Once it has been imported, you can access all the functions of the module by adding `time.` in front of the function name (from the time module) in your code:" ] @@ -496,7 +669,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-36d70980cc576500", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type `dir(time)`, which will generate this list.\n", "\n", @@ -517,14 +699,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f6efa5c8f5b4ef63", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We will use this a lot when using the `numpy` module, shortening its name to `np` when we import it, and also for the `matplotlib.pyplot` submodule, which we will shorten to `plt`. (These are also typically used conventions in the scientific community.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7f7189cfda3188a5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Importing a single function \n", "\n", @@ -542,7 +742,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b9ce119a2966790a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "When you do this, the function `sleep()` will be available directly in your notebook kernel \"namespace\" without any prefix:" ] @@ -562,7 +771,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d62c06ba6b0ce825", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Using `%whos`, we can now see that we have three different ways to use the `sleep()` function:" ] @@ -580,7 +798,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6f0445a2973bf90c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you look around on the internet, you will also find people that will do the following\n", "\n", @@ -599,7 +826,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-59161c880f8689e6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Shift-Tab for getting help\n", "\n", @@ -621,7 +857,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7daff037eb77301f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also find the same help as the output of a code cell by using the `help()` function:" ] @@ -637,14 +882,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c7820259378fabba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "There are extensive online resources for many modules. The most used modules have helpful examples on the functions and how to implement them. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3c31a778f00cba2b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.5 (a)** Find help for the built-in functions `abs`, `int`, and `input`. Which of the help functions are easy to read? Which one does not provide such useful information (compared to the online documentation page)? (Put each help command in a separate cell)" ] @@ -684,7 +947,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-76f8da331b6edf11", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** Import the function `glob` from the library `glob` and print its help information. What does the function `glob(\"../*\")` do? " ] @@ -709,7 +981,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-10519772f1b5d68a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Global variables, local variables, and variable scope\n", "\n", @@ -754,7 +1035,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b4bb5a3316fe89c9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this example, when Python is inside the function `my_func()`, it first looks to see if there is a variable `a1` in the local scope of the function. It does not find one, so it then goes and looks in the global scope. There, it finds a variable `a1`, and so it uses this one. \n", "\n", @@ -778,7 +1068,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-10458e43f3723d0b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This code gives a `NameError` because there is no variable `b1` yet created in the global scope. If we run the following code cell and try the code above again, it will work. " ] @@ -794,7 +1093,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6074899777a25fae", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here you can see one of risks of languages like python: because of the persistent memory of the kernel, code can succeed or fail depending on what code you have run before it...\n", "\n", @@ -812,7 +1120,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e5003608c97f5668", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 3** Variables defined in the local scope of a function are not accessible outside the function" ] @@ -835,7 +1152,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d65b38344a2c4136", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 4** Variables passed to functions cannot be modified by the function (more on this later when we look at more complicated data structures...sometimes this is different)" ] @@ -858,14 +1184,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7f9a0c6f80607325", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This one is a bit subtle (mega-confusing?) because we re-used the same name `a` for the local variable in the function as the global variable outside of the function. However, the operation is quite logical. When the function code starts running, it creates a `local` variable `a` to store the value it received. And now, because there is already a local variable called `a`, using `a` in the function refers to the `local` variable `a`, not the `global` variable `a` we define before calling the function. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5aa7c9bac0e183c9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 5** This one is a tricky one." ] @@ -888,14 +1232,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f637454870e0a4ec", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ " It would seem that the function would refer to the global variable `a` and therefore change it's value. However, it is tricky since we first use `a` in the function in an assignment. An assignment in python will automatically create a variable if it does not exist, and so python creates a new variable named `a` in the local scope. The name `a` inside the function now refers to this newly created local variable, and therefore the global variable will not be changed. In fact, this guarantees that you cannot change global variables inside a function, unless you use the `global` qualifier shown in the next example." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a2c1c2099359531d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Example 6** If you want to make sure that the `a` inside your function is referring to the global variable `a`, you can include the line `global a` inside your function to tell python that you mean the global variable `a`. " ] @@ -919,7 +1281,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b86006c7d643e2ba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that in general, it is considered bad programming practice to use (too many) global variables. Why? When you write longer and bigger sections of code, it is easier to understand what is going in in your function if your function uses only local variables and communicates back and forth using input parameter and return variables. Using too many global variables in a function can be confusing because they are defined in a different place in your code and so you don't have a good oversight of them. (Bigger projects can easily have 10,000+ lines of code!) \n", "\n", @@ -938,14 +1309,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-98aa5a09da4cf9c1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to Exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3c74f16c8b097100", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.1** " ] @@ -954,6 +1343,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-776d867dd1e96a8e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -975,7 +1372,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-63bcaf171511d7a6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.2** " ] @@ -984,6 +1390,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3653f8a5e3438645", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -995,7 +1409,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4e6d55d796044deb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.3** " ] @@ -1004,6 +1427,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ab5a53f50aeb9eb2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1018,7 +1449,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6b6a2cfb8f2059c4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.4** **(a)** " ] @@ -1027,6 +1467,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-abb8b6adcc6f783b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1038,7 +1486,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5741495ca1f6ef79", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** " ] @@ -1046,7 +1503,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-df8f4cc5c689e227", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "a=1.5\n", @@ -1060,7 +1526,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c0897d2e0d18ff59", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 2.5** **(a)**" ] @@ -1068,7 +1543,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2eb1a4094c49679f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "help(abs)" @@ -1077,7 +1561,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6aed86aac101cb2a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "help(int)" @@ -1087,6 +1580,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-114d997d97a70ea6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1096,7 +1597,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a1e85a52b4b3a511", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)**" ] @@ -1104,7 +1614,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-440e78ae4effd486", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "from glob import glob\n", @@ -1114,7 +1633,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a4cc244f58f0c6a1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "glob(\"../*\")\n", @@ -1124,6 +1652,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, diff --git a/Notebook 2/Notebook 2 Functions.md b/Notebook 2/Notebook 2 Functions.md index 5ed869f..feedf2c 100644 --- a/Notebook 2/Notebook 2 Functions.md +++ b/Notebook 2/Notebook 2 Functions.md @@ -13,6 +13,7 @@ jupyter: name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-98b92489332760b6"} --> # Functions in Python In this notebook, we will explore the implementation of functions in Python. @@ -34,6 +35,7 @@ One way to do this is to copy and paste the same piece of code over and over aga For this reason (among others), programming languages allow programmers to define "functions". Functions are pieces of code that you can give a name and then enable you to them use over and over again, without having to retype the code text. As an example, let's say that we want to print out the value of a variables named `a` and `b` using a long sentence: +<!-- #endregion --> ```python a = 6 @@ -62,6 +64,7 @@ print("The value of variable a is", a) print("The value of variable b is", b) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fb10310a1ec1a391"} --> To save a lot of typing, one can define a simple function to do this work for us. To define a function, you use the following syntax: ``` @@ -74,6 +77,7 @@ Here, you replace the `...` with the code you want to function to execute. The P Tabs in Python are VERY IMPORTANT: python uses tabs to know which code is inside the function and which is not. If you make a mistake with the tabs in such a way that python cannot understand what you mean, it will give you an `IdentationError`. In notebooks, you can also select a line, or multiple lines, and then use `Tab` to increase their indentation level, or use `Shift-Tab` to decrease their indentation level. +<!-- #endregion --> ```python def test_tab_and_shift_tab(): @@ -84,9 +88,11 @@ def test_tab_and_shift_tab(): and shift-tab ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c16ac57175c4a00e"} --> Once you have defined your function, you can execute it by using the code `function_name()`. Let's look at how to use a function as a "procedure" to simplify the code above: +<!-- #endregion --> ```python def print_status(): @@ -114,12 +120,15 @@ b = 1 print_status() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a12ce8709fcf3b9f"} --> In this example, it may not be such a big deal, but you can imagine that as the code in your function becomes more and more complicated, it will save you a lot of time. Also, imagine that I wanted to change the wording of the sentence I print: in the case with the function, I would only have to do this once, while in the example without function, I would have to manually change this at 5 different places. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cc004d60a3c3b17c"} --> **Exercise 2.1** Write your own function that contains two lines of code. The first line should make a new variable `var2` that is `var` converted to an integer. The second line of your code should print the value of `var2`. Using this code, play around with the indentation (add extra tabs and spaces for example) to see how 'critical' Python is with indentation. For example: does three spaces work instead of `Tab`? Does one space work? What about `Tab` on the first line and three spaces on the second line? Can you make Python trigger an `IdentationError`? +<!-- #endregion --> ```python var=3.5 @@ -127,6 +136,7 @@ var=3.5 # Your function here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1be65f1712abb44d"} --> ## Functions with input variables Let's say that we wanted to print out the status of variables that we do not know the name of ahead of time, as in the example above. Say we wanted to make a function that could print out a message with the status of value of ANY variable. How could we do this? @@ -141,6 +151,7 @@ def function_name(x): ``` When you do this, for the code INSIDE your function, a variable `x` will be defined that will have the value given by the input value given to the function by the user. Let's look at a specific example: +<!-- #endregion --> ```python def print_status2(x): @@ -155,19 +166,23 @@ print_status2(a) print_status2(1.5323) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-34acac6dd514c884"} --> How does this work? When the function `print_status(a)` is called, Python "sends" ("passes" in computer speak) the value of `a` to the function. Inside the function, Python creates a new (temporary) variable called `x`, that is defined ONLY while the function code is running. This temporary variable `x` is then assigned the value that was sent to the function, and then the code is executed. When the function is finished, the variable `x` is destroyed. (Try adding the code `print(x)` above outside the function and see what happens!) Note, as you can see in the third example, the things you pass to functions do not even need to be variables! This is fine because the function only needs the value of the argument that is passed to the function. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-53fd3c06a26175a3"} --> **Exercise 2.2** Copy your code from exercise 2.1 (but then with just normal indentation using tabs) into the cell below and change it such that it uses a function with input parameters to achieve the same task. +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-581873fe20420dc6"} --> ## Functions with multiple inputs Functions can also take multiple input variables. To do this, you put them all in between the brackets `()`, separated by commas. For example, with 3 variables, the syntax is: @@ -186,6 +201,7 @@ function_name(argument1, argument2, argument3) When you do this, inside the function, `variable1` will get assigned the value of `argument1`, `variable2` will get assigned the value of `argument2`, and `variable3` will get assigned the value of `argument3`. This matching of the position in the list is called matching by "positional order". Note that there are several different names used for the "input variables" of a function: often, computer scientists will also use the name "input arguments" (or just "arguements), or "input parameters" (or just "parameters"). +<!-- #endregion --> ```python def print_status3(x, y): @@ -197,21 +213,21 @@ print_status3(2.5,1.5) print_status3(a, 2*a) ``` -```python - -``` - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-78e05b5cbe11902b"} --> **Exercise 2.3** Make a new function `print_status4()` that takes three variables as arguments and prints out messages telling the user the values of each of them (as above, but with three input variables). Test it to make sure it works. +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fbf7e40e800af968"} --> ## Functions that return a value In addition to receiving values as inputs, functions can also send back values to the person using the function. In computer programming, this is called the "return value". When you create a function, you can use the `return` command to specify what value should be sent back to the person using the function. Let's look at an example: +<!-- #endregion --> ```python def my_formula(x): @@ -219,20 +235,26 @@ def my_formula(x): return y ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3da704c82b41c669"} --> To "capture" the value returned by the function, you can assign it to a varible like this: +<!-- #endregion --> ```python result = my_formula(3.5) print(result) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c4eb4137a0421531"} --> You can also just directly "use" the result of the function if you want: +<!-- #endregion --> ```python print(my_formula(4.6)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9710d6768449ded8"} --> Note that as soon as python sees the `return` command, it stops running the function, so any code after it will not be executed: +<!-- #endregion --> ```python def myfunction(x): @@ -243,10 +265,13 @@ def myfunction(x): print(myfunction(5)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-aef6b10798831fa6"} --> If you want to send back more than one result to the user of your function, you can separate the results with commas when you use the `return` command. How do you make use of these two variables that you send back? You will explore this in this exercise: +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c20b50841a685607"} --> **Exercise 2.4** **(a)** Write a function that takes two real numbers as input and returns the sum and product of the two numbers. In your function, try to send *both* of the calculated numbers back as a return value. We have not taught you that yet so you will have to look it up: I recommend trying a google search for "python function return two variables". +<!-- #endregion --> ```python # Your function here @@ -254,7 +279,9 @@ def product_and_sum(...): ... ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-35103493f788b1d5"} --> **(b)** Now USE your function to calculate the sum and product of `a` and `b`, "capturing" the sum and product in variables `s` and `p`: +<!-- #endregion --> ```python a=1.5 @@ -266,6 +293,7 @@ print("Sum is:", s) print("Product is:", p) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-af1bcb8c3a7ded79"} --> ## Importing functions from libraries One of the big advantages of python is that there are huge collection of libraries that include code for doing a huge number of things for you! We will make extensive use of the library `numpy` for numerical calculations in Python, and the library `matplotlib` for generating scientific plots. Beyond this, nearly anything you want to be able to do on a computer can be found in Python libraries, which is one of the reasons it is so popular. @@ -287,18 +315,23 @@ https://docs.python.org/3/library/time.html#time.sleep ### Importing a whole module The simplest way to be able use the `sleep` function of the `time` module is to import it using the following command: +<!-- #endregion --> ```python import time ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ebea512349a5084"} --> You can see it has been imported by using the `%whos` command: +<!-- #endregion --> ```python %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6d7e43494d90fc23"} --> Once it has been imported, you can access all the functions of the module by adding `time.` in front of the function name (from the time module) in your code: +<!-- #endregion --> ```python print("Starting to sleep") @@ -306,9 +339,11 @@ time.sleep(5) print("Done!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-36d70980cc576500"} --> If you import the whole module, you will have access to all the functions in it. To see what functions are in the module for you to use type `dir(time)`, which will generate this list. Sometimes, if you will be using the functions from the module a lot, you can give it a different "prefix" to save yourself some typing: +<!-- #endregion --> ```python import time as tm @@ -317,18 +352,23 @@ tm.sleep(5) print("Done!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f6efa5c8f5b4ef63"} --> We will use this a lot when using the `numpy` module, shortening its name to `np` when we import it, and also for the `matplotlib.pyplot` submodule, which we will shorten to `plt`. (These are also typically used conventions in the scientific community.) +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7f7189cfda3188a5"} --> ### Importing a single function If you need only a single function from a library, there is also a second commonly used way to import only that single function using the following syntax: +<!-- #endregion --> ```python from time import sleep ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b9ce119a2966790a"} --> When you do this, the function `sleep()` will be available directly in your notebook kernel "namespace" without any prefix: +<!-- #endregion --> ```python print("Starting to sleep") @@ -336,12 +376,15 @@ sleep(5) print("Done!") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d62c06ba6b0ce825"} --> Using `%whos`, we can now see that we have three different ways to use the `sleep()` function: +<!-- #endregion --> ```python %whos ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6f0445a2973bf90c"} --> If you look around on the internet, you will also find people that will do the following ``` @@ -355,8 +398,9 @@ While true, it will save typing, it also comes with a risk: sometimes different If you import both of them, you will overwrite these functions by the second import, and if you're not careful, you will forget which one you are using, and it could cause your code to break. It will also "crowd" your notebooks namespace: using the `whos` function, you will suddenly see hundreds or even thousands of functions, instead of only just a module. For these reasons, it is generally advised not to use `import *`, and it is considered poor coding practice in modern python. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-59161c880f8689e6"} --> ### Shift-Tab for getting help Like the tab completion we saw in the first notebook, Jupyter also can give you help on functions you have imported from libraries if you type `Shift-Tab`. @@ -364,21 +408,27 @@ Like the tab completion we saw in the first notebook, Jupyter also can give you Say I forgot how to use the `sleep()` function. If I type the word "sleep" and then push `Shift-Tab`, Jupyter will bring up a help window for that function. Try it: click on any part of the word `sleep` in the following code cell and push `Shift-Tab`: +<!-- #endregion --> ```python sleep ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7daff037eb77301f"} --> You can also find the same help as the output of a code cell by using the `help()` function: +<!-- #endregion --> ```python help(sleep) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c7820259378fabba"} --> There are extensive online resources for many modules. The most used modules have helpful examples on the functions and how to implement them. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3c31a778f00cba2b"} --> **Exercise 2.5 (a)** Find help for the built-in functions `abs`, `int`, and `input`. Which of the help functions are easy to read? Which one does not provide such useful information (compared to the online documentation page)? (Put each help command in a separate cell) +<!-- #endregion --> ```python # Your code here @@ -392,7 +442,9 @@ There are extensive online resources for many modules. The most used modules hav # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-76f8da331b6edf11"} --> **(b)** Import the function `glob` from the library `glob` and print its help information. What does the function `glob("../*")` do? +<!-- #endregion --> ```python # run the help here @@ -402,6 +454,7 @@ There are extensive online resources for many modules. The most used modules hav # your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-10519772f1b5d68a"} --> ## Global variables, local variables, and variable scope In our first functions above, we saw a couple of examples of using variables inside functions. @@ -425,6 +478,7 @@ If you want to create a global variable inside a function, or make sure the vari Let's take a look at this in more detail by analysing a few examples. **Example 1** Accessing a global variable inside a function +<!-- #endregion --> ```python a1 = 5 @@ -437,7 +491,7 @@ a1 = 6 my_func() ``` -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b4bb5a3316fe89c9"} --> In this example, when Python is inside the function `my_func()`, it first looks to see if there is a variable `a1` in the local scope of the function. It does not find one, so it then goes and looks in the global scope. There, it finds a variable `a1`, and so it uses this one. @@ -451,21 +505,27 @@ def my_func(): my_func() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-10458e43f3723d0b"} --> This code gives a `NameError` because there is no variable `b1` yet created in the global scope. If we run the following code cell and try the code above again, it will work. +<!-- #endregion --> ```python b1 = 6 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6074899777a25fae"} --> Here you can see one of risks of languages like python: because of the persistent memory of the kernel, code can succeed or fail depending on what code you have run before it... If you want to see the error message above again, you can delete variable b1 using this code and run it again: +<!-- #endregion --> ```python del b1 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e5003608c97f5668"} --> **Example 3** Variables defined in the local scope of a function are not accessible outside the function +<!-- #endregion --> ```python def my_func(): @@ -476,7 +536,9 @@ my_func() print(x) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d65b38344a2c4136"} --> **Example 4** Variables passed to functions cannot be modified by the function (more on this later when we look at more complicated data structures...sometimes this is different) +<!-- #endregion --> ```python def my_func(a): @@ -487,10 +549,13 @@ my_func(a) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7f9a0c6f80607325"} --> This one is a bit subtle (mega-confusing?) because we re-used the same name `a` for the local variable in the function as the global variable outside of the function. However, the operation is quite logical. When the function code starts running, it creates a `local` variable `a` to store the value it received. And now, because there is already a local variable called `a`, using `a` in the function refers to the `local` variable `a`, not the `global` variable `a` we define before calling the function. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5aa7c9bac0e183c9"} --> **Example 5** This one is a tricky one. +<!-- #endregion --> ```python a = 6 @@ -503,10 +568,13 @@ my_func() print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f637454870e0a4ec"} --> It would seem that the function would refer to the global variable `a` and therefore change it's value. However, it is tricky since we first use `a` in the function in an assignment. An assignment in python will automatically create a variable if it does not exist, and so python creates a new variable named `a` in the local scope. The name `a` inside the function now refers to this newly created local variable, and therefore the global variable will not be changed. In fact, this guarantees that you cannot change global variables inside a function, unless you use the `global` qualifier shown in the next example. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a2c1c2099359531d"} --> **Example 6** If you want to make sure that the `a` inside your function is referring to the global variable `a`, you can include the line `global a` inside your function to tell python that you mean the global variable `a`. +<!-- #endregion --> ```python a = 6 @@ -520,6 +588,7 @@ my_func() print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b86006c7d643e2ba"} --> Note that in general, it is considered bad programming practice to use (too many) global variables. Why? When you write longer and bigger sections of code, it is easier to understand what is going in in your function if your function uses only local variables and communicates back and forth using input parameter and return variables. Using too many global variables in a function can be confusing because they are defined in a different place in your code and so you don't have a good oversight of them. (Bigger projects can easily have 10,000+ lines of code!) In computer science, this is a topic of often intense debate (resulting in what nerds refer to as a <a href="https://www.urbandictionary.com/define.php?term=flame%20war">flame war</a>), with global variables being branded as "dangerous" like in this stack exchange post: @@ -533,14 +602,17 @@ Summary of the rules for global and local variables: * If a local variable of the same name exists or is created by python (by assignment, for example), then python uses the local varible * If you try to use a variable name that does not exist locally, python checks for a global variable of the same name * If you want to change the value of a global inside a function, then you must use the `global` statement to make it clear to python than you want that name to refer to the global variable +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-98aa5a09da4cf9c1"} --> ## Solutions to Exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3c74f16c8b097100"} --> **Exercise 2.1** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-776d867dd1e96a8e"} var=3.5 # Example: one space is actually enough! But is discouraged, as you can see @@ -556,17 +628,21 @@ def myfunction(): print(var2) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-63bcaf171511d7a6"} --> **Exercise 2.2** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3653f8a5e3438645"} def myfunction(var): var2 = int(var) print(var2) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4e6d55d796044deb"} --> **Exercise 2.3** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ab5a53f50aeb9eb2"} def print_status3(x, y, z): print("The value of the first input variable is ", x) print("The value of the second input variable is ", y) @@ -575,17 +651,21 @@ def print_status3(x, y, z): print(1,2,3) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6b6a2cfb8f2059c4"} --> **Exercise 2.4** **(a)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-abb8b6adcc6f783b"} # Your function here def product_and_sum(a,b): return a*b, a+b ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5741495ca1f6ef79"} --> **(b)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-df8f4cc5c689e227"} a=1.5 b=2.5 @@ -595,28 +675,32 @@ print("Sum is:", s) print("Product is:", p) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c0897d2e0d18ff59"} --> **Exercise 2.5** **(a)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2eb1a4094c49679f"} help(abs) ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6aed86aac101cb2a"} help(int) ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-114d997d97a70ea6"} help(input) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a1e85a52b4b3a511"} --> **(b)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-440e78ae4effd486"} from glob import glob help(glob) ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a4cc244f58f0c6a1"} glob("../*") # It returns list of files and folders in the parent directory of that # in which this notebook is stored diff --git a/Notebook 3/Notebook 3 Program Flow Control.ipynb b/Notebook 3/Notebook 3 Program Flow Control.ipynb index 7778b4e..5b05053 100644 --- a/Notebook 3/Notebook 3 Program Flow Control.ipynb +++ b/Notebook 3/Notebook 3 Program Flow Control.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-820ab9eec85e7818", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Program flow control with Conditional Statements and Loops\n", "\n", @@ -80,7 +89,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8f5002353d747ef9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.1** Try out the following conditional statements, filling in the values specified to replace the `---` in the code cells.\n", "\n", @@ -101,7 +119,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-932ea824ec7ca25c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if `103.51` is true:" ] @@ -120,7 +147,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b4c0600a205af8d1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if `-1` is true:" ] @@ -139,7 +175,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-06bb622a4a2db31e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if condition statements work with boolean variables:" ] @@ -159,7 +204,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f2462c37fb4f8c16", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Check if conditional statements work with numerical variables:" ] @@ -179,7 +233,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ad422752c90b5a9e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Comparison and test operators\n", "\n", @@ -269,14 +332,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2c4696ee3922ab23", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In addition to the `==` operator, there are also several other comparison operators such as `<`, `>`, `>=`, `<=`, `!=`" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f6b4a7e98c6672e0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.2** Test the operators `<`, `>`, `>=`, `<=`, `!=` by trying them in an if statement with numerical values. " ] @@ -343,14 +424,23 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-096011724723bedd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Logical operations\n", "\n", "Python also allows you to build the `expression` out of logical combinations of several conditions using the keywords `and`, `or`, and `not`. The value of these operators is as follows\n", "* `and` evaluates to True if both conditions are True\n", "* `or` evaluates to True if either condition is True\n", - "* `not`evaluates to true if condition is not True\n", + "* `not`evaluates to True if condition is not True\n", "\n", "Below are a few examples. " ] @@ -407,7 +497,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8a0f0f9e5985f291", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.3** Try out the following examples using the if statement form from above for the conditions\n", "\n", @@ -427,7 +526,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fbaf814587160898", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** Do you think that the statement `not False or True` evaluates to `True` or `False`? Try it out and see:" ] @@ -443,7 +551,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a09e89673e9b20d3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To understand what happened in part (b), we have to know if Python first performs the operation `False or True` or if it performs the operation `not False` first. The rules for which order python does things in can be found in the documentation for <a href=https://docs.python.org/3/reference/expressions.html#operator-precedence>operator precedence</a>. In the example above, we can see that the `not` operator had precedence and python performed the `not` before it performed the `or`. \n", "\n", @@ -462,7 +579,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e8a75bf958008fea", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### The `elif` and `else` statements\n", "\n", @@ -517,7 +643,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bcd1475b5312a9de", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Since the code inside your `if` code block is just regular code, you can also add another if statement inside that code block. This creates a <a href=https://en.wikipedia.org/wiki/Nesting_(computing)>nested</a> `if` statement inside your first one:" ] @@ -541,7 +676,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ae510a3a63768c64", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.4** Practice the use of the if-elif-else statement with the following exercise by filling in the missing conditional statements. You must use all three of `if`, `elif` and `else`. You also can not use the `end` operator." ] @@ -572,7 +716,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-636e6a2b17097f10", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Loops\n", "\n", @@ -608,7 +761,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84200ae77714a458", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this example here, we use a while loop to add up all the numbers between 1 and 10:" ] @@ -631,7 +793,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b97c9b714150c6b6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that with `while` loops, you have to be careful to make sure that your code block does something that results in your `expression` becomes false at some point, otherwise the loop will run for ever. \n", "\n", @@ -655,14 +826,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4d529b9055631e33", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**This code will never finish: it will go into an infinite loop!** (You can see that it is still running because the `*` beside the `In` text never gets turned into a number.) Note that for this will have to manually stop the kernel using the stop button in the toolbar or it will run forever..." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bb322dca784c930d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### When should I use a `while` loop? \n", "\n", @@ -698,14 +887,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-98fdacbdf316c332", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note here I have also used a new operator `+=`: this is a special assignment operator that increments the variable by the specified amount. `a += 1` is equivalent to `a = a + 1` (it just saves a bit of typing, remember: programmers are lazy...). " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8419ddf039469350", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.5** Write a function to calculate the factorial of a number using a while loop. The factorial, denoted with !, is the product of all numbers up to that number, i.e. $4!=1\\cdot2\\cdot3\\cdot4$. Note that perhaps unexpectedly for you 0!=1 for which your function also have to give an answer.\n", "\n", @@ -728,7 +935,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8bac96ac75f3228d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### The `for` loop\n", "\n", @@ -761,7 +977,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2fa53e57cfc647d0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.6** Calculate the sum $\\sum_{i=0}^n\\sin^2(i)$ for n=10 using a `for` loop." ] @@ -780,7 +1005,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-58d187a8c4c8dc2e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### The `range()` function\n", "\n", @@ -809,7 +1043,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a1e795446fc32a60", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "#### `range(N,M)`: Print a range of numbers starting from N and ending at M-1\n", "\n", @@ -828,7 +1071,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-adce08f894188b15", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You might ask: why not stop at `M`? If you say the words \"range from N to M\", you would think that this range should include M? \n", "\n", @@ -842,7 +1094,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3f4edd12e92245a9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "#### `range(N,M,S)`: Print a range of numbers less than M, starting from N, with steps of S\n", "\n", @@ -861,14 +1122,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a67520608225fbaf", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that the `range` function works only with integers: `range(1,11,0.5)` is not allowed. (For floating point ranges, you can use the `numpy` function `arange`, more on that later...)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-95d40298b2f365ae", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.7** Calculate the sum of all numbers from 1 to 100 using a `for` loop with the `range()` function. Compare it to the famous value that Gauss calculated in class as an <a href=https://nrich.maths.org/2478>elementary school student</a>. " ] @@ -886,7 +1165,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3cf16acbe0d81ace", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Using `for` loops with things other than ranges of numbers\n", "\n", @@ -916,7 +1204,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-55311578c51712a5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Manually exiting or skipping a loop using `break` and `continue`\n", "\n", @@ -944,7 +1241,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ebe183bb239808e9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "It looks funny at first, because `while True` looks like you will loop forever! But of course, you are saved by the `break` statement. \n", "\n", @@ -980,7 +1286,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5c5df9248db93ce5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The statement `continue` is used if you want to skip the rest of the code in the code block and restart the next loop. This can sometimes be useful if as a way of avoiding adding an `else` statement. (Remember, programmers are lazy typers...and it can be useful if you have a big long complicated block of code...)" ] @@ -1003,14 +1318,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8a7bc126b9fe430a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This is probably not a great example (if you are smart you can do this with less typing), but in any case, you now know what `continue` does. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-10e1b8eed7f40b1c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.8** Write code that creates a variable `i` with an initial value of zero. Use a while loop that increments `i` by one as long as `i` is less than one million. Have your loop stop if `i` satisfies the condition $i(i-10) = 257024$. Have your code print the `i` that satisfies this condition (if there is one). " ] @@ -1028,14 +1361,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-38744ad3ab4ca87f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6f337f4358a682a0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.1** " ] @@ -1044,6 +1395,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5b0313b8e8598466", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1056,6 +1415,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1e3641fb9a779721", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -1068,6 +1435,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4ceedbf1a3c4d051", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1080,6 +1455,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cc4df8371d0432b9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1093,6 +1476,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-123947c9011d4062", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1104,7 +1495,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9e3b271232e2220d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.2**" ] @@ -1113,6 +1513,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a6ac2b6dc510f1aa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1125,6 +1533,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-51027497694978b9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1137,6 +1553,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1e264535ded80168", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1149,6 +1573,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ed3481fa5c1ce2aa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1161,6 +1593,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a8414c045994a401", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1172,7 +1612,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4271a640dc4e16fa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.3** **(a)** " ] @@ -1181,6 +1630,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-40cec335e293301f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1191,7 +1648,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b47fd4e7bdeeb7ed", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**(b)** " ] @@ -1199,7 +1665,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7b133c4093f3de42", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "if not False or True:\n", @@ -1210,7 +1685,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2b945c67ff612ec2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.4** " ] @@ -1219,6 +1703,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-36ba5da4827cc9c8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1243,7 +1735,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a03f32b890804dc7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.5** " ] @@ -1251,7 +1752,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ad76460fb6dee8a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "def factorial(a):\n", @@ -1271,7 +1781,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b7807a055420b17b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.6** " ] @@ -1280,6 +1799,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1536f4bee510c9ac", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1295,7 +1822,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-361135ad0db80c76", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.7** " ] @@ -1304,6 +1840,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4dc583cec91521e9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1321,7 +1865,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-21f178bba5a2f9ad", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 3.8** " ] @@ -1329,7 +1882,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-188d5c2433fb8aa3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "# Solution\n", @@ -1346,6 +1908,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, diff --git a/Notebook 3/Notebook 3 Program Flow Control.md b/Notebook 3/Notebook 3 Program Flow Control.md index cbad16d..1e35f0c 100644 --- a/Notebook 3/Notebook 3 Program Flow Control.md +++ b/Notebook 3/Notebook 3 Program Flow Control.md @@ -13,6 +13,7 @@ jupyter: name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-820ab9eec85e7818"} --> # Program flow control with Conditional Statements and Loops In this notebook, we will learn how to control the flow of execution of python code using conditional statements and loops. @@ -43,6 +44,7 @@ if expression: ``` Here, `expression` is a piece of code the evaluates to either the value `True` or `False`. Here are a few simple examples: +<!-- #endregion --> ```python if True: @@ -66,30 +68,38 @@ if not False: print("not False is True") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8f5002353d747ef9"} --> **Exercise 3.1** Try out the following conditional statements, filling in the values specified to replace the `---` in the code cells. Check if the integer `1` is true: +<!-- #endregion --> ```python if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-932ea824ec7ca25c"} --> Check if `103.51` is true: +<!-- #endregion --> ```python if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b4c0600a205af8d1"} --> Check if `-1` is true: +<!-- #endregion --> ```python if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-06bb622a4a2db31e"} --> Check if condition statements work with boolean variables: +<!-- #endregion --> ```python a = True @@ -97,7 +107,9 @@ if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f2462c37fb4f8c16"} --> Check if conditional statements work with numerical variables: +<!-- #endregion --> ```python b = -73.445 @@ -105,11 +117,13 @@ if ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ad422752c90b5a9e"} --> ### Comparison and test operators In the above, the `expression` in the `if` statements were all directly values that are either True or false (except for the example of `not False`). More generally, however, the `expression` can also include comparisons. Some examples of numerical comparisons are given here below: +<!-- #endregion --> ```python if 5 == 5: @@ -155,10 +169,13 @@ if 2*a == 10: print("You can also use mathematical expressions") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2c4696ee3922ab23"} --> In addition to the `==` operator, there are also several other comparison operators such as `<`, `>`, `>=`, `<=`, `!=` +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f6b4a7e98c6672e0"} --> **Exercise 3.2** Test the operators `<`, `>`, `>=`, `<=`, `!=` by trying them in an if statement with numerical values. +<!-- #endregion --> ```python if --- < ---: @@ -185,14 +202,16 @@ if --- != ---: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-096011724723bedd"} --> ### Logical operations Python also allows you to build the `expression` out of logical combinations of several conditions using the keywords `and`, `or`, and `not`. The value of these operators is as follows * `and` evaluates to True if both conditions are True * `or` evaluates to True if either condition is True -* `not`evaluates to true if condition is not True +* `not`evaluates to True if condition is not True Below are a few examples. +<!-- #endregion --> ```python if True and False: @@ -219,29 +238,36 @@ if 5 < 6 and 10 > 9: print("An example of combining conditional expressions with 'and'") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8a0f0f9e5985f291"} --> **Exercise 3.3** Try out the following examples using the if statement form from above for the conditions **(a)** Check if both 5 is smaller than 6, and 10 is smaller equal than 9: +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fbaf814587160898"} --> **(b)** Do you think that the statement `not False or True` evaluates to `True` or `False`? Try it out and see: +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a09e89673e9b20d3"} --> To understand what happened in part (b), we have to know if Python first performs the operation `False or True` or if it performs the operation `not False` first. The rules for which order python does things in can be found in the documentation for <a href=https://docs.python.org/3/reference/expressions.html#operator-precedence>operator precedence</a>. In the example above, we can see that the `not` operator had precedence and python performed the `not` before it performed the `or`. What if I wanted to have python perform the `or` first? You do this by enclosing `True or False` in brackets: +<!-- #endregion --> ```python if not (False or True): print("not (False or True) is False so this will not be printed") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e8a75bf958008fea"} --> ### The `elif` and `else` statements In Python, you can combine the `if` statement with `elif` and `else` commands in a chain in order to allow you to take actions in the case that the starting `if` statement was false. @@ -252,6 +278,7 @@ In Python, you can combine the `if` statement with `elif` and `else` commands in Note that to be part of the "chain", all the `elif`s and the last `else` must follow directly after each other's code blocks with no other code in between. And a new `if` always starts a new chain. You can see how this works in the following examples: +<!-- #endregion --> ```python a = 5 @@ -277,7 +304,9 @@ if a<6: print("unlike elif, a second if will get executed.") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bcd1475b5312a9de"} --> Since the code inside your `if` code block is just regular code, you can also add another if statement inside that code block. This creates a <a href=https://en.wikipedia.org/wiki/Nesting_(computing)>nested</a> `if` statement inside your first one: +<!-- #endregion --> ```python # example of a nested if-statement @@ -291,7 +320,9 @@ else: print("none of these are the case") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ae510a3a63768c64"} --> **Exercise 3.4** Practice the use of the if-elif-else statement with the following exercise by filling in the missing conditional statements. You must use all three of `if`, `elif` and `else`. You also can not use the `end` operator. +<!-- #endregion --> ```python def check_number(a): @@ -310,6 +341,7 @@ check_number(10) check_number(15) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-636e6a2b17097f10"} --> ## Loops Loops are a construction in a programming language that allow you to execute the same piece of code repeatedly. @@ -328,6 +360,7 @@ while expression: As long as `expression` is true, then the code block will be executed over and over again. A simple example where we use a while loop to count to 10: +<!-- #endregion --> ```python i = 1 @@ -336,7 +369,9 @@ while i <= 10: i = i+1 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84200ae77714a458"} --> In this example here, we use a while loop to add up all the numbers between 1 and 10: +<!-- #endregion --> ```python i = 1 @@ -349,9 +384,11 @@ while i<= 10: print("Sum is", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b97c9b714150c6b6"} --> Note that with `while` loops, you have to be careful to make sure that your code block does something that results in your `expression` becomes false at some point, otherwise the loop will run for ever. For example, when initially I wrote the above cell to calculate the sum, I forgot the `i = i+1` line of code: +<!-- #endregion --> ```python i = 1 @@ -363,9 +400,11 @@ while i<= 10: print("Sum is", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4d529b9055631e33"} --> **This code will never finish: it will go into an infinite loop!** (You can see that it is still running because the `*` beside the `In` text never gets turned into a number.) Note that for this will have to manually stop the kernel using the stop button in the toolbar or it will run forever... +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bb322dca784c930d"} --> ### When should I use a `while` loop? For both of the examples above, one would typically use `for` loop, which we will see in the next section. One place where `while` loops are very useful is if you do not know ahead of time how many iterations of the loop you will need. @@ -377,6 +416,7 @@ S(n) = \sum_{i=1}^n \sin^2(i) $$ Let's say we want to know: for which $n$ does the sum exceed 30? We can easily check this with a `while` loop: +<!-- #endregion --> ```python from numpy import sin @@ -392,12 +432,15 @@ print("Sum is", s) print("i is", i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-98fdacbdf316c332"} --> Note here I have also used a new operator `+=`: this is a special assignment operator that increments the variable by the specified amount. `a += 1` is equivalent to `a = a + 1` (it just saves a bit of typing, remember: programmers are lazy...). +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8419ddf039469350"} --> **Exercise 3.5** Write a function to calculate the factorial of a number using a while loop. The factorial, denoted with !, is the product of all numbers up to that number, i.e. $4!=1\cdot2\cdot3\cdot4$. Note that perhaps unexpectedly for you 0!=1 for which your function also have to give an answer. *If your code doesn't seem to stop running, you may need to "debug" your code to see where your mistake is (I did). A handy way to do this, for example, is to add a `print(i)` statement inside your while loop: it will enable you to see if the variable `i` is doing what you think it should be...* +<!-- #endregion --> ```python def factorial(a): @@ -408,6 +451,7 @@ print(factorial(4)) print(factorial(0)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8bac96ac75f3228d"} --> ### The `for` loop The `for` loop is designed to execute a piece of code a fixed number of times. The syntax of the `for` loop is: @@ -420,6 +464,7 @@ for i in (a, b, c, d, ...): In each subsequent iteration of the for loop, the variable `i` is assigned next value that is supplied in the list values. We can repeat our sum calculation above using the following `for` loop: +<!-- #endregion --> ```python s = 0 @@ -429,13 +474,16 @@ for i in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10): print(s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2fa53e57cfc647d0"} --> **Exercise 3.6** Calculate the sum $\sum_{i=0}^n\sin^2(i)$ for n=10 using a `for` loop. +<!-- #endregion --> ```python # Your code here for...: ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-58d187a8c4c8dc2e"} --> ### The `range()` function You can imagine that if we wanted to perform this sum up to 100, it would be very annoying to type out all of the numbers. For this, Python has a convenient function `range()`, than can automatically generate ranges of numbers for you! @@ -449,22 +497,26 @@ But let's look just at some concrete examples for now: If you give `range` only one argument, it will give a range of numbers starting from 0, and a total number of numbers determined by the argument. As an explicit example, `range(10)` is equivalent to `(0,1,2,3,4,5,6,7,8,9)`: +<!-- #endregion --> ```python for i in range(10): print(i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a1e795446fc32a60"} --> #### `range(N,M)`: Print a range of numbers starting from N and ending at M-1 If you give `range` two arguments `range(N,M)`, it will give a range of numbers starting from `N` and stopping at `M-1`. +<!-- #endregion --> ```python for i in range(1,11): print(i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-adce08f894188b15"} --> You might ask: why not stop at `M`? If you say the words "range from N to M", you would think that this range should include M? There are two reasons I can think of: @@ -474,26 +526,32 @@ There are two reasons I can think of: Maybe there are more, I'm not sure (you'd have to ask the nerds who wrote Python...). But in any case, you just need to remember that `range(N,M)` stops at `M-1`... +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3f4edd12e92245a9"} --> #### `range(N,M,S)`: Print a range of numbers less than M, starting from N, with steps of S This is like the last one, but now with the chance to change the steps: +<!-- #endregion --> ```python for i in range(1,11,2): print(i) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a67520608225fbaf"} --> Note that the `range` function works only with integers: `range(1,11,0.5)` is not allowed. (For floating point ranges, you can use the `numpy` function `arange`, more on that later...) +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-95d40298b2f365ae"} --> **Exercise 3.7** Calculate the sum of all numbers from 1 to 100 using a `for` loop with the `range()` function. Compare it to the famous value that Gauss calculated in class as an <a href=https://nrich.maths.org/2478>elementary school student</a>. +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3cf16acbe0d81ace"} --> ### Using `for` loops with things other than ranges of numbers In the examples above, we looked at using `for` loops to iterate through a list of integers. @@ -501,6 +559,7 @@ In the examples above, we looked at using `for` loops to iterate through a list In Python, however, `for` loops are much more flexible than only iterating over numbers: `for` loops can iterate over any <a href=https://www.programiz.com/python-programming/iterator>iteratable object</a>, including 1-D numpy arrays, which we will see in later notebooks. But, as an example, here is piece of code that uses the `numpy` random number generator to calculate the sum of 10 random integers between 0 and 100: +<!-- #endregion --> ```python from numpy.random import randint @@ -514,11 +573,13 @@ print() print("Sum is ", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-55311578c51712a5"} --> ### Manually exiting or skipping a loop using `break` and `continue` In addition to the examples we saw above, Python offers to extra commands for controlling the flow of execution in a loop: `break` and `continue` `break` is a command you can use to force the exiting of either a `for` loop or a `while` loop. For example, you can replace the while loop above using something like this: +<!-- #endregion --> ```python s = 0 @@ -532,9 +593,11 @@ while True: print(s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ebe183bb239808e9"} --> It looks funny at first, because `while True` looks like you will loop forever! But of course, you are saved by the `break` statement. Using `break` statements can sometimes make your code more readable, particularly if you want to be able to exit the loop under different conditions, or have an exist condition trigger a certain piece of code. Here is an example of two conditions: +<!-- #endregion --> ```python from numpy.random import randint @@ -558,7 +621,9 @@ else: print("We didn't find a 5 in the maximum number number of tries (", max_tries, ")") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5c5df9248db93ce5"} --> The statement `continue` is used if you want to skip the rest of the code in the code block and restart the next loop. This can sometimes be useful if as a way of avoiding adding an `else` statement. (Remember, programmers are lazy typers...and it can be useful if you have a big long complicated block of code...) +<!-- #endregion --> ```python s = 0 @@ -571,94 +636,108 @@ print("The sum of 30 random numbers between 0 and 30 excluding those that are di ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8a7bc126b9fe430a"} --> This is probably not a great example (if you are smart you can do this with less typing), but in any case, you now know what `continue` does. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-10e1b8eed7f40b1c"} --> **Exercise 3.8** Write code that creates a variable `i` with an initial value of zero. Use a while loop that increments `i` by one as long as `i` is less than one million. Have your loop stop if `i` satisfies the condition $i(i-10) = 257024$. Have your code print the `i` that satisfies this condition (if there is one). +<!-- #endregion --> ```python # Your code here ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-38744ad3ab4ca87f"} --> ## Solutions to exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6f337f4358a682a0"} --> **Exercise 3.1** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5b0313b8e8598466"} if 1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1e3641fb9a779721"} if 103.51: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4ceedbf1a3c4d051"} if -1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cc4df8371d0432b9"} a = True if a: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-123947c9011d4062"} b = -73.445 if b: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9e3b271232e2220d"} --> **Exercise 3.2** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a6ac2b6dc510f1aa"} if 5 < 5.1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-51027497694978b9"} if 5 <= 5: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1e264535ded80168"} if 5.1 > 5.1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ed3481fa5c1ce2aa"} if 5.1 >= 5.1: print('The expression is true') ``` -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a8414c045994a401"} # "!=" is "not-equals" if 5 != 5.1: print('The expression is true') ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4271a640dc4e16fa"} --> **Exercise 3.3** **(a)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-40cec335e293301f"} if 5 < 6 and 10 <= 9: print("i don't think this is true") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b47fd4e7bdeeb7ed"} --> **(b)** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7b133c4093f3de42"} if not False or True: print("it is true?") else: print("it wasn't true?") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2b945c67ff612ec2"} --> **Exercise 3.4** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-36ba5da4827cc9c8"} def check_number(a): if a <= 5: print(a, "is less than or equal to 5") @@ -677,9 +756,11 @@ check_number(10) check_number(15) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a03f32b890804dc7"} --> **Exercise 3.5** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ad76460fb6dee8a"} def factorial(a): # f = our factorial result f = 1 @@ -695,9 +776,11 @@ print(factorial(4)) print(factorial(0)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b7807a055420b17b"} --> **Exercise 3.6** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1536f4bee510c9ac"} # Your code here s = 0 @@ -707,9 +790,11 @@ for i in (1,2,3,4,5,6,7,8,9,10): print("Sum is", s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-361135ad0db80c76"} --> **Exercise 3.7** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4dc583cec91521e9"} s = 0 # Note the range should end at 101! @@ -721,9 +806,11 @@ for i in range(1,101): print(s) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-21f178bba5a2f9ad"} --> **Exercise 3.8** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-188d5c2433fb8aa3"} # Solution maxnum=1e6 diff --git a/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb b/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb index df3b844..18ff032 100644 --- a/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb +++ b/Notebook 4/Notebook 4 Scientific Computing with Numpy.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-33372ef05bccfcb0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Scientific Computing in Python with Numpy \n", "\n", @@ -23,7 +32,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7d7b639cd03ba916", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Numpy Arrays\n", "\n", @@ -67,6 +85,14 @@ "cell_type": "code", "execution_count": 9, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4cd897115e9a55da", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -76,7 +102,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2b8a256ccf823e14", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now that we have imported numpy, we can use functions in numpy to create a numpy array. A simple way to do this is to use the function `np.array()` to make a numpy array from a comma-separated list of numbers in square brackets:" ] @@ -93,14 +128,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cfdb9edf4b2e1707", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that numpy does not make a distinction between row vectors and column vectors: there are just vectors. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aa801acee39c518f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Indexing arrays (and counting from zero)\n", "\n", @@ -132,7 +185,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-992a7aa5df6186e4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**WRONG!** Why? Because the makers of Python decided to start counting from zero: the first element of tuple `a` is actually `a[0]`. \n", "\n", @@ -154,7 +216,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ab7bf8fa5a2ac10f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here the `len` function returns the length of the array `a`. As we saw before, Python has very smart `for` loops that can automatically iterate over many types of objects, which means we can also print out all the elements of our array like this:" ] @@ -171,7 +242,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e1299e2159378fdb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In Python, if you try to index beyond the end of the array, you will get an error: " ] @@ -189,7 +269,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-77aff5e6ccdff810", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "(Remember: indexing starts at zero!)\n", "\n", @@ -220,7 +309,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8398fd216f6b2f58", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can also use indexing to change the values of elements in our array:" ] @@ -238,7 +336,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3dae75f8494e44f3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.1** Set the first three, and the last two, entries of the following array to zero:" ] @@ -260,7 +367,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3b080cba952a146f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Slicing numpy arrays\n", "\n", @@ -288,7 +404,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-70abffff9b88cf27", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The notation `a[0:5]` has \"sliced\" out the first five elements of the array.\n", "\n", @@ -319,7 +444,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a8b2ffcd10cfc7b1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Also handy: you can can have Python slice an array with a \"step\" size that is more than one by adding another `:` and a number after that. Find out its operation using:" ] @@ -337,7 +471,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-24b9d17391ceeb7e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Fun: you can also use negative steps:" ] @@ -355,7 +498,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ee08109e60cb51f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "And finally, unlike indexing, Python is a bit lenient if you slice off the end of an array:" ] @@ -373,7 +525,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8d1518c5eafa34e8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.2:** Slicing can also be used to *set* multiple values in an array at the same time. Use slicing to set first 10 entries of the array below to zero in one line of code." ] @@ -394,7 +555,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9b377accb5c0210c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Mathematical operations on arrays\n", "\n", @@ -464,7 +634,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2963828a5132a24a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "What about if I multiply two vectors together? \n", "\n", @@ -496,7 +675,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-713408533e01c84c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "What if I actually want the dot product or the outer product? For that, Python has functions `np.dot()` and `np.outer()`: " ] @@ -521,7 +709,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d29f4ba63452bfee", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Pretty much all operators work with numpy arrays, even comparison operators, which can sometimes be very handy:" ] @@ -538,7 +735,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8657218bbd2eabbc", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.3:** Generate a sequence of the first 20 powers of 2 in a numpy array (starting at $2^0$). \n", "\n", @@ -555,12 +761,21 @@ }, "outputs": [], "source": [ - "code that makes the desired array" + "# your code that makes the desired array" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-809eedfdadcd8b57", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Functions for creating numpy arrays\n", "\n", @@ -589,7 +804,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a3a728bdca7ae5e3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### `np.linspace`\n", "\n", @@ -617,7 +841,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ea30686a4490a44d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that if we wanted to have a step size of exactly 0.5, we need a total of 41 points:" ] @@ -637,7 +870,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-60d973103e7a78a9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.4:** Generate an array that runs from -2 to 1 with 20 points using `linspace`." ] @@ -656,7 +898,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7c4a427b963a9b3c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### `np.arange()`\n", "\n", @@ -678,7 +929,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5b97dd88408ae9d0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here, we already see a small quirk of `arange`: it will stop once the next point it calculates is `<` (not `<=`) to the stop point. If we want to get a range that stops at `20.0`, we need to make the stop point any number a bit bigger than 20 (but smaller than our step size):" ] @@ -698,14 +958,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ab1a61494f1f5b33", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "For this reason, I do not find myself using `np.arange()` very often, and mostly use `np.linspace()`. There are also several other useful functions, such as <a href=https://docs.scipy.org/doc/numpy/reference/generated/numpy.geomspace.html>np.geomspace()</a>, which produces geometrically spaced points (such that they are evenly spaced on a log scale). " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3a0d9465353f954b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.5:** Generate a numpy array that has a first element with value 60 and last element 50 and takes steps of -0.5 between the values. " ] @@ -724,7 +1002,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ed2f97bb11cf695d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Random numbers \n", "\n", @@ -745,14 +1032,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-624c748e1b750b24", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This will generate uniform random numbers on the range of 0 to 1, but there are also several other random number generator functions that can make <a href=https://en.wikipedia.org/wiki/Normal_distribution>normally distributed</a> random numbers, or random integers, and more." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bf4eeb182ddf3fdf", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.6:** Generate a numpy array that contains 300 random grades that have a distribution of a <a href=https://www.mathsisfun.com/data/standard-normal-distribution.html>bell-shaped curve</a> that might represent the final grades of the students in this course, with an average grade of 7.5 and a standard deviation of 1. Make sure your grades are rounded to a half point.\n", "\n", @@ -776,7 +1081,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-042c8d00bb09795d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Multidimensional arrays (matrices)\n", "\n", @@ -795,7 +1109,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-63e005300e7f7098", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "For two dimensional matrices, the usual function `len()` is not enough to tell us about the shape of our matrix. Instead, we can use a property of the numpy matrix itself called its `shape`:" ] @@ -812,7 +1135,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f8e1398b61d09699", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Indexing two dimensional arrays works by using commas inside square brackets to specify the index of the first and second dimensions:" ] @@ -836,7 +1168,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4e9af994bf9d7df7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also use slicing to to assign values to an array from a vector, which can be a handy way to enter a matrix by hand:" ] @@ -856,7 +1197,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-461ca601abc17699", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Similarly, slicing also can be used to extract rows, columns, or blocks of the matrix:" ] @@ -895,7 +1245,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6ab7d9e10575586d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "There are several functions for making matrices which you may find useful someday: \n", "\n", @@ -928,7 +1287,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-484f34f51c8f8c5e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.7:** Use Python to calculate the following matrix multiplication: \n", "\n", @@ -982,7 +1350,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0a8f096130330d1f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Numpy functions \n", "\n", @@ -1010,7 +1387,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-471d12979c1baf8e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Because this is a common operation, there is a function built into numpy `np.average()` that can do this for you:" ] @@ -1026,7 +1412,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-48cd73f9aeaf481e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "This is very handy: it saves us loads of typing! From the function name, it is also easy to understand what you are doing, making the code clearer and easier to read. However, the purpose of numpy functions is not only to save lots of typing: they also can often perform calculations MUCH faster than if you do program the calculation yourself with a `for` loop, as we will see in the next section.\n", "\n", @@ -1068,7 +1463,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3c32880c009136b2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Good question for you to think about: why is this not zero? And what would I have to change above to get the code to return zero? \n", "\n", @@ -1089,7 +1493,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d520c5750ce4f54a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note also here that we used round brackets `()` around the `a**2` in the `print` statement to be able to then index the resulting array `a**2` array using square brackets `[]`. \n", "\n", @@ -1104,7 +1517,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-081110b083e1eeb8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.8:** Make a array `x` that runs from 0 to 4 with 20 points, and calculate an array `y` whose entries are equal to the square root of the entries in `x`. " ] @@ -1123,7 +1545,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-dfed25e05b150ef3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### \"Vectorisation\" and fast code with numpy functions\n", "\n", @@ -1177,7 +1608,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bbb6d85899ba86b1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Why is numpy so much faster? The reason is that Python is an <a href=https://en.wikipedia.org/wiki/Interpreted_language>interpreted language</a>. In each of the steps of the `for` loop, the Python kernel reads in the next step it has to do, translates that into an instruction for your computer processor, asks the computer to perform the step, gets the result back, reads in the next step, translates that into a processor instruction, sends that as an instruction to the computer processor, etc, etc. \n", "\n", @@ -1204,7 +1644,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2a60c21baccb3c7d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ " Here is a nice example of a vectorized way of counting the number of times the number '5' occurs in a random sample of 100 integers between 0 and 20:" ] @@ -1221,7 +1670,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c8bc80849f5e0e0a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To see how this works, we can look at the intermediate steps:" ] @@ -1240,7 +1698,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1a3f30004c3e3ffb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Note that in this case, `np.sum()` will convert the `bool` value `True` into `1` and `False` into `0` for calculating the sum, according the the standard convertion of `bool` types to `int` types. You can see this in action if you want using the function `astype()` that is built into numpy arrays:" ] @@ -1257,7 +1724,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-dd8dc8bd0ab88a1c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Another neat feature that `numpy` has is that is can 'vectorize' normal python functions so that they can take `numpy` functions and make them a bit faster (10-20%). This is done using the `np.frompyfunc` function. An example is given below." ] @@ -1297,14 +1773,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2f8479d8cc410928", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0a4e3eec30674d19", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.1** " ] @@ -1313,6 +1807,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-daf14d85fa463aba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1335,7 +1837,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1f286c69f57cbc80", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.2:** " ] @@ -1344,6 +1855,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-efb5e7292fc18003", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1356,7 +1875,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b47cf723add61750", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.3:** " ] @@ -1365,6 +1893,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-87b5343c8e0fdbce", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1376,7 +1912,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ea7ddbad81cf4ad1", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.4:** " ] @@ -1385,6 +1930,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e6d951d0073dc45f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1395,7 +1948,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d33f7386d70b8e4f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.5:** " ] @@ -1404,6 +1966,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-84bb816a29f6f1bb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1414,7 +1984,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-095bdb4fd5d1fae4", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.6:** " ] @@ -1423,6 +2002,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5a51a5dde00116ba", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -1434,7 +2021,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f8253b40b7bb046e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.7:** " ] @@ -1443,6 +2039,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a69f7838d0b9e25a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -1477,7 +2081,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3ba058efb16a91ad", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 4.8:** " ] @@ -1486,6 +2099,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-57c551e2a58429d8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -1497,6 +2118,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, diff --git a/Notebook 4/Notebook 4 Scientific Computing with Numpy.md b/Notebook 4/Notebook 4 Scientific Computing with Numpy.md index 5fd2389..5fc8001 100644 --- a/Notebook 4/Notebook 4 Scientific Computing with Numpy.md +++ b/Notebook 4/Notebook 4 Scientific Computing with Numpy.md @@ -13,6 +13,7 @@ jupyter: name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-33372ef05bccfcb0"} --> # Scientific Computing in Python with Numpy <a href=https://numpy.org/>Numpy</a> (numerical Python) is a library designed for performing scientific computing in Python. @@ -28,8 +29,9 @@ In this notebook, we will introduce numpy arrays, a data structure introduced in * Student is able to use functions for creating arrays (eg. `np.zeros()`, `np.linspace()`, `np.random.random()` * Student is able to use numpy functions for vectorized calculations * Student is able to demonstrate the speed increase of vectorized calculations using `time()` +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7d7b639cd03ba916"} --> ## Numpy Arrays Until now, the variable types we have been working with in Python represent relatively simple data types: @@ -66,21 +68,26 @@ $$ In linear algebra you are used to manipulate these vectors, this can be done in a similar way with numpy arrays. We will use numpy arrays extensively in Python as vectors, like above, but also for storing, manipulating, and analyzing datasets (like a column of an excel spreadsheet). To use numpy arrays, we first need to import the numpy library, which we will do using the shortened name "np" (to save typing): +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4cd897115e9a55da"} import numpy as np ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2b8a256ccf823e14"} --> Now that we have imported numpy, we can use functions in numpy to create a numpy array. A simple way to do this is to use the function `np.array()` to make a numpy array from a comma-separated list of numbers in square brackets: +<!-- #endregion --> ```python a = np.array([1,2,3,4,5]) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cfdb9edf4b2e1707"} --> Note that numpy does not make a distinction between row vectors and column vectors: there are just vectors. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-aa801acee39c518f"} --> ### Indexing arrays (and counting from zero) One useful thing about arrays is that you can access the elements of the array using square brackets: @@ -98,16 +105,19 @@ a = np.array([1,2,3,4,5]) The first element of `a` is `1`. You might think that if you want to access the first element of `a`, you would use the notation `a[1]`. Right? Let's try it: +<!-- #endregion --> ```python print(a[1]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-992a7aa5df6186e4"} --> **WRONG!** Why? Because the makers of Python decided to start counting from zero: the first element of tuple `a` is actually `a[0]`. (This is a long-standing <a href=https://en.wikipedia.org/wiki/Zero-based_numbering>discussion among computer scientists</a>, and the convention is <a href=https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(array)#Array_dimensions>different</a> in many different languages. There are advantages and disadvantages of both, and even essays written about it...but in any case, Python chose to start arrays at zero.) This also helps better understand the `range()` function: for example, to loop over all the elements in `a`, I can use this code: +<!-- #endregion --> ```python for i in range(len(a)): @@ -115,22 +125,28 @@ for i in range(len(a)): print('a[%d] is %d' % (i,n)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ab7bf8fa5a2ac10f"} --> Here the `len` function returns the length of the array `a`. As we saw before, Python has very smart `for` loops that can automatically iterate over many types of objects, which means we can also print out all the elements of our array like this: +<!-- #endregion --> ```python for n in a: print(n) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e1299e2159378fdb"} --> In Python, if you try to index beyond the end of the array, you will get an error: +<!-- #endregion --> ```python a[5] ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-77aff5e6ccdff810"} --> (Remember: indexing starts at zero!) Python also has a handy feature: negative indices count backwards from the end, and index `-1` corresponds to the last element in the array! +<!-- #endregion --> ```python a[-1] @@ -140,7 +156,9 @@ a[-1] a[-2] ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8398fd216f6b2f58"} --> We can also use indexing to change the values of elements in our array: +<!-- #endregion --> ```python print(a) @@ -148,7 +166,9 @@ a[2] = -1 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3dae75f8494e44f3"} --> **Exercise 4.1** Set the first three, and the last two, entries of the following array to zero: +<!-- #endregion --> ```python a = np.array([1,2,3,4,5,6,7,8,9,10,11,32,55,78,22,99,55,33.2,55.77,99,101.3]) @@ -158,6 +178,7 @@ a = np.array([1,2,3,4,5,6,7,8,9,10,11,32,55,78,22,99,55,33.2,55.77,99,101.3]) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3b080cba952a146f"} --> ### Slicing numpy arrays Numpy arrays also support a special type of indexing called "slicing" that does not just return a single element of an array, but instead returns a whole part of array. @@ -167,6 +188,7 @@ To do this, I put not just a single number inside my square brackets, but instea `a[n:m]` will return a new tuple that consist of all the elements in `a`, starting at element `n` and ending at element `m-1`. Let's look at a concrete example: +<!-- #endregion --> ```python a = np.array(range(10)) @@ -174,9 +196,11 @@ print(a) print(a[0:5]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-70abffff9b88cf27"} --> The notation `a[0:5]` has "sliced" out the first five elements of the array. With slicing, you can also leave off either `n` or `m` from the slice: if leave off `n` it will default to `n=0`, and if you leave off `m`, it will default to the end of the array (also the same as `m=-1` in Python indexing): +<!-- #endregion --> ```python print(a[:5]) @@ -186,25 +210,33 @@ print(a[:5]) print(a[5:]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a8b2ffcd10cfc7b1"} --> Also handy: you can can have Python slice an array with a "step" size that is more than one by adding another `:` and a number after that. Find out its operation using: +<!-- #endregion --> ```python print(a[0:10:2]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-24b9d17391ceeb7e"} --> Fun: you can also use negative steps: +<!-- #endregion --> ```python print(a[-1:-11:-1]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ee08109e60cb51f"} --> And finally, unlike indexing, Python is a bit lenient if you slice off the end of an array: +<!-- #endregion --> ```python print(a[0:20]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8d1518c5eafa34e8"} --> **Exercise 4.2:** Slicing can also be used to *set* multiple values in an array at the same time. Use slicing to set first 10 entries of the array below to zero in one line of code. +<!-- #endregion --> ```python a = np.array(range(20))+1 @@ -213,9 +245,11 @@ some code that sets the first 10 entries to zero print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9b377accb5c0210c"} --> ### Mathematical operations on arrays An advantage of using numpy arrays for scientific computing is that way they behave under mathematical operations. In particular, they very often do exactly what we would want them to do if they were a vector: +<!-- #endregion --> ```python a = np.array([1,2,3,4,5]) @@ -242,6 +276,7 @@ print(a/2) print(a**2) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2963828a5132a24a"} --> What about if I multiply two vectors together? In mathematics, if I multiply two vectors, what I get depends on if I use the "dot product" or the "outer product" for my multiplication: @@ -259,12 +294,15 @@ It turns out: it uses **neither!** In Python, the notation `a*a` produces what i (Mathematically, this has a fancy name called the <a href=https://en.wikipedia.org/wiki/Hadamard_product_(matrices)>Hadamard product</a>, but as you can see, despite the fancy name, it's actually very simple...) We can see this in action here: +<!-- #endregion --> ```python print(a*a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-713408533e01c84c"} --> What if I actually want the dot product or the outer product? For that, Python has functions `np.dot()` and `np.outer()`: +<!-- #endregion --> ```python print(np.dot(a,a)) @@ -274,26 +312,32 @@ print(np.dot(a,a)) print(np.outer(a,a)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d29f4ba63452bfee"} --> Pretty much all operators work with numpy arrays, even comparison operators, which can sometimes be very handy: +<!-- #endregion --> ```python print(a) print(a>3) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8657218bbd2eabbc"} --> **Exercise 4.3:** Generate a sequence of the first 20 powers of 2 in a numpy array (starting at $2^0$). Your output should be an array $[2^0, 2^1, 2^2, 2^3, ...]$. *(Hint: Start with a numpy array created using an appropriate range function that makes an array [0,1,2,3,...])* +<!-- #endregion --> ```python -code that makes the desired array +# your code that makes the desired array ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-809eedfdadcd8b57"} --> ## Functions for creating numpy arrays In numpy, there are also several handy functions for automatically creating arrays: +<!-- #endregion --> ```python a = np.zeros(10) @@ -305,6 +349,7 @@ a = np.ones(10) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a3a728bdca7ae5e3"} --> ### `np.linspace` To automatically generate an array with linerly increasing values you can take `np.linspace()`: @@ -314,6 +359,7 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html It takes three arguments: the starting number, the ending number, and the number of points. This is a bit like the `range` function we saw before, but allows you to pick the total number of points, automatically calculating the (non-integer) step size you need: +<!-- #endregion --> ```python a = np.linspace(0,20,40) @@ -323,7 +369,9 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ea30686a4490a44d"} --> Note that if we wanted to have a step size of exactly 0.5, we need a total of 41 points: +<!-- #endregion --> ```python a = np.linspace(0,20,41) @@ -333,16 +381,20 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-60d973103e7a78a9"} --> **Exercise 4.4:** Generate an array that runs from -2 to 1 with 20 points using `linspace`. +<!-- #endregion --> ```python a = your code print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7c4a427b963a9b3c"} --> ### `np.arange()` If we want to have more control on the exact spacing, we can use the `np.arange()` function. It is like `range()`, asking you for the start, stop, and step size: +<!-- #endregion --> ```python a = np.arange(0,20,0.5) @@ -352,7 +404,9 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5b97dd88408ae9d0"} --> Here, we already see a small quirk of `arange`: it will stop once the next point it calculates is `<` (not `<=`) to the stop point. If we want to get a range that stops at `20.0`, we need to make the stop point any number a bit bigger than 20 (but smaller than our step size): +<!-- #endregion --> ```python a = np.arange(0,20.00000001,0.5) @@ -362,33 +416,41 @@ print("Length is: ", len(a)) print("Step size is: ", a[1]-a[0]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ab1a61494f1f5b33"} --> For this reason, I do not find myself using `np.arange()` very often, and mostly use `np.linspace()`. There are also several other useful functions, such as <a href=https://docs.scipy.org/doc/numpy/reference/generated/numpy.geomspace.html>np.geomspace()</a>, which produces geometrically spaced points (such that they are evenly spaced on a log scale). +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3a0d9465353f954b"} --> **Exercise 4.5:** Generate a numpy array that has a first element with value 60 and last element 50 and takes steps of -0.5 between the values. +<!-- #endregion --> ```python a = your code print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ed2f97bb11cf695d"} --> ### Random numbers Numpy can also generate arrays of random numbers: +<!-- #endregion --> ```python a = np.random.random(40) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-624c748e1b750b24"} --> This will generate uniform random numbers on the range of 0 to 1, but there are also several other random number generator functions that can make <a href=https://en.wikipedia.org/wiki/Normal_distribution>normally distributed</a> random numbers, or random integers, and more. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bf4eeb182ddf3fdf"} --> **Exercise 4.6:** Generate a numpy array that contains 300 random grades that have a distribution of a <a href=https://www.mathsisfun.com/data/standard-normal-distribution.html>bell-shaped curve</a> that might represent the final grades of the students in this course, with an average grade of 7.5 and a standard deviation of 1. Make sure your grades are rounded to a half point. (You may find it useful have to look at the help of the `np.random.normal()` function, and at your answer from Assignment 1 for the rounding.) (Because of the properties of a normal distribution a small pecentage of the grades may be above a 10, you may leave this for now.) +<!-- #endregion --> ```python #Your code here that results in a numpy array rounded_grades @@ -396,23 +458,29 @@ This will generate uniform random numbers on the range of 0 to 1, but there are print(rounded_grades) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-042c8d00bb09795d"} --> ## Multidimensional arrays (matrices) Although we will not use them too much in this course, we can also use numpy also supports two-dimensional (or N-dimensional) numpy arrays, that can represent matrices. To make a 2D numpy array, you can use the `zeros()` function, for example, but with a two-entry list of numbers specifying the size N and M of the matrix: +<!-- #endregion --> ```python m = np.zeros([10,10]) print(m) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-63e005300e7f7098"} --> For two dimensional matrices, the usual function `len()` is not enough to tell us about the shape of our matrix. Instead, we can use a property of the numpy matrix itself called its `shape`: +<!-- #endregion --> ```python print(len(m)) print(m.shape) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f8e1398b61d09699"} --> Indexing two dimensional arrays works by using commas inside square brackets to specify the index of the first and second dimensions: +<!-- #endregion --> ```python a = np.array(range(1,6)) @@ -426,7 +494,9 @@ print("\nAfter changing entry [2,3] to -1:") print(m) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-4e9af994bf9d7df7"} --> You can also use slicing to to assign values to an array from a vector, which can be a handy way to enter a matrix by hand: +<!-- #endregion --> ```python m = np.zeros([3,3]) @@ -436,7 +506,9 @@ m[2,:] = [7,8,9] print(m) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-461ca601abc17699"} --> Similarly, slicing also can be used to extract rows, columns, or blocks of the matrix: +<!-- #endregion --> ```python # A row @@ -453,11 +525,13 @@ print(m[:,1]) print(m[1:,1:]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6ab7d9e10575586d"} --> There are several functions for making matrices which you may find useful someday: https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html including this one which is used often: +<!-- #endregion --> ```python # The identity matrix @@ -469,6 +543,7 @@ print(np.eye(10)) print(np.eye(10,k=-1)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-484f34f51c8f8c5e"} --> **Exercise 4.7:** Use Python to calculate the following matrix multiplication: $$ @@ -492,6 +567,7 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html *(For the nerds: do these two matrices <a href=https://en.wikipedia.org/wiki/Commuting_matrices>commute?</a>)* *(For the real nerds: have your program check if they commute and inform the user!)* +<!-- #endregion --> ```python m1 = np.zeros([3,3]) @@ -511,11 +587,13 @@ print(product) do nerd stuff if you want... ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0a8f096130330d1f"} --> ## Numpy functions We can use `for` loops to iterate through numpy arrays and perform calculations. For example, this code will calculate the average value of all the numbers in an array: +<!-- #endregion --> ```python a = np.linspace(1,20,20) @@ -529,15 +607,19 @@ avg /= len(a) print("Average is", avg) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-471d12979c1baf8e"} --> Because this is a common operation, there is a function built into numpy `np.average()` that can do this for you: +<!-- #endregion --> ```python print("Average is", np.average(a)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-48cd73f9aeaf481e"} --> This is very handy: it saves us loads of typing! From the function name, it is also easy to understand what you are doing, making the code clearer and easier to read. However, the purpose of numpy functions is not only to save lots of typing: they also can often perform calculations MUCH faster than if you do program the calculation yourself with a `for` loop, as we will see in the next section. Python also has many other useful functions for performing calculations using arrays: +<!-- #endregion --> ```python # Calculate the standard deviation @@ -555,9 +637,11 @@ a = np.linspace(-10,10,100) print(np.min(a**2)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3c32880c009136b2"} --> Good question for you to think about: why is this not zero? And what would I have to change above to get the code to return zero? In addition to finding the minimum value in a vector, the function `argmin` can tell you **where** (what index number) the minimum is: +<!-- #endregion --> ```python # Find the index number of the minimum of the array @@ -566,6 +650,7 @@ print(i) print((a**2)[i]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d520c5750ce4f54a"} --> Note also here that we used round brackets `()` around the `a**2` in the `print` statement to be able to then index the resulting array `a**2` array using square brackets `[]`. You can find the full list of mathematical numpy functions on the documentation website: @@ -575,15 +660,18 @@ https://docs.scipy.org/doc/numpy/reference/routines.math.html and the full list of all functions in the reference guide: https://docs.scipy.org/doc/numpy/reference/index.html +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-081110b083e1eeb8"} --> **Exercise 4.8:** Make a array `x` that runs from 0 to 4 with 20 points, and calculate an array `y` whose entries are equal to the square root of the entries in `x`. +<!-- #endregion --> ```python your code to make the requested arrays x and y print(y) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-dfed25e05b150ef3"} --> ### "Vectorisation" and fast code with numpy functions In the first example above, we showed two ways of calculating an average: one using a `for` loop, and one using the numpy function. @@ -593,6 +681,7 @@ Functionally, they are equivalent: they do exactly the same thing. A curious feature of Python is that if you use functions instead of coding loops yourself, often things are **MUCH MUCH** faster. To show this quantitatively, we will use the `time` library to calculate the time it takes to find the average of a pretty big array using both techniques: +<!-- #endregion --> ```python # The time() function from the time library will return a floating point number representing @@ -628,6 +717,7 @@ print("Numpy took %.3f seconds" % (t2-t1)) print("\nNumpy was %.1f times faster!" % (t_forloop/t_np)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bbb6d85899ba86b1"} --> Why is numpy so much faster? The reason is that Python is an <a href=https://en.wikipedia.org/wiki/Interpreted_language>interpreted language</a>. In each of the steps of the `for` loop, the Python kernel reads in the next step it has to do, translates that into an instruction for your computer processor, asks the computer to perform the step, gets the result back, reads in the next step, translates that into a processor instruction, sends that as an instruction to the computer processor, etc, etc. If we did the same test in a <a href=https://en.wikipedia.org/wiki/Compiled_language>compiled programing language</a> like <a href=https://en.wikipedia.org/wiki/C_(programming_language)>C</a>, there would be no difference if we used a library function or if we wrote our own `for` loop. @@ -639,20 +729,25 @@ When you use smart functions in Python libraries, like (many of) those in numpy, In the language of interpreted programmers, finding smart ways of getting what you need done using "compiled library functions" is often referred to as <a href=https://en.wikipedia.org/wiki/Array_programming>vectorisation</a>. Note that even normal mathematical operators are actually "vectorized functions" when they operate: +<!-- #endregion --> ```python # This is actually a vectorized 'for' loop, it involves multiplying 50 million numbers by 5 b = 5*a ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2a60c21baccb3c7d"} --> Here is a nice example of a vectorized way of counting the number of times the number '5' occurs in a random sample of 100 integers between 0 and 20: +<!-- #endregion --> ```python nums = np.random.randint(0,21,100) print("There are %d fives" % np.sum(nums == 5)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c8bc80849f5e0e0a"} --> To see how this works, we can look at the intermediate steps: +<!-- #endregion --> ```python nums = np.random.randint(0,21,100) @@ -661,14 +756,18 @@ print(nums == 5) print("There are %d fives" % np.sum(nums == 5)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1a3f30004c3e3ffb"} --> Note that in this case, `np.sum()` will convert the `bool` value `True` into `1` and `False` into `0` for calculating the sum, according the the standard convertion of `bool` types to `int` types. You can see this in action if you want using the function `astype()` that is built into numpy arrays: +<!-- #endregion --> ```python print(nums == 5) print((nums == 5).astype('int')) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-dd8dc8bd0ab88a1c"} --> Another neat feature that `numpy` has is that is can 'vectorize' normal python functions so that they can take `numpy` functions and make them a bit faster (10-20%). This is done using the `np.frompyfunc` function. An example is given below. +<!-- #endregion --> ```python def fac(N): @@ -698,12 +797,15 @@ except: print("This does not work") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2f8479d8cc410928"} --> ## Solutions to exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0a4e3eec30674d19"} --> **Exercise 4.1** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-daf14d85fa463aba"} a = np.array([1,2,3,4,5,6,7,8,9,10,11,32,55,78,22,99,55,33.2,55.77,99,101.3]) #some code to set the first three and last two entries to zero @@ -720,48 +822,60 @@ a[-1] = 0 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1f286c69f57cbc80"} --> **Exercise 4.2:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-efb5e7292fc18003"} a = np.array(range(20))+1 print(a) a[0:10] = 0 print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b47cf723add61750"} --> **Exercise 4.3:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-87b5343c8e0fdbce"} n = np.array(range(21)) out = 2**n print(out) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ea7ddbad81cf4ad1"} --> **Exercise 4.4:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e6d951d0073dc45f"} a = np.linspace(-2,1,20) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d33f7386d70b8e4f"} --> **Exercise 4.5:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-84bb816a29f6f1bb"} a = np.arange(60,49.9,-0.5) print(a) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-095bdb4fd5d1fae4"} --> **Exercise 4.6:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5a51a5dde00116ba"} raw = np.random.normal(7.5,1,300) rounded_grades = np.round(raw*2)/2 print(rounded_grades) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f8253b40b7bb046e"} --> **Exercise 4.7:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a69f7838d0b9e25a"} m1 = np.zeros([3,3]) m1[:,0] = [1,1,0] m1[:,1] = [0,2,1] @@ -790,9 +904,11 @@ else: print("they don't commute") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3ba058efb16a91ad"} --> **Exercise 4.8:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-57c551e2a58429d8"} x = np.linspace(0,4,20) y = np.sqrt(x) print(y) diff --git a/Notebook 5/Notebook 5 Data in Python.ipynb b/Notebook 5/Notebook 5 Data in Python.ipynb index 680fbbb..ce041f5 100644 --- a/Notebook 5/Notebook 5 Data in Python.ipynb +++ b/Notebook 5/Notebook 5 Data in Python.ipynb @@ -2,7 +2,16 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e43ee4f9ec42e73b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "# Data in Python: Loading, Plotting, and Fitting\n", "\n", @@ -11,7 +20,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-def642c582364cae", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Learning objectives for this notebook:**\n", "\n", @@ -28,7 +46,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f79c995cb536082e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Loading and saving data with Numpy\n", "\n", @@ -92,7 +119,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3aa9fb298e9a108a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here we have assigned the return value of `np.loadtxt` to a variable `data`, which is a numpy array. But what exactly is our variable `data`? We can find out more by looking at the shape of the returned `data` array:" ] @@ -108,7 +144,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5bace50c3249b281", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "When `np.loadtxt()` loads a file, it will return a 2D numpy array with of shape `(n,m)`, where `n` is the number lines in the file and `m` is the number of columns (here, we have 1000 points and 2 columns). \n", "\n", @@ -127,7 +172,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c63f7111a8fd6835", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can look at the first 10 points and see that we have successfully loaded the data from the file! " ] @@ -144,14 +198,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9a33fae5f634b3cb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can check that the data is correct by opening the data file itself from the Jupyter file browser: Jupyter can also directly load and edit text files. (Another way of loading hand-recorded data into Python is just to use Jupyter to create an ASCII `.dat` file yourself.)" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-def50c91314bd96b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.1:** Load the data from the file `exercise_data.dat`. The first column represents a measurement time in seconds, and the second represents a measurement voltage in volts. How many points are in the dataset? \n", "\n", @@ -172,7 +244,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0793eb4c8f85801b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Saving data\n", "\n", @@ -192,7 +273,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-fb66afd2a19403c5", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To \"pack\" them together into a matrix like the one returned by `np.loadtxt()`, we can first create a 2D matrix of the correct size and then use slicing with an assignment operator `=` to give the columns the correct values:" ] @@ -213,7 +303,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7823cff287a7691f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now we are ready to use `np.savetxt()`:" ] @@ -229,7 +328,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9fc0eeb9a99e75d9", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Plotting data and functions with Matplotlib\n", "\n", @@ -244,7 +352,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-24bdaef0687142bb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Plotting basics\n", "\n", @@ -266,7 +383,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d879f0b0f2d9a70f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "I find it handy to also increase the \"dpi\" setting to make the plots a bit bigger. To do this, you need to execute the following in a separate cell from the `import` statement above:" ] @@ -282,7 +408,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0373ada799aeca8e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The routine for making line plots of your data is `plt.plot()`: \n", "\n", @@ -303,7 +438,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2dad246fc48c5b7d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Wow, looks really cool! \n", "\n", @@ -337,7 +481,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-67a673593aba72e2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you want, you can also add a grid (depends on your personal taste preference and what you are using the plots for): " ] @@ -357,7 +510,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-58b15e7163e2da55", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "You can also add vertical or horizontal dashed lines, which I will use here to add zero axis lines:" ] @@ -378,7 +540,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-526802bd953d3531", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Ok, this is all great, but what if I want to plot the voltage vs. time, and not point number? For this, I give `plt.plot()` two arguments:" ] @@ -399,7 +570,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2f9e48b6a1d65039", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Great! This is nearly looking like something I could put into my lab report. But wait: how do I do this? \n", "\n", @@ -421,7 +601,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ed9b147002abbbb6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you look in the file browser, you will now see a file `myplot.pdf`. If you open it, you will see that is a high-quality <a href=https://en.wikipedia.org/wiki/Vector_graphics>vector graphics</a> PDF. \n", "\n", @@ -442,7 +631,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6a6c5a44f576d0c0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Do you want a different point size? Or a different symbol? You can see how to change all of this by looking at the documentation page of the plot function: \n", "\n", @@ -471,7 +669,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1d9eda6969d17554", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Matplotlib will automatically change the color of the second dataset (you can also control them manually, see the <a href= https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>documentation</a>). But it can be hard to tell which one is which, and for this reason, it can useful to add a legend to your plot, which can be done using this code: " ] @@ -492,7 +699,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a4e6050c6111cb50", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Finally, if you want a really big figure, you can also adjust the figure size:" ] @@ -516,7 +732,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1f4975d1b1b7c3a2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.2:** Make a plot of the data you loaded from example 5.1. " ] @@ -534,7 +759,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-93d67850489cb7df", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.3:** As you may have noticed, the data from `example_data.dat` looks like a stright line, while the data from `exercise_data.dat` does not seem like straight line, but instead looks like a power law function $V = at^p$ with power $p$. \n", "\n", @@ -554,7 +788,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-78004ad6fb3f74dd", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Active zooming: The \"notebook\" driver\n", "\n", @@ -1383,7 +1626,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ad73b12e0c6f6822", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Clearly, this function has some tiny little blip around x=2 and another around x=5. As a curious physicist, I'd like to zoom in and see in more detail what these look like. \n", "\n", @@ -1403,7 +1655,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-34cc27094187c60d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "and then plot it again:" ] @@ -2215,7 +2476,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b0da930d38ac497b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now, instead of getting just an image in your web browser, you will get an interactive plot. At the bottom, there are some controls. If you click on the \"rectangle\" one, this will allow you to zoom in the plot by dragging a box around the feature you want to zoom in to. If you press the \"home\" button it will go back to the starting zoom. When you are done zooming around, you need to push the \"power\" button at the top right to finish zooming with the plot. If you want to start zooming again, you need to run the code again to make the plot.\n", "\n", @@ -2233,7 +2503,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6761a172d5149df6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "If you have changed the `dpi` setting, you will need to re-run the command:" ] @@ -2249,7 +2528,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-afe34d963cea156d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Plotting functions in Python\n", "\n", @@ -2277,7 +2565,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-67ab3373ae0fa8cc", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now we want to calculate `y` for all of these x-points. Let's say we pick an angle of 45 degrees and an initial velocity $v_0$ of 10 m/s. We can then just directly use numpy \"vectorized\" calculations to calculate the values of `y` for all of our `x` points using a single line of code: " ] @@ -2299,7 +2596,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3103dddee2c9f829", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Now, let's plot it!" ] @@ -2319,7 +2625,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-a1372d935bab78f8", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can also now see how easy it is to combine plotting functions with plotting data. For example, in with our voltage data above, if we want to plot a straight line function over over the data: " ] @@ -2340,14 +2655,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ecd3b09678cacd45", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here, we also used the `linewidth` parameter (which can be shorted to `lw` to save typing) to make the line a bit fatter so it is easier to see, and used the '--' plot format specifier to make it a dashed line. " ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-46dd5bd28954941b", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.4:** Another way to get the exponent is to plot $\\log(V)$ vs $\\log(t)$ (a log-log plot). If the data really follows a power law relation, then this should give a straight line with a slope determined by the exponent. Try this, and then add a straight line $\\log(V) = p * \\log(t)$ to the plot. Play around with the value of $p$ until the line has the same slope as the data. For this plot, use solid filled circle for the data and a solid line for the theory.\n", "\n", @@ -2367,7 +2700,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8599a077e778277a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Fitting\n", "\n", @@ -2415,7 +2757,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3e41d51a52bac6e2", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "*(Here we also show some examples of more advanced formatting options of the <a href=https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>plt.plot()</a> function to change the colors of the lines (`c=`) and the size of the data markers (`ms=`). I also give an example of adding a title to a graph: this can be handy in a notebook like this one, but in any real lab report, you should NEVER have titles on your plots. Instead, in a report, you should describe what is in the plot using a figure caption.)*\n", "\n", @@ -2428,7 +2779,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b352eec645cffa6c", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.5:** Using the data you loaded in exercise 5.1, try fitting the function $V = at^p$ \"by hand\" to the data by manually adjusting $a$ and $p$ and replotting the function until you think you have the best fit. What are the values of $a$ and $p$ that you find? " ] @@ -2446,7 +2806,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-347f6460e621fd38", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Least squares fitting\n", "\n", @@ -2468,7 +2837,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8cd0ccee9956231a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "To use curve fit, we first need to create a Python function that returns the mathematical function that we want to fit. Our function is:\n", "\n", @@ -2491,7 +2869,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-318b791dd38d2a55", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In the simplest case (and it will always work when you are fitting a stright line), you can just then directly send the function and your x and y data and it will do the fit for you:" ] @@ -2507,7 +2894,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2ae5e791a8ee0965", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Tada! `curve_fit` actually returns two things. The first is an array containing the optimal fit values:" ] @@ -2523,7 +2919,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1a0084e54c4a46fa", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The first one corresponds to the first parameter of the function and the second to the second: in our case, `a` and `b`:" ] @@ -2542,7 +2947,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-dd958e133a172472", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "We can now check what the fit looks like:" ] @@ -2563,7 +2977,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-efff4d4d08c4ef9a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Cool! Also, it turns out that the best statistical fit of the slope to our data is not exactly 2 but slightly higher (about 2.0148)\n", "\n", @@ -2572,7 +2995,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-728ba4c89dc5d7e0", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.6:** Perform a least squares fit to of the data from exercise 5.1 to the function $V = at^p$. Make a plot of the function with the values of $p$ and $a$ from the fit on top of the data. How close are the fitted values to your attempt at fitting by hand? " ] @@ -2590,7 +3022,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bd69b024d2582da3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Initial guesses\n", "The curve fit routine also has the option to provide initial guesses on your parameters using a keyword argument `p0`:" @@ -2610,7 +3051,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-da995318402bc3d7", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "In this case it (nearly) did not make a difference: and in fact, for fitting a linear function, least squares fitting will converge to the same set of values, no matter what initial guess you provide. \n", "\n", @@ -2621,7 +3071,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7019fff9dd353955", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "### Errors on parameters\n", "\n", @@ -2644,7 +3103,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f0ffe772615a4907", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "The most important two for us are the diagonal elements: the first diagonal element tells us the square of the <a href=https://en.wikipedia.org/wiki/Standard_error>standard error</a> $\\sigma_a$ of parameter `a` and the second diagonal element gives us the square of $\\sigma_b$:" ] @@ -2663,7 +3131,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5d97036c0a1d934d", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "(The standard error $\\sigma_a$ is also sometimes also written as $\\Delta a$, and is also sometimes called the parameter \"uncertainty\" instead of the parameter \"error\"). \n", "\n", @@ -2682,7 +3159,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b4032ef7311840a6", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "Here we used a placeholder format `%.2f` designating that we want to print as float with two decimals. There are a \n", "<a href=https://www.python-course.eu/python3_formatted_output.php>lot</a> of other ways to format your string.\n", @@ -2694,7 +3180,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d3c840f7897a22f3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.7** Calculated the errors on the fit parameters from your fit in exercise 5.5. Was your estimate of $p$ from exercise 5.4 within the statistical error margins from you fit? Were the values for $a$ and $p$ you found from \"fitting by hand\" within the statistical error margins? " ] @@ -2714,14 +3209,32 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-7fe0b0fc78334b72", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "## Solutions to exercises" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2c686ad63c7718cb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.1:** " ] @@ -2730,6 +3243,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e723ee13a367dc4a", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -2742,7 +3263,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f248bda277afb213", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.2:**" ] @@ -2751,6 +3281,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-6698a19848ed5646", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -2764,7 +3302,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-16f1a60ee96ec552", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.3:** " ] @@ -2772,7 +3319,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-07471b53dc331edf", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "# 2 looks not bad\n", @@ -2786,7 +3342,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8c4f854c8b58688f", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.4:** " ] @@ -2795,6 +3360,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d36fac25feffba52", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -2819,7 +3392,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b429d77d3941e298", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.5:** " ] @@ -2827,7 +3409,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-20348d9d40f824cb", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "outputs": [], "source": [ "a=2\n", @@ -2841,7 +3432,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9e6875ad01124e78", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.6:** " ] @@ -2850,6 +3450,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f37090f982584db3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": false }, "outputs": [], @@ -2873,7 +3481,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-cd2f7a62303d00d3", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + } + }, "source": [ "**Exercise 5.7**" ] @@ -2882,6 +3499,14 @@ "cell_type": "code", "execution_count": null, "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c98f0fc3f9dfe50e", + "locked": true, + "schema_version": 3, + "solution": false, + "task": false + }, "scrolled": true }, "outputs": [], @@ -2920,6 +3545,7 @@ } ], "metadata": { + "celltoolbar": "Create Assignment", "jupytext": { "formats": "ipynb,md" }, @@ -2938,7 +3564,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" }, "toc": { "base_numbering": "5", @@ -2956,7 +3582,7 @@ "width": "430.797px" }, "toc_section_display": true, - "toc_window_display": true + "toc_window_display": false } }, "nbformat": 4, diff --git a/Notebook 5/Notebook 5 Data in Python.md b/Notebook 5/Notebook 5 Data in Python.md index 85c4fb2..8875410 100644 --- a/Notebook 5/Notebook 5 Data in Python.md +++ b/Notebook 5/Notebook 5 Data in Python.md @@ -5,19 +5,21 @@ jupyter: text_representation: extension: .md format_name: markdown - format_version: '1.1' - jupytext_version: 1.2.2 + format_version: '1.2' + jupytext_version: 1.3.0 kernelspec: display_name: Python 3 language: python name: python3 --- +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e43ee4f9ec42e73b"} --> # Data in Python: Loading, Plotting, and Fitting In this notebook, we will explore how to load and save datafiles in Python using Numpy, and how to plot and explore data and functions with a library called Matplotlib. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-def642c582364cae"} --> **Learning objectives for this notebook:** * Student is able to load data from ASCII text files @@ -29,8 +31,9 @@ In this notebook, we will explore how to load and save datafiles in Python using * Student is able to fit a model to data using "fitting by hand" * Student is able to use `curve_fit` to perform a least-squares fit of a model to a dataset * Student is able to calculate the statistical error in the parameters of a least-squares fit +<!-- #endregion --> -<!-- #region --> +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f79c995cb536082e"} --> ## Loading and saving data with Numpy Here, we will explore some functions provided by numpy for loading data from files into python, and for saving data from python to files. @@ -86,51 +89,64 @@ import numpy as np data = np.loadtxt("v_vs_time.dat") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3aa9fb298e9a108a"} --> Here we have assigned the return value of `np.loadtxt` to a variable `data`, which is a numpy array. But what exactly is our variable `data`? We can find out more by looking at the shape of the returned `data` array: +<!-- #endregion --> ```python data.shape ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5bace50c3249b281"} --> When `np.loadtxt()` loads a file, it will return a 2D numpy array with of shape `(n,m)`, where `n` is the number lines in the file and `m` is the number of columns (here, we have 1000 points and 2 columns). As I mentioned above, the first column represents the time that the measurement was taken, and the second column represents the measured voltage in volts. We will typically want to extract these into two vectors `t` and `v`, which we can do using slicing: +<!-- #endregion --> ```python t = data[:,0] v = data[:,1] ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c63f7111a8fd6835"} --> We can look at the first 10 points and see that we have successfully loaded the data from the file! +<!-- #endregion --> ```python print(t[0:10]) print(v[0:10]) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9a33fae5f634b3cb"} --> We can check that the data is correct by opening the data file itself from the Jupyter file browser: Jupyter can also directly load and edit text files. (Another way of loading hand-recorded data into Python is just to use Jupyter to create an ASCII `.dat` file yourself.) +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-def50c91314bd96b"} --> **Exercise 5.1:** Load the data from the file `exercise_data.dat`. The first column represents a measurement time in seconds, and the second represents a measurement voltage in volts. How many points are in the dataset? *(Important: make sure that you use different variable names than t and v for the data you load, since we will continue to use the data we loaded in the example code in the sections below!)* +<!-- #endregion --> ```python your code here to load the data (using different variable names than t and v!) and check the size of the arrays you get ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0793eb4c8f85801b"} --> ### Saving data We can also save data using a numpy routine `np.savetxt()`. To do this, we have to "pack" the data back into a numpy array of the correct shape. Let's take a look at an example where we calculate the square of the measured voltage and save this back into a new file: +<!-- #endregion --> ```python v_squared = v**2 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-fb66afd2a19403c5"} --> To "pack" them together into a matrix like the one returned by `np.loadtxt()`, we can first create a 2D matrix of the correct size and then use slicing with an assignment operator `=` to give the columns the correct values: +<!-- #endregion --> ```python # Create empty array @@ -141,12 +157,15 @@ to_save[:,0] = t to_save[:,1] = v_squared ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7823cff287a7691f"} --> Now we are ready to use `np.savetxt()`: +<!-- #endregion --> ```python np.savetxt("vsquare_vs_time.dat", to_save) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9fc0eeb9a99e75d9"} --> ## Plotting data and functions with Matplotlib In the above example, we loaded in some measured data from a file into an array. From there, we could perform mathematical operations on our data, and save it back to a new file. @@ -156,8 +175,9 @@ However, what every physicist will want to do is plot the data to see what it lo Here, we will show you how to do this using the `matplotlib` library: https://matplotlib.org +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-24bdaef0687142bb"} --> ### Plotting basics Specifically, we will need the `pyplot` module of `matplotlib`: @@ -165,28 +185,34 @@ Specifically, we will need the `pyplot` module of `matplotlib`: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.html#module-matplotlib.pyplot We will import it with the shortcut / "name" / "prefix" `plt`: +<!-- #endregion --> ```python import matplotlib.pyplot as plt ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d879f0b0f2d9a70f"} --> I find it handy to also increase the "dpi" setting to make the plots a bit bigger. To do this, you need to execute the following in a separate cell from the `import` statement above: +<!-- #endregion --> ```python plt.rcParams['figure.dpi'] = 100 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-0373ada799aeca8e"} --> The routine for making line plots of your data is `plt.plot()`: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html In its simplest form, you can just give it an array and ask it to plot it for you: +<!-- #endregion --> ```python plt.plot(v) plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2dad246fc48c5b7d"} --> Wow, looks really cool! The y axis is clearly the measured voltage. But what is the x-axis? @@ -203,6 +229,7 @@ Now that we know this, we should actually **ALWAYS add axis labels to our plots. **ALWAYS!!!** To do this, you can use the `plt.xlabel()` and `plt.ylabel()` commands: +<!-- #endregion --> ```python plt.plot(v) @@ -211,7 +238,9 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-67a673593aba72e2"} --> If you want, you can also add a grid (depends on your personal taste preference and what you are using the plots for): +<!-- #endregion --> ```python plt.plot(v) @@ -221,7 +250,9 @@ plt.grid() plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-58b15e7163e2da55"} --> You can also add vertical or horizontal dashed lines, which I will use here to add zero axis lines: +<!-- #endregion --> ```python plt.plot(v) @@ -232,7 +263,9 @@ plt.axvline(0, ls=':', c='grey') plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-526802bd953d3531"} --> Ok, this is all great, but what if I want to plot the voltage vs. time, and not point number? For this, I give `plt.plot()` two arguments: +<!-- #endregion --> ```python plt.plot(t,v) @@ -241,9 +274,11 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2f9e48b6a1d65039"} --> Great! This is nearly looking like something I could put into my lab report. But wait: how do I do this? Fortunately, `matplotlib` has functions that easily allow you to save your plot as a high-quality PDF format, perfect for importing into <a href=https://en.wikipedia.org/wiki/LaTeX>LaTeX</a> for writing your awesome lab report! +<!-- #endregion --> ```python plt.plot(t,v) @@ -253,9 +288,11 @@ plt.savefig("myplot.pdf") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ed9b147002abbbb6"} --> If you look in the file browser, you will now see a file `myplot.pdf`. If you open it, you will see that is a high-quality <a href=https://en.wikipedia.org/wiki/Vector_graphics>vector graphics</a> PDF. Sometimes, one may want to plot the data as points instead of a connected line. You can do this by adding a '.' after your arrays: +<!-- #endregion --> ```python plt.plot(t,v, '.') @@ -264,6 +301,7 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6a6c5a44f576d0c0"} --> Do you want a different point size? Or a different symbol? You can see how to change all of this by looking at the documentation page of the plot function: https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html @@ -271,6 +309,7 @@ https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html You can also bring up the built-in help of the function by clicking on the function name in the code cell above and typing `Shift-Tab`. You may want to have more than one thing plotted in your plot. To do this, you simply have to run the plot command twice: +<!-- #endregion --> ```python # A "fake" dataset to illustrate plotting @@ -283,7 +322,9 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1d9eda6969d17554"} --> Matplotlib will automatically change the color of the second dataset (you can also control them manually, see the <a href= https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>documentation</a>). But it can be hard to tell which one is which, and for this reason, it can useful to add a legend to your plot, which can be done using this code: +<!-- #endregion --> ```python plt.plot(t,v, label="Voltage 1") @@ -294,7 +335,9 @@ plt.legend() plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a4e6050c6111cb50"} --> Finally, if you want a really big figure, you can also adjust the figure size: +<!-- #endregion --> ```python plt.figure(figsize=(12,8)) @@ -306,27 +349,33 @@ plt.legend() plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1f4975d1b1b7c3a2"} --> **Exercise 5.2:** Make a plot of the data you loaded from example 5.1. +<!-- #endregion --> ```python code to plot the data you loaded in example 5.1 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-93d67850489cb7df"} --> **Exercise 5.3:** As you may have noticed, the data from `example_data.dat` looks like a stright line, while the data from `exercise_data.dat` does not seem like straight line, but instead looks like a power law function $V = at^p$ with power $p$. One way to test for such power law relations is to make a plot with an x-axis that is not $t$ but instead $t^p$: if you get the correct $p$, then the data plotted will form a straight line. Write code to plot $V$ against $t^p$ and they put in different guesses at the power $p$ to see if you can see what the power is. +<!-- #endregion --> ```python code to plot data from example 5.1 with a different x-axis ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-78004ad6fb3f74dd"} --> ### Active zooming: The "notebook" driver The above examples are great for plotting your data for a report or exploring functions. Sometimes, though, you may want to be able to zoom in on a tiny little features in your function or data. Here is an example of some data that one might want to zoom in on: +<!-- #endregion --> ```python # unpack=True is also a handy way to load data @@ -343,17 +392,21 @@ plt.ylabel("y") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ad73b12e0c6f6822"} --> Clearly, this function has some tiny little blip around x=2 and another around x=5. As a curious physicist, I'd like to zoom in and see in more detail what these look like. I could do this using the `plt.xlim()` and `plt.ylim()` functions, but what I'd really like to be able to do is to click and zoom in on them. For these, we can use `matplotlib` with a different driver called the `notebook` driver. To turn this on, we can use this command: +<!-- #endregion --> ```python %matplotlib notebook ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-34cc27094187c60d"} --> and then plot it again: +<!-- #endregion --> ```python plt.plot(x,y) @@ -362,20 +415,25 @@ plt.ylabel("y") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b0da930d38ac497b"} --> Now, instead of getting just an image in your web browser, you will get an interactive plot. At the bottom, there are some controls. If you click on the "rectangle" one, this will allow you to zoom in the plot by dragging a box around the feature you want to zoom in to. If you press the "home" button it will go back to the starting zoom. When you are done zooming around, you need to push the "power" button at the top right to finish zooming with the plot. If you want to start zooming again, you need to run the code again to make the plot. This can be quite useful, but can also be annoying (for example if you forget to turn the "power" button off). To turn off the `notebook` driver, use the `%matplotlib` command again with the option `inline`: +<!-- #endregion --> ```python %matplotlib inline ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6761a172d5149df6"} --> If you have changed the `dpi` setting, you will need to re-run the command: +<!-- #endregion --> ```python plt.rcParams['figure.dpi'] = 100 ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-afe34d963cea156d"} --> ### Plotting functions in Python Sometimes (often?) in physics, we physicists will go through a long mathematical derivation to come up with a formula describing a physical problem. Sometimes this formula is very complicated, and it is then handy to be able to plot the formula to get feeling for what the function looks like. @@ -389,12 +447,15 @@ y = -\frac{g}{2 v_0^2 \cos^2 \theta} x^2 + x \tan \theta $$ The first step is to make a numpy array `x` that includes the points at which we want to evaluate the function. Let's guess and say we want to look in the range of x from 0 to 12 meters. We also need to pick the number of points: to get a smooth curve, let's pick say 1000 points: +<!-- #endregion --> ```python x = np.linspace(0,12,1000) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-67ab3373ae0fa8cc"} --> Now we want to calculate `y` for all of these x-points. Let's say we pick an angle of 45 degrees and an initial velocity $v_0$ of 10 m/s. We can then just directly use numpy "vectorized" calculations to calculate the values of `y` for all of our `x` points using a single line of code: +<!-- #endregion --> ```python # Parameters @@ -406,7 +467,9 @@ g = 9.8 # m/s^2 y = -g/(2*v0**2*np.cos(theta)**2)*x**2 + x*np.tan(theta) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3103dddee2c9f829"} --> Now, let's plot it! +<!-- #endregion --> ```python plt.plot(x,y) @@ -416,7 +479,9 @@ plt.axhline(0, ls=":", c="grey") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-a1372d935bab78f8"} --> We can also now see how easy it is to combine plotting functions with plotting data. For example, in with our voltage data above, if we want to plot a straight line function over over the data: +<!-- #endregion --> ```python line = 2*t @@ -427,17 +492,21 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-ecd3b09678cacd45"} --> Here, we also used the `linewidth` parameter (which can be shorted to `lw` to save typing) to make the line a bit fatter so it is easier to see, and used the '--' plot format specifier to make it a dashed line. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-46dd5bd28954941b"} --> **Exercise 5.4:** Another way to get the exponent is to plot $\log(V)$ vs $\log(t)$ (a log-log plot). If the data really follows a power law relation, then this should give a straight line with a slope determined by the exponent. Try this, and then add a straight line $\log(V) = p * \log(t)$ to the plot. Play around with the value of $p$ until the line has the same slope as the data. For this plot, use solid filled circle for the data and a solid line for the theory. *(Do you get an warning message? Why?)* +<!-- #endregion --> ```python your code ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8599a077e778277a"} --> ## Fitting In experimental physics, one of our primary goals is to try to figure out if our observed experimental measurements match the theoretical predictions that we can make from mathematical models. @@ -462,6 +531,7 @@ where $a$ is some parameter with the units of V/s representing the slope of the The fact that the the line $V = 2t$ (the orange dashed line) seems to pass through the middle of our blue data already suggests that for our data (the blue points), $a = 2$ V/s and $b = 0$ V seems like pretty good parameters for our model to describe the data. We can also see that `a = 1.7` and `b = 1.7` are also OK (they go through a lot of the data points), but are probably not as good: +<!-- #endregion --> ```python line1 = 1.7*t+1.7 @@ -476,6 +546,7 @@ plt.title("a = 1.7 and b = 1.7 are also OK, but not as good as a = 2 and b = 0") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-3e41d51a52bac6e2"} --> *(Here we also show some examples of more advanced formatting options of the <a href=https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.plot.html>plt.plot()</a> function to change the colors of the lines (`c=`) and the size of the data markers (`ms=`). I also give an example of adding a title to a graph: this can be handy in a notebook like this one, but in any real lab report, you should NEVER have titles on your plots. Instead, in a report, you should describe what is in the plot using a figure caption.)* From "fitting by hand" (just plotting the data and adjusting the parameters), you can already get a good feeling for functions and parameters fit "pretty well" and which "not as well". @@ -483,14 +554,17 @@ From "fitting by hand" (just plotting the data and adjusting the parameters), yo But: how do I find the BEST parameters (`a` and `b` in this case) for my data? And how do I know how accurately my data allows me to determine these parameters? For that, we can use a procedure called <a href=https://en.wikipedia.org/wiki/Least_squares>least squares fitting</a>. In the next part of the lab course, you will learn more about least squares fitting and how it works. Here, we will look at how to code it in Python, how to use it to get the best fit parameters of a model for you data, and how to find out what the <a href=https://en.wikipedia.org/wiki/Errors_and_residuals>statistical error</a> is on the parameters of your model given the data you are using for the fit. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b352eec645cffa6c"} --> **Exercise 5.5:** Using the data you loaded in exercise 5.1, try fitting the function $V = at^p$ "by hand" to the data by manually adjusting $a$ and $p$ and replotting the function until you think you have the best fit. What are the values of $a$ and $p$ that you find? +<!-- #endregion --> ```python code to fit data "by hand" to a power law ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-347f6460e621fd38"} --> ### Least squares fitting To have Python automatically find the best parameters for fitting your data with a given function, you can use the `curve_fit()` routine from the `scipy` (scientific Python) package: @@ -498,11 +572,13 @@ To have Python automatically find the best parameters for fitting your data with https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html Since we will use only the `curve_fit` routine, we will import it on its own: +<!-- #endregion --> ```python from scipy.optimize import curve_fit ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8cd0ccee9956231a"} --> To use curve fit, we first need to create a Python function that returns the mathematical function that we want to fit. Our function is: $$ @@ -510,25 +586,32 @@ f(x) = ax + b $$ It has one variable, $x$, and two parameters, $a$ and $b$. For using `curve_fit`, we will need to make a Python function that takes three arguments: the first is the variable `x` (for the above, it will be time), and the other two arguments are the fit parameters `a` and `b`, and which should return the predicted voltage: +<!-- #endregion --> ```python def f(x,a,b): return a*x+b ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-318b791dd38d2a55"} --> In the simplest case (and it will always work when you are fitting a stright line), you can just then directly send the function and your x and y data and it will do the fit for you: +<!-- #endregion --> ```python values, covariance = curve_fit(f, t, v) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2ae5e791a8ee0965"} --> Tada! `curve_fit` actually returns two things. The first is an array containing the optimal fit values: +<!-- #endregion --> ```python print(values) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-1a0084e54c4a46fa"} --> The first one corresponds to the first parameter of the function and the second to the second: in our case, `a` and `b`: +<!-- #endregion --> ```python a_fit = values[0] @@ -537,7 +620,9 @@ print(a_fit) print(b_fit) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-dd958e133a172472"} --> We can now check what the fit looks like: +<!-- #endregion --> ```python plt.plot(t,v, '.',c='k', ms=2) @@ -548,19 +633,24 @@ plt.ylabel("Voltage (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-efff4d4d08c4ef9a"} --> Cool! Also, it turns out that the best statistical fit of the slope to our data is not exactly 2 but slightly higher (about 2.0148) In addition, sometimes in measurements, we also have error bars on the each of the individual black data points in the plot, and sometimes some data points could have more error than others. In these cases, the fitting routine can take these individual error bars into account when performing this fit: we will see more about this later in the course. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-728ba4c89dc5d7e0"} --> **Exercise 5.6:** Perform a least squares fit to of the data from exercise 5.1 to the function $V = at^p$. Make a plot of the function with the values of $p$ and $a$ from the fit on top of the data. How close are the fitted values to your attempt at fitting by hand? +<!-- #endregion --> ```python code that performs a least square fit to the exercise data ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-bd69b024d2582da3"} --> ### Initial guesses The curve fit routine also has the option to provide initial guesses on your parameters using a keyword argument `p0`: +<!-- #endregion --> ```python initial_guesses = (2.5,1) @@ -569,13 +659,15 @@ print(values2) print(values) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-da995318402bc3d7"} --> In this case it (nearly) did not make a difference: and in fact, for fitting a linear function, least squares fitting will converge to the same set of values, no matter what initial guess you provide. This is NOT true for functions $f(x)$ that are not linear in $x$. In really bad cases, the parameter values that `curve_fit` finds can even be highly sensitive to the initial conditions. For this reason, it is always a good idea, and sometimes absolutely necessary, to first do some "fitting by hand", and then ideally provide these initial conditions to the `curve_fit` routine. And even if you do not provide hand-tuned initial guesses (which you may not if you are automatically fitting a huge number of datasets), it is important that you always make a plot of the the fitted curves that you find on top of the data just to make sure nothing goes wrong. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7019fff9dd353955"} --> ### Errors on parameters We found above that the best statistical fit to our data was not our initial estimate of `a = 2`, but actually more like `a = 2.0148`. @@ -583,13 +675,16 @@ We found above that the best statistical fit to our data was not our initial est But the line with `a = 2` also looked pretty good, right? How much better is the fit with `a = 2.0148`? To find this out, we can use the other variable that `curve_fit` returned: the <a href=https://en.wikipedia.org/wiki/Covariance_matrix>covariance matrix</a>. With two parameters, the covariance matrix is a 2x2 array: +<!-- #endregion --> ```python values, covariance = curve_fit(f, t, v) print(covariance) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f0ffe772615a4907"} --> The most important two for us are the diagonal elements: the first diagonal element tells us the square of the <a href=https://en.wikipedia.org/wiki/Standard_error>standard error</a> $\sigma_a$ of parameter `a` and the second diagonal element gives us the square of $\sigma_b$: +<!-- #endregion --> ```python a_err = np.sqrt(covariance[0,0]) @@ -598,24 +693,29 @@ print(a_err) print(b_err) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-5d97036c0a1d934d"} --> (The standard error $\sigma_a$ is also sometimes also written as $\Delta a$, and is also sometimes called the parameter "uncertainty" instead of the parameter "error"). We can now quote the full values of the parameters from our fit, including errors: +<!-- #endregion --> ```python print("The value of a is %.2f +/- %.2f" % (a_fit, a_err)) print("The value of b is %.2f +/- %.2f" % (b_fit, b_err)) ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b4032ef7311840a6"} --> Here we used a placeholder format `%.2f` designating that we want to print as float with two decimals. There are a <a href=https://www.python-course.eu/python3_formatted_output.php>lot</a> of other ways to format your string. Note that typically when you quote an error, you should pick only one significant digit. And also, when you quote the value of a parameter, you should also quote it with the same number of decimal places as your error. So in this case, I quote the value of `a` as 2.015, and not its "exact" value of 2.0148415283012246. Why? The reason is that doesn't make sense to quote more digits on the value of `a` than a number of digits corresponding to the size of the statistical error, and so physicists and scientists truncate the accuracy of the numbers they report to a level comparable to the error. +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d3c840f7897a22f3"} --> **Exercise 5.7** Calculated the errors on the fit parameters from your fit in exercise 5.5. Was your estimate of $p$ from exercise 5.4 within the statistical error margins from you fit? Were the values for $a$ and $p$ you found from "fitting by hand" within the statistical error margins? +<!-- #endregion --> ```python code to calculate the parameter uncertainties (errors) and check if your earlier @@ -623,21 +723,26 @@ estimates agree with the least-square-fit values to within the error margins of the fit ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-7fe0b0fc78334b72"} --> ## Solutions to exercises +<!-- #endregion --> - +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-2c686ad63c7718cb"} --> **Exercise 5.1:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-e723ee13a367dc4a"} # You can also use the option "unpack=True" to have loadtxt send back all columns separately t2,v2 = np.loadtxt("exercise_data.dat", unpack=True) print("Loaded", len(v2), "points") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f248bda277afb213"} --> **Exercise 5.2:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-6698a19848ed5646"} plt.plot(t2,v2) # ALL PLOTS MUST HAVE LABELS WITH UNITS!!!! plt.xlabel("t (s)") @@ -645,9 +750,11 @@ plt.ylabel("v (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-16f1a60ee96ec552"} --> **Exercise 5.3:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-07471b53dc331edf"} # 2 looks not bad p = 2 plt.plot(t2**p,v2) @@ -657,9 +764,11 @@ plt.ylabel("v (V)") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-8c4f854c8b58688f"} --> **Exercise 5.4:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-d36fac25feffba52"} # 2 looks not bad plt.plot(np.log(t2),np.log(v2),'o', label="Data") @@ -678,9 +787,11 @@ plt.ylabel("log(v) (log(V))") plt.show() ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-b429d77d3941e298"} --> **Exercise 5.5:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-20348d9d40f824cb"} a=2 p=2.34 plt.plot(t2,v2, 'o', label='Data') @@ -690,9 +801,11 @@ plt.xlabel("t (s)") plt.ylabel("v (V)") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-9e6875ad01124e78"} --> **Exercise 5.6:** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-f37090f982584db3"} def f2(t,a,p): return a*t**p @@ -710,9 +823,11 @@ plt.xlabel("t (s)") plt.ylabel("v (V)") ``` +<!-- #region nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-cd2f7a62303d00d3"} --> **Exercise 5.7** +<!-- #endregion --> -```python +```python nbgrader={"schema_version": 3, "solution": false, "grade": false, "locked": true, "task": false, "grade_id": "cell-c98f0fc3f9dfe50e"} a_err = np.sqrt(covariance[0,0]) p_err = np.sqrt(covariance[1,1]) -- GitLab