diff --git a/Python/Module1_GettingStartedWithPython/GettingStartedWithPython.md b/Python/Module1_GettingStartedWithPython/GettingStartedWithPython.md index ed1f2f1a..08c6ce40 100644 --- a/Python/Module1_GettingStartedWithPython/GettingStartedWithPython.md +++ b/Python/Module1_GettingStartedWithPython/GettingStartedWithPython.md @@ -5,7 +5,7 @@ jupyter: extension: .md format_name: markdown format_version: '1.2' - jupytext_version: 1.3.0rc1 + jupytext_version: 1.9.1 kernelspec: display_name: Python 3 language: python @@ -15,7 +15,7 @@ jupyter: .. meta:: :description: Topic: Basic description of the Python programming language, Difficulty: Easy, Category: Background - :keywords: python, install, basics, scripts, interpreter, foundations + :keywords: python, install, basics, scripts, interpreter, foundations, versions @@ -144,6 +144,62 @@ As such, Python is a language that is conducive to rapidly prototyping and testi We will be relying heavily on Python and a library for doing optimized numerical computations, called NumPy, throughout this course. + +## Understanding Different Versions of Python + +New versions of Python come out periodically, bringing new features and fixes. +You can keep track of what's new in Python by [bookmarking and checking this page every few months](https://docs.python.org/3/whatsnew/index.html). +As we take on coding projects in Python, whether it be for work, school, or a personal hobby, it is important that we remain aware of +the ways in which the language is changing, and that we take care to write code that will remain functional in the future. + +All Python version numbers use the `A.B.C` format, in accordance with [semantic versioning](https://semver.org/). +The three numbers denote major releases, minor releases, and patches. +For example, as of writing this, the current release of Python is version `3.9.1`. + +The first number denotes major releases to the language. +When a major release comes out, it means that older code will not necessarily work with the new release, and vice versa. +For example, the following code worked in Python 2 but no longer works because the `xrange` function was removed from the language +upon the release of Python 3. + +```python +# `xrange` was a frequently-used function in Python 2, but +# was removed from the language in Python 3 in favor of `range`. +# Thus the following code does not work in any version of Python 3. +count = 0 +for i in xrange(10): + count = count + 1 +``` + +The most current major release is Python 3; Python 2 is no longer supported by any bug or security fixes and should not be used. +All releases in the near future will be improvements to Python 3, and thus they will come in the form of minor releases and patches. + +The second number denotes a minor release. +A minor release will be compatible with code from the preceding release, but it might add new features to the language that are not backwards-compatible. +For example, Python 3.6 introduced [formatted string literals](https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals), which are +commonly referred to as "f-strings". +Thus as of Python 3.6 you could write code like + +```python +# Python 3.6 introduced the "f-string" feature, which is not +# backwards compatible with Python 3.5 +>>> f"one plus two is {1 + 2}" +'one plus two is 3' +``` + +but this code would not run in Python 3.5.X. + +The third and final number denotes a patch, which generally means bug fixes and performance improvements. +All code within the same minor release will run on all other patches within that minor release +For example, all Python 3.7.8 code is compatible with a Python 3.7.1 interpreter, and vice versa. +Patches are released fairly often, and their changes only occur 'under the hood'. +[Here is a list of changes](https://docs.python.org/3/whatsnew/changelog.html#python-3-9-1-final) were introduced by the patch level increment from Python 3.9.0 to Python 3.9.1; +few of these would affect our day-to-day experience with using Python (which isn't to say that they aren't important!). + +In simpler terms, major releases are neither backward nor forward compatible. +Minor releases are forward compatible but not necessarily fully backward compatible, and patches are both forward and backward compatible. + + + ## Summary - Python is a programming language - it provides us with a simple set of grammatical rules that allow us to write human-readable text, which can be translated unambiguously to instruct a computer to perform tasks. diff --git a/Python/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.md b/Python/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.md index 2390a6b2..d780d32f 100644 --- a/Python/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.md +++ b/Python/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.md @@ -5,7 +5,7 @@ jupyter: extension: .md format_name: markdown format_version: '1.2' - jupytext_version: 1.3.0rc1 + jupytext_version: 1.9.1 kernelspec: display_name: Python 3 language: python @@ -39,41 +39,51 @@ First and foremost, a good IDE will provide a text editor that will: An IDE also often provides debugging tools so that you can test your code; it will also typically interface with version-control software, like Git, so that you can keep track of versions of your code as you modify it. We will not discuss these useful, but more advanced features here. -### Recommended IDEs +## Recommended IDEs There are many excellent IDEs that can be configured to work well with Python. Two IDEs that we endorse are: -[PyCharm](https://www.jetbrains.com/pycharm/download): A powerful IDE dedicated to Python. +### PyCharm + +[PyCharm](https://www.jetbrains.com/pycharm/download) is a powerful and highly-polished IDE dedicated to developing Python code. **Pros** -- works well out-of-the-box -- long-supported by professionals and thus is very reliable -- highly configurable -- fully-featured, with an excellent debugger, context-dependent "intellisense", type-inference, and more -- the free "community version" is extremely robust and feature-rich. +- Works well out-of-the-box. +- Long-supported by professionals and thus is very reliable. +- Highly configurable. +- Fully-featured, with an excellent debugger, context-dependent "intellisense", type-inference, and more. +- The free "community version" is extremely robust and feature-rich. +- Generally provides an extremely high-quality and responsive experience for doing Python development. **Cons** - - can be resource-heavy, especially for a laptop - - may be overwhelming to new users (but has good documentation and tutorials) - - Jupyter notebook support requires the premium version of PyCharm, making it inaccessible to newcomers + - Can be resource-heavy, especially for a laptop. + - May be overwhelming to new users (but has good documentation and tutorials). + - Jupyter notebook support requires the premium version of PyCharm, making it inaccessible to newcomers. -[Visual Studio Code](https://code.visualstudio.com/) with the [Python extension](https://code.visualstudio.com/docs/languages/python): A lightweight, highly customizable IDE. +### Visual Studio Code + +[Visual Studio Code](https://code.visualstudio.com/) (with the [Python extension](https://code.visualstudio.com/docs/languages/python)) is a lightweight, highly customizable IDE that works with many different languages. + +Note: if you decide to use VSCode to do Python development, it is highly recommended that you install Microsoft's [PyLance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) +extension. +This adds many useful features to the IDE that will make writing Python code a more delightful experience. **Pros** -- lightweight and elegant -- completely free -- works with many different languages, so you only need to familiarize yourself with one IDE if you are a polyglot programmer -- a huge number of extensions can be downloaded to add functionality to the editor; these are created by a large community of open-source developers. -- [has native support for Jupyter notebooks](https://code.visualstudio.com/docs/python/jupyter-support), meaning that you get VSCode's intellisense, debugger, and ability to inspect variables, all in a notebook. +- Lightweight and elegant. +- Completely free. +- Works with many different languages, so you only need to familiarize yourself with one IDE if you are a polyglot programmer. +- Offers a huge number of extensions that can be downloaded to add functionality to the editor; these are created by a large community of open-source developers. +- [Has native support for Jupyter notebooks](https://code.visualstudio.com/docs/python/jupyter-support), meaning that you get VSCode's intellisense, debugger, and ability to inspect variables, all in a notebook. +- Provides incredibly robust [remote coding](https://code.visualstudio.com/docs/remote/remote-overview) and [collaborative coding](https://visualstudio.microsoft.com/services/live-share/) capabilities. **Cons** -- configuring VSCode for python development can have a moderate learning curve for newcomers -- many features, like context-aware intellisense and type-inference, are simply more polished and powerful in PyCharm +- Configuring VSCode for Python development can have a moderate learning curve for newcomers. +- Some features, like context-aware intellisense and type-inference, are simply more polished and powerful in PyCharm. + -
- © Copyright 2019, Ryan Soklaski + © Copyright 2021, Ryan Soklaski.
- © Copyright 2019, Ryan Soklaski + © Copyright 2021, Ryan Soklaski.
[4]:
+
+<matplotlib.legend.Legend at 0x2a496ed7f10>
+
+Notice that this notebook interface is great for making adjustments to this plot. You can easily change the color or line-style of the plot and redraw it without having to recompute the functions. You simply re-execute the cell containing the plot code. This is especially nice when the numerical computations required to generate the curves are costly.
@@ -1384,29 +667,29 @@- © Copyright 2019, Ryan Soklaski + © Copyright 2021, Ryan Soklaski.
Utilizing an array in a mathematical expression involving the arithmetic operators (+, -, *, /, //, **) returns an entirely distinct array, that does not share memory with the original array.
# mathematical expressions like `subarray + 2`
# produce distinct arrays, not views
>>> np.shares_memory(subarray + 2, subarray)
-False
+False
Thus updating a variable subarray via subarray = subarray + 2 does not overwrite the original data referenced by subarray. Rather, subarray + 2 assigns that new array to the variable subarray. NumPy does provide mechanisms for performing mathematical operations to directly update the underlying data of an array without having to create a distinct array. We will discuss these mechanisms in the next subsection.
x and the created variable reference the same underlying array data? Check your work by using np.shares_memory.
a1 = xa2 = x[0, 0]a3 = x[:, 0]a4 = x[:, 0] + np.array([-1, -2, -3])a5 = np.copy(x[:, 0])a6 = x[np.newaxis]a7 = x.reshape(2, 3, 2)a8 = 2 + xa1 = x
a2 = x[0, 0]
a3 = x[:, 0]
a4 = x[:, 0] + np.array([-1, -2, -3])
a5 = np.copy(x[:, 0])
a6 = x[np.newaxis]
a7 = x.reshape(2, 3, 2)
a8 = 2 + x
Because basic indexing produces a view of an array’s underlying data, we must take time to understand the ways in which we can augment that underlying data, versus performing operations that produce an array with distinct data. Here we will see that:
out argumentin-place assignments
augmented assignments
NumPy functions with the out argument
can all be used to augment array data in-place.
Performing an assignment on a view of a, i.e. a[:], instructs NumPy to perform the assignment to replace a’s data in-place.
This view-assignment mechanism can be used update a subsection of an array in-place.
@@ -557,7 +565,7 @@out<
>>> a = np.array([0., 0.2, 0.4, 0.6, 0.8, 1.])
>>> b = a[:]
>>> np.shares_memory(a, b)
-True
+True
# specifying 'out=a' instructs NumPy
# to overwrite the data referenced by `a`
@@ -666,7 +674,6 @@ Benefits and Risks of Augmenting Data In-Place# 5.
>>> np.log(x[1:3], out=x[1:3])
->>> y += 3
# 6.
@@ -674,7 +681,7 @@ Benefits and Risks of Augmenting Data In-Place# 7.
->>> y = y + 2
+>>> x = np.square(x)
# 8.
@@ -686,6 +693,10 @@ Benefits and Risks of Augmenting Data In-Place>>> f(y)
+# 10.
+>>> np.square(y, out=y)
+
+
Takeaway:
@@ -696,8 +707,8 @@ Benefits and Risks of Augmenting Data In-Place
Links to Official Documentation¶
@@ -709,14 +720,14 @@ Reading Comprehension Solutions
-arr[0] ✔
-arr[:-1, 0] ✔
-arr[(2, 3)] ✔
-arr[[2, 0]] ✘ (index is a list, not a tuple)
-arr[np.array([2, 0])] ✘ (index is a numpy.ndarray, not a tuple)
-arr[:, (2, 3)] ✘ (index contains a tuple; only int, slice, np.newaxis, Ellipsis allowed)
-arr[slice(None), ...] ✔
-arr[(np.newaxis, 0, slice(1, 2), np.newaxis)] ✔
+arr[0] ✔
+arr[:-1, 0] ✔
+arr[(2, 3)] ✔
+arr[[2, 0]] ✘ (index is a list, not a tuple)
+arr[np.array([2, 0])] ✘ (index is a numpy.ndarray, not a tuple)
+arr[:, (2, 3)] ✘ (index contains a tuple; only int, slice, np.newaxis, Ellipsis allowed)
+arr[slice(None), ...] ✔
+arr[(np.newaxis, 0, slice(1, 2), np.newaxis)] ✔
Views: Solution
Given,
@@ -727,14 +738,14 @@ Reading Comprehension Solutionsx? That is, in which cases do x and the created variable reference the same underlying array data? Check your work by using np.shares_memory.
-a1 = x ✔
-a2 = x[0, 0] ✘; when basic indexing returns a single number, that number does not share memory with the parent array.
-a3 = x[:, 0] ✔
-a4 = x[:, 0] + np.array([-1, -2, -3]) ✘; arithmetic operations on NumPy arrays create distinct arrays by default.
-a5 = np.copy(x[:, 0]) ✘; numpy.copy informs NumPy to create a distinct copy of an array.
-a6 = x[np.newaxis] ✔
-a7 = x.reshape(2, 3, 2) ✔
-a8 = 2 + x ✘; arithmetic operations on NumPy arrays create distinct arrays by default.
+a1 = x ✔
+a2 = x[0, 0] ✘; when basic indexing returns a single number, that number does not share memory with the parent array.
+a3 = x[:, 0] ✔
+a4 = x[:, 0] + np.array([-1, -2, -3]) ✘; arithmetic operations on NumPy arrays create distinct arrays by default.
+a5 = np.copy(x[:, 0]) ✘; numpy.copy informs NumPy to create a distinct copy of an array.
+a6 = x[np.newaxis] ✔
+a7 = x.reshape(2, 3, 2) ✔
+a8 = 2 + x ✘; arithmetic operations on NumPy arrays create distinct arrays by default.
Augmenting Array Data In-Place: Solution
Given,
@@ -796,29 +807,29 @@ Reading Comprehension Solutions
-
- Next
-
-
- Previous
-
+ Next
+ Previous
-
- © Copyright 2019, Ryan Soklaski
+ © Copyright 2021, Ryan Soklaski.
- Built with Sphinx using a theme provided by Read the Docs.
+
+
+
+ Built with Sphinx using a
+
+ theme
+
+ provided by Read the Docs.
-
@@ -827,7 +838,6 @@ Reading Comprehension Solutions
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
diff --git a/docs_backup/Module3_IntroducingNumpy/BasicIndexing.ipynb b/docs_backup/Module3_IntroducingNumpy/BasicIndexing.ipynb
new file mode 100644
index 00000000..8160ac87
--- /dev/null
+++ b/docs_backup/Module3_IntroducingNumpy/BasicIndexing.ipynb
@@ -0,0 +1,805 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "raw_mimetype": "text/restructuredtext"
+ },
+ "source": [
+ ".. meta::\n",
+ " :description: Topic: Numpy array basic indexing, Difficulty: Medium, Category: Section\n",
+ " :keywords: basic index, slice, no copy index, multidimensional array, nd array, view, reverse, axis"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Introducing Basic and Advanced Indexing\n",
+ "\n",
+ "Thus far we have seen that we can access the contents of a NumPy array by specifying an integer or slice-object as an index for each one of its dimensions. Indexing into and slicing along the dimensions of an array are known as basic indexing. NumPy also provides a sophisticated system of \"advanced indexing\", which permits us powerful means for accessing elements of an array that is flexible beyond specifying integers and slices along axes. For example, we can use advanced indexing to access all of the negative-valued elements from `x`.\n",
+ "\n",
+ "```python\n",
+ "# demonstrating basic indexing and advanced indexing\n",
+ ">>> import numpy as np\n",
+ ">>> x = np.array([[ -5, 2, 0, -7],\n",
+ "... [ -1, 9, 3, 8],\n",
+ "... [ -3, -3, 4, 6]])\n",
+ "\n",
+ "# Access the column-1 of row-0 and row-2.\n",
+ "# This is an example of basic indexing. \n",
+ "# A \"view\" of the underlying data in `x`\n",
+ "# is produced; no data is copied.\n",
+ ">>> x[::2, 1]\n",
+ "array([ 2, -3])\n",
+ "\n",
+ "# An example of advanced indexing.\n",
+ "# Access all negative elements in `x`.\n",
+ "# This produces a copy of the accessed data.\n",
+ ">>> x[x < 0]\n",
+ "array([-5, -7, -1, -3, -3])\n",
+ "```\n",
+ "\n",
+ "We will see that, where basic indexing provides us with a *view* of the data within the array, without making a copy of it, advanced indexing requires that a copy of the accessed data be made. Here, we will define basic indexing and understand the nuances of working with views of arrays. The next section, then, is dedicated to understanding advanced indexing. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Basic Indexing\n",
+ "We begin this subsection by defining precisely what basic indexing is. Next, we will touch on each component of this definition, and lastly we will delve into the significance of basic indexing in the way it permits us to reference the underlying data of an array without copying it.\n",
+ "\n",
+ " \n",
+ "\n",
+ "**Definition: Basic Indexing**: \n",
+ "\n",
+ "Given an $N$-dimensional array, `x`, `x[index]` invokes **basic indexing** whenever `index` is a *tuple* containing any combination of the following types of objects:\n",
+ "\n",
+ "- integers\n",
+ "- [slice](http://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/SequenceTypes.html#Slicing) objects\n",
+ "- [Ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis) objects\n",
+ "- [numpy.newaxis](http://www.pythonlikeyoumeanit.com/Module3_IntroducingNumpy/Broadcasting.html#Inserting-Size-1-Dimensions-into-An-Array) objects\n",
+ "\n",
+ "Accessing the contents of an array via basic indexing *does not create a copy of those contents*. Rather, a \"view\" of the same underlying data is produced.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Indexing with Integers and Slice Objects\n",
+ "Our discussion of [accessing data along multiple dimensions of a NumPy array](http://www.pythonlikeyoumeanit.com/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html) already provided a comprehensive rundown on the use of integers and slices to access the contents of an array. According to the preceding definition, *these were all examples of basic indexing*.\n",
+ "\n",
+ "To review the material discussed in that section, recall that one can access an individual element or a \"subsection\" of an $N$-dimensional array by specifying $N$ integers or slice-objects, or a combination of the two. We also saw that, when supplied fewer-than $N$ indices, NumPy will automatically \"fill-in\" the remaining indices with trailing slices. Keep in mind that the indices start at 0, such that the 4th column in `x` corresponds to column-3.\n",
+ "\n",
+ "```python \n",
+ "# Accessing the element located\n",
+ "# at row-1, last-column of `x`\n",
+ ">>> x[1, -1]\n",
+ "8\n",
+ "\n",
+ "# Access the subarray of `x`\n",
+ "# contained within the first two rows\n",
+ "# and the first three columns\n",
+ ">>> x[:2, :3]\n",
+ "array([[-5, 2, 0],\n",
+ " [-1, 9, 3]])\n",
+ "\n",
+ "# NumPy fills in \"trailing\" slices\n",
+ "# if we don't supply as many indices\n",
+ "# as there are dimensions in that array\n",
+ ">>> x[0] # equivalent to x[0, :]\n",
+ "array([-5, 2, 0, -7])\n",
+ "```\n",
+ "\n",
+ "Recall that the familiar [slicing](http://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/SequenceTypes.html#Slicing) syntax actually forms `slice` objects \"behind the scenes\".\n",
+ "\n",
+ "```python\n",
+ "# Reviewing the `slice` object\n",
+ "\n",
+ "# equivalent: x[:2, :3]\n",
+ ">>> x[slice(None, 2), slice(None, 3)]\n",
+ "array([[-5, 2, 0],\n",
+ " [-1, 9, 3]])\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Using a Tuple as an N-dimensional Index\n",
+ "According to its definition, we must supply our array-indices as a tuple in order to invoke basic indexing. As it turns out, we have been forming tuples of indices all along! That is, every time that we index into an array using the syntax `x[i, j, k]`, we are actually forming a [tuple](http://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/SequenceTypes.html#Tuples) containing those indices. That is, `x[i, j, k]` is equivalent to `x[(i, j, k)]`.\n",
+ "\n",
+ "`x[i, j, k]` forms the tuple `(i, j, k)` and passes that to the array's \"get-item\" mechanism. Thus, `x[0, 3]` is equivalent to `x[(0, 3)]`. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```python\n",
+ "# N-dimensional indexing utilizes tuples:\n",
+ "# `x[i, j, k]` is equivalent to `x[(i, j, k)]`\n",
+ "\n",
+ "# equivalent: x[1, -1]\n",
+ ">>> x[(1, -1)] \n",
+ "8\n",
+ "\n",
+ "# equivalent: x[:2, :3]\n",
+ ">>> x[(slice(None, 2), slice(None, 3))] \n",
+ "array([[-5, 2, 0],\n",
+ " [-1, 9, 3]])\n",
+ "\n",
+ "# equivalent: x[0]\n",
+ ">>> x[(0,)]\n",
+ "array([-5, 2, 0, -7])\n",
+ "```\n",
+ "\n",
+ "All objects used in this \"get-item\" syntax are packed into a tuple. For instance, `x[0, (0, 1)]` is equivalent to `x[(0, (0, 1))]`. You may be surprised to find that this is a valid index. However, see that *it does not invoke basic indexing*; the index used here is a tuple that contains an integer *and another tuple*, which is not permitted by the rules of basic indexing.\n",
+ "\n",
+ "Finally, note that the rules of basic indexing specifically call for a *tuple* of indices. Supplying a list of indices triggers advanced indexing rather than basic indexing!\n",
+ "\n",
+ "```python\n",
+ "# basic indexing specifically requires a tuple\n",
+ ">>> x[(1, -1)] \n",
+ "8\n",
+ "\n",
+ "# indexing with a list triggers advanced indexing\n",
+ ">>> x[[1, -1]]\n",
+ "array([[-1, 9, 3, 8],\n",
+ " [-3, -3, 4, 6]])\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Ellipsis and Newaxis objects\n",
+ "Recall from our discussion of broadcasting, that the `numpy.newaxis` object can be passed as an index to an array, in order to [insert a size-1 dimension into the array](http://www.pythonlikeyoumeanit.com/Module3_IntroducingNumpy/Broadcasting.html#Inserting-Size-1-Dimensions-into-An-Array).\n",
+ "\n",
+ "```python\n",
+ "# inserting size-1 dimensions with `np.newaxis`\n",
+ ">>> x.shape\n",
+ "(3, 4)\n",
+ "\n",
+ ">>> x[np.newaxis, :, :, np.newaxis].shape\n",
+ "(1, 3, 4, 1)\n",
+ "\n",
+ "# forming the index as an explicit tuple\n",
+ ">>> x[(np.newaxis, slice(None), slice(None), np.newaxis)].shape\n",
+ "(1, 3, 4, 1)\n",
+ "```\n",
+ "\n",
+ "We can also use the built-in `Ellipsis` object in order to insert slices into our index such that the index has as many entries as the array has dimensions. In the same way that `:` can be used to represent a `slice` object, `...` can be used to represent an `Ellipsis` object.\n",
+ "\n",
+ "```python\n",
+ ">>> y = np.array([[[ 0, 1, 2, 3],\n",
+ "... [ 4, 5, 6, 7]],\n",
+ "... \n",
+ "... [[ 8, 9, 10, 11],\n",
+ "... [12, 13, 14, 15]],\n",
+ "... \n",
+ "... [[16, 17, 18, 19],\n",
+ "... [20, 21, 22, 23]]])\n",
+ "\n",
+ "# equivalent: `y[:, :, 0]`\n",
+ ">>> y[..., 0]\n",
+ "array([[ 0, 4],\n",
+ " [ 8, 12],\n",
+ " [16, 20]])\n",
+ "\n",
+ "# using an explicit tuple\n",
+ ">>> y[(Ellipsis, 0)]\n",
+ "array([[ 0, 4],\n",
+ " [ 8, 12],\n",
+ " [16, 20]])\n",
+ "\n",
+ "# equivalent: `y[0, :, 1]`\n",
+ ">>> y[0, ..., 1]\n",
+ "array([1, 5])\n",
+ "```\n",
+ "\n",
+ "An index cannot possess more than one `Ellipsis` entry. This can be extremely useful when working with arrays of varying dimensionalities. To access column-0 along all dimensions of an array, `z`, would look like `z[:, 0]` for a 2D array, `z[:, :, 0]` for a 3D array, and so on. `z[..., 0]` succinctly encapsulates all iterations of this. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ "\n",
+ "**Takeaway:** \n",
+ "\n",
+ "Basic indexing is triggered whenever a tuple of: integer, `slice`, `numpy.newaxis`, and/or `Ellipsis` objects, is used as an index for a NumPy array. An array produced via basic indexing is a *view* of the same underlying data as the array that was indexed into; no data is copied through basic indexing. \n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ " \n",
+ "**Reading Comprehension: Ellipsis**\n",
+ "\n",
+ "Given a $N$-dimensional array, `x`, index into `x` such that you access entry-0 of axis-0, the last entry of axis-$N-1$, slicing along all intermediate dimensions. $N$ is at least $2$.\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ " \n",
+ "**Reading Comprehension: Basic Indexing**\n",
+ "\n",
+ "Given a shape-(4, 3) array,\n",
+ "\n",
+ "```python\n",
+ ">>> arr = np.array([[ 0, 1, 2, 3],\n",
+ "... [ 4, 5, 6, 7],\n",
+ "... [ 8, 9, 10, 11]])\n",
+ "```\n",
+ "\n",
+ "which of the following indexing schemes perform basic indexing? That is, in which instances does the index satisfy the rules of basic indexing?\n",
+ "\n",
+ " - `arr[0]`\n",
+ " - `arr[:-1, 0]`\n",
+ " - `arr[(2, 3)]`\n",
+ " - `arr[[2, 0]]`\n",
+ " - `arr[np.array([2, 0])]`\n",
+ " - `arr[(0, 1), (2, 3)]`\n",
+ " - `arr[slice(None), ...]`\n",
+ " - `arr[(np.newaxis, 0, slice(1, 2), np.newaxis)]`\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Producing a View of an Array\n",
+ "As stated above, using basic indexing does not return a copy of the data being accessed, rather it produces a *view* of the underlying data. NumPy provides the function `numpy.shares_memory` to determine if two arrays refer to the same underlying data.\n",
+ "\n",
+ "```python\n",
+ ">>> z = np.array([[ 3.31, 4.71, 0.4 ],\n",
+ "... [ 0.21, 2.85, 3.21],\n",
+ "... [-3.77, 4.53, -1.15]])\n",
+ "\n",
+ "# `subarray` is column-0 of `z`, via\n",
+ "# basic indexing\n",
+ ">>> subarray = z[:, 0]\n",
+ ">>> subarray\n",
+ "array([ 3.31, 0.21, -3.77])\n",
+ "\n",
+ "# `subarray` is a view of the array data \n",
+ "# referenced by `z`\n",
+ ">>> np.shares_memory(subarray, z)\n",
+ "True\n",
+ "```\n",
+ "\n",
+ "A single number returned by basic indexing *does not* share memory with the parent array.\n",
+ "```python\n",
+ ">>> z[0, 0]\n",
+ "3.31\n",
+ "\n",
+ ">>> np.shares_memory(z[0, 0], z)\n",
+ "False\n",
+ "```\n",
+ "The function `numpy.copy` can be used to create a copy of an array, such that it no longer shares memory with any other array.\n",
+ "\n",
+ "```python\n",
+ "# creating a distinct copy of an array\n",
+ ">>> new_subarray = np.copy(subarray)\n",
+ ">>> new_subarray\n",
+ "array([ 3.31, 0.21, -3.77])\n",
+ "\n",
+ ">>> np.shares_memory(new_subarray, z)\n",
+ "False\n",
+ "```\n",
+ "\n",
+ "Utilizing an array in a mathematical expression involving the arithmetic operators (`+, -, *, /, //, **`) returns an entirely distinct array, that does not share memory with the original array.\n",
+ "\n",
+ "```python\n",
+ "# mathematical expressions like `subarray + 2`\n",
+ "# produce distinct arrays, not views\n",
+ ">>> np.shares_memory(subarray + 2, subarray)\n",
+ "False\n",
+ "```\n",
+ "\n",
+ "Thus updating a variable `subarray` via `subarray = subarray + 2` does *not* overwrite the original data referenced by `subarray`. Rather, `subarray + 2` assigns that new array to the variable `subarray`. NumPy does provide mechanisms for performing mathematical operations to directly update the underlying data of an array without having to create a distinct array. We will discuss these mechanisms in the next subsection."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ " \n",
+ "**Reading Comprehension: Views**\n",
+ "\n",
+ "Given, \n",
+ "\n",
+ "```python\n",
+ "x = np.array([[ 0, 1, 2, 3],\n",
+ " [ 4, 5, 6, 7],\n",
+ " [ 8, 9, 10, 11]])\n",
+ "```\n",
+ "\n",
+ "Which of the following expressions create views of `x`? That is, in which cases do `x` and the created variable reference the same underlying array data? Check your work by using `np.shares_memory`.\n",
+ "\n",
+ "- `a1 = x`\n",
+ "- `a2 = x[0, 0]`\n",
+ "- `a3 = x[:, 0]`\n",
+ "- `a4 = x[:, 0] + np.array([-1, -2, -3])`\n",
+ "- `a5 = np.copy(x[:, 0])`\n",
+ "- `a6 = x[np.newaxis]`\n",
+ "- `a7 = x.reshape(2, 3, 2)`\n",
+ "- `a8 = 2 + x`\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Augmenting the Underlying Data of an Array \n",
+ "Because basic indexing produces a *view* of an array's underlying data, we must take time to understand the ways in which we can *augment* that underlying data, versus performing operations that produce an array with distinct data. Here we will see that:\n",
+ "\n",
+ "- in-place assignments \n",
+ "- augmented assignments\n",
+ "- NumPy functions with the `out` argument \n",
+ "\n",
+ "can all be used to augment array data in-place. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### In-Place Assignments\n",
+ "\n",
+ "The assignment operator, `=`, can be used to update an array's data in-place. Consider the array `a`, and its view `b`.\n",
+ "```python\n",
+ ">>> a = np.array([0, 1, 2, 3, 4])\n",
+ ">>> b = a[:]\n",
+ ">>> np.shares_memory(a, b)\n",
+ "True\n",
+ "```\n",
+ "\n",
+ "Assigning a new array to `a` simply changes the data that `a` references, divorcing `a` and `b`, and leaving `b` unchanged.\n",
+ "```python\n",
+ "# `a` is now assigned to reference a distinct array \n",
+ ">>> a = np.array([0, -1, -2, -3, -4])\n",
+ "\n",
+ "# `b` still references the original data\n",
+ ">>> b\n",
+ "array([0, 1, 2, 3, 4])\n",
+ "\n",
+ ">>> np.shares_memory(a, b)\n",
+ "False\n",
+ "```\n",
+ "\n",
+ "Performing an assignment on a *view* of `a`, i.e. `a[:]`, instructs NumPy to perform the assignment to replace `a`'s data in-place. \n",
+ "\n",
+ "```python\n",
+ "# reinitialize `a` and `b`. \n",
+ "# `b` is again a view of `a`\n",
+ ">>> a = np.array([0, 1, 2, 3, 4])\n",
+ ">>> b = a[:]\n",
+ "\n",
+ "# assigning an array to a *view* of `a` \n",
+ "# causes NumPy to update the data in-place\n",
+ ">>> a[:] = np.array([0, -1, -2, -3, -4])\n",
+ ">>> a\n",
+ "array([ 0, -1, -2, -3, -4])\n",
+ "\n",
+ "# `b` a view of the same data, thus\n",
+ "# it is affected by this in-place assignment\n",
+ ">>> b\n",
+ "array([ 0, -1, -2, -3, -4])\n",
+ "\n",
+ ">>> np.shares_memory(a, b)\n",
+ "True\n",
+ "```\n",
+ "\n",
+ "This view-assignment mechanism can be used update a subsection of an array in-place.\n",
+ "\n",
+ "```python\n",
+ ">>> p = np.array([[ 0, 1, 2, 3],\n",
+ "... [ 4, 5, 6, 7],\n",
+ "... [ 8, 9, 10, 11]])\n",
+ ">>> q = p[0, :]\n",
+ "\n",
+ "# Assign row-0, column-0 the value -40\n",
+ "# and row-0, column-2 the value -50\n",
+ ">>> p[0, ::2] = (-40, -50)\n",
+ "\n",
+ "# broadcast-assign -1 to a subsection of `p`\n",
+ ">>> p[1:, 2:] = -1\n",
+ ">>> p\n",
+ "array([[-40, 1, -50, 3],\n",
+ " [ 4, 5, -1, -1],\n",
+ " [ 8, 9, -1, -1]])\n",
+ "```\n",
+ "Again, this updates the underlying data, and thus all views of this data reflect this change.\n",
+ "```python\n",
+ "# `q` is still a view of row-0 of `p`\n",
+ ">>> q\n",
+ "array([-40, 1, -50, 3])\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Augmented Assignments\n",
+ "Recall from our discussion of basic mathematical expressions in Python, that [augmented assignment expressions](http://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Basic_Objects.html#Augmented-Assignment-Statements) provide a nice shorthand notation for updating the value of a variable. For example, the assignment expression `x = x + 5` can be rewritten using the augmented assignment `x += 5`. \n",
+ "\n",
+ "While `x += 5` is truly only a shorthand in the context of basic Python objects (integers floats, etc.), *augmented assignments on NumPy arrays behave fundamentally different than their long-form counterparts*. Specifically, they directly update the underlying data referenced by the updated array, rather than creating a distinct array, thus affecting any arrays that are views of that data. We will demonstrate this here.\n",
+ "\n",
+ "```python\n",
+ "# Demonstrating that augmented assignments on NumPy\n",
+ "# arrays update the underlying data reference by that\n",
+ "# array.\n",
+ ">>> a = np.array([[ 0, 1, 2, 3],\n",
+ "... [ 4, 5, 6, 7],\n",
+ "... [ 8, 9, 10, 11]])\n",
+ "\n",
+ "# `b` and `c` are both views of row-0 of `a`, via basic indexing\n",
+ ">>> b = a[0]\n",
+ ">>> c = a[0]\n",
+ ">>> np.shares_memory(a, b) and np.shares_memory(a, c)\n",
+ "True\n",
+ "\n",
+ "# updating `b` using a mathematical expression creates\n",
+ "# a distinct array, which is divorced from `a` and `c`\n",
+ ">>> b = b * -1\n",
+ ">>> b\n",
+ "array([ 0, -1, -2, -3])\n",
+ "\n",
+ ">>> np.shares_memory(a, b)\n",
+ "False\n",
+ "\n",
+ "# updating `c` using augmented assignment updates the \n",
+ "# underlying data that `c` is a view of\n",
+ ">>> c *= -2\n",
+ ">>> c\n",
+ "array([ 0, -2, -4, -6])\n",
+ "\n",
+ ">>> np.shares_memory(a, c)\n",
+ "True\n",
+ "\n",
+ "# note that this update is reflected in `a` as well,\n",
+ "# as it still shares memory with `c`\n",
+ ">>> a\n",
+ "array([[ 0, -2, -4, -6],\n",
+ " [ 4, 5, 6, 7],\n",
+ " [ 8, 9, 10, 11]])\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Specifying `out` to Perform NumPy Operations In-Place \n",
+ "There is no reason why we should only be able to augment data using arithmetic operations. Indeed, [NumPy's various mathematical functions](https://www.pythonlikeyoumeanit.com/Module3_IntroducingNumpy/VectorizedOperations.html#NumPy%E2%80%99s-Mathematical-Functions) have an optional keyword argument, `out`, which can be used to specify where to \"store\" the result of the mathematical operation. By default, the operation will create a distinct array in memory, leaving the input data unaffected.\n",
+ "\n",
+ "```python\n",
+ "# Specifying the 'out' argument in a `numpy.exp` \n",
+ "# to augment the data of an array\n",
+ "\n",
+ "# `b` is a view of `a`\n",
+ ">>> a = np.array([0., 0.2, 0.4, 0.6, 0.8, 1.])\n",
+ ">>> b = a[:]\n",
+ ">>> np.shares_memory(a, b)\n",
+ "True\n",
+ "\n",
+ "# specifying 'out=a' instructs NumPy\n",
+ "# to overwrite the data referenced by `a`\n",
+ ">>> np.exp(a, out=a)\n",
+ "array([ 1., 1.22140276, 1.4918247, 1.8221188, 2.22554093, 2.71828183])\n",
+ "\n",
+ "# `b` is still a view of the now-augmented data\n",
+ ">>> b\n",
+ "array([ 1., 1.22140276, 1.4918247, 1.8221188, 2.22554093, 2.71828183])\n",
+ "```\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Benefits and Risks of Augmenting Data In-Place\n",
+ "It is critical to understand the relationship between arrays and the underlying data that they reference. *Operations that augment data in-place are more efficient than their counterparts that must allocate memory for a new array.* That is, an expression like `array += 3` is more efficient than `array = array + 3`. \n",
+ "\n",
+ "That being said, to *unwittingly* augment the data of an array, and thus affect all views of that data, is a big mistake; this produces hard-to-find bugs in the code of novice NumPy users. See that the following function, `add_3`, will change the data of the input array.\n",
+ "\n",
+ "```python\n",
+ "# updating an array in-place within a function\n",
+ "def add_3(x):\n",
+ " x += 3 \n",
+ " return x\n",
+ "\n",
+ ">>> x = np.array([0, 1, 2])\n",
+ ">>> y = add_3(x)\n",
+ ">>> y\n",
+ "array([3, 4, 5])\n",
+ "\n",
+ "# `x` is updated each time `f(x)` is called\n",
+ ">>> x\n",
+ "array([3, 4, 5])\n",
+ "```\n",
+ "\n",
+ "This is hugely problematic unless you intended for `add_3` to affect the input array. To remedy this, you can simply begin the function by making a copy of the input array; afterwards you can freely augment this copied data.\n",
+ "```python\n",
+ "def add_3(x):\n",
+ " x = np.copy(x)\n",
+ " x += 3 \n",
+ " return x\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ "\n",
+ "**Reading Comprehension: Augmenting Array Data In-Place**\n",
+ "\n",
+ "Given, \n",
+ "\n",
+ "```python\n",
+ "x = np.array([[ 0., 1., 2., 3.],\n",
+ " [ 4., 5., 6., 7.],\n",
+ " [ 8., 9., 10., 11.]])\n",
+ "\n",
+ "y = x[0, :]\n",
+ "```\n",
+ "\n",
+ "Which of the following expressions updates the data originally referenced by `x`?\n",
+ "\n",
+ "```python \n",
+ "# 1.\n",
+ ">>> x += 3\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 2.\n",
+ ">>> y *= 2.4\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 3.\n",
+ ">>> x = x + 3\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 4.\n",
+ ">>> y = np.copy(y)\n",
+ ">>> y += 3\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 5.\n",
+ ">>> np.log(x[1:3], out=x[1:3])\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 6.\n",
+ ">>> y[:] = y + 2\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 7.\n",
+ ">>> x = np.square(x)\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 8.\n",
+ ">>> x[:] = 0\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 9.\n",
+ ">>> def f(z): z /= 3\n",
+ ">>> f(y)\n",
+ "```\n",
+ "\n",
+ "```python\n",
+ "# 10.\n",
+ ">>> np.square(y, out=y)\n",
+ "```\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ "\n",
+ "**Takeaway:** \n",
+ "\n",
+ "Assignments to views of an array, augmented assignments, and NumPy functions that provide an `out` argument, are all methods for augmenting the data of an array in-place. This will affect any arrays that are views of that data. Furthermore, these in-place operations are more efficient than their counterparts that allocate memory for a new array. That being said, in-place data augmentation must not be used haphazardly, for this will inevitably lead to treacherous bugs in one's code.\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Links to Official Documentation\n",
+ "\n",
+ "- [Basic indexing](https://numpy.org/doc/stable/reference/arrays.indexing.html#indexing)\n",
+ "- [Definition of 'view'](https://numpy.org/doc/stable/glossary.html#term-view)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Reading Comprehension Solutions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Ellipsis: Solution**\n",
+ "\n",
+ "Given a $N$-dimensional array, `x`, index into `x` such that you axis entry-0 of axis-0, the last entry of axis-$(N-1)$, slicing along all intermediate dimensions. $N$ is at least $2$.\n",
+ "\n",
+ "Using an `Ellipsis` object in the index allows us to signal NumPy to insert the slices along the $N - 2$ intermediate axis of `x`:\n",
+ "\n",
+ "`x[0, ..., -1]` or `x[0, Ellipsis, -1]`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Basic Indexing: Solution**\n",
+ "\n",
+ "In which instances does the index used satisfy the rules of basic indexing?\n",
+ "\n",
+ " - `arr[0]` ✔\n",
+ " - `arr[:-1, 0]` ✔\n",
+ " - `arr[(2, 3)]` ✔\n",
+ " - `arr[[2, 0]]` ✘ (index is a `list`, not a `tuple`)\n",
+ " - `arr[np.array([2, 0])]` ✘ (index is a `numpy.ndarray`, not a `tuple`)\n",
+ " - `arr[:, (2, 3)]` ✘ (index contains a tuple; only `int`, `slice`, `np.newaxis`, `Ellipsis` allowed)\n",
+ " - `arr[slice(None), ...]` ✔\n",
+ " - `arr[(np.newaxis, 0, slice(1, 2), np.newaxis)]` ✔"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Views: Solution**\n",
+ "\n",
+ "Given, \n",
+ "\n",
+ "```python\n",
+ "x = np.array([[ 0, 1, 2, 3],\n",
+ " [ 4, 5, 6, 7],\n",
+ " [ 8, 9, 10, 11]])\n",
+ "```\n",
+ "\n",
+ "Which of the following expressions create views `x`? That is, in which cases do `x` and the created variable reference the same underlying array data? Check your work by using `np.shares_memory`.\n",
+ "\n",
+ "- `a1 = x` ✔\n",
+ "- `a2 = x[0, 0]` ✘; when basic indexing returns a single number, that number does not share memory with the parent array.\n",
+ "- `a3 = x[:, 0]` ✔\n",
+ "- `a4 = x[:, 0] + np.array([-1, -2, -3])` ✘; arithmetic operations on NumPy arrays create distinct arrays by default.\n",
+ "- `a5 = np.copy(x[:, 0])` ✘; `numpy.copy` informs NumPy to create a distinct copy of an array.\n",
+ "- `a6 = x[np.newaxis]` ✔\n",
+ "- `a7 = x.reshape(2, 3, 2)` ✔\n",
+ "- `a8 = 2 + x` ✘; arithmetic operations on NumPy arrays create distinct arrays by default."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Augmenting Array Data In-Place: Solution**\n",
+ "\n",
+ "Given, \n",
+ "\n",
+ "```python\n",
+ "x = np.array([[ 0., 1., 2., 3.],\n",
+ " [ 4., 5., 6., 7.],\n",
+ " [ 8., 9., 10., 11.]])\n",
+ "\n",
+ "y = x[0, :]\n",
+ "```\n",
+ "\n",
+ "Which of the following expressions updates the data originally referenced by `x`?\n",
+ "\n",
+ "```python \n",
+ "# 1.\n",
+ ">>> x += 3 ✔\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 2.\n",
+ ">>> y *= 2.4 ✔\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 3.\n",
+ ">>> x = x + 3 ✘\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 4.\n",
+ ">>> y = np.copy(y)\n",
+ ">>> y += 3 ✘\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 5.\n",
+ ">>> np.log(x[1:3], out=x[1:3]) ✔\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 6.\n",
+ ">>> y[:] = y + 2 ✔\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 7.\n",
+ ">>> x = np.square(x) ✘\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 8.\n",
+ ">>> x[:] = 0 ✔\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 9.\n",
+ ">>> def f(z): z /= 3\n",
+ ">>> f(y) ✔\n",
+ "```\n",
+ "\n",
+ "```python \n",
+ "# 10.\n",
+ ">>> np.square(y, out=y) ✔\n",
+ "```"
+ ]
+ }
+ ],
+ "metadata": {
+ "jupytext": {
+ "text_representation": {
+ "extension": ".md",
+ "format_name": "markdown",
+ "format_version": "1.2",
+ "jupytext_version": "1.9.1"
+ }
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs_backup/Module3_IntroducingNumpy/Broadcasting.html b/docs_backup/Module3_IntroducingNumpy/Broadcasting.html
index 1731ff3f..4ada3896 100644
--- a/docs_backup/Module3_IntroducingNumpy/Broadcasting.html
+++ b/docs_backup/Module3_IntroducingNumpy/Broadcasting.html
@@ -1,45 +1,49 @@
-
-
+
-
+
-
+
Array Broadcasting — Python Like You Mean It
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
@@ -84,6 +88,7 @@
+
+
@@ -169,11 +175,13 @@
+
+
-
- © Copyright 2019, Ryan Soklaski
+ © Copyright 2021, Ryan Soklaski.
- Built with Sphinx using a theme provided by Read the Docs.
+
+
+
+ Built with Sphinx using a
+
+ theme
+
+ provided by Read the Docs.
-
@@ -433,7 +441,6 @@ Links to Official Documentation
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
diff --git a/docs_backup/Module3_IntroducingNumpy/FunctionsForCreatingNumpyArrays.ipynb b/docs_backup/Module3_IntroducingNumpy/FunctionsForCreatingNumpyArrays.ipynb
new file mode 100644
index 00000000..012497e2
--- /dev/null
+++ b/docs_backup/Module3_IntroducingNumpy/FunctionsForCreatingNumpyArrays.ipynb
@@ -0,0 +1,265 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "raw_mimetype": "text/restructuredtext"
+ },
+ "source": [
+ ".. meta::\n",
+ " :description: Topic: Creating numpy arrays, Difficulty: Easy, Category: Section\n",
+ " :keywords: create array, ndarray, ones, random, zeros, empty, examples, arange, linspace, reshape, hstack, vstack"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Functions for Creating NumPy Arrays \n",
+ "This section presents standard methods for creating NumPy arrays of varying shapes and contents. NumPy provides a laundry list of functions for creating arrays:\n",
+ "\n",
+ "```python\n",
+ ">>> import numpy as np\n",
+ "\n",
+ "# creating an array from a Python sequence\n",
+ ">>> np.array([i**2 for i in range(5)])\n",
+ "array([ 0, 1, 4, 9, 16])\n",
+ "\n",
+ "# creating an array filled with ones\n",
+ ">>> np.ones((2, 4))\n",
+ "array([[ 1., 1., 1., 1.],\n",
+ " [ 1., 1., 1., 1.]])\n",
+ "\n",
+ "# creating an array of evenly-spaced points\n",
+ ">>> np.linspace(0, 10, 5)\n",
+ "array([ 0. , 2.5, 5. , 7.5, 10. ])\n",
+ "\n",
+ "# creating an array by sampling 10 numbers \n",
+ "# randomly from a mean-1, std-dev-5 normal\n",
+ "# distribution\n",
+ ">>> np.random.normal(1, 5, 10)\n",
+ "array([ 2.549537 , 2.75144951, 0.60031823, 3.75185732, 4.65543858,\n",
+ " 0.55779525, 1.15574987, -1.98461337, 5.39771083, -7.81395192])\n",
+ "\n",
+ "# creating an array of a specified datatype\n",
+ ">>> np.array([1.5, 3.20, 5.78], dtype=int)\n",
+ "array([1, 3, 5])\n",
+ "```\n",
+ "\n",
+ "## Creating Arrays from Python Sequences\n",
+ "You can create an array from a Python `list` or `tuple` by using NumPy's `array` function. NumPy will interpret the structure of the data it receives to determine the dimensionality and shape of the array. For example, a single list of numbers will be used to create a 1-dimensional array: \n",
+ "\n",
+ "```python\n",
+ "# a list of numbers will become a 1D-array\n",
+ ">>> np.array([1., 2., 3.]) # shape: (3,)\n",
+ "array([ 1., 2., 3.])\n",
+ "```\n",
+ "\n",
+ "Nested lists/tuples will be used to construct multidimensional arrays. For example, a \"list of equal-length lists of numbers\" will lead to a 2-dimensional array; each of the inner-lists comprises a row of the array. Thus a list of two, length-three lists will produce a (2,3)-shaped array:\n",
+ " \n",
+ "```python\n",
+ "# a list of lists of numbers will produce a 2D-array\n",
+ ">>> np.array([[1., 2., 3.], [4., 5., 6.]]) # shape: (2, 3)\n",
+ "array([[ 1., 2., 3.],\n",
+ " [ 4., 5., 6.]])\n",
+ "```\n",
+ "\n",
+ "A \"list of equal-length lists, of equal-length lists of numbers\" creates a 3D-array, and so on. Recall that using repeated concatenation, `[0]*3` will produce `[0, 0, 0]`. Using this, let's create two lists, each containing three lists, each containing four zeros; feeding this to `np.array` thus produces a 2x3x4 array of zeros:\n",
+ "```python\n",
+ "# A list of lists of lists of zeros creates a 3D-array\n",
+ ">>> np.array([[[0]*4]*3]*2)\n",
+ "array([[[0, 0, 0, 0],\n",
+ " [0, 0, 0, 0],\n",
+ " [0, 0, 0, 0]],\n",
+ "\n",
+ " [[0, 0, 0, 0],\n",
+ " [0, 0, 0, 0],\n",
+ " [0, 0, 0, 0]]])\n",
+ "```\n",
+ "\n",
+ "You will seldom use lists to form high-dimensional arrays like this. Instead, there are other array-creation functions that are more amendable to generating high-dimensional data, which we will introduce next. For example, we will see that the `np.zeros` function is a much more civilized way to create a high-dimensional array of zeros. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " \n",
+ "\n",
+ "**Warning!** \n",
+ "\n",
+ "You actually *can* create an array from lists of *unequal* lengths. The resulting array is **not** an ND-array as it has no well-defined dimensionality. Instead, something called an *object-array* is produced, which does not benefit from the majority of NumPy's features. This is a relatively obscure feature of the NumPy library, and should be avoided unless you really know what you're doing!\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Creating Constant Arrays: `zeros` and `ones`\n",
+ "NumPy provides the functions `zeros` and `ones`, which will fill an array of user-specified shape with 0s and 1s, respectively:\n",
+ "\n",
+ "```python\n",
+ "# create a 3x4 array of zeros\n",
+ ">>> np.zeros((3, 4))\n",
+ "array([[ 0., 0., 0., 0.],\n",
+ " [ 0., 0., 0., 0.],\n",
+ " [ 0., 0., 0., 0.]])\n",
+ "\n",
+ "# create a shape-(4,) array of ones\n",
+ ">>> np.ones((4,))\n",
+ "array([ 1., 1., 1., 1.])\n",
+ "```\n",
+ "\n",
+ "NumPy provides additional functions for creating constant-valued arrays. Please refer to [the official documentation](https://numpy.org/doc/stable/reference/routines.array-creation.html#ones-and-zeros) for a complete listing. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Creating Sequential Arrays: `arange` and `linspace`\n",
+ "The [arange](https://numpy.org/doc/stable/reference/generated/numpy.arange.html#numpy.arange) function allows you to initialize a sequence of integers based on a starting point (inclusive), stopping point (exclusive), and step size. This is very similar to the `range` function; however `arange` immediately creates this sequence as an array, whereas `range` produces a generator.\n",
+ "```python\n",
+ ">>> np.arange(0, 10, 1) # start (included): 0, stop (excluded): 10, step:1 \n",
+ "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
+ "\n",
+ "# supplying one value to `arange` amounts to specifying the stop value \n",
+ "# start=0 and step=1 are then used as defaults\n",
+ ">>> np.arange(10) # equivalent to: start: 0, stop: 10, step:1 \n",
+ "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
+ "\n",
+ ">>> np.arange(-5, 6, 2) # start (included): -5, stop (excluded): 6, step:2 \n",
+ "array([-5, -3, -1, 1, 3, 5])\n",
+ "```\n",
+ "\n",
+ "The [linspace](https://numpy.org/doc/stable/reference/generated/numpy.linspace.html) function allows you to generate $N$ *evenly-spaced* points within a user-specified interval $[i, j]$ ($i$ and $j$ are included in the interval). This is often used to generate a domain of values on which to evaluate a mathematical function (e.g. if you want to the sine function from $-\\pi$ to $\\pi$ on a finely-divided grid).\n",
+ "\n",
+ "```python\n",
+ "# generate five evenly-spaced points on the interval [-1, 1]\n",
+ ">>> np.linspace(-1, 1, 5)\n",
+ "array([-1. , -0.5, 0. , 0.5, 1. ])\n",
+ "\n",
+ "# generate two evenly-spaced points on the interval [3, 4]\n",
+ ">>> np.linspace(3, 4, 2)\n",
+ "array([ 3., 4.])\n",
+ "\n",
+ "# generate 100 evenly-spaced points on the interval [-pi, pi]\n",
+ ">>> np.linspace(-np.pi, np.pi, 100)\n",
+ "array([-3.14159265, ..., 3.14159265])\n",
+ "```\n",
+ "\n",
+ "Numpy has other functions for creating sequential arrays, such as producing an array spaced evenly on a log-scaled interval. See the [official documentation](https://numpy.org/doc/stable/reference/routines.array-creation.html#numerical-ranges) for a complete listing."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Creating Arrays Using Random Sampling\n",
+ "Several functions can be accessed from `np.random`, which populate arrays of a user-specified shape by drawing randomly from a specified statistical distribution:\n",
+ "```python\n",
+ "# create a shape-(3,3) array by drawing its entries randomly\n",
+ "# from the uniform distribution [0, 1) \n",
+ ">>> np.random.rand(3,3)\n",
+ "array([[ 0.09542611, 0.13183498, 0.39836068],\n",
+ " [ 0.7358235 , 0.77640024, 0.74913595],\n",
+ " [ 0.37702688, 0.86617624, 0.39846429]])\n",
+ "\n",
+ "# create a shape-(5,) array by drawing its entries randomly\n",
+ "# from a mean-0, variance-1 normal (a.k.a. Gaussian) distribution\n",
+ ">>> np.random.randn(5)\n",
+ "array([-1.11262121, -0.35392007, 0.4245215 , -0.81995588, 0.65412323])\n",
+ "```\n",
+ "There are [many more functions](https://numpy.org/doc/stable/reference/routines.random.html#distributions) to read about that allow you to draw from a wide variety of statistical distributions. This only scratches the surface of random number generation in NumPy.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Creating an Array with a Specified Data Type\n",
+ "Each of the preceding functions used to create an array can be passed a so-called 'keyword' argument, `dtype`, which instructs NumPy to use a specified data type when producing the contents of the array.\n",
+ "\n",
+ "```python\n",
+ "# populate an array using 32-bit floating point numbers\n",
+ ">>> np.array([1, 2, 3], dtype=\"float32\") \n",
+ "array([ 1., 2., 3.], dtype=float32)\n",
+ "\n",
+ "# default data type produced by `arange` is 32-bit integers\n",
+ ">>> np.arange(0, 4).dtype \n",
+ "dtype('int32')\n",
+ "\n",
+ "# the data type produced by `arange` can be specified otherwise\n",
+ ">>> np.arange(0, 4, dtype=\"float16\")\n",
+ "array([ 0., 1., 2., 3.], dtype=float16)\n",
+ "\n",
+ "# generate shape-(4,4) array of 64-bit complex-valued 0s\n",
+ ">>> np.zeros((4, 4), dtype=\"complex64\")\n",
+ "array([[ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex64)\n",
+ "```\n",
+ "\n",
+ "Refer to [the official NumPy documentation](https://numpy.org/doc/stable/user/basics.types.html#array-types-and-conversions-between-types) for the complete list of available array datatypes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Joining Arrays Together\n",
+ "Similar to Python lists and tuples, NumPy arrays can be concatenated together. However, because NumPy's arrays can be multi-dimensional, we can choose the dimension along which arrays are joined. \n",
+ "```python\n",
+ "# demonstrating methods for joining arrays \n",
+ ">>> x = np.array([1, 2, 3])\n",
+ ">>> y = np.array([-1, -2, -3])\n",
+ "\n",
+ "# stack `x` and `y` \"vertically\"\n",
+ ">>> np.vstack([x, y])\n",
+ "array([[ 1, 2, 3],\n",
+ " [-1, -2, -3]])\n",
+ "\n",
+ "# stack `x` and `y` \"horizontally\"\n",
+ ">>> np.hstack([x, y])\n",
+ "array([ 1, 2, 3, -1, -2, -3])\n",
+ "```\n",
+ "\n",
+ "A complete listing of functions for joining arrays can be [found in the official NumPy documentation](https://numpy.org/doc/stable/reference/routines.array-manipulation.html#joining-arrays). There are also corresponding functions for splitting an array into independent arrays."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Links to Official Documentation\n",
+ "\n",
+ "- [Constant arrays](https://numpy.org/doc/stable/reference/routines.array-creation.html#ones-and-zeros)\n",
+ "- [numpy.array](https://numpy.org/doc/stable/reference/generated/numpy.array.html#numpy-array)\n",
+ "- [Sequential arrays](https://numpy.org/doc/stable/reference/routines.array-creation.html#numerical-ranges)\n",
+ "- [Random distributions](https://numpy.org/doc/stable/reference/routines.random.html#distributions)\n",
+ "- [Array types](https://numpy.org/doc/stable/user/basics.types.html#array-types-and-conversions-between-types)\n",
+ "- [Joining arrays](https://numpy.org/doc/stable/reference/routines.array-manipulation.html#joining-arrays)"
+ ]
+ }
+ ],
+ "metadata": {
+ "jupytext": {
+ "text_representation": {
+ "extension": ".md",
+ "format_name": "markdown",
+ "format_version": "1.2",
+ "jupytext_version": "1.9.1"
+ }
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.html b/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.html
index 6faf56f5..1128e9bd 100644
--- a/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.html
+++ b/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.html
@@ -1,45 +1,49 @@
-
-
+
-
+
-
+
Introducing the ND-array — Python Like You Mean It
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
@@ -84,6 +88,7 @@
+
+
@@ -155,11 +161,13 @@
+
+
@@ -320,7 +328,6 @@ Links to Official Documentation
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
diff --git a/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.ipynb b/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.ipynb
new file mode 100644
index 00000000..a3731239
--- /dev/null
+++ b/docs_backup/Module3_IntroducingNumpy/IntroducingTheNDarray.ipynb
@@ -0,0 +1,135 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "metadata": {
+ "raw_mimetype": "text/restructuredtext"
+ },
+ "source": [
+ ".. meta::\n",
+ " :description: Topic: Introduction to numpy arrays, Difficulty: Easy, Category: Section\n",
+ " :keywords: numpy array, ndarray, introduction, overview"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Introducing the ND-array\n",
+ "It is time to start familiarizing ourselves with NumPy, the premiere library for doing numerical work in Python. To use this package, we need to be sure to \"import\" the NumPy module into our code:\n",
+ "\n",
+ "```python\n",
+ "import numpy as np\n",
+ "```\n",
+ "\n",
+ "You could have run `import numpy` instead, but the prescribed method allows us to use the abbreviation 'np' throughout our code, instead of having to write 'numpy'. This is a very common abbreviation to use.\n",
+ "\n",
+ "The ND-array (N-dimensional array) is the star of the show for NumPy. This array simply stores a sequence of numbers. Like a Python list, you can access individual entries in this array by \"indexing\" into the array, and you can access a sub-sequence of the array by \"slicing\" it. So what distinguishes NumPy's ND-array from a Python list, and why is there a whole numerical library that revolves around this array? There are two major features that makes the ND-array special. It can:\n",
+ "\n",
+ " 1. Provide an interface for its underlying data to be accessed along multiple dimensions.\n",
+ " 2. Rapidly perform mathematical operations over all of its elements, or over patterned subsequences of its elements, using compiled C code instead of Python; this is a process called vectorization.\n",
+ " \n",
+ "Let's take a sneak peek to see what this module has in store. The following code creates an ND-array containing the numbers 0-8:\n",
+ "\n",
+ "```python\n",
+ ">>> import numpy as np\n",
+ ">>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])\n",
+ "```\n",
+ "\n",
+ "This object belongs to the NumPy-defined type `numpy.ndarray`.\n",
+ "\n",
+ "```python\n",
+ "# An ND-array belongs to the type `numpy.ndarray`\n",
+ ">>> type(x)\n",
+ "numpy.ndarray\n",
+ "\n",
+ ">>> isinstance(x, np.ndarray)\n",
+ "True\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can \"reshape\" this array so that its contents can be accessed along 2 dimensions:\n",
+ "```python\n",
+ ">>> x = x.reshape(3,3)\n",
+ ">>> x\n",
+ "array([[0, 1, 2],\n",
+ " [3, 4, 5],\n",
+ " [6, 7, 8]])\n",
+ "```\n",
+ "\n",
+ "We will utilize one of NumPy's \"vectorized\" functions to square each entry in the array (without us needing to write a for-loop) \n",
+ "```python\n",
+ ">>> np.power(x, 2) # can also be calculated using the shorthand: x**2\n",
+ "array([[ 0, 1, 4],\n",
+ " [ 9, 16, 25],\n",
+ " [36, 49, 64]], dtype=int32)\n",
+ "```\n",
+ "\n",
+ "Let's take the mean value along the three distinct rows of our data:\n",
+ "```python\n",
+ ">>> np.mean(x, axis=1)\n",
+ "array([ 1., 4., 7.])\n",
+ "```\n",
+ "\n",
+ "We can use broadcasting to raise each column of `x` to a different power:\n",
+ "```python\n",
+ ">>> x ** np.array([0., 1., 2.])\n",
+ "array([[ 1., 1., 4.],\n",
+ " [ 1., 4., 25.],\n",
+ " [ 1., 7., 64.]])\n",
+ "```\n",
+ "\n",
+ "Basic indexing allows us to access multi-dimensional slices of `x`:\n",
+ "```python\n",
+ ">>> x[:2, :3]\n",
+ "array([[0, 1, 2],\n",
+ " [3, 4, 5]])\n",
+ "```\n",
+ "\n",
+ "Advanced indexing can be used to access all even-valued entries of `x`; let's update `x` so that all of its even-valued entries are multiplied by -1:\n",
+ "\n",
+ "```python\n",
+ ">>> x[x % 2 == 0] *= -1\n",
+ ">>> x\n",
+ "array([[ 0, 1, -2],\n",
+ " [ 3, -4, 5],\n",
+ " [-6, 7, -8]])\n",
+ "```\n",
+ "\n",
+ "By the end of this module, these code snippets should make good sense, and NumPy's tremendous utility should be clear."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Links to Official Documentation\n",
+ "\n",
+ "- [The N-dimensional array](https://numpy.org/doc/stable/reference/arrays.ndarray.html)\n",
+ "- [NumPy Basics](https://numpy.org/doc/stable/user/basics.html#numpy-basics)\n",
+ "- [NumPy reference](https://numpy.org/doc/stable/reference/index.html)"
+ ]
+ }
+ ],
+ "metadata": {
+ "jupytext": {
+ "text_representation": {
+ "extension": ".md",
+ "format_name": "markdown",
+ "format_version": "1.2",
+ "jupytext_version": "1.9.1"
+ }
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/docs_backup/Module3_IntroducingNumpy/Problems/Approximating_pi.html b/docs_backup/Module3_IntroducingNumpy/Problems/Approximating_pi.html
index 0de96ed8..39fdc687 100644
--- a/docs_backup/Module3_IntroducingNumpy/Problems/Approximating_pi.html
+++ b/docs_backup/Module3_IntroducingNumpy/Problems/Approximating_pi.html
@@ -1,43 +1,47 @@
-
-
+
-
+
-
+
Playing Darts and Estimating Pi — Python Like You Mean It
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
@@ -82,6 +86,7 @@
+
+
@@ -152,11 +158,13 @@
+
+
Relevant Reading¶
@@ -432,7 +488,7 @@ Relevant Reading
Tips¶
-It is helpful to know about NumPy’s cumulative-sum function, numpy.cumsum. This is useful for keeping a running tally - i.e. keeping the history of the number of darts that have fallen within the circle, and not just the current count.
+It is helpful to know about NumPy’s cumulative-sum function, numpy.cumsum. This is useful for keeping a running tally - i.e. keeping the history of the number of darts that have fallen within the circle, and not just the current count.
-
[5]:
@@ -520,8 +576,9 @@ Solution (Unvectorized)[5]:
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
@@ -84,6 +88,7 @@
+
+
@@ -149,11 +155,13 @@
+
+