{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": true }, "source": [ "

Table of Contents

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> All content here is under a Creative Commons Attribution [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) and all source code is released under a [BSD-2 clause license](https://en.wikipedia.org/wiki/BSD_licenses). Parts of these materials were inspired by https://github.com/engineersCode/EngComp/ (CC-BY 4.0), L.A. Barba, N.C. Clementi.\n", ">\n", ">Please reuse, remix, revise, and [reshare this content](https://github.com/kgdunn/python-basic-notebooks) in any way, keeping this notice." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Module 6: Overview \n", "\n", "In the [prior module](https://yint.org/pybasic05) you became comfortable doing calculations with vectors, matrices and arrays.\n", "\n", "We now take a detour to look at ***Python functions***. \n", "\n", "Eventually the calculations you wrote code for in prior modules can be generalized. This code can be reused in the future, with other input data. Functions make this all possible.\n", "\n", "So this module introduces new ideas, but also reuses the content from the prior modules.\n", "\n", "

Start a new (or use an existing) version controlled repository for your work. Commit your work regularly, where ever you see this icon.\n", "\n", "
\n", "\n", "### Preparing for this module###\n", "\n", "You should have:\n", "1. Completed [worksheet 5](https://yint.org/pybasic05)\n", "2. Read sections 12.1 to 12.5 from [Foundations of Python Programming (FOPP)](https://runestone.academy/runestone/static/fopp/Functions/toctree.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: a template\n", "\n", "A standard feature in any programming language is the ability to write functions: a chunk of code that takes 0 or more inputs, and returns 0 or more outputs. Inputs are also sometimes referred to as function ***arguments***.\n", "\n", "Functions help you:\n", "* split up your code, \n", "* make it modular, \n", "* allow you to reuse these modular blocks in the future, in other code;\n", "* help you with debugging: you can then isolate bugs in your code by elimination. Functions that have been tested and checked are not likely to be the source of a problem any more.\n", "\n", "### A template\n", "\n", "Please consider using this standard template for your functions:\n", "\n", ">```python\n", ">def verb_description(...):\n", "> \"\"\"Comment of what the function does goes here.\"\"\"\n", "> \n", "> # Commands for the function\n", "> a = ...\n", "> \n", "> return ...\n", ">```\n", "\n", "1. Making the function name start with a verb makes it clear to you - and others - what it does. E.g. ``plot_curves(...)`` or ``transform_data(...)`` or ``save_file(...)``, etc. Starting with a verb is not a fixed requirement, and sometimes a clear function name need not have verb: in a math library, the ``log(...)`` function clearly *calculates* a log of the input; no need for ``calculate_log(...)``.\n", "2. But a verb does prevent you from making the function do more than it should. It just does that verb.\n", "3. Start the code with a triple-quote comment: ```\"\"\" Calculates the mean of ... \"\"\"```\n", "4. Using the 4-space indent on the left, write the various statements and loops to do the work in the function.\n", "5. It is OK if a function is just a single line! Why? In the future you might expand it a bit, e.g. to check the inputs, handle missing values, add extra inputs to the function. Don't think: \"It's just one line; why bother?\". If you suspect you will reuse the idea of that 1 line in several places, rather put it in a function.\n", "6. Return something from your function. It is not mandatory, but it is clearer. Even ```return True``` is a good signal to the user, to indicate the function did its work successfully.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: no output\n", "\n", "Strange as it seems, some functions have no output. They are called for their side-effects. You have seen several of them already:\n", "\n", "```python\n", "print(...) \n", "a = print(...) # does not really have value assigning its output\n", "\n", "\n", "annual_income = [40214, 66141, 8313, 97132, 8030124, 39120]\n", "annual_income.sort() # no output\n", "q = annual_income.sort() # again, no value to doing this\n", "```\n", "\n", "These are 2 examples where the function is called for what it does, not for the output it produces on the left-hand side. Can you remember any others you have used which are like this?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: no inputs\n", "\n", "This might also seem strange, but such functions are useful. They often return the state of your device, the time, or something about the object they are attached to.\n", "\n", "```python\n", "import os\n", "os.getcwd() # cwd = current working directory\n", "\n", "import sys\n", "sys.getwindowsversion()\n", "\n", "import datetime\n", "datetime.datetime.now()\n", "\n", "import time\n", "cpu_time_start = time.process_time()\n", "# do some heavy calculations\n", "delta_time_used = time.process_time() - cpu_time_start\n", "\n", "# also\n", "exit()\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: one input, one output\n", "\n", "\n", "Many functions in Python are of this type. Think for example about a list:\n", "\n", "```python\n", "numbers = [1, 2, 3, 3, 3, 3, 2, 1]\n", "numbers.count(3)\n", "```\n", "\n", "The ``.count(...)`` function takes **only** 1 input and returns only 1 output. Try this: ``numbers.count()`` or ``numbers.count(3, 2)`` for example.\n", "\n", "Now it is your turn.\n", "
\n", "\n", "1. In [module 1](http://yint.org/pybasic01#Last-exercises) we considered the problem where the population of a country could be approximated by the formula $$p(t) = \\dfrac{197273000}{1+e^{− 0.03134(t − 1913)}}$$ where $t$ was the time, measured in years.\n", "\n", " Write a function which accepts that value $t$ as an input, and returns the population size $p(t)$ as output.\n", " \n", " \n", "2. That was (or should have been) a short one-line function. Now expand your function to check that $t$ is a numeric value. If $t$ is numeric, then return $p(t)$, else return ``None``. You should use: ``isinstance(t, (float, int))``. \n", "\n", " Don't just copy/paste that: what does ``ininstance`` do? Use ``help(isinstance)`` to understand.\n", "\n", "\n", "3. Finally expand the function once more, to ensure that $t$ is larger than 1913. If not, return ``NaN`` (not a number), which you can obtain either from the NumPy library ``np.nan``, or use the built-in NaN: ``float('nan')``." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: one input, two outputs\n", "\n", "Remember you can make your function provide:\n", "* no output: ``return`` or more explicitly ``return None``\n", "* 1 output: ``return answer``\n", "* more than 1 output: ``return (value_one, object_two, object_three)``\n", "\n", "In that last version we use a ``tuple`` to create a single grouped output. Recall from [module 1](http://yint.org/pybasic01#Creating-variables) where we saw you can create multiple variables in one line of code: ``a, b, c = (1, 2, 3)``.\n", "\n", "In the same way you can make your function return multiple outputs and assign them. This code shows how the function is created and then used:\n", "\n", "```python \n", "def calculate_summary_statistics(vector):\n", " \"\"\"Calculates the mean, median, standard deviation and MAD.\"\"\"\n", " # code goes here\n", " \n", " return (mean, median, stddev, mad)\n", " \n", "x = ... # a NumPy vector\n", "x_avg, x_median, x_std, x_mad = calculate_summary_statistics(x)\n", "```\n", "\n", "The tuple output from the function on the right-hand side is split across the 4 variables on the left-hand side.\n", "\n", "Now it is your turn.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Complete the above code** so that it will accept a NumPy *vector* and then return these 4 outputs:\n", "\n", "* the mean\n", "* the standard deviation\n", "* the median [a robust mean]\n", "* the median absolute deviation (MAD) [a robust standard deviation]\n", " \n", "You might need this definition for MAD: it is the median of the absolute deviations from the median: $$ \\text{median} \\big( \\| x - \\text{median}(x)\\|\\big)$$\n", " \n", "First calculate the median, then the deviations from the median, then the absolute value, then the median of that.\n", "\n", "Test it on this vector to understand the usefulness of the median and MAD:\n", "```python\n", "x = [6, 9, 5, 6, 3, 8, 5, 72, 9, 6, 6, 7, 8, 0]\n", "```\n", "The standard deviation is more than twice as big as it should be, due to that single outlier.\n", " \n", "### Extend yourself (later?)\n", "\n", "Rather continue below, but if you have time, return back to the weather data from the [previous module](http://yint.org/pybasic05#%E2%9E%9C-Challenge-yourself:-summarizing-values-from-a-matrix). Load the data from the Dutch meteorological service (KNMI), and use that as input for the above function you wrote. \n", "\n", "* How *similar* are the mean and the median? \n", "* How *similar* are the standard deviation and the MAD? Does this make sense?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: two or more inputs, one output\n", "\n", "There are also several functions of this sort. You have just used one of these above:\n", "\n", "```python\n", "t = 45\n", "isinstance(t, (float, int))\n", "\n", "t = '45'\n", "isinstance(t, (float, int))\n", "\n", "isinstance(t, (float, int, str))\n", "\n", "isinstance(t) # will raise an error\n", "\n", "```\n", "\n", "Now it is your turn to create a function with more than one input.\n", "
\n", "\n", "In [module 3](yint.org/pybasic03#Challenge-3) you saw code, similar to what is below, that reads a text file:\n", "\n", "```python\n", "import os\n", "base_folder_mac_or_linux = '/users/home/yourname'\n", "base_folder_windows = r'C:\\Users\\home\\yourname' \n", "filename = 'myfile.txt'\n", "full_filename = os.path.join(base_folder_windows, filename)\n", "N_lines = 15\n", "with open(full_filename, \"r\") as f:\n", " lines = []\n", " for i in range(N_lines):\n", " line = f.readline()\n", " lines.append(line)\n", " \n", " # The file is then closed at this point.\n", "\n", "# Show the file preview\n", "print(lines)\n", "```\n", "\n", "Do several things with this:\n", "* First copy the code as it is, and ensure it actually works on a file. You will need to modify the first few lines to make this work.\n", "* Next, modify the code to make it a function: put the actual activity of opening and getting the first few lines of text into a function.\n", " > ```python\n", " > def preview_textfile(filename, N):\n", " > # complete this part to return a preview in a list with `N` entries\n", " > ```\n", "* Complete your function to ensure that it returns the list called ``lines``.\n", "* Modify the rest of the code, outside the function, so you can call your new function in a single line:\n", " > ```python\n", " > print(preview_textfile(full_filename, 15))\n", " > ```\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: optional (default) inputs\n", "\n", "Python allows you to specify the value of optional function inputs. In other words, you can specify default values if the user does not. The user can of course always override the values if they specify them.\n", "\n", "With a small change, you can modify your function above:\n", " > ```python\n", " > def preview_textfile(filename, N=10):\n", " > \"\"\"Returns the first `N`(int) lines of `filename`.\n", " > By default, the first 10 lines are returned.\"\"\"\n", " > ```\n", " \n", "and ensure that the follow 3 instances of calling the function work as expected:\n", "```python\n", "print(preview_textfile(full_filename))\n", "print(preview_textfile(full_filename, 15))\n", "print(preview_textfile(full_filename, N=5))\n", "```\n", "\n", "What do you need to change in your function to guard against user error?\n", "```python\n", "print(preview_textfile(full_filename, N=5.0))\n", "print(preview_textfile(full_filename, N=5.5))\n", "print(preview_textfile(full_filename, '15'))\n", "print(preview_textfile(full_filename, '5.5'))\n", "```\n", "As you can/should see, with a simple tweak, you can make your function far more tolerant of user input, and therefore more widely applicable." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: multiple inputs and outputs\n", "\n", "It is time to bring all the above together.\n", "

Start a new file in your version control repository." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ➜ Challenge yourself 1: simulated cooling\n", "\n", "***Hint***: read the entire problem first. \n", "\n", "In [module 3](yint.org/pybasic03#Challenge-3) you had a challenge problem related to the cooling of an object in a fridge. The temperature of the object, $T$, changing over time $t$, can be modeled as:\n", "$$ \\dfrac{dT}{dt} = -k (T-F)$$\n", "\n", "The fridge has a constant temperature, $F=5$°C; and for this system, the value of $k = 0.08$. The equation can be rewritten as: $$ \\dfrac{\\Delta T}{\\delta t} = -k (T - F)$$ for a short change in time, $\\delta t = 0.5$ minutes.\n", "$$T_{i+1} = T_i -k (\\delta t)(T_i - F)$$ \n", "\n", "which shows how the temperature at time point $i+1$ (one step in the future) is related to the temperature now, at time $i$. The object starts off with a temperature of 25 °C.\n", "\n", "The challenge is to create a short function that the user can call:\n", "* To always get 2 NumPy vectors containing the ``time`` of simulation and the ``temp``erature of the object.\n", "* The user should be able to call the function in several ways:\n", "\n", " 1. Only specify the total simulation time: \n", " ```python \n", " time, temp = simulate_cooling(time_final=30)\n", " ```\n", " 2. Specify the total time, and initial temperature of the object:\n", " ```python \n", " time, temp = simulate_cooling(time_final=30, initial_temp=25)\n", " ```\n", " 3. Specify the total time, and initial temperature of the object, and simulation resolution:\n", " ```python \n", " time, temp = simulate_cooling(time_final=30, initial_temp=25, delta_t=2.5)\n", " ```\n", " 4. Future-proof your function! We will learn in a later module how you can plot data. For now though, you can add a plotting option to your function, which will optionally plot the temperature against time. But because you don't know yet how to do so, at least add it to the function ***signature***, for the future. Save your code in version control, and come back and add it later.\n", " ```python \n", " time, temp = simulate_cooling(time_final=30, initial_temp=25, delta_t=2.5, show_plot=False)\n", " ```\n", "\n", "***Hint***: to save yourself some time, you can get code to solve this problem already: https://github.com/kgdunn/python-basic-notebooks. Clone that repository, and look in the `code` subdirectory for the file called ``fridge.py``. Modify that file to answer the above 4 questions.\n", "\n", "### Extend yourself (later?)\n", "1. Does the function give you the output you expect if you put an object in the fridge which is frozen already; i.e. the initial temperature is $-18$°C?\n", "2. Modify the function to return a 3rd output, the true value of simulating the fridge: $$T_i = F +(T_{i=0} − F) e^{−Kt}$$\n", " Notice that the equation gives $T_i = T_{i=0}$ when $t=0$ (at the start of the simulation), and that as $t$ tends to get big, that the object temperature tends to the fridge temperature, $F$. \n", "3. Add a 4th output, the simulation error. As you can see, this is quickly getting \"ugly\": all the outputs are vectors of the same length. Why not combine them into a matrix, with 4 columns. In the next module we will learn how." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: positional arguments\n", "\n", "Once 2 or more inputs are possible, it raises the question if the order of the inputs is important.\n", "\n", "For example, if you have a function ***signature*** of:\n", "```python\n", "def random_normal_values(size, mean=0.0, stddev=1.0):\n", " \"\"\" Returns a vector of length `size` with randomly\n", " distributed values. The values will come from a normal \n", " distribution with `mean` and standard deviation of `stddev`.\"\"\"\n", "```\n", "\n", "you can call it in several ways to get 3 values from the normal distribution $\\mathcal{N}\\left(\\mu=6, \\sigma=9 \\right)$:\n", "```python\n", "random_normal_values(3, 6, 9)\n", "random_normal_values(3, 6, stddev=9)\n", "random_normal_values(3, mean=6, stddev=9)\n", "random_normal_values(size=3, mean=6, stddev=9)\n", "\n", "# Yes, you can do this also! \n", "random_normal_values(stddev=9, mean=6, size=3)\n", "random_normal_values(mean=6, stddev=9, size=3)\n", "random_normal_values(size=3, stddev=9, mean=6)\n", "random_normal_values(stddev=9, size=3, mean=6)\n", "random_normal_values(mean=6, size=3, stddev=9)\n", "```\n", "You can also use the default arguments, and specify only what you need to be different from the defaults:\n", "```python\n", "random_normal_values(3, mean=6)\n", "random_normal_values(3, stddev=9)\n", "\n", "random_normal_values(3)\n", "random_normal_values(size=3)\n", "\n", "# But these will NOT work. Why?\n", "random_normal_values(mean=6, stddev=9)\n", "random_normal_values(mean=6)\n", "random_normal_values(stddev=3)\n", "```\n", "\n", "This ability to call functions with default inputs, and inputs in different order provides tremendous flexibility. You don't need to remember the order of the arguments for a function. Specify them by name, and place them in a (logical!) order. In fact, always specifying the argument names when using a function is more explicit, and makes for clearer code.\n", "\n", "Reading ``random_normal_values(3, 6, 9)`` a few months after you wrote the code will almost certainly force you to go back to the original function to see what position 1, 2 and 3 of the inputs were. But seeing ``random_normal_values(size=3, mean=6, stddev=9)`` is immediately clear; saving you (and others that use your code) substantial time. A little bit of extra typing now pays off in the future.\n", "\n", "There are of course exceptions to the choice of argument order. Take the NumPy ``np.arange(start, stop, step)`` function as an example:\n", "```python\n", "np.arange(start=3, stop=10, step=2) # [3, 5, 7, 9]\n", "np.arange(step=2, stop=10, start=3) # [3, 5, 7, 9]\n", "```\n", "The second version has the same output, but it is less readable and less intuitive. Therefore when using named inputs arguments, pick a logical order that aides (human) readability." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions: scope (variable values in a function)\n", "\n", "The location in computer memory of a variable in a function is different to the location of variables outside that function.\n", "\n", ">```python\n", ">def funcA(x):\n", "> x = x + 2\n", "> print('In the function the value of \"x\" is {}'.format(x))\n", "> return x\n", "> \n", ">x = 12\n", ">out = funcA(x)\n", ">print('Outside and after the function \"x\" is still {}, while \"out\" = {}'.format(x, out))\n", ">```\n", "\n", "will print\n", "\n", "``In the function the value of \"x\" is 14``\n", "\n", "``Outside and after the function \"x\" is still 12, while \"out\" = 14``\n", "\n", "The memory location of the variable ``x`` in the main part of the program is different to the memory location of ``x`` inside the function, even though they have the same name. The identical name is coincidental. You can replace the ``x`` inside the function with ``y`` and it will still run the same: ``y = x + 2``.\n", "\n", "We say the ***scope*** of the variable is different inside and outside the function. That is because variable ``x`` inside the function on the left hand side of ``x = x + 2`` is created for the duration of the function. We say that it has ***local scope***. After the function is finished, that variable is freed up and deleted.\n", "\n", "Scoping can be a tough concept to understand at first, and is also more complex than described here. We mention it here for now, so it is not surprising. You can [read more about scoping](https://nbviewer.jupyter.org/github/rasbt/python_reference/blob/master/tutorials/scope_resolution_legb_rule.ipynb) in that notebook." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ➜ Challenge yourself 2: zero of a function\n", "\n", "No introduction to functions is complete without applying it to Newton's method. We already saw Newton's law of cooling in the fridge simulation above, but he also spent his time thinking about mathematical ***functions***, and when they cross the zero line on the $x$-axis.\n", "\n", "The idea is if you have a math function, for example: $$f(x) = x^3 - 3x + 5$$ that the value of $x$ that makes $f(x)=0$ can have special meaning. It is also called the ***zero*** of a mathematical function. You can cheat, and [plot the function](http://fooplot.com/#W3sidHlwZSI6MCwiZXEiOiJ4XjMtM3grNSIsImNvbG9yIjoiIzAwMDAwMCJ9LHsidHlwZSI6MTAwMCwid2luZG93IjpbIi02LjUiLCI2LjUiLCItMSIsIjEwIl19XQ--), but this just gives you an idea of where the zero is, not the exact value.\n", "\n", "Newton's method shows that if you start with a reasonable estimate of where the zero is $x_{i=0}$ (the value of $x$ for the iteration when $i=0$), that you can find that zero, by successive repetition:\n", "$$ x_{i+1} = x_i - \\dfrac{f(x_i)}{f'(x_i)}$$\n", "\n", "Start with $x_i$ on the right hand side, and update your estimate to $x_{i+1}$. Then repeat; every time you repeat, the value of $i$ increases, starting from zero: $i = 0, 1, 2, \\ldots$. \n", "\n", "You only need to know the original function value, and the derivative of the function, shown as $f'(x)$. You also need to know when to stop.\n", "\n", "Complete this template code, which stops when 2 conditions are met. What are they?\n", "```python\n", "def f_poly(x):\n", " # Complete this: returns f(x) = x^3 - 3x + 5\n", " pass\n", "\n", "def f_derivative_poly(x):\n", " # Complete this: returns f'(x) = 3x^2 - 3\n", " pass\n", " \n", "# Initial guess\n", "x = ...\n", "iterations = 0\n", "max_error = 1.0E-8\n", "max_iterations = 20\n", "relative_error = 10000\n", "while (relative_error > max_error) and (iterations <= max_iterations):\n", " iterations += 1\n", " x_prior = x\n", " \n", " x = x - f_poly(x) / f_derivative_poly(x)\n", " relative_error = abs((x - x_prior) / x)\n", " \n", " # Add a print statement here to track the code's progress in the loop\n", " \n", "print('The zero of f(x) was found to be {}'.format(x))\n", "```\n", "\n", "* Use NumPy to complete the two sub-functions. Understand that due to local scoping, the value of `x` in those functions is different to the `x` outside.\n", "* How many iterations did you require to converge to a zero?\n", "* Change parameters such as the maximum number of iterations, or the relative error tolerance to get a feel for the performance.\n", "* Try different initial guesses. Can you make the method crash? (For example, try an initial guess of 1.0).\n", "\n", "Here's an interesting feature. Functions in Python are also objects. (Remember, we said ***everything*** in Python is an object). As such, a function can also be an input argument, since all arguments must be objects.\n", "\n", "Do the following:\n", "1. Check your existing code into version control. So you can always come back to this point.\n", "2. Modify the code so you can call a function ``newton_zero(...)`` and give it these inputs:\n", " \n", " * an initial guess [required]\n", " * a function that calculates $f(x)$ [required]\n", " * a function that calculates $f'(x)$ [required]\n", " * the maximum iterations [optional; default is 25]\n", " * the maximum allowable error [optional; default is 10E-10]\n", " \n", " Call your function several times. Use this function signature:\n", " ```python\n", " zero = newton_zero(guess = -3, f_x=f_poly, df_dx=f_derivative_poly)\n", " ```\n", " \n", "3. Check your completed code into version control and submit it.\n", "4. Try using this method now with a different function $f(x)=2x^2-x^4$ and its derivative; see if you can lead Newton's method astray." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ➜ Challenge yourself 3: linear regression\n", "\n", "In the [prior module](https://yint.org/pybasic05) you wrote a short piece of code for linear regression. \n", "Linear regression (least squares) is a tool you might frequently use in your work. \n", "\n", "$$ y = b_0 + b_1x + e$$\n", "\n", "There are [ways to use it](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html), which are built-in with NumPy, but it only does the basics. We usually want more information:\n", "\n", "* we want a function that accepts the vector `x`\n", "* the second required input is a vector `y`\n", "* the function must check that `x` and `y` are the same length, to make sure you have the right data\n", "* it must return several outputs in this order, in a tuple:\n", "\n", " 1. the regression intercept, $b_0$\n", " 2. the regression slope, $b_1$\n", " 3. the vector of residuals $e = y - \\hat{y}$, where $\\hat{y} = b_0 + b_1 x$\n", " 4. the standard error = $\\sqrt{ \\dfrac{\\sum{ e_i^2 }} {n - 2}}$, where $n$ is the length of $x$ or $y$.\n", "\n", "\n", "Complete this template, turning it into a function, and completing all the requirements:\n", "```python\n", "import numpy as np\n", "x = np.array(...)\n", "y = np.array(...)\n", "\n", "# Check that lengths are the same. \n", "if not(...):\n", " # Return early if lengths do not match\n", " return (np.nan, np.nan, None, np.nan)\n", "\n", "X = np.vstack([np.ones(len(x)), x]).T\n", "\n", "# b is defined as (X^T X)^{-1} X^T y\n", "b_vector = ...\n", "\n", "# residuals = y - y_hat\n", "residuals = ...\n", "\n", "# Standard error:\n", "se = ...\n", "\n", "return (...)\n", "```\n", "\n", "Test your code:\n", "1. With these inputs:\n", " ```python\n", " x = [1, 2, 3, 4]\n", " y = [1, 0, 2, 5, 7]\n", " ```\n", " you should get an output of ``(nan, nan, None, nan)``\n", " \n", "2. With these inputs:\n", "```python\n", " x = [0.019847603, 0.039695205, 0.059542808, 0.07939041, 0.099238013, 0.119085616, 0.138933218]\n", " y = [0.2, 0.195089996, 0.284090012, 0.37808001, 0.46638, 0.561559975, 0.652559996]\n", "```\n", " you should get:\n", " * an intercept of `0.06641`, \n", " * a slope of `4.08993`, \n", " * a standard error of `0.03206`, \n", " * the smallest residual being `-0.0336679`,\n", " * and the largest one being `0.052417`.\n", " \n", " \n", "This last step hints at what is called [test-driven development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development). You actually first write tests to check your function. Then you start coding your function. You stop when all tests are successfully passed. In the above, the values are from another software package, which is known to be tested. In the Advanced classes we will return to TDD." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Wrap up this section by committing all your work. Have you used a good commit message? Push your work, to refer to later, but also as a backup." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ">***Feedback and comments about this worksheet?***\n", "> Please provide any anonymous [comments, feedback and tips](https://docs.google.com/forms/d/1Fpo0q7uGLcM6xcLRyp4qw1mZ0_igSUEnJV6ZGbpG4C4/edit)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# IGNORE this. Execute this cell to load the notebook's style sheet.\n", "from IPython.core.display import HTML\n", "css_file = './images/style.css'\n", "HTML(open(css_file, \"r\").read())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "222px" }, "toc_section_display": true, "toc_window_display": true }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }