{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# NumPy - multidimensional data arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ondrej Lexa" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `numpy` package (module) is used in almost all numerical computation using Python. It is a package that provide high-performance vector, matrix and higher-dimensional data structures for Python. It is implemented in C and Fortran so when calculations are vectorized (formulated with vectors and matrices), performance is very good.\n", "\n", "`NumPy` adds basic MATLAB-like capability to Python:\n", "\n", " * multidimensional arrays with homogeneous data types\n", " * specific numeric data types (e.g. `int8`, `uint32`, `float64`)\n", " * array manipulation functions (e.g. `reshape`, `transpose`, `concatenate`)\n", " * array generation (e.g. `ones`, `zeros`, `eye`, `random`)\n", " * element-wise math operations (e.g. `add`, `multiply`, `max`, `sin`)\n", " * matrix math operations (e.g. `inner`/`outer` product, `rank`, `trace`)\n", " * linear algebra (e.g. `inv`, `pinv`, `svd`, `eig`, `det`, `qr`)\n", "\n", "`SciPy` builds on `NumPy` (much like MATLAB toolboxes) adding:\n", "\n", " * multidimensional image processing\n", " * non-linear solvers, optimization, root finding\n", " * signal processing, fast Fourier transforms\n", " * numerical integration, interpolation, statistical functions\n", " * sparse matrices, sparse solvers\n", " * clustering algorithms, distance metrics, spatial data structures\n", " * file IO (including to MATLAB .mat files)\n", "\n", "`Matplotlib` adds MATLAB-like plotting capability on top of `NumPy`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing the numpy module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are several ways to import `numpy`. The standard approach is to use a simple import\n", "statement.\n", "```python\n", "import numpy\n", "```\n", "However, for large amounts of calls to `numpy` functions, it can become tedious to write\n", "`numpy.X` over and over again. Instead, it is common to import under the briefer name `np`.\n", "```python\n", "import numpy as np\n", "```\n", "This statement will allow us to access `numpy` objects using `np.X` instead of `numpy.X`. It is\n", "also possible to import `numpy` directly into the **current namespace** so that we don't have to use\n", "dot notation at all, but rather simply call the functions as if they were built-in:\n", "```python\n", "from numpy import *\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Interactive Scientific Python (aka PyLab)\n", "**PyLab** is a meta-package that import most of the `NumPy` and `Matplotlib` into the global name space. It is the easiest (and most MATLAB-like) way to work with scientific Python.\n", "```python\n", "from pylab import *\n", "```\n", "In Jupyter notebooks we can use `%pylab` magic to initiate PyLab evnironment. Option `inline` set graphical output to be shown in notebook. For other magic commands see [Built-in magic commands](http://ipython.readthedocs.io/en/stable/interactive/magics.html). `%pylab` makes the following imports.\n", "```python\n", "import numpy\n", "import matplotlib\n", "from matplotlib import pylab, mlab, pyplot\n", "np = numpy\n", "plt = pyplot\n", "\n", "from IPython.display import display\n", "from IPython.core.pylabtools import figsize, getfigs\n", "\n", "from pylab import *\n", "from numpy import *\n", "```" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Populating the interactive namespace from numpy and matplotlib\n" ] } ], "source": [ "%pylab inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Writing scripts and programs\n", "When writing scripts or programs it is recommended that you:\n", "\n", " * only import what you need, for efficiency\n", " * import packages into namespaces, to avoid name clashes\n", "\n", "The community has adopted abbreviated naming conventions\n", "```python\n", "import numpy as np\n", "import scipy as sp\n", "import matplotlib as mpl\n", "import matplotlib.pyplot as plt\n", "```\n", "Some different ways of working with `NumPy` are:\n", "```python\n", "from numpy import eye, array # Import only what you need\n", "from numpy.linalg import svd\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### NumPy arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the `numpy` package the terminology used for vectors, matrices and higher-dimensional data sets is `numpy.ndarray`.\n", "\n", "The `numpy.ndarray` looks awefully much like a Python list (or nested list). Why not simply use Python lists for computations instead of creating a new array type? \n", "\n", "There are several reasons:\n", "\n", "* Python lists are very general. They can contain any kind of object. They are dynamically typed. They do not support mathematical functions such as matrix and dot multiplications, etc. Implementing such functions for Python lists would not be very efficient because of the dynamic typing.\n", "* Numpy arrays are **statically typed** and **homogeneous**. The type of the elements is determined when the array is created.\n", "* Numpy arrays are memory efficient.\n", "* Because of the static typing, fast implementation of mathematical functions such as multiplication and addition of `numpy` arrays can be implemented in a compiled language (C and Fortran is used).\n", "\n", "There are a number of ways to initialize new numpy arrays, for example from\n", "\n", "* a Python list or tuples\n", "* using functions that are dedicated to generating numpy arrays, such as `arange`, `linspace`, etc.\n", "* reading data from files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### From lists" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, to create new vector and matrix arrays from Python lists we can use the `numpy.array` function." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 8])" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# a vector: the argument to the array function is a Python list\n", "v = array([1,2,3,8])\n", "v" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2],\n", " [3, 4]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# a matrix: the argument to the array function is a nested Python list\n", "M = array([[1, 2], [3, 4]])\n", "M" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `v` and `M` objects are both of the type `ndarray` that the `numpy` module provides." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(numpy.ndarray, numpy.ndarray)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(v), type(M)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `ndarray` properties" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference between the `v` and `M` arrays is only their shapes. We can get information about the shape of an array by using the `ndarray.shape` property." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(4,)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v.shape" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, 2)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The number of elements in the array is available through the `ndarray.size` property:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.size" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Equivalently, we could use the function `numpy.shape` and `numpy.size`" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, 2)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape(M)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "size(M)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The number of dimensions of the array is available through the `ndarray.ndim` property:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v.ndim" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.ndim" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.itemsize # bytes per element" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "32" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.nbytes # number of bytes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using the `dtype` (data type) property of an `ndarray`, we can see what type the data of an array has:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype('int64')" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.dtype" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We get an error if we try to assign a value of the wrong type to an element in a numpy array:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "invalid literal for int() with base 10: 'hello'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mM\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"hello\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: invalid literal for int() with base 10: 'hello'" ] } ], "source": [ "M[0,0] = \"hello\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using array-generating functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For larger arrays it is inpractical to initialize the data manually, using explicit python lists. Instead we can use one of the many functions in `numpy` that generate arrays of different forms. Some of the more common are:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### arange" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create a range\n", "x = arange(0, 10, 1) # arguments: start, stop, step\n", "x" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([-1.00000000e+00, -9.00000000e-01, -8.00000000e-01, -7.00000000e-01,\n", " -6.00000000e-01, -5.00000000e-01, -4.00000000e-01, -3.00000000e-01,\n", " -2.00000000e-01, -1.00000000e-01, -2.22044605e-16, 1.00000000e-01,\n", " 2.00000000e-01, 3.00000000e-01, 4.00000000e-01, 5.00000000e-01,\n", " 6.00000000e-01, 7.00000000e-01, 8.00000000e-01, 9.00000000e-01])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = arange(-1, 1, 0.1)\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### linspace and logspace" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ,\n", " 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5, 10. ])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# using linspace, both end points ARE included\n", "linspace(0, 10, 21)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1. , 1.66810054, 2.7825594 , 4.64158883,\n", " 7.74263683, 12.91549665, 21.5443469 , 35.93813664,\n", " 59.94842503, 100. ])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "logspace(0, 2, 10, base=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### meshgrid" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "xa = linspace(-5, 5, 11)\n", "ya = linspace(-3, 3, 7)\n", "x, y = meshgrid(xa, ya) # similar to meshgrid in MATLAB" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.],\n", " [-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.],\n", " [-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.],\n", " [-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.],\n", " [-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.],\n", " [-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.],\n", " [-5., -4., -3., -2., -1., 0., 1., 2., 3., 4., 5.]])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-3., -3., -3., -3., -3., -3., -3., -3., -3., -3., -3.],\n", " [-2., -2., -2., -2., -2., -2., -2., -2., -2., -2., -2.],\n", " [-1., -1., -1., -1., -1., -1., -1., -1., -1., -1., -1.],\n", " [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [ 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.],\n", " [ 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3.]])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### diag" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 0, 0],\n", " [0, 2, 0],\n", " [0, 0, 3]])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# a diagonal matrix\n", "diag([1,2,3])" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0, 1, 0, 0],\n", " [0, 0, 2, 0],\n", " [0, 0, 0, 3],\n", " [0, 0, 0, 0]])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# diagonal with offset from the main diagonal\n", "diag([1,2,3], k=1) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### zeros and ones" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0., 0., 0.],\n", " [0., 0., 0.],\n", " [0., 0., 0.]])" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zeros((3,3))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 1., 1.],\n", " [1., 1., 1.],\n", " [1., 1., 1.]])" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ones((3,3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### identity" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 0., 0.],\n", " [0., 1., 0.],\n", " [0., 0., 1.]])" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "identity(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### zeros_like and ones_like" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n", " [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zeros_like(x)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ones_like(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manipulating arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Indexing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can index elements in an array using square brackets and indices:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# v is a vector, and has only one dimension, taking one index\n", "v[0]" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# M is a matrix, or a 2 dimensional array. Index could be chained\n", "M[1][1]" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# M is a matrix, or a 2 dimensional array, index can take also two indices \n", "M[1,1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we omit an index of a multidimensional array it returns the whole row (or, in general, a N-1 dimensional array) " ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2],\n", " [3, 4]])" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3, 4])" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M[1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same thing can be achieved with using `:` instead of an index: " ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3, 4])" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M[1,:] # row 1" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2, 4])" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M[:,1] # column 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can assign new values to elements in an array using indexing:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "M[0,0] = -1" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-1, 2],\n", " [ 3, 4]])" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "# also works for rows and columns\n", "M[0,:] = 0\n", "M[:,1] = -1" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, -1],\n", " [ 3, -1]])" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Index slicing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Index slicing is the technical name for the syntax `M[lower:upper:step]` to extract part of an array:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 4, 5])" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A = array([1,2,3,4,5])\n", "A" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2, 3])" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[1:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Array slices are *mutable*: if they are assigned a new value the original array from which the slice was extracted is modified:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, -2, -3, 4, 5])" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[1:3] = [-2,-3]\n", "\n", "A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can omit any of the three parameters in `M[lower:upper:step]`:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, -2, -3, 4, 5])" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[::] # lower, upper, step all take the default values" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, -3, 5])" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[::2] # step is 2, lower and upper defaults to the beginning and end of the array" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, -2, -3])" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[:3] # first three elements" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([4, 5])" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[3:] # elements from index 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Negative indices counts from the end of the array (positive index from the begining):" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [], "source": [ "A = array([1,2,3,4,5])" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[-1] # the last element in the array" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3, 4, 5])" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[-3:] # the last three elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Index slicing works exactly the same way for multidimensional arrays:" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 2, 3, 4],\n", " [10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34],\n", " [40, 41, 42, 43, 44]])" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A = array([[n+m*10 for n in range(5)] for m in range(5)]) # note nested list comprehension\n", "A" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[11, 12, 13],\n", " [21, 22, 23],\n", " [31, 32, 33]])" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# a block from the original array\n", "A[1:4, 1:4]" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 2, 4],\n", " [20, 22, 24],\n", " [40, 42, 44]])" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# strides\n", "A[::2, ::2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fancy indexing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fancy indexing is the name for when an array or list is used in-place of an index: " ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34]])" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "row_indices = [1, 2, 3]\n", "A[row_indices]" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([11, 22, 34])" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "col_indices = [1, 2, -1] # remember, index -1 means the last element\n", "A[row_indices, col_indices]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also use index masks: If the index mask is an Numpy array of data type `bool`, then an element is selected (True) or not (False) depending on the value of the index mask at the position of each element: " ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 1, 2, 3, 4])" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B = array([n for n in range(5)])\n", "B" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 2])" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "row_mask = array([True, False, True, False, False])\n", "B[row_mask]" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 2])" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# same thing\n", "row_mask = array([1,0,1,0,0], dtype=bool)\n", "B[row_mask]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This feature is very useful to conditionally select elements from an array, using for example comparison operators:" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. ,\n", " 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = arange(0, 10, 0.5)\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### bitwise opeartions with boolean arrays\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
OperatorFunctionDescription
&bitwise_and(x1, x2)Compute the bit-wise AND of two arrays element-wise.
|bitwise_or(x1, x2)Compute the bit-wise OR of two arrays element-wise.
^bitwise_xor(x1, x2)Compute the bit-wise XOR of two arrays element-wise.
~invert(x)Compute bit-wise inversion, or bit-wise NOT, element-wise.
" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[False False False False False False False False False False False True\n", " True True True False False False False False]\n", "[5.5 6. 6.5 7. ]\n" ] } ], "source": [ "mask = (5 < x) & (x <= 7)\n", "print(mask)\n", "print(x[mask])" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ True True True True True True True True False False False False\n", " False False False True True True True True]\n", "[0. 0.5 1. 1.5 2. 2.5 3. 3.5 7.5 8. 8.5 9. 9.5]\n" ] } ], "source": [ "mask = (x < 4) | (x > 7)\n", "print(mask)\n", "print(x[mask])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Array math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Basic mathematical functions operate elementwise on arrays, and are available both as operator overloads and as functions in the `numpy` module. Vectorizing code is the key to writing efficient numerical calculation with Python/Numpy. That means that as much as possible of a program should be formulated in terms of matrix and vector operations, like matrix-matrix multiplication." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scalar-array operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use the usual arithmetic operators to multiply, add, subtract, and divide arrays with scalar numbers." ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "v1 = arange(0, 5)" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 2, 4, 6, 8])" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v1 * 2" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2, 3, 4, 5, 6])" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v1 + 2" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 2, 4, 6, 8],\n", " [20, 22, 24, 26, 28],\n", " [40, 42, 44, 46, 48],\n", " [60, 62, 64, 66, 68],\n", " [80, 82, 84, 86, 88]])" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A * 2" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 2, 3, 4, 5, 6],\n", " [12, 13, 14, 15, 16],\n", " [22, 23, 24, 25, 26],\n", " [32, 33, 34, 35, 36],\n", " [42, 43, 44, 45, 46]])" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A + 2" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 11, 22, 33, 44],\n", " [11, 22, 33, 44, 55],\n", " [22, 33, 44, 55, 66],\n", " [33, 44, 55, 66, 77],\n", " [44, 55, 66, 77, 88]])" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A + A.T" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above we have used the .T to transpose the matrix object v. We could also have used the transpose function to accomplish the same thing." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Element-wise array-array operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When we add, subtract, multiply and divide arrays with each other, the default behaviour is **element-wise** operations:" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 4, 9, 16],\n", " [ 100, 121, 144, 169, 196],\n", " [ 400, 441, 484, 529, 576],\n", " [ 900, 961, 1024, 1089, 1156],\n", " [1600, 1681, 1764, 1849, 1936]])" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A * A # element-wise multiplication" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 1, 4, 9, 16])" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v1 * v1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we multiply arrays with compatible shapes, we get an element-wise multiplication of each row:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((5, 5), (5,))" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A.shape, v1.shape" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 4, 9, 16],\n", " [ 0, 11, 24, 39, 56],\n", " [ 0, 21, 44, 69, 96],\n", " [ 0, 31, 64, 99, 136],\n", " [ 0, 41, 84, 129, 176]])" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A * v1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Numpy provides many useful functions for performing computations on arrays; one of the most useful is `sum`" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 1 2 3 4]\n", " [10 11 12 13 14]\n", " [20 21 22 23 24]\n", " [30 31 32 33 34]\n", " [40 41 42 43 44]]\n", "Sum of all elements: 550\n", "Sum of each column: [100 105 110 115 120]\n", "Sum of each row: [ 10 60 110 160 210]\n" ] } ], "source": [ "print(A)\n", "print('Sum of all elements:', sum(A))\n", "print('Sum of each column: ', sum(A, axis=0))\n", "print('Sum of each row: ', sum(A, axis=1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Broadcasting\n", "Broadcasting is a powerful mechanism that allows numpy to work with arrays of different shapes when performing arithmetic operations. Frequently we have a smaller array and a larger array, and we want to use the smaller array multiple times to perform some operation on the larger array." ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 1 2 3]\n", " [ 4 5 6]\n", " [ 7 8 9]\n", " [10 11 12]]\n" ] } ], "source": [ "x = array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])\n", "v = array([1, 0, 1])\n", "print(x)" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 2 2 4]\n", " [ 5 5 7]\n", " [ 8 8 10]\n", " [11 11 13]]\n" ] } ], "source": [ "print(x + v)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `x + v` works even though `x` has shape `(4, 3)` and `v` has shape `(3,)` due to broadcasting; this line works as if `v` actually had shape `(4, 3)`, where each row was a copy of `v`, and the sum was performed elementwise. It is similar for other mathematical operations as well." ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 1 0 3]\n", " [ 4 0 6]\n", " [ 7 0 9]\n", " [10 0 12]]\n" ] } ], "source": [ "print(x * v)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Linear algebra" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that unlike MATLAB, `*` is elementwise multiplication, not matrix multiplication. We instead use the `dot` function to compute inner products of vectors, to multiply a vector by a matrix, and to multiply matrices. `dot` is available both as a function in the numpy module and as an instance method of array objects." ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 300, 310, 320, 330, 340],\n", " [1300, 1360, 1420, 1480, 1540],\n", " [2300, 2410, 2520, 2630, 2740],\n", " [3300, 3460, 3620, 3780, 3940],\n", " [4300, 4510, 4720, 4930, 5140]])" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dot(A, A)" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 30, 130, 230, 330, 430])" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dot(A, v1)" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 30, 130, 230, 330, 430])" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A.dot(v1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From **Python 3.5** there is new operator '@' for matrix multiplication and numpy has support for it." ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 30, 130, 230, 330, 430])" ] }, "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A @ v1" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(30, 30, 30)" ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dot(v1,v1), v1.dot(v1), v1 @ v1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, we can cast the array objects to the type `matrix`. This changes the behavior of the standard arithmetic operators `+, -, *` to use matrix algebra." ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [], "source": [ "M = matrix(A)\n", "v = matrix(v1).T # make it a column vector" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "matrix([[0],\n", " [1],\n", " [2],\n", " [3],\n", " [4]])" ] }, "execution_count": 82, "metadata": {}, "output_type": "execute_result" } ], "source": [ "v" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "matrix([[ 300, 310, 320, 330, 340],\n", " [1300, 1360, 1420, 1480, 1540],\n", " [2300, 2410, 2520, 2630, 2740],\n", " [3300, 3460, 3620, 3780, 3940],\n", " [4300, 4510, 4720, 4930, 5140]])" ] }, "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M * M" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "matrix([[ 30],\n", " [130],\n", " [230],\n", " [330],\n", " [430]])" ] }, "execution_count": 84, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M * v" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "matrix([[30]])" ] }, "execution_count": 85, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# inner product\n", "v.T * v" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "matrix([[ 30],\n", " [131],\n", " [232],\n", " [333],\n", " [434]])" ] }, "execution_count": 86, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# with matrix objects, standard matrix algebra applies\n", "v + M*v" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we try to add, subtract or multiply objects with incomplatible shapes we get an error:" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [], "source": [ "v = matrix([1,2,3,4,5,6]).T" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((5, 5), (6, 1))" ] }, "execution_count": 88, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape(M), shape(v)" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "shapes (5,5) and (6,1) not aligned: 5 (dim 1) != 6 (dim 0)", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mM\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/miniconda3/lib/python3.7/site-packages/numpy/matrixlib/defmatrix.py\u001b[0m in \u001b[0;36m__mul__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 218\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mN\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0;31m# This promotes 1-D vectors to row vectors\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 220\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mN\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0masmatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 221\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misscalar\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'__rmul__'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 222\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mN\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36mdot\u001b[0;34m(*args, **kwargs)\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: shapes (5,5) and (6,1) not aligned: 5 (dim 1) != 6 (dim 0)" ] } ], "source": [ "M * v" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "See also the related functions: `inner`, `outer`, `cross`, `kron`, `tensordot`. Try for example `help(kron)`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matrix computations" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "M = array([[1, 2], [3, 4]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Inverse" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [ { "ename": "LinAlgError", "evalue": "Singular matrix", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mLinAlgError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0minv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mM\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# equivalent to M.I when M is matrix\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36minv\u001b[0;34m(*args, **kwargs)\u001b[0m\n", "\u001b[0;32m~/miniconda3/lib/python3.7/site-packages/numpy/linalg/linalg.py\u001b[0m in \u001b[0;36minv\u001b[0;34m(a)\u001b[0m\n\u001b[1;32m 549\u001b[0m \u001b[0msignature\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'D->D'\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misComplexType\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m'd->d'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 550\u001b[0m \u001b[0mextobj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_linalg_error_extobj\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_raise_linalgerror_singular\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 551\u001b[0;31m \u001b[0mainv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_umath_linalg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msignature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0msignature\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextobj\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mextobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 552\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mwrap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mainv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult_t\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 553\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/miniconda3/lib/python3.7/site-packages/numpy/linalg/linalg.py\u001b[0m in \u001b[0;36m_raise_linalgerror_singular\u001b[0;34m(err, flag)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_raise_linalgerror_singular\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflag\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mLinAlgError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Singular matrix\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_raise_linalgerror_nonposdef\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mflag\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mLinAlgError\u001b[0m: Singular matrix" ] } ], "source": [ "inv(M) # equivalent to M.I when M is matrix" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dot(inv(M), M)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Determinant" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "det(M)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "det(inv(M))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data processing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Often it is useful to store datasets in Numpy arrays. Numpy provides a number of functions to calculate statistics of datasets in arrays." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = arange(1, 11)\n", "d" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### mean" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.mean(), mean(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### standard deviations and variance" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.std(), d.var(), std(d), var(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### min and max" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.min(), min(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.max(), max(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### sum, prod, and trace" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# sum up all elements\n", "d.sum(), sum(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# product of all elements\n", "d.prod(), prod(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# cummulative sum\n", "d.cumsum(), cumsum(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# cummulative product\n", "d. cumprod(), cumprod(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# same as: diag(A).sum()\n", "trace(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Calculations with higher-dimensional data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When functions such as `min`, `max`, etc. are applied to a multidimensional arrays, it is sometimes useful to apply the calculation to the entire array, and sometimes only on a row or column basis. Using the `axis` argument we can specify how these functions should behave: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "M = rand(3,4)\n", "M" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# global max\n", "M.max()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# max in each column\n", "M.max(axis=0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# max in each row\n", "M.max(axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Many other functions and methods in the `array` and `matrix` classes accept the same (optional) `axis` keyword argument." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reshaping, resizing and stacking arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The shape of an Numpy array can be modified without copying the underlaying data, which makes it a fast operation even for large arrays." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "M" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "M.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N = M.reshape((6, 2))\n", "N" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "O = M.reshape((1, 12))\n", "O" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N[0:2,:] = 1 # modify the array\n", "N" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "M # and the original variable is also changed. B is only a different view of the same data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "O" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also use the function `flatten` to make a higher-dimensional array into a vector. But this function create a copy of the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "F = M.flatten()\n", "F" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "F[0:5] = 0\n", "F" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "M # now M has not changed, because F's data is a copy of M's, not refering to the same data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Stacking and repeating arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using function `repeat`, `tile`, `vstack`, `hstack`, and `concatenate` we can create larger vectors and matrices from smaller ones:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### tile and repeat" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = array([[1, 2], [3, 4]])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# repeat each element 3 times\n", "repeat(a, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# tile the matrix 3 times \n", "tile(a, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### concatenate" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b = array([[5, 6]])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "concatenate((a, b), axis=0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "concatenate((a, b.T), axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### hstack and vstack" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vstack((a,b))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hstack((a,b.T))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linear equations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "System of linear equations like:\n", "$$\\begin{array}{rcl}x + 2y & = & 5\\\\3x + 4y & = & 7\\end{array}$$\n", "\n", "or\n", "\n", "$$\\begin{pmatrix}1 & 2\\\\3 & 4\\end{pmatrix}\\cdot\\begin{pmatrix}x \\\\ y\\end{pmatrix} = \\begin{pmatrix}5 \\\\ 7\\end{pmatrix}$$\n", "\n", "could be written in matrix form as $\\mathbf {Ax} = \\mathbf b$ and could be solved using numpy `solve`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = array([[1, 2], [3, 4]])\n", "b = array([5,7])\n", "solve(A,b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dot(inv(A),b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Copy and \"deep copy\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To achieve high performance, assignments in Python usually do not copy the underlaying objects. This is important for example when objects are passed between functions, to avoid an excessive amount of memory copying when it is not necessary (technical term: pass by reference). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now B is referring to the same array data as A \n", "B = A " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# changing B affects A\n", "B[0,0] = 10\n", "B" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to avoid this behavior, so that when we get a new completely independent object `B` copied from `A`, then we need to do a so-called \"deep copy\" using the function `copy`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "B = copy(A)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# now, if we modify B, A is not affected\n", "B[0,0] = -5\n", "B" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Iterating over array elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Generally, we want to avoid iterating over the elements of arrays whenever we can (at all costs). The reason is that in a interpreted language like Python (or MATLAB), iterations are really slow compared to vectorized operations. \n", "\n", "However, sometimes iterations are unavoidable. For such cases, the Python `for` loop is the most convenient way to iterate over an array:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v = array([1,2,3,4])\n", "\n", "for element in v:\n", " print(element)" ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Row [1 2]\n", "Element 1\n", "Element 2\n", "Row [3 4]\n", "Element 3\n", "Element 4\n" ] } ], "source": [ "M = array([[1,2], [3,4]])\n", "\n", "for row in M:\n", " print('Row', row)\n", " \n", " for element in row:\n", " print('Element', element)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When we need to iterate over each element of an array and modify its elements, it is convenient to use the `enumerate` function to obtain both the element and its index in the `for` loop: " ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "row_idx 0 row [1 2]\n", "row_idx 0 col_idx 0 Element 1\n", "row_idx 0 col_idx 1 Element 2\n", "row_idx 1 row [3 4]\n", "row_idx 1 col_idx 0 Element 3\n", "row_idx 1 col_idx 1 Element 4\n" ] } ], "source": [ "for row_idx, row in enumerate(M):\n", " print(\"row_idx\", row_idx, \"row\", row)\n", " \n", " for col_idx, element in enumerate(row):\n", " print(\"row_idx\", row_idx, \"col_idx\", col_idx, \"Element\", element)\n", " \n", " # update the matrix M: square each element\n", " M[row_idx, col_idx] = element ** 2" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 4],\n", " [ 9, 16]])" ] }, "execution_count": 94, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# each element in M is now squared\n", "M" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using arrays in conditions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When using arrays in conditions,for example `if` statements and other boolean expressions, one needs to use `any` or `all`, which requires that any or all elements in the array evalutes to `True`:" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 4],\n", " [ 9, 16]])" ] }, "execution_count": 95, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "at least one element in M is larger than 5\n" ] } ], "source": [ "if (M > 5).any():\n", " print(\"at least one element in M is larger than 5\")\n", "else:\n", " print(\"no element in M is larger than 5\")" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "all elements in M are not larger than 5\n" ] } ], "source": [ "if (M > 5).all():\n", " print(\"all elements in M are larger than 5\")\n", "else:\n", " print(\"all elements in M are not larger than 5\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## File I/O\n", "\n", "For all possibilities check documentation on [Input and output](https://docs.scipy.org/doc/numpy/reference/routines.io.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Comma-separated values (CSV)\n", "A very common file format for data files is comma-separated values (CSV), or related formats such as TSV (tab-separated values). To read data from such files into Numpy arrays we can use the `numpy.loadtxt` function. For example we will read historical temperature data measured at Prague Clementinum. Here is how file looks like:" ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "year,month,day,avg,max,min,prec\r\n", "1972,1,1,0.6,2.8,-0.3,0.0\r\n", "1972,1,2,1.6,2.5,-1.4,0.0\r\n", "1972,1,3,3.5,4.0,2.3,0.0\r\n", "1972,1,4,4.0,4.8,2.9,0.2\r\n", "1972,1,5,2.2,3.6,1.3,3.0\r\n", "1972,1,6,0.5,2.3,-1.2,0.0\r\n", "1972,1,7,1.7,2.3,0.8,0.6\r\n", "1972,1,8,1.2,1.8,0.0,1.1\r\n", "1972,1,9,1.7,2.2,0.2,0.0\r\n" ] } ], "source": [ "!head clementinum.csv" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [], "source": [ "# read CSV file, skip one row with headings\n", "data = loadtxt('clementinum.csv', skiprows=1, delimiter=',')" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(16071, 7)" ] }, "execution_count": 100, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's calculate some properties from the Prague temperature dataset used above." ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The daily mean temperature has been 10.77°C.\n", "The highest daily average temperature has been 31.00°C.\n", "The highest measured temperature has been 37.80°C.\n", "The lowest daily average temperature has been -18.00°C.\n", "The lowest measured temperature has been -20.20°C.\n" ] } ], "source": [ "# Prague temperature over the last 43 years\n", "# the average temperature data is in column 3, max in column 4 a min in column 5\n", "print('The daily mean temperature has been {:.2f}°C.'.format(data[:,3].mean()))\n", "print('The highest daily average temperature has been {:.2f}°C.'.format(data[:,3].max()))\n", "print('The highest measured temperature has been {:.2f}°C.'.format(data[:,4].max()))\n", "print('The lowest daily average temperature has been {:.2f}°C.'.format(data[:,3].min()))\n", "print('The lowest measured temperature has been {:.2f}°C.'.format(data[:,5].min()))" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running using Python 3.7.5 (default, Oct 25 2019, 15:51:11) \n", "[GCC 7.3.0]\n", "Testing Python version-> py3.7 OK\n", "Testing numpy... -> numpy OK\n", "Testing scipy ... -> scipy OK\n", "Testing matplotlib... -> pylab OK\n", "Testing sympy -> sympy OK\n", "-----------------------------\n", "All the IPython Notebooks in this lecture series are available at:\n", "https://github.com/ondrolexa/r-python\n" ] } ], "source": [ "!python scripts/footnote.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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.5" } }, "nbformat": 4, "nbformat_minor": 2 }