intro-to-python/08_mfr_02_exercises.ipynb

1202 lines
34 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Important**: Click on \"*Kernel*\" > \"*Restart Kernel and Run All*\" *after* finishing the exercises in [JupyterLab <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_jp.png\">](https://jupyterlab.readthedocs.io/en/stable/) (e.g., in the cloud on [MyBinder <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_mb.png\">](https://mybinder.org/v2/gh/webartifex/intro-to-python/master?urlpath=lab/tree/08_mfr_02_exercises.ipynb)) to ensure that your solution runs top to bottom *without* any errors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Chapter 8: Map, Filter, & Reduce"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Coding Exercises"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The exercises below assume that you have read [Chapter 8 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mfr_00_content.ipynb) in the book.\n",
"\n",
"The `...`'s in the code cells indicate where you need to fill in code snippets. The number of `...`'s within a code cell give you a rough idea of how many lines of code are needed to solve the task. You should not need to create any additional code cells for your final solution. However, you may want to use temporary code cells to try out some ideas."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Packing & Unpacking with Functions (continued)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1.1**: Copy your solution to **Q2.10** from the [Chapter 7 Exercises <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_02_exercises.ipynb#Packing-&-Unpacking-with-Functions) into the code cell below!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import collections.abc as abc"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def product(*args, ...):\n",
" \"\"\"Multiply all arguments.\"\"\"\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" ...\n",
" ...\n",
"\n",
" return ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1.2**: Verify that all test cases below work (i.e., the `assert` statements must *not* raise an `AssertionError`)!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(42) == 42"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(2, 5, 10) == 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(2, 5, 10, start=2) == 200"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"one_hundred = [2, 5, 10]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(one_hundred) == 100"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert product(*one_hundred) == 100"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1.3**: Verify that `product()` raises a `TypeError` when called without any arguments!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This implementation of `product()` is convenient to use, in particular, because we can pass it any *collection* object with or without *unpacking* it.\n",
"\n",
"However, `product()` suffers from one last flaw: We cannot pass it a **stream** of data, as modeled, for example, with an *iterator* object that produces elements on a one-by-one basis.\n",
"\n",
"**Q1.4**: Click through the following example!\n",
"\n",
"The [*stream.py* <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_gh.png\">](https://github.com/webartifex/intro-to-python/blob/master/stream.py) module in the book's repository provides a `make_finite_stream()` function. It is a *factory* function creating objects of type `generator` that we use to model *streaming* data."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from stream import make_finite_stream"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"stream = make_finite_stream()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"stream"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"type(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Being a `generator` object, `stream` is also an `Iterator` in the abstract sense."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"isinstance(stream, abc.Iterator)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*Iterators* are good for only *one* thing: Giving us the \"next\" element in a series of possibly *infinitely* many objects. While the `stream` object is finite (i.e., execute the next code cell until you see a `StopIteration` exception), ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... it has *no* concept of a \"length:\" The built-in [len() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#len) function raises a `TypeError`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use the built-in [list() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-list) constructor to *materialize* the elements. However, in a real-world scenario, these may *not* fit into our machine's memory!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(stream)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To be more realistic, `make_finite_stream()` creates `generator` objects producing a varying number of elements."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(make_finite_stream())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(make_finite_stream())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"list(make_finite_stream())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's see what happens if we pass an *iterator*, as created by `make_finite_stream()`, instead of a materialized *collection*, like `one_hundred`, to `product()`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(make_finite_stream())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1.5**: What line causes the `TypeError`? What line is really the problem in `product()`? Hint: These may be different lines. Describe what happens on each line in the function's body until the exception is raised!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q1.6**: Adapt `product()` one last time to make it work with *iterators* as well!\n",
"\n",
"Hints: This task is as easy as replacing `Collection` with something else. Which of the three behaviors of *collections* do *iterators* also exhibit? You may want to look at the documentations on the built-in [max() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#max), [min() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#min), and [sum() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#sum) functions: What kind of argument do they take?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def product(*args, ...):\n",
" \"\"\"Multiply all arguments.\"\"\"\n",
" ...\n",
" ...\n",
" ...\n",
" ...\n",
"\n",
" ...\n",
" ...\n",
"\n",
" return ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The final version of `product()` behaves like built-ins in edge cases (i.e., `sum()` also raises a `TypeError` when called without arguments), ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... works with the arguments passed either separately as *positional* arguments, *packed* together into a single *collection* argument, or *unpacked*, ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(42)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(2, 5, 10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product([2, 5, 10])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(*[2, 5, 10])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... and can handle *streaming* data with *indefinite* \"length.\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"product(make_finite_stream())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In real-world projects, the data science practitioner must decide if it is worthwhile to make a function usable in various different forms as we do in this exercise. This may be over-engineered.\n",
"\n",
"Yet, two lessons are important to take away:\n",
"- It is a good idea to *mimic* the behavior of *built-ins*, and\n",
"- make functions capable of working with *streaming* data."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Removing Outliers in Streaming Data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's say we are given a `list` object with random integers like `sample` below, and we want to calculate some basic statistics on them."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample = [\n",
" 45, 46, 40, 49, 36, 53, 49, 42, 25, 40, 39, 36, 38, 40, 40, 52, 36, 52, 40, 41,\n",
" 35, 29, 48, 43, 42, 30, 29, 33, 55, 33, 38, 50, 39, 56, 52, 28, 37, 56, 45, 37,\n",
" 41, 41, 37, 30, 51, 32, 23, 40, 53, 40, 45, 39, 99, 42, 34, 42, 34, 39, 39, 53,\n",
" 43, 37, 46, 36, 45, 42, 32, 38, 57, 34, 36, 44, 47, 51, 46, 39, 28, 40, 35, 46,\n",
" 41, 51, 41, 23, 46, 40, 40, 51, 50, 32, 47, 36, 38, 29, 32, 53, 34, 43, 39, 41,\n",
" 40, 34, 44, 40, 41, 43, 47, 57, 50, 42, 38, 25, 45, 41, 58, 37, 45, 55, 44, 53,\n",
" 82, 31, 45, 33, 32, 39, 46, 48, 42, 47, 40, 45, 51, 35, 31, 46, 40, 44, 61, 57,\n",
" 40, 36, 35, 55, 40, 56, 36, 35, 86, 36, 51, 40, 54, 50, 49, 36, 41, 37, 48, 41,\n",
" 42, 44, 40, 43, 51, 47, 46, 50, 40, 23, 40, 39, 28, 38, 42, 46, 46, 42, 46, 31,\n",
" 32, 40, 48, 27, 40, 40, 30, 32, 25, 31, 30, 43, 44, 29, 45, 41, 63, 32, 33, 58,\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(sample)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.1**: `list` objects are **sequences**. What *four* behaviors do they always come with?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.2**: Write a function `mean()` that calculates the simple arithmetic mean of a given `sequence` with numbers!\n",
"\n",
"Hints: You can solve this task with [built-in functions <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html) only. A `for`-loop is *not* needed."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def mean(sequence):\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_mean = mean(sample)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_mean"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.3**: Write a function `std()` that calculates the [standard deviation <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Standard_deviation) of a `sequence` of numbers! Integrate your `mean()` version from before and the [sqrt() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/math.html#math.sqrt) function from the [math <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/math.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html) provided to you below. Make sure `std()` calls `mean()` only *once* internally! Repeated calls to `mean()` would be a waste of computational resources.\n",
"\n",
"Hints: Parts of the code are probably too long to fit within the suggested 79 characters per line. So, use *temporary variables* inside your function. Instead of a `for`-loop, you may want to use a *list comprehension* or, even better, a memoryless *generator expression*."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from math import sqrt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def std(sequence):\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_std = std(sample)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_std"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.4**: Complete `standardize()` below that takes a `sequence` of numbers and returns a `list` object with the **[z-scores <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_wiki.png\">](https://en.wikipedia.org/wiki/Standard_score)** of these numbers! A z-score is calculated by subtracting the mean and dividing by the standard deviation. Re-use `mean()` and `std()` from before. Again, ensure that `standardize()` calls `mean()` and `std()` only *once*! Further, round all z-scores with the built-in [round() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#round) function and pass on the keyword-only argument `digits` to it.\n",
"\n",
"Hint: You may want to use a *list comprehension* instead of a `for`-loop."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def standardize(sequence, *, digits=3):\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"z_scores = standardize(sample)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The [pprint() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/pprint.html#pprint.pprint) function from the [pprint <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/pprint.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html) allows us to \"pretty print\" long `list` objects compactly."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pprint import pprint"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pprint(z_scores, compact=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We know that `standardize()` works correctly if the resulting z-scores' mean and standard deviation approach `0` and `1` for a long enough `sequence`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mean(z_scores), std(z_scores)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Even though `standardize()` calls `mean()` and `std()` only once each, `mean()` is called *twice*! That is so because `std()` internally also re-uses `mean()`!\n",
"\n",
"**Q2.5.1**: Rewrite `std()` to take an optional keyword-only argument `seq_mean`, defaulting to `None`. If provided, `seq_mean` is used instead of the result of calling `mean()`. Otherwise, the latter is called.\n",
"\n",
"Hint: You must check if `seq_mean` is still the default value."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def std(sequence, *, seq_mean=None):\n",
" ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`std()` continues to work as before."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_std = std(sample)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_std"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.5.2**: Now, rewrite `standardize()` to pass on the return value of `mean()` to `std()`! In summary, `standardize()` calculates the z-scores for the numbers in the `sequence` with as few computational steps as possible."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def standardize(sequence, *, digits=3):\n",
" ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"z_scores = standardize(sample)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mean(z_scores), std(z_scores)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.6**: With both `sample` and `z_scores` being materialized `list` objects, we can loop over pairs consisting of a number from `sample` and its corresponding z-score. Write a `for`-loop that prints out all the \"outliers,\" as which we define numbers with an absolute z-score above `1.96`. There are *four* of them in the `sample`.\n",
"\n",
"Hint: Use the [abs() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#abs) and [zip() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#zip) built-ins."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We provide a `stream` module with a `data` object that models an *infinite* **stream** of data (cf., the [*stream.py* <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_gh.png\">](https://github.com/webartifex/intro-to-python/blob/master/stream.py) file in the repository)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from stream import data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`data` is of type `generator` and has *no* length."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"type(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Being a `generator`, it is an `Iterator` in the abstract sense ..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import collections.abc as abc"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"isinstance(data, abc.Iterator)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... and so the only thing we can do with it is to pass it to the built-in [next() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#next) function and go over the numbers it streams one by one."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.7**: What happens if you call `mean()` with `data` as the argument? What is the problem?\n",
"\n",
"Hints: If you try it out, you may have to press the \"Stop\" button in the toolbar at the top. Your computer should *not* crash, but you will *have to* restart this Jupyter notebook with \"Kernel\" > \"Restart\" and import `data` again."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mean(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.8**: Write a function `take_sample()` that takes an `iterator` as its argument, like `data`, and creates a *materialized* `list` object out of its first `n` elements, defaulting to `1_000`!\n",
"\n",
"Hints: [next() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#next) and the [range() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#func-range) built-in may be helpful. You may want to use a *list comprehension* instead of a `for`-loop and write a one-liner. Audacious students may want to look at [isclice() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/itertools.html#itertools.islice) in the [itertools <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/itertools.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/index.html)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def take_sample(iterator, *, n=1_000):\n",
" ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We take a `new_sample` from the stream of `data`, and its statistics are similar to the initial `sample`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"new_sample = take_sample(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(new_sample)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mean(new_sample)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"std(new_sample)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.9**: Convert `standardize()` into a *new* function `standardized()` that implements the *same* logic but works on a possibly *infinite* stream of data, provided as an `iterable`, instead of a *finite* `sequence`.\n",
"\n",
"To calculate a z-score, we need the stream's overall mean and standard deviation, and that is *impossible* to calculate if we do not know how long the stream is, and, in particular, if it is *infinite*. So, `standardized()` first takes a sample from the `iterable` internally, and uses the sample's mean and standard deviation to calculate the z-scores.\n",
"\n",
"Hint: `standardized()` *must* return a `generator` object. So, use a *generator expression* as the return value; unless you know about the `yield` statement already (cf., [reference <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement))."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def standardized(iterable, *, digits=3):\n",
" ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`standardized()` works almost like `standardize()` except that we use it with [next() <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_py.png\">](https://docs.python.org/3/library/functions.html#next) to obtain the z-scores one by one."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"z_scores = standardized(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"z_scores"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"type(z_scores)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(z_scores)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.10.1**: `standardized()` allows us to go over an *infinite* stream of z-scores. What we want to do instead is to loop over the stream's raw numbers and skip the outliers. In the remainder of this exercise, you look at the parts that make up the `skip_outliers()` function below to achieve precisely that.\n",
"\n",
"The first steps in `skip_outliers()` are the same as in `standardized()`: We take a `sample` from the stream of `data` and calculate its statistics."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample = ...\n",
"seq_mean = ...\n",
"seq_std = ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.10.2**: Just as in `standardized()`, write a *generator expression* that produces z-scores one by one! However, instead of just generating a z-score, the resulting `generator` object should produce `tuple` objects consisting of a \"raw\" number from `data` and its z-score.\n",
"\n",
"Hint: Look at the revisited \"*Averaging Even Numbers*\" example in [Chapter 7 <img height=\"12\" style=\"display: inline-block\" src=\"static/link_to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Example:-Averaging-all-even-Numbers-in-a-List-%28revisited%29) for some inspiration, which also contains a generator expression producing `tuple` objects."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"standardizer = (... for ... in data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`standardizer` should produce `tuple` objects."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(standardizer)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.10.3**: Write another generator expression that loops over `standardizer`. It contains an `if`-clause that keeps only numbers with an absolute z-score below the `threshold_z`. If you fancy, use *tuple unpacking*."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"threshold_z = 1.96"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"no_outliers = (... for ... in standardizer if ...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`no_outliers` should produce `int` objects."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(no_outliers)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.10.4**: Lastly, put everything together in the `skip_outliers()` function! Make sure you refer to `iterable` inside the function and not the global `data`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def skip_outliers(iterable, *, threshold_z=1.96):\n",
" sample = ...\n",
" seq_mean = ...\n",
" seq_std = ...\n",
" standardizer = ...\n",
" no_outliers = ...\n",
" return no_outliers"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we can create a `generator` object and loop over the `data` in the stream with outliers skipped. Instead of the default `1.96`, we use a `threshold_z` of only `0.05`: That filters out all numbers except `42`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"skipper = skip_outliers(data, threshold_z=0.05)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"skipper"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"type(skipper)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"next(skipper)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Q2.11**: You implemented the functions `mean()`, `std()`, `standardize()`, `standardized()`, and `skip_outliers()`. Which of them are **eager**, and which are **lazy**? How do these two concepts relate to **finite** and **infinite** data?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" < your answer >"
]
}
],
"metadata": {
"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.4"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}