{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# Introduction to NumPy\n", "\n", "The topic is very broad: datasets can come from a wide range of sources and a wide range of formats, including be collections of documents, collections of images, collections of sound clips, collections of numerical measurements, or nearly anything else.\n", "Despite this apparent heterogeneity, it will help us to think of all data fundamentally as arrays of numbers.\n", "\n", "For this reason, efficient storage and manipulation of numerical arrays is absolutely fundamental to the process of doing data science.\n", "\n", "NumPy (short for *Numerical Python*) provides an efficient interface to store and operate on dense data buffers.\n", "In some ways, NumPy arrays are like Python's built-in ``list`` type, but NumPy arrays provide much more efficient storage and data operations as the arrays grow larger in size.\n", "NumPy arrays form the core of nearly the entire ecosystem of data science tools in Python, so time spent learning to use NumPy effectively will be valuable no matter what aspect of data science interests you." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'1.19.5'" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy\n", "numpy.__version__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By convention, you'll find that most people in the SciPy/PyData world will import NumPy using ``np`` as an alias:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Throughout this chapter, and indeed the rest of the book, you'll find that this is the way we will import and use NumPy." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Understanding Data Types in Python\n", "\n", "Effective data-driven science and computation requires understanding how data is stored and manipulated.\n", "\n", "Here we outlines and contrasts how arrays of data are handled in the Python language itself, and how NumPy improves on this.\n", "\n", "Python offers several different options for storing data in efficient, fixed-type data buffers.\n", "The built-in ``array`` module (available since Python 3.3) can be used to create dense arrays of a uniform type:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import array\n", "L = list(range(10))\n", "A = array.array('i', L)\n", "A" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array.array" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(A)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[x ** 2 for x in range(10)]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "list" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(_)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here ``'i'`` is a type code indicating the contents are integers.\n", "\n", "\n", "\n", "Much more useful, however, is the ``ndarray`` object of the NumPy package.\n", "While Python's ``array`` object provides efficient storage of array-based data, NumPy adds to this efficient *operations* on that data." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Creating Arrays from Python Lists\n", "\n", "First, we can use ``np.array`` to create arrays from Python lists:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 4, 2, 5, 3])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([1, 4, 2, 5, 3])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember that unlike Python lists, NumPy is constrained to arrays that all contain the same type.\n", "If types do not match, NumPy will upcast if possible (here, integers are up-cast to floating point):" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3.14, 4. , 2. , 3. ])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([3.14, 4, 2, 3])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to explicitly set the data type of the resulting array, we can use the ``dtype`` keyword:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1., 2., 3., 4.], dtype=float32)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([1, 2, 3, 4], dtype='float32')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Creating Arrays from Scratch\n", "\n", "Especially for larger arrays, it is more efficient to create arrays from scratch using routines built into NumPy:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.zeros(10, dtype=int)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1.],\n", " [1., 1., 1., 1., 1.]])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.ones((3, 5), dtype=float)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[3.14, 3.14, 3.14, 3.14, 3.14],\n", " [3.14, 3.14, 3.14, 3.14, 3.14],\n", " [3.14, 3.14, 3.14, 3.14, 3.14]])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.full((3, 5), 3.14)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.arange(0, 20, 2)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([0. , 0.25, 0.5 , 0.75, 1. ])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.linspace(0, 1, 5)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.49760049, 0.67705904, 0.59093804],\n", " [0.99268699, 0.42792808, 0.8336333 ],\n", " [0.44928886, 0.70924885, 0.1681015 ]])" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.random.random((3, 3))" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-0.84044642, 1.54753956, -0.023514 ],\n", " [ 1.09749938, 0.70455525, 0.57204258],\n", " [ 0.47691043, 0.89482679, -2.07735954]])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.random.normal(0, 1, (3, 3))" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 0., 0.],\n", " [0., 1., 0.],\n", " [0., 0., 1.]])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.eye(3)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## NumPy Standard Data Types\n", "\n", "NumPy arrays contain values of a single type, so have a look at those types and their bounds:\n", "\n", "\n", "| Data type\t | Description |\n", "|---------------|-------------|\n", "| ``bool_`` | Boolean (True or False) stored as a byte |\n", "| ``int_`` | Default integer type (same as C ``long``; normally either ``int64`` or ``int32``)| \n", "| ``intc`` | Identical to C ``int`` (normally ``int32`` or ``int64``)| \n", "| ``intp`` | Integer used for indexing (same as C ``ssize_t``; normally either ``int32`` or ``int64``)| \n", "| ``int8`` | Byte (-128 to 127)| \n", "| ``int16`` | Integer (-32768 to 32767)|\n", "| ``int32`` | Integer (-2147483648 to 2147483647)|\n", "| ``int64`` | Integer (-9223372036854775808 to 9223372036854775807)| \n", "| ``uint8`` | Unsigned integer (0 to 255)| \n", "| ``uint16`` | Unsigned integer (0 to 65535)| \n", "| ``uint32`` | Unsigned integer (0 to 4294967295)| \n", "| ``uint64`` | Unsigned integer (0 to 18446744073709551615)| \n", "| ``float_`` | Shorthand for ``float64``.| \n", "| ``float16`` | Half precision float: sign bit, 5 bits exponent, 10 bits mantissa| \n", "| ``float32`` | Single precision float: sign bit, 8 bits exponent, 23 bits mantissa| \n", "| ``float64`` | Double precision float: sign bit, 11 bits exponent, 52 bits mantissa| \n", "| ``complex_`` | Shorthand for ``complex128``.| \n", "| ``complex64`` | Complex number, represented by two 32-bit floats| \n", "| ``complex128``| Complex number, represented by two 64-bit floats| " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## INTERMEZZO" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[10000, 6561, 4096, 2401, 1296, 625, 256, 81, 16, 1]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[x**4 for i, x in enumerate(range(10, 0, -1))]" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[10000, 6561, 4096, 2401, 1296, 625, 256, 81, 16, 1]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[81, 256]" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ _**4 for (x, _, _) in [(1, 2, 3), (2, 3, 4)]]" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(1, 8), (16, 27)]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ tuple([x**4, y**3]) for (x, y, _) in [(1, 2, 3), (2, 3, 4)]]" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "a = (2, 3, 4)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'tuple' object has no attribute 'append'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\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[0ma\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'tuple' object has no attribute 'append'" ] } ], "source": [ "a.append(5)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, 3, 4, 5)" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b = a + (5,)\n", "b" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "assert a != b" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((1, 2, 3), [1, 2, 3])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(1,2,3), [1, 2, 3]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0,\n", " 1,\n", " 2,\n", " 3,\n", " 4,\n", " 5,\n", " 6,\n", " 7,\n", " 8,\n", " 9,\n", " 10,\n", " 11,\n", " 12,\n", " 13,\n", " 14,\n", " 15,\n", " 16,\n", " 17,\n", " 18,\n", " 19,\n", " 20,\n", " 21,\n", " 22,\n", " 23,\n", " 24,\n", " 25,\n", " 26,\n", " 27,\n", " 28,\n", " 29,\n", " 30,\n", " 31,\n", " 32,\n", " 33,\n", " 34,\n", " 35,\n", " 36,\n", " 37,\n", " 38,\n", " 39,\n", " 40,\n", " 41,\n", " 42,\n", " 43,\n", " 44,\n", " 45,\n", " 46,\n", " 47,\n", " 48,\n", " 49,\n", " 50,\n", " 51,\n", " 52,\n", " 53,\n", " 54,\n", " 55,\n", " 56,\n", " 57,\n", " 58,\n", " 59,\n", " 60,\n", " 61,\n", " 62,\n", " 63,\n", " 64,\n", " 65,\n", " 66,\n", " 67,\n", " 68,\n", " 69,\n", " 70,\n", " 71,\n", " 72,\n", " 73,\n", " 74,\n", " 75,\n", " 76,\n", " 77,\n", " 78,\n", " 79,\n", " 80,\n", " 81,\n", " 82,\n", " 83,\n", " 84,\n", " 85,\n", " 86,\n", " 87,\n", " 88,\n", " 89,\n", " 90,\n", " 91,\n", " 92,\n", " 93,\n", " 94,\n", " 95,\n", " 96,\n", " 97,\n", " 98,\n", " 99)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tuple(range(100))" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "def A(a, b=0, c=1):\n", " return a+b+c" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A(1, 2,)" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{1, 2, 23}" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{1, 2, 23,}" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "L = [\n", " '/my/path/to/an/interesting/file0',\n", " '/my/path/to/an/interesting/file1',\n", " '/my/path/to/an/interesting/file2',\n", " '/my/path/to/an/interesting/file3',\n", " '/my/path/to/an/interesting/file4',\n", " '/my/path/to/an/interesting/file5',\n", "]" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['/my/path/to/an/interesting/file0',\n", " '/my/path/to/an/interesting/file1',\n", " '/my/path/to/an/interesting/file2',\n", " '/my/path/to/an/interesting/file3',\n", " '/my/path/to/an/interesting/file4',\n", " '/my/path/to/an/interesting/file5']" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "L" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[, 3, 3.14, 'hello world']" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[object(), 3, 3.14, 'hello world']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# The Basics of NumPy Arrays\n", "\n", "Data manipulation in Python is nearly synonymous with NumPy array manipulation: even newer tools like Pandas are built around the NumPy array.\n", "\n", "- *Attributes of arrays*: Determining the size, shape, memory consumption, and data types of arrays\n", "- *Indexing of arrays*: Getting and setting the value of individual array elements\n", "- *Slicing of arrays*: Getting and setting smaller subarrays within a larger array\n", "- *Reshaping of arrays*: Changing the shape of a given array\n", "- *Joining and splitting of arrays*: Combining multiple arrays into one, and splitting one array into many\n", "- *Universal functions and broadcasting*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## NumPy Array Attributes\n", "\n", "First let's discuss some useful array attributes.\n", "We'll start by defining three random arrays, a one-dimensional, two-dimensional, and three-dimensional array:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "np.random.seed(0) # seed for reproducibility\n", "\n", "x1 = np.random.randint(10, size=6) # One-dimensional array\n", "x2 = np.random.randint(10, size=(3, 4)) # Two-dimensional array\n", "x3 = np.random.randint(10, size=(3, 4, 5)) # Three-dimensional array" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Each array has attributes ``ndim`` (the number of dimensions), ``shape`` (the size of each dimension), ``size`` (the total size of the array) and ``dtype`` (the data type of the array):" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x3 ndim: 3\n", "x3 shape: (3, 4, 5)\n", "x3 size: 60\n", "dtype: int64\n" ] } ], "source": [ "print(\"x3 ndim: \", x3.ndim)\n", "print(\"x3 shape:\", x3.shape)\n", "print(\"x3 size: \", x3.size)\n", "print(\"dtype:\", x3.dtype)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Array Indexing: Accessing Single Elements\n", "\n", "In a one-dimensional array, the $i^{th}$ value (counting from zero) can be accessed by specifying the desired index in square brackets, just as with Python lists:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([5, 0, 3, 3, 7, 9])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x1" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x1[0]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x1[-1] # To index from the end of the array, you can use negative indices." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "In a multi-dimensional array, items can be accessed using a comma-separated tuple of indices:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[3, 5, 2, 4],\n", " [7, 6, 8, 8],\n", " [1, 6, 7, 7]])" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2[0, 0]" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2[2, -1]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Values can also be modified using any of the above index notation:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[12, 5, 2, 4],\n", " [ 7, 6, 8, 8],\n", " [ 1, 6, 7, 7]])" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2[0, 0] = 12\n", "x2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keep in mind that, unlike Python lists, NumPy arrays have a fixed type." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3, 0, 3, 3, 7, 9])" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x1[0] = 3.14159 # this will be truncated!\n", "x1" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Array Slicing: Accessing Subarrays\n", "\n", "Just as we can use square brackets to access individual array elements, we can also use them to access subarrays with the *slice* notation, marked by the colon (``:``) character.\n", "\n", "The NumPy slicing syntax follows that of the standard Python list; to access a slice of an array ``x``, use this:\n", "``` python\n", "x[start:stop:step]\n", "```\n", "If any of these are unspecified, they default to the values ``start=0``, ``stop=``*``size of dimension``*, ``step=1``.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### One-dimensional subarrays" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.arange(10)\n", "x" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 1, 2, 3, 4])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[:5] # first five elements" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([5, 6, 7, 8, 9])" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[5:] # elements after index 5" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([4, 5, 6])" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[4:7] # middle sub-array" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 2, 4, 6, 8])" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[::2] # every other element" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 3, 5, 7, 9])" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[1::2] # every other element, starting at index 1" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "A potentially confusing case is when the ``step`` value is negative.\n", "In this case, the defaults for ``start`` and ``stop`` are swapped.\n", "This becomes a convenient way to reverse an array:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[::-1] # all elements, reversed" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([5, 3, 1])" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[5::-2] # reversed every other from index 5" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Multi-dimensional subarrays\n", "\n", "Multi-dimensional slices work in the same way, with multiple slices separated by commas:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[12, 5, 2, 4],\n", " [ 7, 6, 8, 8],\n", " [ 1, 6, 7, 7]])" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[12, 5, 2],\n", " [ 7, 6, 8]])" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2[:2, :3] # two rows, three columns" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[12, 2],\n", " [ 7, 8],\n", " [ 1, 7]])" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2[:3, ::2] # all rows, every other column" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 7, 7, 6, 1],\n", " [ 8, 8, 6, 7],\n", " [ 4, 2, 5, 12]])" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2[::-1, ::-1]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Accessing array rows and columns\n", "\n", "One commonly needed routine is accessing of single rows or columns of an array:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[12 7 1]\n" ] } ], "source": [ "print(x2[:, 0]) # first column of x2" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[12 5 2 4]\n" ] } ], "source": [ "print(x2[0, :]) # first row of x2" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[12 5 2 4]\n" ] } ], "source": [ "print(x2[0]) # equivalent to x2[0, :]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Subarrays as no-copy views\n", "\n", "One important–and extremely useful–thing to know about array slices is that they return *views* rather than *copies* of the array data.\n", "\n", "This is one area in which NumPy array slicing differs from Python list slicing: in lists, slices will be copies." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[12, 5, 2, 4],\n", " [ 7, 6, 8, 8],\n", " [ 1, 6, 7, 7]])" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([[12, 5],\n", " [ 7, 6]])" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2_sub = x2[:2, :2]\n", "x2_sub" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[99, 5, 2, 4],\n", " [ 7, 6, 8, 8],\n", " [ 1, 6, 7, 7]])" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x2_sub[0, 0] = 99 # if we modify this subarray, the original array is changed too\n", "x2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is sometimes useful to instead explicitly copy the data within an array or a subarray. This can be most easily done with the ``copy()`` method." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Reshaping of Arrays\n", "\n", "If you want to put the numbers 1 through 9 in a $3 \\times 3$ grid:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 4, 5, 6, 7, 8, 9])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.arange(1, 10)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(9,)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_.shape" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3],\n", " [4, 5, 6],\n", " [7, 8, 9]])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "__.reshape((3, 3))" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.array([1, 2, 3])\n", "x" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.shape" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3]])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.reshape((1, 3)) # row vector via reshape" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 3)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_.shape" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.shape # therefore `reshape` doesn't modify in place the array we are working on" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3]])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[np.newaxis, :] # row vector via newaxis" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 3)" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_.shape" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.shape" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1],\n", " [2],\n", " [3]])" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.reshape((3, 1)) # column vector via reshape" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3, 1)" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_.shape" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.shape" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1],\n", " [2],\n", " [3]])" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[:, np.newaxis] # column vector via newaxis" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3, 1)" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_.shape" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.shape" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Concatenation of arrays\n", "\n", "``np.concatenate`` takes a tuple or list of arrays as its first argument:" ] }, { "cell_type": "code", "execution_count": 105, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 3, 2, 1])" ] }, "execution_count": 105, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.array([1, 2, 3])\n", "y = np.array([3, 2, 1])\n", "np.concatenate([x, y])" ] }, { "cell_type": "code", "execution_count": 106, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, 2, 3, 3, 2, 1, 99, 99, 99])" ] }, "execution_count": 106, "metadata": {}, "output_type": "execute_result" } ], "source": [ "z = [99, 99, 99]\n", "np.concatenate([x, y, z])" ] }, { "cell_type": "code", "execution_count": 107, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "grid = np.array([[1, 2, 3],\n", " [4, 5, 6]])" ] }, { "cell_type": "code", "execution_count": 108, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3],\n", " [4, 5, 6],\n", " [1, 2, 3],\n", " [4, 5, 6]])" ] }, "execution_count": 108, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.concatenate([grid, grid]) # concatenate along the first axis" ] }, { "cell_type": "code", "execution_count": 109, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3, 1, 2, 3],\n", " [4, 5, 6, 4, 5, 6]])" ] }, "execution_count": 109, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.concatenate([grid, grid], axis=1) # concatenate along the second axis (zero-indexed)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "For working with arrays of mixed dimensions, it can be clearer to use the ``np.vstack`` (vertical stack) and ``np.hstack`` (horizontal stack) functions:" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3],\n", " [9, 8, 7],\n", " [6, 5, 4]])" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.array([1, 2, 3])\n", "grid = np.array([[9, 8, 7],\n", " [6, 5, 4]])\n", "\n", "np.vstack([x, grid]) # vertically stack the arrays" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 9, 8, 7, 99],\n", " [ 6, 5, 4, 99]])" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y = np.array([[99],\n", " [99]])\n", "np.hstack([grid, y]) # horizontally stack the arrays" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Splitting of arrays\n", "\n", "The opposite of concatenation is splitting, we can pass a list of indices giving the split points:" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 2 3] [99 99] [3 2 1]\n" ] } ], "source": [ "x = [1, 2, 3, 99, 99, 3, 2, 1]\n", "x1, x2, x3 = np.split(x, [3, 5])\n", "print(x1, x2, x3)" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 2, 3],\n", " [ 4, 5, 6, 7],\n", " [ 8, 9, 10, 11],\n", " [12, 13, 14, 15]])" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "grid = np.arange(16).reshape((4, 4))\n", "grid" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[array([[0, 1, 2, 3],\n", " [4, 5, 6, 7]]),\n", " array([[ 8, 9, 10, 11],\n", " [12, 13, 14, 15]])]" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.vsplit(grid, [2])" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[array([[ 0, 1],\n", " [ 4, 5],\n", " [ 8, 9],\n", " [12, 13]]),\n", " array([[ 2, 3],\n", " [ 6, 7],\n", " [10, 11],\n", " [14, 15]])]" ] }, "execution_count": 68, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.hsplit(grid, [2])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Computation on NumPy Arrays: Universal Functions\n", "\n", "`Numpy` provides an easy and flexible interface to optimized computation with arrays of data.\n", "\n", "The key to making it fast is to use *vectorized* operations, generally implemented through NumPy's *universal functions* (ufuncs)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## The Slowness of Loops\n", "\n", "Python's default implementation (known as CPython) does some operations very slowly, this is in part due to the dynamic, interpreted nature of the language.\n", "\n", "The relative sluggishness of Python generally manifests itself in situations where many small operations are being repeated – for instance looping over arrays to operate on each element.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "For example, pretend to compute the reciprocal of values contained in a array:" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.16666667, 1. , 0.25 , 0.25 , 0.125 ])" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.random.seed(0)\n", "\n", "def compute_reciprocals(values):\n", " output = np.empty(len(values))\n", " for i in range(len(values)):\n", " output[i] = 1.0 / values[i]\n", " return output\n", " \n", "values = np.random.randint(1, 10, size=5)\n", "compute_reciprocals(values)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "If we measure the execution time of this code for a large input, we see that this operation is very slow, perhaps surprisingly so!" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2.63 s ± 29.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "big_array = np.random.randint(1, 100, size=1000000)\n", "%timeit compute_reciprocals(big_array)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It takes $2.63$ seconds to compute these million operations and to store the result.\n", "\n", "It turns out that the bottleneck here is not the operations themselves, but the type-checking and function dispatches that CPython must do at each cycle of the loop.\n", "\n", "If we were working in compiled code instead, this type specification would be known before the code executes and the result could be computed much more efficiently." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Introducing UFuncs\n", "\n", "For many types of operations, NumPy provides a convenient interface into just this kind of compiled routine. \n", "\n", "This is known as a *vectorized* operation.\n", "\n", "This can be accomplished by performing an operation on the array, which will then be applied to each element." ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2.97 ms ± 35.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%timeit (1.0 / big_array)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vectorized operations in NumPy are implemented via *ufuncs*, whose main purpose is to quickly execute repeated operations on values in NumPy arrays." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "\n", "Ufuncs are extremely flexible – before we saw an operation between a scalar and an array, but we can also operate between two arrays:" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0. , 0.5 , 0.66666667, 0.75 , 0.8 ])" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.arange(5) / np.arange(1, 6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And ufunc operations are not limited to one-dimensional arrays–they can also act on multi-dimensional arrays as well:" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 2, 4],\n", " [ 8, 16, 32],\n", " [ 64, 128, 256]])" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.arange(9).reshape((3, 3))\n", "2 ** x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Any time you see such a loop in a Python script, you should consider whether it can be replaced with a vectorized expression._" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Array arithmetic\n", "\n", "NumPy's ufuncs feel very natural to use because they make use of Python's native arithmetic operators:" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = [0 1 2 3]\n", "x + 5 = [5 6 7 8]\n", "x - 5 = [-5 -4 -3 -2]\n", "x * 2 = [0 2 4 6]\n", "x / 2 = [0. 0.5 1. 1.5]\n", "x // 2 = [0 0 1 1]\n", "-x = [ 0 -1 -2 -3]\n", "x ** 2 = [0 1 4 9]\n", "x % 2 = [0 1 0 1]\n" ] } ], "source": [ "x = np.arange(4)\n", "print(\"x =\", x)\n", "print(\"x + 5 =\", x + 5)\n", "print(\"x - 5 =\", x - 5)\n", "print(\"x * 2 =\", x * 2)\n", "print(\"x / 2 =\", x / 2)\n", "print(\"x // 2 =\", x // 2) # floor division\n", "print(\"-x = \", -x)\n", "print(\"x ** 2 = \", x ** 2)\n", "print(\"x % 2 = \", x % 2)" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([-1. , -2.25, -4. , -6.25])" ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "-(0.5*x + 1) ** 2 # can be strung together also" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Trigonometric functions\n", "\n", "`NumPy` provides a large number of useful ufuncs, we'll start by defining an array of angles:" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [], "source": [ "theta = np.linspace(0, np.pi, 3)" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "theta = [0. 1.57079633 3.14159265]\n", "sin(theta) = [0.0000000e+00 1.0000000e+00 1.2246468e-16]\n", "cos(theta) = [ 1.000000e+00 6.123234e-17 -1.000000e+00]\n", "tan(theta) = [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]\n" ] } ], "source": [ "print(\"theta = \", theta)\n", "print(\"sin(theta) = \", np.sin(theta))\n", "print(\"cos(theta) = \", np.cos(theta))\n", "print(\"tan(theta) = \", np.tan(theta))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Exponents and logarithms\n", "\n", "Another common `NumPy` ufunc are the exponentials (that are useful for maintaining precision with very small inputs)" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = [1, 2, 3]\n", "e^x = [ 2.71828183 7.3890561 20.08553692]\n", "2^x = [2. 4. 8.]\n", "3^x = [ 3 9 27]\n" ] } ], "source": [ "x = [1, 2, 3]\n", "print(\"x =\", x)\n", "print(\"e^x =\", np.exp(x))\n", "print(\"2^x =\", np.exp2(x))\n", "print(\"3^x =\", np.power(3, x))" ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = [1, 2, 4, 10]\n", "ln(x) = [0. 0.69314718 1.38629436 2.30258509]\n", "log2(x) = [0. 1. 2. 3.32192809]\n", "log10(x) = [0. 0.30103 0.60205999 1. ]\n" ] } ], "source": [ "x = [1, 2, 4, 10]\n", "print(\"x =\", x)\n", "print(\"ln(x) =\", np.log(x))\n", "print(\"log2(x) =\", np.log2(x))\n", "print(\"log10(x) =\", np.log10(x))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Specifying output\n", "\n", "For large calculations, it is sometimes useful to be able to specify the array where the result of the calculation will be stored:" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0. 10. 20. 30. 40.]\n" ] } ], "source": [ "x = np.arange(5)\n", "y = np.empty(5)\n", "np.multiply(x, 10, out=y)\n", "print(y)" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 1. 0. 2. 0. 4. 0. 8. 0. 16. 0.]\n" ] } ], "source": [ "y = np.zeros(10)\n", "np.power(2, x, out=y[::2])\n", "print(y)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Outer products\n", "\n", "Finally, any ufunc can compute the output of all pairs of two different inputs using the ``outer`` method:" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 2, 3, 4, 5],\n", " [ 2, 4, 6, 8, 10],\n", " [ 3, 6, 9, 12, 15],\n", " [ 4, 8, 12, 16, 20],\n", " [ 5, 10, 15, 20, 25]])" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.arange(1, 6)\n", "np.multiply.outer(x, x)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Aggregations: Min, Max, and Everything In Between" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "\n", "## Summing the Values in an Array\n", "\n", "As a quick example, consider computing the sum of all values in an array.\n", "Python itself can do this using the built-in ``sum`` function:" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "54.43983466916921" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "L = np.random.random(100)\n", "sum(L)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "54.439834669169194" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sum(L)" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "222 ms ± 6.88 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", "791 µs ± 7.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" ] } ], "source": [ "big_array = np.random.rand(1_000_000)\n", "%timeit sum(big_array)\n", "%timeit np.sum(big_array)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Minimum and Maximum\n", "\n", "Similarly, Python has built-in ``min`` and ``max`` functions:" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7.071203171893359e-07, 0.9999997207656334)" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "min(big_array), max(big_array)" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7.071203171893359e-07, 0.9999997207656334)" ] }, "execution_count": 94, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.min(big_array), np.max(big_array)" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "118 ms ± 1.78 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", "656 µs ± 8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" ] } ], "source": [ "%timeit min(big_array)\n", "%timeit np.min(big_array)" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7.071203171893359e-07, 0.9999997207656334, 500216.8034810001)" ] }, "execution_count": 97, "metadata": {}, "output_type": "execute_result" } ], "source": [ "big_array.min(), big_array.max(), big_array.sum()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Multi dimensional aggregates\n", "\n", "One common type of aggregation operation is an aggregate along a row or column:" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.07452786, 0.41843762, 0.99939192, 0.66974416],\n", " [0.54717434, 0.82711104, 0.23097044, 0.16283152],\n", " [0.27950484, 0.58540569, 0.90657413, 0.18671025]])" ] }, "execution_count": 99, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M = np.random.random((3, 4))\n", "M" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.888383818472106" ] }, "execution_count": 100, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.sum() # By default, each NumPy aggregation function works on the whole array" ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.07452786, 0.41843762, 0.23097044, 0.16283152])" ] }, "execution_count": 101, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.min(axis=0) # specifying the axis along which the aggregate is computed" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.99939192, 0.82711104, 0.90657413])" ] }, "execution_count": 102, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.max(axis=1) # find the maximum value within each row" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Other aggregation functions\n", "\n", "Additionally, most aggregates have a ``NaN``-safe counterpart that computes the result while ignoring missing values, which are marked by the special IEEE floating-point ``NaN`` value \n", "\n", "|Function Name | NaN-safe Version | Description |\n", "|-------------------|---------------------|-----------------------------------------------|\n", "| ``np.sum`` | ``np.nansum`` | Compute sum of elements |\n", "| ``np.prod`` | ``np.nanprod`` | Compute product of elements |\n", "| ``np.mean`` | ``np.nanmean`` | Compute mean of elements |\n", "| ``np.std`` | ``np.nanstd`` | Compute standard deviation |\n", "| ``np.var`` | ``np.nanvar`` | Compute variance |\n", "| ``np.min`` | ``np.nanmin`` | Find minimum value |\n", "| ``np.max`` | ``np.nanmax`` | Find maximum value |\n", "| ``np.argmin`` | ``np.nanargmin`` | Find index of minimum value |\n", "| ``np.argmax`` | ``np.nanargmax`` | Find index of maximum value |\n", "| ``np.median`` | ``np.nanmedian`` | Compute median of elements |\n", "| ``np.percentile`` | ``np.nanpercentile``| Compute rank-based statistics of elements |\n", "| ``np.any`` | N/A | Evaluate whether any elements are true |\n", "| ``np.all`` | N/A | Evaluate whether all elements are true |" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Computation on Arrays: Broadcasting\n", "\n", "Another means of vectorizing operations is to use NumPy's *broadcasting* functionality.\n", "\n", "Broadcasting is simply a set of rules for applying binary ufuncs (e.g., addition, subtraction, multiplication, etc.) on arrays of different sizes." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "\n", "## Introducing Broadcasting\n", "\n", "Recall that for arrays of the same size, binary operations are performed on an element-by-element basis:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([5, 6, 7])" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = np.array([0, 1, 2])\n", "b = np.array([5, 5, 5])\n", "a + b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Broadcasting allows these types of binary operations to be performed on arrays of different sizes:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([5, 6, 7])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a + 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can think of this as an operation that stretches or duplicates the value ``5`` into the array ``[5, 5, 5]``, and adds the results; the advantage of NumPy's broadcasting is that this duplication of values does not actually take place." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "We can similarly extend this to arrays of higher dimensions:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 1., 1.],\n", " [1., 1., 1.],\n", " [1., 1., 1.]])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M = np.ones((3, 3))\n", "M" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 2., 3.],\n", " [1., 2., 3.],\n", " [1., 2., 3.]])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M + a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here the one-dimensional array ``a`` is stretched, or broadcast across the second dimension in order to match the shape of ``M``.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "More complicated cases can involve broadcasting of both arrays:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([0, 1, 2]),\n", " array([[0],\n", " [1],\n", " [2]]))" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = np.arange(3)\n", "b = np.arange(3)[:, np.newaxis]\n", "a, b" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0, 1, 2],\n", " [1, 2, 3],\n", " [2, 3, 4]])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a + b" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Rules of Broadcasting\n", "\n", "Broadcasting in NumPy follows a strict set of rules to determine the interaction between the two arrays:\n", "\n", "- Rule 1: If the two arrays differ in their number of dimensions, the shape of the one with fewer dimensions is *padded* with ones on its leading (left) side.\n", "- Rule 2: If the shape of the two arrays does not match in any dimension, the array with shape equal to 1 in that dimension is stretched to match the other shape.\n", "- Rule 3: If in any dimension the sizes disagree and neither is equal to 1, an error is raised.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Centering an array\n", "\n", "Imagine you have an array of 10 observations, each of which consists of 3 values, we'll store this in a $10 \\times 3$ array:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "X = np.random.random((10, 3))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.55965135, 0.52179051, 0.41008518])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Xmean = X.mean(0)\n", "Xmean" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "X_centered = X - Xmean" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([-6.66133815e-17, 3.33066907e-17, -7.77156117e-17])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_centered.mean(0) # To double-check, we can check that the centered array has near 0 means." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Plotting a two-dimensional function\n", "\n", "One place that broadcasting is very useful is in displaying images based on two-dimensional functions.\n", "If we want to define a function $z = f(x, y)$, broadcasting can be used to compute the function across the grid:" ] }, { "cell_type": "code", "execution_count": 117, "metadata": {}, "outputs": [], "source": [ "steps = 500\n", "x = np.linspace(0, 5, steps) # # x and y have 500 steps from 0 to 5\n", "y = np.linspace(0, 5, steps)[:, np.newaxis]\n", "z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)" ] }, { "cell_type": "code", "execution_count": 116, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "plt.imshow(z, origin='lower', extent=[0, 5, 0, 5], cmap='viridis')\n", "plt.colorbar();" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# Comparisons, Masks, and Boolean Logic\n", "\n", "Masking comes up when you want to extract, modify, count, or otherwise manipulate values in an array based on some criterion: for example, you might wish to count all values greater than a certain value, or perhaps remove all outliers that are above some threshold.\n", "In NumPy, Boolean masking is often the most efficient way to accomplish these types of tasks." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Comparison Operators as ufuncs" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [], "source": [ "x = np.array([1, 2, 3, 4, 5])" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ True, True, False, False, False])" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x < 3 # less than" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([False, False, False, True, True])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x > 3 # greater than" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ True, True, False, True, True])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x != 3 # not equal" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([False, True, False, False, False])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(2 * x) == (x ** 2)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Just as in the case of arithmetic ufuncs, these will work on arrays of any size and shape:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[5, 0, 3, 3],\n", " [7, 9, 3, 5],\n", " [2, 4, 7, 6]])" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rng = np.random.RandomState(0)\n", "x = rng.randint(10, size=(3, 4))\n", "x" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ True, True, True, True],\n", " [False, False, True, True],\n", " [ True, True, False, False]])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x < 6" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Counting entries\n", "\n", "To count the number of ``True`` entries in a Boolean array, ``np.count_nonzero`` is useful:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.count_nonzero(x < 6) # how many values less than 6?" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sum(x < 6)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([4, 2, 2])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sum(x < 6, axis=1) # how many values less than 6 in each row?" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.any(x > 8) # are there any values greater than 8?" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.any(x < 0) # are there any values less than zero?" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.all(x < 10) # are all values less than 10?" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ True, False, True])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.all(x < 8, axis=1) # are all values in each row less than 8?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Boolean Arrays as Masks\n", "\n", "A more powerful pattern is to use Boolean arrays as masks, to select particular subsets of the data themselves:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[5, 0, 3, 3],\n", " [7, 9, 3, 5],\n", " [2, 4, 7, 6]])" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[False, True, True, True],\n", " [False, False, True, False],\n", " [ True, True, False, False]])" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x < 5" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 3, 3, 3, 2, 4])" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[x < 5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is returned is a one-dimensional array filled with all the values that meet this condition; in other words, all the values in positions at which the mask array is ``True``.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Fancy Indexing\n", "\n", "We saw how to access and modify portions of arrays using simple indices (e.g., ``arr[0]``), slices (e.g., ``arr[:5]``), and Boolean masks (e.g., ``arr[arr > 0]``).\n", "\n", "We'll look at another style of array indexing, known as *fancy indexing*, that is like the simple indexing we've already seen, but we pass arrays of indices in place of single scalars." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Fancy indexing is conceptually simple: it means passing an array of indices to access multiple array elements at once:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([51, 92, 14, 71, 60, 20, 82, 86, 74, 74])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rand = np.random.RandomState(42)\n", "\n", "x = rand.randint(100, size=10)\n", "x" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[71, 86, 14]" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[x[3], x[7], x[2]] # Suppose we want to access three different elements." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([71, 86, 60])" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ind = [3, 7, 4]\n", "x[ind] # Alternatively, we can pass a single list or array of indices" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "When using fancy indexing, the shape of the result reflects the shape of the index arrays rather than the shape of the array being indexed:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[71, 86],\n", " [60, 20]])" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ind = np.array([[3, 7],\n", " [4, 5]])\n", "x[ind]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Fancy indexing also works in multiple dimensions:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 2, 3],\n", " [ 4, 5, 6, 7],\n", " [ 8, 9, 10, 11]])" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X = np.arange(12).reshape((3, 4))\n", "X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like with standard indexing, the first index refers to the row, and the second to the column:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 2, 5, 11])" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "row = np.array([0, 1, 2])\n", "col = np.array([2, 1, 3])\n", "X[row, col]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The pairing of indices in fancy indexing follows all the broadcasting rules that we've already seen:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 2, 1, 3],\n", " [ 6, 5, 7],\n", " [10, 9, 11]])" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X[row[:, np.newaxis], col]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "each row value is matched with each column vector, exactly as we saw in broadcasting of arithmetic operations" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0, 0, 0],\n", " [2, 1, 3],\n", " [4, 2, 6]])" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "row[:, np.newaxis] * col" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember: _with fancy indexing that the return value reflects the **broadcasted shape of the indices**, rather than the shape of the array being indexed_." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Combined Indexing\n", "\n", "For even more powerful operations, fancy indexing can be combined with the other indexing schemes we've seen:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 2, 3],\n", " [ 4, 5, 6, 7],\n", " [ 8, 9, 10, 11]])" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([10, 8, 9])" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X[2, [2, 0, 1]] # combine fancy and simple indices" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 6, 4, 5],\n", " [10, 8, 9]])" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X[1:, [2, 0, 1]] # combine fancy indexing with slicing" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 2],\n", " [ 4, 6],\n", " [ 8, 10]])" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mask = np.array([1, 0, 1, 0], dtype=bool)\n", "X[row[:, np.newaxis], mask] # combine fancy indexing with masking" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Example: Selecting Random Points\n", "\n", "One common use of fancy indexing is the selection of subsets of rows from a matrix.\n", "\n", "For example, we might have an $N$ by $D$ matrix representing $N$ points in $D$ dimensions, such as the following points drawn from a two-dimensional normal distribution:" ] }, { "cell_type": "code", "execution_count": 119, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(100, 2)" ] }, "execution_count": 119, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mean = [0, 0]\n", "cov = [[1, 2],\n", " [2, 5]]\n", "X = rand.multivariate_normal(mean, cov, 100)\n", "X.shape" ] }, { "cell_type": "code", "execution_count": 120, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.scatter(X[:, 0], X[:, 1]);" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let's use fancy indexing to select 20 random points. We'll do this by first choosing 20 random indices with no repeats, and use these indices to select a portion of the original array:" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([22, 56, 83, 30, 58, 32, 78, 14, 36, 33, 8, 98, 25, 6, 41, 84, 13,\n", " 92, 7, 34])" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "indices = np.random.choice(X.shape[0], 20, replace=False)\n", "indices" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(20, 2)" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "selection = X[indices] # fancy indexing here\n", "selection.shape" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Now to see which points were selected, let's over-plot large circles at the locations of the selected points:" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.scatter(X[:, 0], X[:, 1], alpha=0.3);" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Modifying Values with Fancy Indexing\n", "\n", "Fancy indexing it can also be used to modify parts of an array:" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 99, 99, 3, 99, 5, 6, 7, 99, 9])" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.arange(10)\n", "i = np.array([2, 1, 8, 4])\n", "x[i] = 99\n", "x" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 89, 89, 3, 89, 5, 6, 7, 89, 9])" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[i] -= 10 # use any assignment-type operator for this\n", "x" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Notice, though, that repeated indices with these operations can cause some potentially unexpected results:" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([6., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.zeros(10)\n", "x[[0, 0]] = [4, 6]\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Where did the 4 go? The result of this operation is to first assign ``x[0] = 4``, followed by ``x[0] = 6``.\n", "The result, of course, is that ``x[0]`` contains the value 6." ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "i = [2, 3, 3, 4, 4, 4]\n", "x[i] += 1\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might expect that ``x[3]`` would contain the value 2, and ``x[4]`` would contain the value 3, as this is how many times each index is repeated. Why is this not the case?\n", "\n", "Conceptually, this is because ``x[i] += 1`` is meant as a shorthand of ``x[i] = x[i] + 1``. ``x[i] + 1`` is evaluated, and then the result is assigned to the indices in x.\n", "\n", "With this in mind, it is not the augmentation that happens multiple times, but the assignment, which leads to the rather nonintuitive results." ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([0., 0., 1., 2., 3., 0., 0., 0., 0., 0.])" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.zeros(10)\n", "np.add.at(x, i, 1)\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``at()`` method does an in-place application of the given operator at the specified indices (here, ``i``) with the specified value (here, 1).\n", "Another method that is similar in spirit is the ``reduceat()`` method of ufuncs, which you can read about in the NumPy documentation." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Example: Binning Data\n", "\n", "You can use these ideas to efficiently bin data to create a histogram by hand.\n", "For example, imagine we have 1,000 values and would like to quickly find where they fall within an array of bins.\n", "We could compute it using ``ufunc.at`` like this:" ] }, { "cell_type": "code", "execution_count": 121, "metadata": {}, "outputs": [], "source": [ "np.random.seed(42)\n", "x = np.random.randn(100)\n", "\n", "# compute a histogram by hand\n", "bins = np.linspace(-5, 5, 20)\n", "counts = np.zeros_like(bins)\n", "\n", "# find the appropriate bin for each x\n", "i = np.searchsorted(bins, x)\n", "\n", "# add 1 to each of these bins\n", "np.add.at(counts, i, 1)" ] }, { "cell_type": "code", "execution_count": 131, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# The counts now reflect the number of points \n", "# within each bin–in other words, a histogram:\n", "line, = plt.plot(bins, counts);\n", "line.set_drawstyle(\"steps\") " ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "NumPy routine:\n", "35.1 µs ± 209 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", "Custom routine:\n", "18.5 µs ± 405 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" ] } ], "source": [ "print(\"NumPy routine:\")\n", "%timeit counts, edges = np.histogram(x, bins)\n", "\n", "print(\"Custom routine:\")\n", "%timeit np.add.at(counts, np.searchsorted(bins, x), 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our own one-line algorithm is several times faster than the optimized algorithm in NumPy! How can this be?\n", "If you dig into the ``np.histogram`` source code (you can do this in IPython by typing ``np.histogram??``), you'll see that it's quite a bit more involved than the simple search-and-count that we've done; this is because NumPy's algorithm is more flexible, and particularly is designed for better performance when the number of data points becomes large..." ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "NumPy routine:\n", "95.9 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", "Custom routine:\n", "142 ms ± 115 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "x = np.random.randn(1000000)\n", "print(\"NumPy routine:\")\n", "%timeit counts, edges = np.histogram(x, bins)\n", "\n", "print(\"Custom routine:\")\n", "%timeit np.add.at(counts, np.searchsorted(bins, x), 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What this comparison shows is that algorithmic efficiency is almost never a simple question. An algorithm efficient for large datasets will not always be the best choice for small datasets, and vice versa.\n", "\n", "The key to efficiently using Python in data-intensive applications is knowing about general convenience routines like ``np.histogram`` and when they're appropriate, but also knowing how to make use of lower-level functionality when you need more pointed behavior." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Sorting Arrays\n", "\n", "Up to this point we have been concerned mainly with tools to access and operate on array data with NumPy.\n", "This section covers algorithms related to sorting values in NumPy arrays." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Fast Sorting in NumPy: ``np.sort`` and ``np.argsort``\n", "\n", "Although Python has built-in ``sort`` and ``sorted`` functions to work with lists, NumPy's ``np.sort`` function turns out to be much more efficient and useful.\n", "\n", "To return a sorted version of the array *without modifying the input*, you can use ``np.sort``:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 4, 5])" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.array([2, 1, 4, 3, 5])\n", "np.sort(x)" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2, 1, 4, 3, 5])" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "A related function is ``argsort``, which instead returns the *indices* of the sorted elements:" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 0, 3, 2, 4])" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "i = np.argsort(x)\n", "i" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first element of this result gives the index of the smallest element, the second value gives the index of the second smallest, and so on.\n", "\n", "These indices can then be used (via fancy indexing) to construct the sorted array if desired:" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 4, 5])" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x[i]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Sorting along rows or columns" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[6, 3, 7, 4, 6, 9],\n", " [2, 6, 7, 4, 3, 7],\n", " [7, 2, 5, 4, 1, 7],\n", " [5, 1, 4, 0, 9, 5]])" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rand = np.random.RandomState(42)\n", "X = rand.randint(0, 10, (4, 6))\n", "X" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "array([[2, 1, 4, 0, 1, 5],\n", " [5, 2, 5, 4, 3, 7],\n", " [6, 3, 7, 4, 6, 7],\n", " [7, 6, 7, 4, 9, 9]])" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sort(X, axis=0) # sort each column of X" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[3, 4, 6, 6, 7, 9],\n", " [2, 3, 4, 6, 7, 7],\n", " [1, 2, 4, 5, 7, 7],\n", " [0, 1, 4, 5, 5, 9]])" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sort(X, axis=1) # sort each row of X" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keep in mind that this treats each row or column as an independent array, and any relationships between the row or column values will be lost!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Partial Sorts: Partitioning\n", "\n", "Sometimes we're not interested in sorting the entire array, but simply want to find the *k* smallest values in the array. ``np.partition`` takes an array and a number *K*; the result is a new array with the smallest *K* values to the left of the partition, and the remaining values to the right, in arbitrary order:" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([2, 1, 3, 4, 6, 5, 7])" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = np.array([7, 2, 3, 1, 6, 5, 4])\n", "np.partition(x, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the first three values in the resulting array are the three smallest in the array, and the remaining array positions contain the remaining values.\n", "\n", "*Within the two partitions, the elements have arbitrary order.*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Similarly to sorting, we can partition along an arbitrary axis of a multidimensional array:" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[3, 4, 6, 7, 6, 9],\n", " [2, 3, 4, 7, 6, 7],\n", " [1, 2, 4, 5, 7, 7],\n", " [0, 1, 4, 5, 9, 5]])" ] }, "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.partition(X, 2, axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The result is an array where the first two slots in each row contain the smallest values from that row, with the remaining values filling the remaining slots.\n", "\n", "Finally, just as there is a ``np.argsort`` that computes indices of the sort, there is a ``np.argpartition`` that computes indices of the partition." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Example: k-Nearest Neighbors\n", "\n", "Let's quickly see how we might use this ``argsort`` function along multiple axes to find the nearest neighbors of each point in a set.\n", "\n", "We'll start by creating a random set of 10 points on a two-dimensional plane:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'rand' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\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[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrand\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrand\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m50\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'rand' is not defined" ] } ], "source": [ "X = rand.rand(50, 2)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'plt' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\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[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscatter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m100\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[0;31mNameError\u001b[0m: name 'plt' is not defined" ] } ], "source": [ "plt.scatter(X[:, 0], X[:, 1], s=100);" ] }, { "cell_type": "code", "execution_count": 95, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "((50, 50), True)" ] }, "execution_count": 95, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# compute the distance between each pair of points\n", "dist_sq = np.sum((X[:, np.newaxis, :] - X[np.newaxis, :, :]) ** 2, axis=-1)\n", "dist_sq.shape, np.all(dist_sq.diagonal() == 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the pairwise square-distances converted, we can now use ``np.argsort`` to sort along each row. \n", "\n", "The leftmost columns will then give the indices of the nearest neighbors:" ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49])" ] }, "execution_count": 98, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nearest = np.argsort(dist_sq, axis=1)\n", "nearest[:,0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the first column is order because each point's closest neighbor is itself." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "If we're simply interested in the nearest $k$ neighbors, all we need is to partition each row so that the smallest $k + 1$ squared distances come first, with larger distances filling the remaining positions of the array:" ] }, { "cell_type": "code", "execution_count": 99, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "K = 2\n", "nearest_partition = np.argpartition(dist_sq, K + 1, axis=1)" ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.scatter(X[:, 0], X[:, 1], s=100)\n", "K = 2 # draw lines from each point to its two nearest neighbors\n", "for i in range(X.shape[0]):\n", " for j in nearest_partition[i, :K+1]:\n", " plt.plot(*zip(X[j], X[i]), color='black')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "At first glance, it might seem strange that some of the points have more than two lines coming out of them: this is due to the fact that if point A is one of the two nearest neighbors of point B, this does not necessarily imply that point B is one of the two nearest neighbors of point A.\n", "\n", "You might be tempted to do the same type of operation by manually looping through the data and sorting each set of neighbors individually. The beauty of our approach is that *it's written in a way that's agnostic to the size of the input data*: we could just as easily compute the neighbors among 100 or 1,000,000 points in any number of dimensions, and the code would look the same." ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [], "source": [ "def A(a: int) -> (3 if 0 else 4):\n", " return 4" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A(3)" ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'a': int, 'return': 4}" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A.__annotations__" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(_)" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [], "source": [ "def B(f):\n", " print(f.__annotations__)" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'a': , 'return': }\n" ] } ], "source": [ "B(A)" ] }, { "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.9.0" } }, "nbformat": 4, "nbformat_minor": 4 }