From d52331cd5a7b91d1d9de6d5d4cd5fb864c25d6be Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Mon, 13 Mar 2017 04:31:53 -0400 Subject: [PATCH] Add new numpy notebooks Source: https://github.com/jakevdp/PythonDataScienceHandbook unmodified --- numpy/02.00-Introduction-to-NumPy.ipynb | 160 ++ numpy/02.01-Understanding-Data-Types.ipynb | 827 +++++++++ numpy/02.02-The-Basics-Of-NumPy-Arrays.ipynb | 1569 +++++++++++++++++ .../02.03-Computation-on-arrays-ufuncs.ipynb | 1106 ++++++++++++ ....04-Computation-on-arrays-aggregates.ipynb | 643 +++++++ ...5-Computation-on-arrays-broadcasting.ipynb | 801 +++++++++ numpy/02.06-Boolean-Arrays-and-Masks.ipynb | 1280 ++++++++++++++ numpy/02.07-Fancy-Indexing.ipynb | 929 ++++++++++ numpy/02.08-Sorting.ipynb | 783 ++++++++ numpy/02.09-Structured-Data-NumPy.ipynb | 600 +++++++ 10 files changed, 8698 insertions(+) create mode 100644 numpy/02.00-Introduction-to-NumPy.ipynb create mode 100644 numpy/02.01-Understanding-Data-Types.ipynb create mode 100644 numpy/02.02-The-Basics-Of-NumPy-Arrays.ipynb create mode 100644 numpy/02.03-Computation-on-arrays-ufuncs.ipynb create mode 100644 numpy/02.04-Computation-on-arrays-aggregates.ipynb create mode 100644 numpy/02.05-Computation-on-arrays-broadcasting.ipynb create mode 100644 numpy/02.06-Boolean-Arrays-and-Masks.ipynb create mode 100644 numpy/02.07-Fancy-Indexing.ipynb create mode 100644 numpy/02.08-Sorting.ipynb create mode 100644 numpy/02.09-Structured-Data-NumPy.ipynb diff --git a/numpy/02.00-Introduction-to-NumPy.ipynb b/numpy/02.00-Introduction-to-NumPy.ipynb new file mode 100644 index 0000000..ee15a4b --- /dev/null +++ b/numpy/02.00-Introduction-to-NumPy.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [More IPython Resources](01.08-More-IPython-Resources.ipynb) | [Contents](Index.ipynb) | [Understanding Data Types in Python](02.01-Understanding-Data-Types.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction to NumPy\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This chapter, along with chapter 3, outlines techniques for effectively loading, storing, and manipulating in-memory data in Python.\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 example, images–particularly digital images–can be thought of as simply two-dimensional arrays of numbers representing pixel brightness across the area.\n", + "Sound clips can be thought of as one-dimensional arrays of intensity versus time.\n", + "Text can be converted in various ways into numerical representations, perhaps binary digits representing the frequency of certain words or pairs of words.\n", + "No matter what the data are, the first step in making it analyzable will be to transform them into arrays of numbers.\n", + "(We will discuss some specific examples of this process later in [Feature Engineering](05.04-Feature-Engineering.ipynb))\n", + "\n", + "For this reason, efficient storage and manipulation of numerical arrays is absolutely fundamental to the process of doing data science.\n", + "We'll now take a look at the specialized tools that Python has for handling such numerical arrays: the NumPy package, and the Pandas package (discussed in Chapter 3).\n", + "\n", + "This chapter will cover NumPy in detail. 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.\n", + "\n", + "If you followed the advice outlined in the Preface and installed the Anaconda stack, you already have NumPy installed and ready to go.\n", + "If you're more the do-it-yourself type, you can go to http://www.numpy.org/ and follow the installation instructions found there.\n", + "Once you do, you can import NumPy and double-check the version:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'1.11.1'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy\n", + "numpy.__version__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the pieces of the package discussed here, I'd recommend NumPy version 1.8 or later.\n", + "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": { + "collapsed": false + }, + "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": {}, + "source": [ + "## Reminder about Built In Documentation\n", + "\n", + "As you read through this chapter, don't forget that IPython gives you the ability to quickly explore the contents of a package (by using the tab-completion feature), as well as the documentation of various functions (using the ``?`` character – Refer back to [Help and Documentation in IPython](01.01-Help-And-Documentation.ipynb)).\n", + "\n", + "For example, to display all the contents of the numpy namespace, you can type this:\n", + "\n", + "```ipython\n", + "In [3]: np.\n", + "```\n", + "\n", + "And to display NumPy's built-in documentation, you can use this:\n", + "\n", + "```ipython\n", + "In [4]: np?\n", + "```\n", + "\n", + "More detailed documentation, along with tutorials and other resources, can be found at http://www.numpy.org." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [More IPython Resources](01.08-More-IPython-Resources.ipynb) | [Contents](Index.ipynb) | [Understanding Data Types in Python](02.01-Understanding-Data-Types.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.01-Understanding-Data-Types.ipynb b/numpy/02.01-Understanding-Data-Types.ipynb new file mode 100644 index 0000000..a521f3f --- /dev/null +++ b/numpy/02.01-Understanding-Data-Types.ipynb @@ -0,0 +1,827 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Introduction to NumPy](02.00-Introduction-to-NumPy.ipynb) | [Contents](Index.ipynb) | [The Basics of NumPy Arrays](02.02-The-Basics-Of-NumPy-Arrays.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Understanding Data Types in Python" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Effective data-driven science and computation requires understanding how data is stored and manipulated.\n", + "This section outlines and contrasts how arrays of data are handled in the Python language itself, and how NumPy improves on this.\n", + "Understanding this difference is fundamental to understanding much of the material throughout the rest of the book.\n", + "\n", + "Users of Python are often drawn-in by its ease of use, one piece of which is dynamic typing.\n", + "While a statically-typed language like C or Java requires each variable to be explicitly declared, a dynamically-typed language like Python skips this specification. For example, in C you might specify a particular operation as follows:\n", + "\n", + "```C\n", + "/* C code */\n", + "int result = 0;\n", + "for(int i=0; i<100; i++){\n", + " result += i;\n", + "}\n", + "```\n", + "\n", + "While in Python the equivalent operation could be written this way:\n", + "\n", + "```python\n", + "# Python code\n", + "result = 0\n", + "for i in range(100):\n", + " result += i\n", + "```\n", + "\n", + "Notice the main difference: in C, the data types of each variable are explicitly declared, while in Python the types are dynamically inferred. This means, for example, that we can assign any kind of data to any variable:\n", + "\n", + "```python\n", + "# Python code\n", + "x = 4\n", + "x = \"four\"\n", + "```\n", + "\n", + "Here we've switched the contents of ``x`` from an integer to a string. The same thing in C would lead (depending on compiler settings) to a compilation error or other unintented consequences:\n", + "\n", + "```C\n", + "/* C code */\n", + "int x = 4;\n", + "x = \"four\"; // FAILS\n", + "```\n", + "\n", + "This sort of flexibility is one piece that makes Python and other dynamically-typed languages convenient and easy to use.\n", + "Understanding *how* this works is an important piece of learning to analyze data efficiently and effectively with Python.\n", + "But what this type-flexibility also points to is the fact that Python variables are more than just their value; they also contain extra information about the type of the value. We'll explore this more in the sections that follow." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A Python Integer Is More Than Just an Integer\n", + "\n", + "The standard Python implementation is written in C.\n", + "This means that every Python object is simply a cleverly-disguised C structure, which contains not only its value, but other information as well. For example, when we define an integer in Python, such as ``x = 10000``, ``x`` is not just a \"raw\" integer. It's actually a pointer to a compound C structure, which contains several values.\n", + "Looking through the Python 3.4 source code, we find that the integer (long) type definition effectively looks like this (once the C macros are expanded):\n", + "\n", + "```C\n", + "struct _longobject {\n", + " long ob_refcnt;\n", + " PyTypeObject *ob_type;\n", + " size_t ob_size;\n", + " long ob_digit[1];\n", + "};\n", + "```\n", + "\n", + "A single integer in Python 3.4 actually contains four pieces:\n", + "\n", + "- ``ob_refcnt``, a reference count that helps Python silently handle memory allocation and deallocation\n", + "- ``ob_type``, which encodes the type of the variable\n", + "- ``ob_size``, which specifies the size of the following data members\n", + "- ``ob_digit``, which contains the actual integer value that we expect the Python variable to represent.\n", + "\n", + "This means that there is some overhead in storing an integer in Python as compared to an integer in a compiled language like C, as illustrated in the following figure:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Integer Memory Layout](figures/cint_vs_pyint.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here ``PyObject_HEAD`` is the part of the structure containing the reference count, type code, and other pieces mentioned before.\n", + "\n", + "Notice the difference here: a C integer is essentially a label for a position in memory whose bytes encode an integer value.\n", + "A Python integer is a pointer to a position in memory containing all the Python object information, including the bytes that contain the integer value.\n", + "This extra information in the Python integer structure is what allows Python to be coded so freely and dynamically.\n", + "All this additional information in Python types comes at a cost, however, which becomes especially apparent in structures that combine many of these objects." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A Python List Is More Than Just a List\n", + "\n", + "Let's consider now what happens when we use a Python data structure that holds many Python objects.\n", + "The standard mutable multi-element container in Python is the list.\n", + "We can create a list of integers as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "L = list(range(10))\n", + "L" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "int" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(L[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or, similarly, a list of strings:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "L2 = [str(c) for c in L]\n", + "L2" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "str" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(L2[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because of Python's dynamic typing, we can even create heterogeneous lists:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[bool, str, float, int]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "L3 = [True, \"2\", 3.0, 4]\n", + "[type(item) for item in L3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But this flexibility comes at a cost: to allow these flexible types, each item in the list must contain its own type info, reference count, and other information–that is, each item is a complete Python object.\n", + "In the special case that all variables are of the same type, much of this information is redundant: it can be much more efficient to store data in a fixed-type array.\n", + "The difference between a dynamic-type list and a fixed-type (NumPy-style) array is illustrated in the following figure:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Array Memory Layout](figures/array_vs_list.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At the implementation level, the array essentially contains a single pointer to one contiguous block of data.\n", + "The Python list, on the other hand, contains a pointer to a block of pointers, each of which in turn points to a full Python object like the Python integer we saw earlier.\n", + "Again, the advantage of the list is flexibility: because each list element is a full structure containing both data and type information, the list can be filled with data of any desired type.\n", + "Fixed-type NumPy-style arrays lack this flexibility, but are much more efficient for storing and manipulating data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fixed-Type Arrays in Python\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": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import array\n", + "L = list(range(10))\n", + "A = array.array('i', L)\n", + "A" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here ``'i'`` is a type code indicating the contents are integers.\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.\n", + "We will explore these operations in later sections; here we'll demonstrate several ways of creating a NumPy array.\n", + "\n", + "We'll start with the standard NumPy import, under the alias ``np``:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 4, 2, 5, 3])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# integer array:\n", + "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": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 3.14, 4. , 2. , 3. ])" + ] + }, + "execution_count": 9, + "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": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1., 2., 3., 4.], dtype=float32)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array([1, 2, 3, 4], dtype='float32')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, unlike Python lists, NumPy arrays can explicitly be multi-dimensional; here's one way of initializing a multidimensional array using a list of lists:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 3, 4],\n", + " [4, 5, 6],\n", + " [6, 7, 8]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# nested lists result in multi-dimensional arrays\n", + "np.array([range(i, i + 3) for i in [2, 4, 6]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The inner lists are treated as rows of the resulting two-dimensional array." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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.\n", + "Here are several examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a length-10 integer array filled with zeros\n", + "np.zeros(10, dtype=int)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1.],\n", + " [ 1., 1., 1., 1., 1.]])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a 3x5 floating-point array filled with ones\n", + "np.ones((3, 5), dtype=float)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "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": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a 3x5 array filled with 3.14\n", + "np.full((3, 5), 3.14)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create an array filled with a linear sequence\n", + "# Starting at 0, ending at 20, stepping by 2\n", + "# (this is similar to the built-in range() function)\n", + "np.arange(0, 20, 2)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 0.25, 0.5 , 0.75, 1. ])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create an array of five values evenly spaced between 0 and 1\n", + "np.linspace(0, 1, 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0.99844933, 0.52183819, 0.22421193],\n", + " [ 0.08007488, 0.45429293, 0.20941444],\n", + " [ 0.14360941, 0.96910973, 0.946117 ]])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a 3x3 array of uniformly distributed\n", + "# random values between 0 and 1\n", + "np.random.random((3, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1.51772646, 0.39614948, -0.10634696],\n", + " [ 0.25671348, 0.00732722, 0.37783601],\n", + " [ 0.68446945, 0.15926039, -0.70744073]])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a 3x3 array of normally distributed random values\n", + "# with mean 0 and standard deviation 1\n", + "np.random.normal(0, 1, (3, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2, 3, 4],\n", + " [5, 7, 8],\n", + " [0, 5, 0]])" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a 3x3 array of random integers in the interval [0, 10)\n", + "np.random.randint(0, 10, (3, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1., 0., 0.],\n", + " [ 0., 1., 0.],\n", + " [ 0., 0., 1.]])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a 3x3 identity matrix\n", + "np.eye(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1., 1., 1.])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create an uninitialized array of three integers\n", + "# The values will be whatever happens to already exist at that memory location\n", + "np.empty(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## NumPy Standard Data Types\n", + "\n", + "NumPy arrays contain values of a single type, so it is important to have detailed knowledge of those types and their limitations.\n", + "Because NumPy is built in C, the types will be familiar to users of C, Fortran, and other related languages.\n", + "\n", + "The standard NumPy data types are listed in the following table.\n", + "Note that when constructing an array, they can be specified using a string:\n", + "\n", + "```python\n", + "np.zeros(10, dtype='int16')\n", + "```\n", + "\n", + "Or using the associated NumPy object:\n", + "\n", + "```python\n", + "np.zeros(10, dtype=np.int16)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "| 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": [ + "More advanced type specification is possible, such as specifying big or little endian numbers; for more information, refer to the [NumPy documentation](http://numpy.org/).\n", + "NumPy also supports compound data types, which will be covered in [Structured Data: NumPy's Structured Arrays](02.09-Structured-Data-NumPy.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Introduction to NumPy](02.00-Introduction-to-NumPy.ipynb) | [Contents](Index.ipynb) | [The Basics of NumPy Arrays](02.02-The-Basics-Of-NumPy-Arrays.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.02-The-Basics-Of-NumPy-Arrays.ipynb b/numpy/02.02-The-Basics-Of-NumPy-Arrays.ipynb new file mode 100644 index 0000000..53cfc00 --- /dev/null +++ b/numpy/02.02-The-Basics-Of-NumPy-Arrays.ipynb @@ -0,0 +1,1569 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Understanding Data Types in Python](02.01-Understanding-Data-Types.ipynb) | [Contents](Index.ipynb) | [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The Basics of NumPy Arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data manipulation in Python is nearly synonymous with NumPy array manipulation: even newer tools like Pandas ([Chapter 3](03.00-Introduction-to-Pandas.ipynb)) are built around the NumPy array.\n", + "This section will present several examples of using NumPy array manipulation to access data and subarrays, and to split, reshape, and join the arrays.\n", + "While the types of operations shown here may seem a bit dry and pedantic, they comprise the building blocks of many other examples used throughout the book.\n", + "Get to know them well!\n", + "\n", + "We'll cover a few categories of basic array manipulations here:\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" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## NumPy Array Attributes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "We'll use NumPy's random number generator, which we will *seed* with a set value in order to ensure that the same random arrays are generated each time this code is run:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "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": {}, + "source": [ + "Each array has attributes ``ndim`` (the number of dimensions), ``shape`` (the size of each dimension), and ``size`` (the total size of the array):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x3 ndim: 3\n", + "x3 shape: (3, 4, 5)\n", + "x3 size: 60\n" + ] + } + ], + "source": [ + "print(\"x3 ndim: \", x3.ndim)\n", + "print(\"x3 shape:\", x3.shape)\n", + "print(\"x3 size: \", x3.size)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another useful attribute is the ``dtype``, the data type of the array (which we discussed previously in [Understanding Data Types in Python](02.01-Understanding-Data-Types.ipynb)):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dtype: int64\n" + ] + } + ], + "source": [ + "print(\"dtype:\", x3.dtype)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Other attributes include ``itemsize``, which lists the size (in bytes) of each array element, and ``nbytes``, which lists the total size (in bytes) of the array:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "itemsize: 8 bytes\n", + "nbytes: 480 bytes\n" + ] + } + ], + "source": [ + "print(\"itemsize:\", x3.itemsize, \"bytes\")\n", + "print(\"nbytes:\", x3.nbytes, \"bytes\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In general, we expect that ``nbytes`` is equal to ``itemsize`` times ``size``." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Array Indexing: Accessing Single Elements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you are familiar with Python's standard list indexing, indexing in NumPy will feel quite familiar.\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": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([5, 0, 3, 3, 7, 9])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x1" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x1[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x1[4]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To index from the end of the array, you can use negative indices:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "9" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x1[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x1[-2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In a multi-dimensional array, items can be accessed using a comma-separated tuple of indices:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[3, 5, 2, 4],\n", + " [7, 6, 8, 8],\n", + " [1, 6, 7, 7]])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2[0, 0]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2[2, 0]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2[2, -1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Values can also be modified using any of the above index notation:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[12, 5, 2, 4],\n", + " [ 7, 6, 8, 8],\n", + " [ 1, 6, 7, 7]])" + ] + }, + "execution_count": 14, + "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.\n", + "This means, for example, that if you attempt to insert a floating-point value to an integer array, the value will be silently truncated. Don't be caught unaware by this behavior!" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([3, 0, 3, 3, 7, 9])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x1[0] = 3.14159 # this will be truncated!\n", + "x1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Array Slicing: Accessing Subarrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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", + "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", + "We'll take a look at accessing sub-arrays in one dimension and in multiple dimensions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### One-dimensional subarrays" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.arange(10)\n", + "x" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[:5] # first five elements" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([5, 6, 7, 8, 9])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[5:] # elements after index 5" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4, 5, 6])" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[4:7] # middle sub-array" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 2, 4, 6, 8])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[::2] # every other element" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 3, 5, 7, 9])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[1::2] # every other element, starting at index 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[::-1] # all elements, reversed" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([5, 3, 1])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[5::-2] # reversed every other from index 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Multi-dimensional subarrays\n", + "\n", + "Multi-dimensional slices work in the same way, with multiple slices separated by commas.\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[12, 5, 2, 4],\n", + " [ 7, 6, 8, 8],\n", + " [ 1, 6, 7, 7]])" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[12, 5, 2],\n", + " [ 7, 6, 8]])" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2[:2, :3] # two rows, three columns" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[12, 2],\n", + " [ 7, 8],\n", + " [ 1, 7]])" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2[:3, ::2] # all rows, every other column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, subarray dimensions can even be reversed together:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 7, 7, 6, 1],\n", + " [ 8, 8, 6, 7],\n", + " [ 4, 2, 5, 12]])" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x2[::-1, ::-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Accessing array rows and columns\n", + "\n", + "One commonly needed routine is accessing of single rows or columns of an array.\n", + "This can be done by combining indexing and slicing, using an empty slice marked by a single colon (``:``):" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[12 7 1]\n" + ] + } + ], + "source": [ + "print(x2[:, 0]) # first column of x2" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[12 5 2 4]\n" + ] + } + ], + "source": [ + "print(x2[0, :]) # first row of x2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the case of row access, the empty slice can be omitted for a more compact syntax:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[12 5 2 4]\n" + ] + } + ], + "source": [ + "print(x2[0]) # equivalent to x2[0, :]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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", + "This is one area in which NumPy array slicing differs from Python list slicing: in lists, slices will be copies.\n", + "Consider our two-dimensional array from before:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[12 5 2 4]\n", + " [ 7 6 8 8]\n", + " [ 1 6 7 7]]\n" + ] + } + ], + "source": [ + "print(x2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's extract a $2 \\times 2$ subarray from this:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[12 5]\n", + " [ 7 6]]\n" + ] + } + ], + "source": [ + "x2_sub = x2[:2, :2]\n", + "print(x2_sub)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now if we modify this subarray, we'll see that the original array is changed! Observe:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[99 5]\n", + " [ 7 6]]\n" + ] + } + ], + "source": [ + "x2_sub[0, 0] = 99\n", + "print(x2_sub)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[99 5 2 4]\n", + " [ 7 6 8 8]\n", + " [ 1 6 7 7]]\n" + ] + } + ], + "source": [ + "print(x2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This default behavior is actually quite useful: it means that when we work with large datasets, we can access and process pieces of these datasets without the need to copy the underlying data buffer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating copies of arrays\n", + "\n", + "Despite the nice features of array views, 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": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[99 5]\n", + " [ 7 6]]\n" + ] + } + ], + "source": [ + "x2_sub_copy = x2[:2, :2].copy()\n", + "print(x2_sub_copy)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we now modify this subarray, the original array is not touched:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[42 5]\n", + " [ 7 6]]\n" + ] + } + ], + "source": [ + "x2_sub_copy[0, 0] = 42\n", + "print(x2_sub_copy)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[99 5 2 4]\n", + " [ 7 6 8 8]\n", + " [ 1 6 7 7]]\n" + ] + } + ], + "source": [ + "print(x2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reshaping of Arrays\n", + "\n", + "Another useful type of operation is reshaping of arrays.\n", + "The most flexible way of doing this is with the ``reshape`` method.\n", + "For example, if you want to put the numbers 1 through 9 in a $3 \\times 3$ grid, you can do the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1 2 3]\n", + " [4 5 6]\n", + " [7 8 9]]\n" + ] + } + ], + "source": [ + "grid = np.arange(1, 10).reshape((3, 3))\n", + "print(grid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that for this to work, the size of the initial array must match the size of the reshaped array. \n", + "Where possible, the ``reshape`` method will use a no-copy view of the initial array, but with non-contiguous memory buffers this is not always the case.\n", + "\n", + "Another common reshaping pattern is the conversion of a one-dimensional array into a two-dimensional row or column matrix.\n", + "This can be done with the ``reshape`` method, or more easily done by making use of the ``newaxis`` keyword within a slice operation:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3]])" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.array([1, 2, 3])\n", + "\n", + "# row vector via reshape\n", + "x.reshape((1, 3))" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3]])" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# row vector via newaxis\n", + "x[np.newaxis, :]" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1],\n", + " [2],\n", + " [3]])" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# column vector via reshape\n", + "x.reshape((3, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1],\n", + " [2],\n", + " [3]])" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# column vector via newaxis\n", + "x[:, np.newaxis]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will see this type of transformation often throughout the remainder of the book." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Array Concatenation and Splitting\n", + "\n", + "All of the preceding routines worked on single arrays. It's also possible to combine multiple arrays into one, and to conversely split a single array into multiple arrays. We'll take a look at those operations here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Concatenation of arrays\n", + "\n", + "Concatenation, or joining of two arrays in NumPy, is primarily accomplished using the routines ``np.concatenate``, ``np.vstack``, and ``np.hstack``.\n", + "``np.concatenate`` takes a tuple or list of arrays as its first argument, as we can see here:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 3, 2, 1])" + ] + }, + "execution_count": 43, + "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": "markdown", + "metadata": {}, + "source": [ + "You can also concatenate more than two arrays at once:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 1 2 3 3 2 1 99 99 99]\n" + ] + } + ], + "source": [ + "z = [99, 99, 99]\n", + "print(np.concatenate([x, y, z]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It can also be used for two-dimensional arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "grid = np.array([[1, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [4, 5, 6],\n", + " [1, 2, 3],\n", + " [4, 5, 6]])" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# concatenate along the first axis\n", + "np.concatenate([grid, grid])" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3, 1, 2, 3],\n", + " [4, 5, 6, 4, 5, 6]])" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# concatenate along the second axis (zero-indexed)\n", + "np.concatenate([grid, grid], axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 48, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [9, 8, 7],\n", + " [6, 5, 4]])" + ] + }, + "execution_count": 48, + "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", + "# vertically stack the arrays\n", + "np.vstack([x, grid])" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 9, 8, 7, 99],\n", + " [ 6, 5, 4, 99]])" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# horizontally stack the arrays\n", + "y = np.array([[99],\n", + " [99]])\n", + "np.hstack([grid, y])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similary, ``np.dstack`` will stack arrays along the third axis." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Splitting of arrays\n", + "\n", + "The opposite of concatenation is splitting, which is implemented by the functions ``np.split``, ``np.hsplit``, and ``np.vsplit``. For each of these, we can pass a list of indices giving the split points:" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": false + }, + "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": "markdown", + "metadata": {}, + "source": [ + "Notice that *N* split-points, leads to *N + 1* subarrays.\n", + "The related functions ``np.hsplit`` and ``np.vsplit`` are similar:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": false + }, + "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": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "grid = np.arange(16).reshape((4, 4))\n", + "grid" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0 1 2 3]\n", + " [4 5 6 7]]\n", + "[[ 8 9 10 11]\n", + " [12 13 14 15]]\n" + ] + } + ], + "source": [ + "upper, lower = np.vsplit(grid, [2])\n", + "print(upper)\n", + "print(lower)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 1]\n", + " [ 4 5]\n", + " [ 8 9]\n", + " [12 13]]\n", + "[[ 2 3]\n", + " [ 6 7]\n", + " [10 11]\n", + " [14 15]]\n" + ] + } + ], + "source": [ + "left, right = np.hsplit(grid, [2])\n", + "print(left)\n", + "print(right)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly, ``np.dsplit`` will split arrays along the third axis." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Understanding Data Types in Python](02.01-Understanding-Data-Types.ipynb) | [Contents](Index.ipynb) | [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.03-Computation-on-arrays-ufuncs.ipynb b/numpy/02.03-Computation-on-arrays-ufuncs.ipynb new file mode 100644 index 0000000..eb90b6a --- /dev/null +++ b/numpy/02.03-Computation-on-arrays-ufuncs.ipynb @@ -0,0 +1,1106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [The Basics of NumPy Arrays](02.02-The-Basics-Of-NumPy-Arrays.ipynb) | [Contents](Index.ipynb) | [Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Computation on NumPy Arrays: Universal Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Up until now, we have been discussing some of the basic nuts and bolts of NumPy; in the next few sections, we will dive into the reasons that NumPy is so important in the Python data science world.\n", + "Namely, it provides an easy and flexible interface to optimized computation with arrays of data.\n", + "\n", + "Computation on NumPy arrays can be very fast, or it can be very slow.\n", + "The key to making it fast is to use *vectorized* operations, generally implemented through NumPy's *universal functions* (ufuncs).\n", + "This section motivates the need for NumPy's ufuncs, which can be used to make repeated calculations on array elements much more efficient.\n", + "It then introduces many of the most common and useful arithmetic ufuncs available in the NumPy package." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Slowness of Loops\n", + "\n", + "Python's default implementation (known as CPython) does some operations very slowly.\n", + "This is in part due to the dynamic, interpreted nature of the language: the fact that types are flexible, so that sequences of operations cannot be compiled down to efficient machine code as in languages like C and Fortran.\n", + "Recently there have been various attempts to address this weakness: well-known examples are the [PyPy](http://pypy.org/) project, a just-in-time compiled implementation of Python; the [Cython](http://cython.org) project, which converts Python code to compilable C code; and the [Numba](http://numba.pydata.org/) project, which converts snippets of Python code to fast LLVM bytecode.\n", + "Each of these has its strengths and weaknesses, but it is safe to say that none of the three approaches has yet surpassed the reach and popularity of the standard CPython engine.\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", + "For example, imagine we have an array of values and we'd like to compute the reciprocal of each.\n", + "A straightforward approach might look like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.16666667, 1. , 0.25 , 0.25 , 0.125 ])" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "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": {}, + "source": [ + "This implementation probably feels fairly natural to someone from, say, a C or Java background.\n", + "But if we measure the execution time of this code for a large input, we see that this operation is very slow, perhaps surprisingly so!\n", + "We'll benchmark this with IPython's ``%timeit`` magic (discussed in [Profiling and Timing Code](01.07-Timing-and-Profiling.ipynb)):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loop, best of 3: 2.91 s per loop\n" + ] + } + ], + "source": [ + "big_array = np.random.randint(1, 100, size=1000000)\n", + "%timeit compute_reciprocals(big_array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It takes several seconds to compute these million operations and to store the result!\n", + "When even cell phones have processing speeds measured in Giga-FLOPS (i.e., billions of numerical operations per second), this seems almost absurdly slow.\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", + "Each time the reciprocal is computed, Python first examines the object's type and does a dynamic lookup of the correct function to use for that type.\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": {}, + "source": [ + "## Introducing UFuncs\n", + "\n", + "For many types of operations, NumPy provides a convenient interface into just this kind of statically typed, compiled routine. This is known as a *vectorized* operation.\n", + "This can be accomplished by simply performing an operation on the array, which will then be applied to each element.\n", + "This vectorized approach is designed to push the loop into the compiled layer that underlies NumPy, leading to much faster execution.\n", + "\n", + "Compare the results of the following two:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0.16666667 1. 0.25 0.25 0.125 ]\n", + "[ 0.16666667 1. 0.25 0.25 0.125 ]\n" + ] + } + ], + "source": [ + "print(compute_reciprocals(values))\n", + "print(1.0 / values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the execution time for our big array, we see that it completes orders of magnitude faster than the Python loop:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 loops, best of 3: 4.6 ms per loop\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.\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": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 0.5 , 0.66666667, 0.75 , 0.8 ])" + ] + }, + "execution_count": 5, + "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": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1, 2, 4],\n", + " [ 8, 16, 32],\n", + " [ 64, 128, 256]])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.arange(9).reshape((3, 3))\n", + "2 ** x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Computations using vectorization through ufuncs are nearly always more efficient than their counterpart implemented using Python loops, especially as the arrays grow in size.\n", + "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": {}, + "source": [ + "## Exploring NumPy's UFuncs\n", + "\n", + "Ufuncs exist in two flavors: *unary ufuncs*, which operate on a single input, and *binary ufuncs*, which operate on two inputs.\n", + "We'll see examples of both these types of functions here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Array arithmetic\n", + "\n", + "NumPy's ufuncs feel very natural to use because they make use of Python's native arithmetic operators.\n", + "The standard addition, subtraction, multiplication, and division can all be used:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "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" + ] + } + ], + "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" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is also a unary ufunc for negation, and a ``**`` operator for exponentiation, and a ``%`` operator for modulus:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-x = [ 0 -1 -2 -3]\n", + "x ** 2 = [0 1 4 9]\n", + "x % 2 = [0 1 0 1]\n" + ] + } + ], + "source": [ + "print(\"-x = \", -x)\n", + "print(\"x ** 2 = \", x ** 2)\n", + "print(\"x % 2 = \", x % 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition, these can be strung together however you wish, and the standard order of operations is respected:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-1. , -2.25, -4. , -6.25])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "-(0.5*x + 1) ** 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each of these arithmetic operations are simply convenient wrappers around specific functions built into NumPy; for example, the ``+`` operator is a wrapper for the ``add`` function:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 3, 4, 5])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.add(x, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following table lists the arithmetic operators implemented in NumPy:\n", + "\n", + "| Operator\t | Equivalent ufunc | Description |\n", + "|---------------|---------------------|---------------------------------------|\n", + "|``+`` |``np.add`` |Addition (e.g., ``1 + 1 = 2``) |\n", + "|``-`` |``np.subtract`` |Subtraction (e.g., ``3 - 2 = 1``) |\n", + "|``-`` |``np.negative`` |Unary negation (e.g., ``-2``) |\n", + "|``*`` |``np.multiply`` |Multiplication (e.g., ``2 * 3 = 6``) |\n", + "|``/`` |``np.divide`` |Division (e.g., ``3 / 2 = 1.5``) |\n", + "|``//`` |``np.floor_divide`` |Floor division (e.g., ``3 // 2 = 1``) |\n", + "|``**`` |``np.power`` |Exponentiation (e.g., ``2 ** 3 = 8``) |\n", + "|``%`` |``np.mod`` |Modulus/remainder (e.g., ``9 % 4 = 1``)|\n", + "\n", + "Additionally there are Boolean/bitwise operators; we will explore these in [Comparisons, Masks, and Boolean Logic](02.06-Boolean-Arrays-and-Masks.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Absolute value\n", + "\n", + "Just as NumPy understands Python's built-in arithmetic operators, it also understands Python's built-in absolute value function:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 1, 0, 1, 2])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.array([-2, -1, 0, 1, 2])\n", + "abs(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The corresponding NumPy ufunc is ``np.absolute``, which is also available under the alias ``np.abs``:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 1, 0, 1, 2])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.absolute(x)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 1, 0, 1, 2])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.abs(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This ufunc can also handle complex data, in which the absolute value returns the magnitude:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 5., 5., 2., 1.])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.array([3 - 4j, 4 - 3j, 2 + 0j, 0 + 1j])\n", + "np.abs(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Trigonometric functions\n", + "\n", + "NumPy provides a large number of useful ufuncs, and some of the most useful for the data scientist are the trigonometric functions.\n", + "We'll start by defining an array of angles:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "theta = np.linspace(0, np.pi, 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can compute some trigonometric functions on these values:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "theta = [ 0. 1.57079633 3.14159265]\n", + "sin(theta) = [ 0.00000000e+00 1.00000000e+00 1.22464680e-16]\n", + "cos(theta) = [ 1.00000000e+00 6.12323400e-17 -1.00000000e+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": {}, + "source": [ + "The values are computed to within machine precision, which is why values that should be zero do not always hit exactly zero.\n", + "Inverse trigonometric functions are also available:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x = [-1, 0, 1]\n", + "arcsin(x) = [-1.57079633 0. 1.57079633]\n", + "arccos(x) = [ 3.14159265 1.57079633 0. ]\n", + "arctan(x) = [-0.78539816 0. 0.78539816]\n" + ] + } + ], + "source": [ + "x = [-1, 0, 1]\n", + "print(\"x = \", x)\n", + "print(\"arcsin(x) = \", np.arcsin(x))\n", + "print(\"arccos(x) = \", np.arccos(x))\n", + "print(\"arctan(x) = \", np.arctan(x))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exponents and logarithms\n", + "\n", + "Another common type of operation available in a NumPy ufunc are the exponentials:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "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": "markdown", + "metadata": {}, + "source": [ + "The inverse of the exponentials, the logarithms, are also available.\n", + "The basic ``np.log`` gives the natural logarithm; if you prefer to compute the base-2 logarithm or the base-10 logarithm, these are available as well:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "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": {}, + "source": [ + "There are also some specialized versions that are useful for maintaining precision with very small input:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exp(x) - 1 = [ 0. 0.0010005 0.01005017 0.10517092]\n", + "log(1 + x) = [ 0. 0.0009995 0.00995033 0.09531018]\n" + ] + } + ], + "source": [ + "x = [0, 0.001, 0.01, 0.1]\n", + "print(\"exp(x) - 1 =\", np.expm1(x))\n", + "print(\"log(1 + x) =\", np.log1p(x))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When ``x`` is very small, these functions give more precise values than if the raw ``np.log`` or ``np.exp`` were to be used." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Specialized ufuncs\n", + "\n", + "NumPy has many more ufuncs available, including hyperbolic trig functions, bitwise arithmetic, comparison operators, conversions from radians to degrees, rounding and remainders, and much more.\n", + "A look through the NumPy documentation reveals a lot of interesting functionality.\n", + "\n", + "Another excellent source for more specialized and obscure ufuncs is the submodule ``scipy.special``.\n", + "If you want to compute some obscure mathematical function on your data, chances are it is implemented in ``scipy.special``.\n", + "There are far too many functions to list them all, but the following snippet shows a couple that might come up in a statistics context:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from scipy import special" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gamma(x) = [ 1.00000000e+00 2.40000000e+01 3.62880000e+05]\n", + "ln|gamma(x)| = [ 0. 3.17805383 12.80182748]\n", + "beta(x, 2) = [ 0.5 0.03333333 0.00909091]\n" + ] + } + ], + "source": [ + "# Gamma functions (generalized factorials) and related functions\n", + "x = [1, 5, 10]\n", + "print(\"gamma(x) =\", special.gamma(x))\n", + "print(\"ln|gamma(x)| =\", special.gammaln(x))\n", + "print(\"beta(x, 2) =\", special.beta(x, 2))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "erf(x) = [ 0. 0.32862676 0.67780119 0.84270079]\n", + "erfc(x) = [ 1. 0.67137324 0.32219881 0.15729921]\n", + "erfinv(x) = [ 0. 0.27246271 0.73286908 inf]\n" + ] + } + ], + "source": [ + "# Error function (integral of Gaussian)\n", + "# its complement, and its inverse\n", + "x = np.array([0, 0.3, 0.7, 1.0])\n", + "print(\"erf(x) =\", special.erf(x))\n", + "print(\"erfc(x) =\", special.erfc(x))\n", + "print(\"erfinv(x) =\", special.erfinv(x))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are many, many more ufuncs available in both NumPy and ``scipy.special``.\n", + "Because the documentation of these packages is available online, a web search along the lines of \"gamma function python\" will generally find the relevant information." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Advanced Ufunc Features\n", + "\n", + "Many NumPy users make use of ufuncs without ever learning their full set of features.\n", + "We'll outline a few specialized features of ufuncs here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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.\n", + "Rather than creating a temporary array, this can be used to write computation results directly to the memory location where you'd like them to be.\n", + "For all ufuncs, this can be done using the ``out`` argument of the function:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "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": "markdown", + "metadata": {}, + "source": [ + "This can even be used with array views. For example, we can write the results of a computation to every other element of a specified array:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "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": {}, + "source": [ + "If we had instead written ``y[::2] = 2 ** x``, this would have resulted in the creation of a temporary array to hold the results of ``2 ** x``, followed by a second operation copying those values into the ``y`` array.\n", + "This doesn't make much of a difference for such a small computation, but for very large arrays the memory savings from careful use of the ``out`` argument can be significant." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aggregates\n", + "\n", + "For binary ufuncs, there are some interesting aggregates that can be computed directly from the object.\n", + "For example, if we'd like to *reduce* an array with a particular operation, we can use the ``reduce`` method of any ufunc.\n", + "A reduce repeatedly applies a given operation to the elements of an array until only a single result remains.\n", + "\n", + "For example, calling ``reduce`` on the ``add`` ufunc returns the sum of all elements in the array:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "15" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.arange(1, 6)\n", + "np.add.reduce(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly, calling ``reduce`` on the ``multiply`` ufunc results in the product of all array elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "120" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.multiply.reduce(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we'd like to store all the intermediate results of the computation, we can instead use ``accumulate``:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1, 3, 6, 10, 15])" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.add.accumulate(x)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1, 2, 6, 24, 120])" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.multiply.accumulate(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that for these particular cases, there are dedicated NumPy functions to compute the results (``np.sum``, ``np.prod``, ``np.cumsum``, ``np.cumprod``), which we'll explore in [Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Outer products\n", + "\n", + "Finally, any ufunc can compute the output of all pairs of two different inputs using the ``outer`` method.\n", + "This allows you, in one line, to do things like create a multiplication table:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "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": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.arange(1, 6)\n", + "np.multiply.outer(x, x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``ufunc.at`` and ``ufunc.reduceat`` methods, which we'll explore in [Fancy Indexing](02.07-Fancy-Indexing.ipynb), are very helpful as well.\n", + "\n", + "Another extremely useful feature of ufuncs is the ability to operate between arrays of different sizes and shapes, a set of operations known as *broadcasting*.\n", + "This subject is important enough that we will devote a whole section to it (see [Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb))." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ufuncs: Learning More" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "More information on universal functions (including the full list of available functions) can be found on the [NumPy](http://www.numpy.org) and [SciPy](http://www.scipy.org) documentation websites.\n", + "\n", + "Recall that you can also access information directly from within IPython by importing the packages and using IPython's tab-completion and help (``?``) functionality, as described in [Help and Documentation in IPython](01.01-Help-And-Documentation.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [The Basics of NumPy Arrays](02.02-The-Basics-Of-NumPy-Arrays.ipynb) | [Contents](Index.ipynb) | [Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.04-Computation-on-arrays-aggregates.ipynb b/numpy/02.04-Computation-on-arrays-aggregates.ipynb new file mode 100644 index 0000000..f33e5d6 --- /dev/null +++ b/numpy/02.04-Computation-on-arrays-aggregates.ipynb @@ -0,0 +1,643 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb) | [Contents](Index.ipynb) | [Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Aggregations: Min, Max, and Everything In Between" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Often when faced with a large amount of data, a first step is to compute summary statistics for the data in question.\n", + "Perhaps the most common summary statistics are the mean and standard deviation, which allow you to summarize the \"typical\" values in a dataset, but other aggregates are useful as well (the sum, product, median, minimum and maximum, quantiles, etc.).\n", + "\n", + "NumPy has fast built-in aggregation functions for working on arrays; we'll discuss and demonstrate some of them here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 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": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "55.61209116604941" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "L = np.random.random(100)\n", + "sum(L)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The syntax is quite similar to that of NumPy's ``sum`` function, and the result is the same in the simplest case:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "55.612091166049424" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sum(L)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, because it executes the operation in compiled code, NumPy's version of the operation is computed much more quickly:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 104 ms per loop\n", + "1000 loops, best of 3: 442 µs per loop\n" + ] + } + ], + "source": [ + "big_array = np.random.rand(1000000)\n", + "%timeit sum(big_array)\n", + "%timeit np.sum(big_array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Be careful, though: the ``sum`` function and the ``np.sum`` function are not identical, which can sometimes lead to confusion!\n", + "In particular, their optional arguments have different meanings, and ``np.sum`` is aware of multiple array dimensions, as we will see in the following section." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimum and Maximum\n", + "\n", + "Similarly, Python has built-in ``min`` and ``max`` functions, used to find the minimum value and maximum value of any given array:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(1.1717128136634614e-06, 0.9999976784968716)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "min(big_array), max(big_array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NumPy's corresponding functions have similar syntax, and again operate much more quickly:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(1.1717128136634614e-06, 0.9999976784968716)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.min(big_array), np.max(big_array)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 82.3 ms per loop\n", + "1000 loops, best of 3: 497 µs per loop\n" + ] + } + ], + "source": [ + "%timeit min(big_array)\n", + "%timeit np.min(big_array)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For ``min``, ``max``, ``sum``, and several other NumPy aggregates, a shorter syntax is to use methods of the array object itself:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.17171281366e-06 0.999997678497 499911.628197\n" + ] + } + ], + "source": [ + "print(big_array.min(), big_array.max(), big_array.sum())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whenever possible, make sure that you are using the NumPy version of these aggregates when operating on NumPy arrays!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Multi dimensional aggregates\n", + "\n", + "One common type of aggregation operation is an aggregate along a row or column.\n", + "Say you have some data stored in a two-dimensional array:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.8967576 0.03783739 0.75952519 0.06682827]\n", + " [ 0.8354065 0.99196818 0.19544769 0.43447084]\n", + " [ 0.66859307 0.15038721 0.37911423 0.6687194 ]]\n" + ] + } + ], + "source": [ + "M = np.random.random((3, 4))\n", + "print(M)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, each NumPy aggregation function will return the aggregate over the entire array:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "6.0850555667307118" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M.sum()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Aggregation functions take an additional argument specifying the *axis* along which the aggregate is computed. For example, we can find the minimum value within each column by specifying ``axis=0``:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.66859307, 0.03783739, 0.19544769, 0.06682827])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M.min(axis=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function returns four values, corresponding to the four columns of numbers.\n", + "\n", + "Similarly, we can find the maximum value within each row:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.8967576 , 0.99196818, 0.6687194 ])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M.max(axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The way the axis is specified here can be confusing to users coming from other languages.\n", + "The ``axis`` keyword specifies the *dimension of the array that will be collapsed*, rather than the dimension that will be returned.\n", + "So specifying ``axis=0`` means that the first axis will be collapsed: for two-dimensional arrays, this means that values within each column will be aggregated." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Other aggregation functions\n", + "\n", + "NumPy provides many other aggregation functions, but we won't discuss them in detail here.\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 (for a fuller discussion of missing data, see [Handling Missing Data](03.04-Missing-Values.ipynb)).\n", + "Some of these ``NaN``-safe functions were not added until NumPy 1.8, so they will not be available in older NumPy versions.\n", + "\n", + "The following table provides a list of useful aggregation functions available in NumPy:\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 median 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 |\n", + "\n", + "We will see these aggregates often throughout the rest of the book." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example: What is the Average Height of US Presidents?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Aggregates available in NumPy can be extremely useful for summarizing a set of values.\n", + "As a simple example, let's consider the heights of all US presidents.\n", + "This data is available in the file *president_heights.csv*, which is a simple comma-separated list of labels and values:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "order,name,height(cm)\r\n", + "1,George Washington,189\r\n", + "2,John Adams,170\r\n", + "3,Thomas Jefferson,189\r\n" + ] + } + ], + "source": [ + "!head -4 data/president_heights.csv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll use the Pandas package, which we'll explore more fully in [Chapter 3](03.00-Introduction-to-Pandas.ipynb), to read the file and extract this information (note that the heights are measured in centimeters)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[189 170 189 163 183 171 185 168 173 183 173 173 175 178 183 193 178 173\n", + " 174 183 183 168 170 178 182 180 183 178 182 188 175 179 183 193 182 183\n", + " 177 185 188 188 182 185]\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "data = pd.read_csv('data/president_heights.csv')\n", + "heights = np.array(data['height(cm)'])\n", + "print(heights)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have this data array, we can compute a variety of summary statistics:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean height: 179.738095238\n", + "Standard deviation: 6.93184344275\n", + "Minimum height: 163\n", + "Maximum height: 193\n" + ] + } + ], + "source": [ + "print(\"Mean height: \", heights.mean())\n", + "print(\"Standard deviation:\", heights.std())\n", + "print(\"Minimum height: \", heights.min())\n", + "print(\"Maximum height: \", heights.max())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that in each case, the aggregation operation reduced the entire array to a single summarizing value, which gives us information about the distribution of values.\n", + "We may also wish to compute quantiles:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25th percentile: 174.25\n", + "Median: 182.0\n", + "75th percentile: 183.0\n" + ] + } + ], + "source": [ + "print(\"25th percentile: \", np.percentile(heights, 25))\n", + "print(\"Median: \", np.median(heights))\n", + "print(\"75th percentile: \", np.percentile(heights, 75))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the median height of US presidents is 182 cm, or just shy of six feet.\n", + "\n", + "Of course, sometimes it's more useful to see a visual representation of this data, which we can accomplish using tools in Matplotlib (we'll discuss Matplotlib more fully in [Chapter 4](04.00-Introduction-To-Matplotlib.ipynb)). For example, this code generates the following chart:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import seaborn; seaborn.set() # set plot style" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfYAAAFtCAYAAAD1Skg8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xl8VNXdx/HvJJNEskAWEyzIJiWgqLwKFFHBIsrrCRZq\nArUImIBaqUKEVqgIRJBSjMCTUkCwRNyIYKxABCyCLxTxAZTNumBBVLayBxJCEsAsc58/eDGSlZkk\nMxNOPu+/Mss953fuuck3d5k7NsuyLAEAACP4+boAAABQdwh2AAAMQrADAGAQgh0AAIMQ7AAAGIRg\nBwDAIAQ76r0OHTrozJkzZZ7LysrS448/fsVl//CHP+iHH36o9j0TJkzQa6+9Vulr8+fP10cffVTh\n+SNHjuimm25SQkKCEhIS9Jvf/EYDBw7Uu+++63zP3LlztXLlymr7rqr98stXtg6u5Ouvv9aUKVMk\nSbt27dKYMWPcWr4mHA6HnnjiCcXFxWnJkiVlXqtqzh5//HHneissLNSzzz6r/v376/7779eAAQP0\nzjvvVNpXVlaWunbtqoSEBA0YMEDx8fEaMmSIvvjiizobT1Xbz7p165SYmFirth999FG35xRwhd3X\nBQBXYrPZarzswoULa9X3Z599pnbt2lX62jXXXKOsrCzn46NHj2r48OEKCQlRnz59NHr06Fq1f/ny\nNVkH3333nU6cOCFJuvnmmzVnzhy323DX8ePHtWXLFn3xxRc1qjktLU0hISFavXq1JCk7O1uDBg1S\n8+bNdccdd1R4f9euXfWPf/zD+XjDhg1KTk7WJ598Ij+/2u+3VLf91Ga7lKTNmzfXanmgKgQ76r0r\n3UOpuLhY//u//6vt27fL4XDoxhtvVEpKikJCQtS7d2/NmzdPHTt2VHp6upYvX66QkBB17dpV69ev\nd+4tf/7551q3bp1Onz6t2NhYpaWlafny5dq1a5dmzpwpPz8/3XvvvdXW0axZM40ePVqvvPKK+vTp\nowkTJig2NlYPP/yw5s6dqw8//FABAQEKDw9XamqqPvjggzLtf/jhhzpz5owOHz6sXr166dSpU87l\nLcvS3/72N+3atUuWZWnMmDHq1auXsrKytG7dOme4XXr83HPPad68eSooKNDEiRMVHx+vadOmafXq\n1SooKNDUqVO1Z88e2Ww29ezZU2PHjpWfn59uvfVWjRgxQps3b1Z2drYSExM1bNiwCmPdsWOHZs2a\npQsXLiggIEBjxoxR586d9dhjj6mkpEQDBgzQ3Llz1aJFC7fmOjs7W9dee62Ki4sVEBCg6OhozZs3\nT02aNHFp+dtvv12nT5/W2bNnNWPGjDLrc/To0VVuJ0uXLtXbb7+twMBABQUFaerUqWrbtm2Z7WfO\nnDl67733FBERoZYtWzr7vNL2N2DAAH366ac6duyY7rvvPo0bN04TJkyQJCUlJenll1/Whx9+WGn/\nQE1wKB5XhaSkJOdh7/j4eM2dO9f5Wnp6uux2u1asWKF3331XMTExSktLK7P8pk2b9O6772r58uVa\nsWKFCgsLy+xxnTx5UosXL9a6det07NgxffDBBxo6dKhuvvlmPf3001cM9Us6dOigb7/9tsxzx48f\n1+LFi7Vs2TItW7ZMPXr00FdffeVsf/z48c72f/zxR61evVpjx46t0HarVq20YsUKzZw5U+PHj1du\nbm6VdVx33XUaPXq0unTpoueff77Ma9OmTVNERIRWr16t5cuXa8+ePXrllVckSUVFRYqMjNRbb72l\nOXPmKC0tTUVFRWWWP3PmjMaMGaOUlBStXLlSL7zwgv785z/rzJkzSk9PV1BQkLKystwOdUlKTk7W\nli1bdPvtt+v3v/+9FixYoJCQEF1//fUuLZ+Zmal27dopPDxcUtn1WdV24nA4lJqaqldeeUXvvPOO\nfve73+nzzz8v0+6HH36o9evXa9WqVcrMzFRBQYHztSttf+fOndOSJUv01ltvKSMjQ0eOHFFqaqok\nKSMjQ9HR0VfsH3AHe+y4KmRkZJTZa7u0ZypJH3/8sfLz852HNktKShQVFVVm+Y0bNyouLk6hoaGS\npKFDh+qzzz5zvn7PPfcoMDBQkhQbG6ucnJwa1Wmz2dSoUaMyzzVt2lQ33nijEhIS1LNnT9111126\n/fbbna9ffkSic+fOVbb94IMPSpLatWundu3a1fhc8v/93/8pMzNTkhQQEKDBgwfrjTfe0GOPPSbp\n4rqQpI4dO6q4uFjnz593rhtJ+vLLL9WqVSvdcsstkqSf//zn6ty5s7Zt26Zu3bpV2W9Vh64dDofz\nsHn79u21bt06/ec//9G2bdu0efNmLVy4UHPmzFGvXr0qLLtjxw4lJCRIurjnfMMNN2jevHnO1y9f\nn1VtJ35+furbt68GDRqkXr166c4771T//v3L9PPpp5+qT58+zrkdOHCgMjIyqm33kkvrs2nTpoqK\n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\n8GXJdery8T/11FNKSkpSYmKievfurbFjx0oyd/5dGftf//rXBjH3u3fv1qBBgzR06FBNmjTJ+R5T\n515ybfwN5Xf/m2++0QMPPKCHHnpIf/3rX53vaSjzX9X43Z5/qx54+eWXrX79+lmDBg2yLMuynnnm\nGev999+3LMuyPvvsM+vjjz+2srOzrX79+lnFxcVWfn6+1a9fP6uoqMiXZdcZV8ZvWZY1ePBgKzc3\n12d1ekr58V+Sl5dnxcfHW6dOnTJ2/l0Zu2U1nLkfNWqU9cknn1iWZVljx461NmzYYOzcW5Zr47es\nhjP/AwYMsL744gvLsixr9uzZ1qpVqxrU/Fc2fstyf/7rxR57q1atNH/+fOfjzz//XMePH9fDDz+s\n9957T7fddpu++uordenSRXa7XaGhoWrdurXzI3NXO1fGb1mWDh48qMmTJ2vw4MFavny5DyuuW+XH\nf8ncuXP10EMPKSoqytj5d2XsDWnub7zxRuXm5sqyLBUWFsputxs795Jr429I83/ixAl16tRJ0sV7\nnuzYsaNBzX/58e/cubNG818vgr1Pnz7y9/d3Pj5y5IjCw8P12muv6brrrlN6enqFW9EGBwcrPz/f\nF+XWOVfGf+7cOSUmJmrWrFlatGiRli5dqr179/qw6rpTfvzSxdMRW7du1YABAyRVvBWxKfPvytgb\n0ty3bt1a06dP169//Wvl5OSoW7duxs695Nr4G9L8t2jRQjt27JAkbdiwQRcuXGhQ819+/OfPn9f5\n8+fdnv96EezlhYeH6+6775Yk9e7dW7t27VJYWFiZ8wqFhYVq3Lixr0r0qPLj/+abbxQcHKzExEQF\nBQUpJCRE3bt31549e3xcqeesXbtW/fr1k8128X7RoaGhDWb+y4+9UaNGDWbup0+frqVLl2rNmjX6\nzW9+oxdeeKFB/e5XNv6G9Lv//PPP6x//+IcefvhhRUVFKSIiokHNf2Xjr8nvf70M9i5dujhvO7t9\n+3a1a9dOt9xyi3bu3KmioiLl5+dr3759ateunY8r9Yzy4//5z3+uffv2afDgwbIsS8XFxdq5c6c6\nduzo40rrlnXZvZI+/fRT3XXXXc7Ht956q9HzX93Y9+/fb/zcXxIeHq7Q0FBJUtOmTXX27NkG9btf\n2fgbwu/+JRs3blRaWppee+01nTlzRnfccUeDmv/Kxl+T+a9/XwMmafz48UpJSdFbb72lsLAwpaWl\nKSwszHmVuGVZeuqppxQYGOjrUj2iqvHHx8frgQceUEBAgBISEtS2bVtfl1qnLu2hStKBAwfUokUL\n5+Nrr72o4tndAAAEk0lEQVTW6Pmvbuxt27Y1fu4vmTZtmv74xz/KbrcrMDBQ06ZNM37uL1fZ+Js1\na9Zg5r9Vq1YaNmyYGjVqpNtuu835D25Dmf+qxu/u/HNLWQAADFIvD8UDAICaIdgBADAIwQ4AgEEI\ndgAADEKwAwBgEIIdAACDEOzAVWTbtm3Ob4JyVUJCQrWvZ2VlacKECRWeLygo0KhRo6pc7plnnlF2\ndrZbtZQ3Y8YM7d69u1ZtACiLYAeuMpffzMYVWVlZNernzJkzVd668uOPP1bTpk0VHR1do7YvGTFi\nhJ5//vlatQGgLIIduMrk5ORoxIgRiouL08iRI1VcXCxJevfddzVgwAAlJCQoJSVFRUVFkqQOHTpI\nurgHPnLkSPXv31+PP/64EhISdPToUUnSwYMHlZiYqHvvvVeTJ0+WdPG+5SdPntSTTz5ZoYZFixYp\nPj5ekpSXl6fk5GTdd999SkhI0NatWyVJPXr00LPPPqu+ffsqKSlJa9eu1dChQ3Xvvfc6v+giIiJC\nkZGR2rZtmwfXGNCwEOzAVebYsWN67rnntHbtWmVnZ2vLli36/vvv9c477ygzM1NZWVmKjIzUq6++\nKumnPfwXX3xRN9xwg1avXq3k5OQy3xB1/PhxLViwQGvWrNHGjRv1ww8/KCUlRTExMZo3b16Z/vPy\n8nTgwAG1adNGkjRnzhy1atVKa9as0YwZMzR79mxJ0qlTp9S7d2+9//77kqT169dryZIlSk5O1htv\nvOFsr2vXrvroo488t8KABqZe3iseQNU6dOigZs2aSbp4H/nc3FwdPnxYBw8e1KBBg2RZlkpKSip8\nUcSWLVuUlpYmSbr55pvVvn1752tdu3Z1fjVmy5YtlZubq5/97GeV9n/o0CHFxMQ4H2/fvt3Zbmxs\nrDIzMyVd/IeiZ8+ekqTmzZurS5cukqRmzZopLy/PuXyzZs20efPmmq8QAGUQ7MBV5vLvb760N15a\nWqq+fftq0qRJkqTz58+rtLS0wnIOh8P5+PKviSj/nfDVfYWEn5+f7Paf/nRc/rMk7du3z7k3X937\nLn/ez4+Dh0Bd4bcJMEC3bt20fv165eTkyLIsTZkyRa+//rqkn0L6jjvu0HvvvSdJ+vbbb/Xdd99V\neyGe3W6v8M+BJF1//fU6fvy48/Evf/lL/etf/5Ik/fDDD3rsscdks9mq/efgcocPH1arVq1cei+A\nKyPYAQN06NBBo0aN0rBhw9S/f39ZlqURI0ZI+mmv/oknntDBgwd1//3368UXX1R0dLSCgoIqtHXp\n/VFRUbruuus0bNiwMq83adJELVu21A8//CBJevLJJ3XgwAHdf//9evrppzVr1qwy7VzJ1q1bdc89\n99Rs4AAq4GtbgQZi1apVatGihX7xi1/o2LFjSkxM1Pr162vU1oYNG7Rt2zaNHz++VjWdPn1ao0eP\n1pIlS2rVDoCfcI4daCBuuOEGTZkyRQ6HQ/7+/po2bVqN27r77ru1Zs0aZWdn1+qz7Onp6Zo4cWKN\nlwdQEXvsAAAYhHPsAAAYhGAHAMAgBDsAAAYh2AEAMAjBDgCAQQh2AAAM8v/gmhQSmQZxLgAAAABJ\nRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(heights)\n", + "plt.title('Height Distribution of US Presidents')\n", + "plt.xlabel('height (cm)')\n", + "plt.ylabel('number');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These aggregates are some of the fundamental pieces of exploratory data analysis that we'll explore in more depth in later chapters of the book." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb) | [Contents](Index.ipynb) | [Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.05-Computation-on-arrays-broadcasting.ipynb b/numpy/02.05-Computation-on-arrays-broadcasting.ipynb new file mode 100644 index 0000000..0bfb4c3 --- /dev/null +++ b/numpy/02.05-Computation-on-arrays-broadcasting.ipynb @@ -0,0 +1,801 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb) | [Contents](Index.ipynb) | [Comparisons, Masks, and Boolean Logic](02.06-Boolean-Arrays-and-Masks.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Computation on Arrays: Broadcasting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We saw in the previous section how NumPy's universal functions can be used to *vectorize* operations and thereby remove slow Python loops.\n", + "Another means of vectorizing operations is to use NumPy's *broadcasting* functionality.\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": {}, + "source": [ + "## 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": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "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–for example, we can just as easily add a scalar (think of it as a zero-dimensional array) to an array:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "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.\n", + "The advantage of NumPy's broadcasting is that this duplication of values does not actually take place, but it is a useful mental model as we think about broadcasting.\n", + "\n", + "We can similarly extend this to arrays of higher dimension. Observe the result when we add a one-dimensional array to a two-dimensional array:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "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": { + "collapsed": false + }, + "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", + "\n", + "While these examples are relatively easy to understand, more complicated cases can involve broadcasting of both arrays. Consider the following example:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0 1 2]\n", + "[[0]\n", + " [1]\n", + " [2]]\n" + ] + } + ], + "source": [ + "a = np.arange(3)\n", + "b = np.arange(3)[:, np.newaxis]\n", + "\n", + "print(a)\n", + "print(b)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "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": {}, + "source": [ + "Just as before we stretched or broadcasted one value to match the shape of the other, here we've stretched *both* ``a`` and ``b`` to match a common shape, and the result is a two-dimensional array!\n", + "The geometry of these examples is visualized in the following figure (Code to produce this plot can be found in the [appendix](06.00-Figure-Code.ipynb#Broadcasting), and is adapted from source published in the [astroML](http://astroml.org) documentation. Used by permission)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Broadcasting Visual](figures/02.05-broadcasting.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The light boxes represent the broadcasted values: again, this extra memory is not actually allocated in the course of the operation, but it can be useful conceptually to imagine that it is." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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", + "\n", + "To make these rules clear, let's consider a few examples in detail." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Broadcasting example 1\n", + "\n", + "Let's look at adding a two-dimensional array to a one-dimensional array:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "M = np.ones((2, 3))\n", + "a = np.arange(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's consider an operation on these two arrays. The shape of the arrays are\n", + "\n", + "- ``M.shape = (2, 3)``\n", + "- ``a.shape = (3,)``\n", + "\n", + "We see by rule 1 that the array ``a`` has fewer dimensions, so we pad it on the left with ones:\n", + "\n", + "- ``M.shape -> (2, 3)``\n", + "- ``a.shape -> (1, 3)``\n", + "\n", + "By rule 2, we now see that the first dimension disagrees, so we stretch this dimension to match:\n", + "\n", + "- ``M.shape -> (2, 3)``\n", + "- ``a.shape -> (2, 3)``\n", + "\n", + "The shapes match, and we see that the final shape will be ``(2, 3)``:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1., 2., 3.],\n", + " [ 1., 2., 3.]])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M + a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Broadcasting example 2\n", + "\n", + "Let's take a look at an example where both arrays need to be broadcast:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "a = np.arange(3).reshape((3, 1))\n", + "b = np.arange(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, we'll start by writing out the shape of the arrays:\n", + "\n", + "- ``a.shape = (3, 1)``\n", + "- ``b.shape = (3,)``\n", + "\n", + "Rule 1 says we must pad the shape of ``b`` with ones:\n", + "\n", + "- ``a.shape -> (3, 1)``\n", + "- ``b.shape -> (1, 3)``\n", + "\n", + "And rule 2 tells us that we upgrade each of these ones to match the corresponding size of the other array:\n", + "\n", + "- ``a.shape -> (3, 3)``\n", + "- ``b.shape -> (3, 3)``\n", + "\n", + "Because the result matches, these shapes are compatible. We can see this here:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 1, 2],\n", + " [1, 2, 3],\n", + " [2, 3, 4]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a + b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Broadcasting example 3\n", + "\n", + "Now let's take a look at an example in which the two arrays are not compatible:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "M = np.ones((3, 2))\n", + "a = np.arange(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is just a slightly different situation than in the first example: the matrix ``M`` is transposed.\n", + "How does this affect the calculation? The shape of the arrays are\n", + "\n", + "- ``M.shape = (3, 2)``\n", + "- ``a.shape = (3,)``\n", + "\n", + "Again, rule 1 tells us that we must pad the shape of ``a`` with ones:\n", + "\n", + "- ``M.shape -> (3, 2)``\n", + "- ``a.shape -> (1, 3)``\n", + "\n", + "By rule 2, the first dimension of ``a`` is stretched to match that of ``M``:\n", + "\n", + "- ``M.shape -> (3, 2)``\n", + "- ``a.shape -> (3, 3)``\n", + "\n", + "Now we hit rule 3–the final shapes do not match, so these two arrays are incompatible, as we can observe by attempting this operation:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "operands could not be broadcast together with shapes (3,2) (3,) ", + "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[0ma\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (3,2) (3,) " + ] + } + ], + "source": [ + "M + a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note the potential confusion here: you could imagine making ``a`` and ``M`` compatible by, say, padding ``a``'s shape with ones on the right rather than the left.\n", + "But this is not how the broadcasting rules work!\n", + "That sort of flexibility might be useful in some cases, but it would lead to potential areas of ambiguity.\n", + "If right-side padding is what you'd like, you can do this explicitly by reshaping the array (we'll use the ``np.newaxis`` keyword introduced in [The Basics of NumPy Arrays](02.02-The-Basics-Of-NumPy-Arrays.ipynb)):" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 1)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a[:, np.newaxis].shape" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1., 1.],\n", + " [ 2., 2.],\n", + " [ 3., 3.]])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M + a[:, np.newaxis]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Also note that while we've been focusing on the ``+`` operator here, these broadcasting rules apply to *any* binary ``ufunc``.\n", + "For example, here is the ``logaddexp(a, b)`` function, which computes ``log(exp(a) + exp(b))`` with more precision than the naive approach:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1.31326169, 1.31326169],\n", + " [ 1.69314718, 1.69314718],\n", + " [ 2.31326169, 2.31326169]])" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.logaddexp(M, a[:, np.newaxis])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For more information on the many available universal functions, refer to [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Broadcasting in Practice" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Broadcasting operations form the core of many examples we'll see throughout this book.\n", + "We'll now take a look at a couple simple examples of where they can be useful." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Centering an array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous section, we saw that ufuncs allow a NumPy user to remove the need to explicitly write slow Python loops. Broadcasting extends this ability.\n", + "One commonly seen example is when centering an array of data.\n", + "Imagine you have an array of 10 observations, each of which consists of 3 values.\n", + "Using the standard convention (see [Data Representation in Scikit-Learn](05.02-Introducing-Scikit-Learn.ipynb#Data-Representation-in-Scikit-Learn)), we'll store this in a $10 \\times 3$ array:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "X = np.random.random((10, 3))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can compute the mean of each feature using the ``mean`` aggregate across the first dimension:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0.53514715, 0.66567217, 0.44385899])" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Xmean = X.mean(0)\n", + "Xmean" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we can center the ``X`` array by subtracting the mean (this is a broadcasting operation):" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "X_centered = X - Xmean" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To double-check that we've done this correctly, we can check that the centered array has near zero mean:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 2.22044605e-17, -7.77156117e-17, -1.66533454e-17])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_centered.mean(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To within machine precision, the mean is now zero." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting a two-dimensional function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# x and y have 50 steps from 0 to 5\n", + "x = np.linspace(0, 5, 50)\n", + "y = np.linspace(0, 5, 50)[:, np.newaxis]\n", + "\n", + "z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll use Matplotlib to plot this two-dimensional array (these tools will be discussed in full in [Density and Contour Plots](04.04-Density-and-Contour-Plots.ipynb)):" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nrQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(z, origin='lower', extent=[0, 5, 0, 5],\n", + " cmap='viridis')\n", + "plt.colorbar();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The result is a compelling visualization of the two-dimensional function." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb) | [Contents](Index.ipynb) | [Comparisons, Masks, and Boolean Logic](02.06-Boolean-Arrays-and-Masks.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.06-Boolean-Arrays-and-Masks.ipynb b/numpy/02.06-Boolean-Arrays-and-Masks.ipynb new file mode 100644 index 0000000..f58df50 --- /dev/null +++ b/numpy/02.06-Boolean-Arrays-and-Masks.ipynb @@ -0,0 +1,1280 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb) | [Contents](Index.ipynb) | [Fancy Indexing](02.07-Fancy-Indexing.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comparisons, Masks, and Boolean Logic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This section covers the use of Boolean masks to examine and manipulate values within NumPy arrays.\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": {}, + "source": [ + "## Example: Counting Rainy Days\n", + "\n", + "Imagine you have a series of data that represents the amount of precipitation each day for a year in a given city.\n", + "For example, here we'll load the daily rainfall statistics for the city of Seattle in 2014, using Pandas (which is covered in more detail in [Chapter 3](03.00-Introduction-to-Pandas.ipynb)):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(365,)" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# use pandas to extract rainfall inches as a NumPy array\n", + "rainfall = pd.read_csv('data/Seattle2014.csv')['PRCP'].values\n", + "inches = rainfall / 254 # 1/10mm -> inches\n", + "inches.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The array contains 365 values, giving daily rainfall in inches from January 1 to December 31, 2014.\n", + "\n", + "As a first quick visualization, let's look at the histogram of rainy days, which was generated using Matplotlib (we will explore this tool more fully in [Chapter 4](04.00-Introduction-To-Matplotlib.ipynb)):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import seaborn; seaborn.set() # set plot styles" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nAIUTawAo3ITHulqtZtOmTVm9enXWrl2bF1544S33P/zww7nsssuyevXq/PKXv5zo6TBGI63fz3/+\n81xyySVZu3Zt1q5dm+eee25qJsq72rNnT7q7u992u22vPrzb+tn2yvbGG2/kuuuuy5e+9KVcfvnl\nefjhh99y/5i3v+oE+93vfle9/vrrq9VqtfrEE09Ur7766qH7BgcHq8uWLav29fVVBwYGqpdeemn1\n1VdfnegpMQbDrV+1Wq1++9vfru7du3cqpsYo/OxnP6tecskl1S9+8Ytvud22Vx/ebf2qVdte6R54\n4IHq7bffXq1Wq9W//vWv1SVLlgzdN57tb8L3rIe7Xvizzz6befPmpbW1NU1NTens7Mzu3bsnekqM\nwUjXe9+7d2+2bt2arq6u/PSnP52KKTKMefPmZfPmzW+73bZXH95t/RLbXulWrFiRDRs2JElOnjyZ\nxsa/Xd17PNvfhMf63a4X/k73tbS0pK+vb6KnxBgMt35JcvHFF+eWW27Jfffdl8cff3zoCnaUYdmy\nZe/4JQa2vfrwbuuX2PZKN3v27DQ3N6dSqWTDhg255pprhu4bz/Y34bFubW1Nf3//0M9//8Uera2t\nqVT+djH6/v7+zJkzZ6KnxBgMt35JcuWVV2bu3LlpbGzMZz7zmezbt28qpskY2fbqn22vfC+99FKu\nvPLKrFy5Mp/97GeHbh/P9jfhsR7ueuHz58/P888/n6NHj2ZgYCC7d+/OBRdcMNFTYgyGW79KpZJL\nLrkkr7/+eqrVav74xz/m3HPPnaqpMozqP1yo0LZXX/5x/Wx75Tt8+HB6enpy7bXXZuXKlW+5bzzb\nX02/IvOdLFu2LI8++mhWr16d5D+vF/6b3/wmr7/+elatWpWNGzfmqquuSrVazapVq/K+9w3/xfFM\nrpHWr7e3N93d3TnttNPyqU99KosXL57iGfNOGhr+86s6bXv16Z3Wz7ZXtq1bt+bo0aPZsmVLNm/e\nnIaGhlx++eXj3v5cGxwACueiKABQOLEGgMKJNQAUTqwBoHBiDQCFE2sAKJxYA0Dh/h/uLOJdBEs5\nngAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.hist(inches, 40);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This histogram gives us a general idea of what the data looks like: despite its reputation, the vast majority of days in Seattle saw near zero measured rainfall in 2014.\n", + "But this doesn't do a good job of conveying some information we'd like to see: for example, how many rainy days were there in the year? What is the average precipitation on those rainy days? How many days were there with more than half an inch of rain?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Digging into the data\n", + "\n", + "One approach to this would be to answer these questions by hand: loop through the data, incrementing a counter each time we see values in some desired range.\n", + "For reasons discussed throughout this chapter, such an approach is very inefficient, both from the standpoint of time writing code and time computing the result.\n", + "We saw in [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb) that NumPy's ufuncs can be used in place of loops to do fast element-wise arithmetic operations on arrays; in the same way, we can use other ufuncs to do element-wise *comparisons* over arrays, and we can then manipulate the results to answer the questions we have.\n", + "We'll leave the data aside for right now, and discuss some general tools in NumPy to use *masking* to quickly answer these types of questions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparison Operators as ufuncs\n", + "\n", + "In [Computation on NumPy Arrays: Universal Functions](02.03-Computation-on-arrays-ufuncs.ipynb) we introduced ufuncs, and focused in particular on arithmetic operators. We saw that using ``+``, ``-``, ``*``, ``/``, and others on arrays leads to element-wise operations.\n", + "NumPy also implements comparison operators such as ``<`` (less than) and ``>`` (greater than) as element-wise ufuncs.\n", + "The result of these comparison operators is always an array with a Boolean data type.\n", + "All six of the standard comparison operations are available:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "x = np.array([1, 2, 3, 4, 5])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ True, True, False, False, False], dtype=bool)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x < 3 # less than" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([False, False, False, True, True], dtype=bool)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x > 3 # greater than" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ True, True, True, False, False], dtype=bool)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x <= 3 # less than or equal" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([False, False, True, True, True], dtype=bool)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x >= 3 # greater than or equal" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ True, True, False, True, True], dtype=bool)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x != 3 # not equal" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([False, False, True, False, False], dtype=bool)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x == 3 # equal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to do an element-wise comparison of two arrays, and to include compound expressions:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([False, True, False, False, False], dtype=bool)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(2 * x) == (x ** 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As in the case of arithmetic operators, the comparison operators are implemented as ufuncs in NumPy; for example, when you write ``x < 3``, internally NumPy uses ``np.less(x, 3)``.\n", + " A summary of the comparison operators and their equivalent ufunc is shown here:\n", + "\n", + "| Operator\t | Equivalent ufunc || Operator\t | Equivalent ufunc |\n", + "|---------------|---------------------||---------------|---------------------|\n", + "|``==`` |``np.equal`` ||``!=`` |``np.not_equal`` |\n", + "|``<`` |``np.less`` ||``<=`` |``np.less_equal`` |\n", + "|``>`` |``np.greater`` ||``>=`` |``np.greater_equal`` |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Just as in the case of arithmetic ufuncs, these will work on arrays of any size and shape.\n", + "Here is a two-dimensional example:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[5, 0, 3, 3],\n", + " [7, 9, 3, 5],\n", + " [2, 4, 7, 6]])" + ] + }, + "execution_count": 12, + "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": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ True, True, True, True],\n", + " [False, False, True, True],\n", + " [ True, True, False, False]], dtype=bool)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x < 6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In each case, the result is a Boolean array, and NumPy provides a number of straightforward patterns for working with these Boolean results." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Working with Boolean Arrays\n", + "\n", + "Given a Boolean array, there are a host of useful operations you can do.\n", + "We'll work with ``x``, the two-dimensional array we created earlier." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[5 0 3 3]\n", + " [7 9 3 5]\n", + " [2 4 7 6]]\n" + ] + } + ], + "source": [ + "print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# how many values less than 6?\n", + "np.count_nonzero(x < 6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that there are eight array entries that are less than 6.\n", + "Another way to get at this information is to use ``np.sum``; in this case, ``False`` is interpreted as ``0``, and ``True`` is interpreted as ``1``:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sum(x < 6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The benefit of ``sum()`` is that like with other NumPy aggregation functions, this summation can be done along rows or columns as well:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([4, 2, 2])" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# how many values less than 6 in each row?\n", + "np.sum(x < 6, axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This counts the number of values less than 6 in each row of the matrix.\n", + "\n", + "If we're interested in quickly checking whether any or all the values are true, we can use (you guessed it) ``np.any`` or ``np.all``:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# are there any values greater than 8?\n", + "np.any(x > 8)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# are there any values less than zero?\n", + "np.any(x < 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# are all values less than 10?\n", + "np.all(x < 10)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# are all values equal to 6?\n", + "np.all(x == 6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``np.all`` and ``np.any`` can be used along particular axes as well. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ True, False, True], dtype=bool)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# are all values in each row less than 4?\n", + "np.all(x < 8, axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here all the elements in the first and third rows are less than 8, while this is not the case for the second row.\n", + "\n", + "Finally, a quick warning: as mentioned in [Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb), Python has built-in ``sum()``, ``any()``, and ``all()`` functions. These have a different syntax than the NumPy versions, and in particular will fail or produce unintended results when used on multidimensional arrays. Be sure that you are using ``np.sum()``, ``np.any()``, and ``np.all()`` for these examples!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Boolean operators\n", + "\n", + "We've already seen how we might count, say, all days with rain less than four inches, or all days with rain greater than two inches.\n", + "But what if we want to know about all days with rain less than four inches and greater than one inch?\n", + "This is accomplished through Python's *bitwise logic operators*, ``&``, ``|``, ``^``, and ``~``.\n", + "Like with the standard arithmetic operators, NumPy overloads these as ufuncs which work element-wise on (usually Boolean) arrays.\n", + "\n", + "For example, we can address this sort of compound question as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "29" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sum((inches > 0.5) & (inches < 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So we see that there are 29 days with rainfall between 0.5 and 1.0 inches.\n", + "\n", + "Note that the parentheses here are important–because of operator precedence rules, with parentheses removed this expression would be evaluated as follows, which results in an error:\n", + "\n", + "``` python\n", + "inches > (0.5 & inches) < 1\n", + "```\n", + "\n", + "Using the equivalence of *A AND B* and *NOT (NOT A OR NOT B)* (which you may remember if you've taken an introductory logic course), we can compute the same result in a different manner:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "29" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sum(~( (inches <= 0.5) | (inches >= 1) ))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Combining comparison operators and Boolean operators on arrays can lead to a wide range of efficient logical operations.\n", + "\n", + "The following table summarizes the bitwise Boolean operators and their equivalent ufuncs:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "| Operator\t | Equivalent ufunc || Operator\t | Equivalent ufunc |\n", + "|---------------|---------------------||---------------|---------------------|\n", + "|``&`` |``np.bitwise_and`` ||| |``np.bitwise_or`` |\n", + "|``^`` |``np.bitwise_xor`` ||``~`` |``np.bitwise_not`` |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using these tools, we might start to answer the types of questions we have about our weather data.\n", + "Here are some examples of results we can compute when combining masking with aggregations:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number days without rain: 215\n", + "Number days with rain: 150\n", + "Days with more than 0.5 inches: 37\n", + "Rainy days with < 0.2 inches : 75\n" + ] + } + ], + "source": [ + "print(\"Number days without rain: \", np.sum(inches == 0))\n", + "print(\"Number days with rain: \", np.sum(inches != 0))\n", + "print(\"Days with more than 0.5 inches:\", np.sum(inches > 0.5))\n", + "print(\"Rainy days with < 0.2 inches :\", np.sum((inches > 0) &\n", + " (inches < 0.2)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Boolean Arrays as Masks\n", + "\n", + "In the preceding section we looked at aggregates computed directly on Boolean arrays.\n", + "A more powerful pattern is to use Boolean arrays as masks, to select particular subsets of the data themselves.\n", + "Returning to our ``x`` array from before, suppose we want an array of all values in the array that are less than, say, 5:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[5, 0, 3, 3],\n", + " [7, 9, 3, 5],\n", + " [2, 4, 7, 6]])" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can obtain a Boolean array for this condition easily, as we've already seen:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[False, True, True, True],\n", + " [False, False, True, False],\n", + " [ True, True, False, False]], dtype=bool)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x < 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now to *select* these values from the array, we can simply index on this Boolean array; this is known as a *masking* operation:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 3, 3, 3, 2, 4])" + ] + }, + "execution_count": 28, + "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", + "\n", + "We are then free do operate on these values as we wish.\n", + "For example, we can compute some relevant statistics on our Seattle rain data:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Median precip on rainy days in 2014 (inches): 0.194881889764\n", + "Median precip on summer days in 2014 (inches): 0.0\n", + "Maximum precip on summer days in 2014 (inches): 0.850393700787\n", + "Median precip on non-summer rainy days (inches): 0.200787401575\n" + ] + } + ], + "source": [ + "# construct a mask of all rainy days\n", + "rainy = (inches > 0)\n", + "\n", + "# construct a mask of all summer days (June 21st is the 172nd day)\n", + "days = np.arange(365)\n", + "summer = (days > 172) & (days < 262)\n", + "\n", + "print(\"Median precip on rainy days in 2014 (inches): \",\n", + " np.median(inches[rainy]))\n", + "print(\"Median precip on summer days in 2014 (inches): \",\n", + " np.median(inches[summer]))\n", + "print(\"Maximum precip on summer days in 2014 (inches): \",\n", + " np.max(inches[summer]))\n", + "print(\"Median precip on non-summer rainy days (inches):\",\n", + " np.median(inches[rainy & ~summer]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By combining Boolean operations, masking operations, and aggregates, we can very quickly answer these sorts of questions for our dataset." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Aside: Using the Keywords and/or Versus the Operators &/|\n", + "\n", + "One common point of confusion is the difference between the keywords ``and`` and ``or`` on one hand, and the operators ``&`` and ``|`` on the other hand.\n", + "When would you use one versus the other?\n", + "\n", + "The difference is this: ``and`` and ``or`` gauge the truth or falsehood of *entire object*, while ``&`` and ``|`` refer to *bits within each object*.\n", + "\n", + "When you use ``and`` or ``or``, it's equivalent to asking Python to treat the object as a single Boolean entity.\n", + "In Python, all nonzero integers will evaluate as True. Thus:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, False)" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(42), bool(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(42 and 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(42 or 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you use ``&`` and ``|`` on integers, the expression operates on the bits of the element, applying the *and* or the *or* to the individual bits making up the number:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'0b101010'" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bin(42)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'0b111011'" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bin(59)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'0b101010'" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bin(42 & 59)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'0b111011'" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bin(42 | 59)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that the corresponding bits of the binary representation are compared in order to yield the result.\n", + "\n", + "When you have an array of Boolean values in NumPy, this can be thought of as a string of bits where ``1 = True`` and ``0 = False``, and the result of ``&`` and ``|`` operates similarly to above:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ True, True, True, False, True, True], dtype=bool)" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A = np.array([1, 0, 1, 0, 1, 0], dtype=bool)\n", + "B = np.array([1, 1, 1, 0, 1, 1], dtype=bool)\n", + "A | B" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using ``or`` on these arrays will try to evaluate the truth or falsehood of the entire array object, which is not a well-defined value:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()", + "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[0mA\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mB\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()" + ] + } + ], + "source": [ + "A or B" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly, when doing a Boolean expression on a given array, you should use ``|`` or ``&`` rather than ``or`` or ``and``:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([False, False, False, False, False, True, True, True, False, False], dtype=bool)" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.arange(10)\n", + "(x > 4) & (x < 8)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Trying to evaluate the truth or falsehood of the entire array will give the same ``ValueError`` we saw previously:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()", + "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[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m8\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()" + ] + } + ], + "source": [ + "(x > 4) and (x < 8)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So remember this: ``and`` and ``or`` perform a single Boolean evaluation on an entire object, while ``&`` and ``|`` perform multiple Boolean evaluations on the content (the individual bits or bytes) of an object.\n", + "For Boolean NumPy arrays, the latter is nearly always the desired operation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb) | [Contents](Index.ipynb) | [Fancy Indexing](02.07-Fancy-Indexing.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.07-Fancy-Indexing.ipynb b/numpy/02.07-Fancy-Indexing.ipynb new file mode 100644 index 0000000..f35a286 --- /dev/null +++ b/numpy/02.07-Fancy-Indexing.ipynb @@ -0,0 +1,929 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Comparisons, Masks, and Boolean Logic](02.06-Boolean-Arrays-and-Masks.ipynb) | [Contents](Index.ipynb) | [Sorting Arrays](02.08-Sorting.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fancy Indexing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous sections, 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", + "In this section, we'll look at another style of array indexing, known as *fancy indexing*.\n", + "Fancy indexing is like the simple indexing we've already seen, but we pass arrays of indices in place of single scalars.\n", + "This allows us to very quickly access and modify complicated subsets of an array's values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exploring Fancy Indexing\n", + "\n", + "Fancy indexing is conceptually simple: it means passing an array of indices to access multiple array elements at once.\n", + "For example, consider the following array:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[51 92 14 71 60 20 82 86 74 74]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "rand = np.random.RandomState(42)\n", + "\n", + "x = rand.randint(100, size=10)\n", + "print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we want to access three different elements. We could do it like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[71, 86, 14]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[x[3], x[7], x[2]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, we can pass a single list or array of indices to obtain the same result:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([71, 86, 60])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ind = [3, 7, 4]\n", + "x[ind]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[71, 86],\n", + " [60, 20]])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ind = np.array([[3, 7],\n", + " [4, 5]])\n", + "x[ind]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Fancy indexing also works in multiple dimensions. Consider the following array:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1, 2, 3],\n", + " [ 4, 5, 6, 7],\n", + " [ 8, 9, 10, 11]])" + ] + }, + "execution_count": 5, + "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": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 2, 5, 11])" + ] + }, + "execution_count": 6, + "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": {}, + "source": [ + "Notice that the first value in the result is ``X[0, 2]``, the second is ``X[1, 1]``, and the third is ``X[2, 3]``.\n", + "The pairing of indices in fancy indexing follows all the broadcasting rules that were mentioned in [Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb).\n", + "So, for example, if we combine a column vector and a row vector within the indices, we get a two-dimensional result:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 2, 1, 3],\n", + " [ 6, 5, 7],\n", + " [10, 9, 11]])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X[row[:, np.newaxis], col]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, each row value is matched with each column vector, exactly as we saw in broadcasting of arithmetic operations.\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0, 0, 0],\n", + " [2, 1, 3],\n", + " [4, 2, 6]])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "row[:, np.newaxis] * col" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is always important to 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": {}, + "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": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 1 2 3]\n", + " [ 4 5 6 7]\n", + " [ 8 9 10 11]]\n" + ] + } + ], + "source": [ + "print(X)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can combine fancy and simple indices:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([10, 8, 9])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X[2, [2, 0, 1]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also combine fancy indexing with slicing:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 6, 4, 5],\n", + " [10, 8, 9]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X[1:, [2, 0, 1]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And we can combine fancy indexing with masking:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 2],\n", + " [ 4, 6],\n", + " [ 8, 10]])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mask = np.array([1, 0, 1, 0], dtype=bool)\n", + "X[row[:, np.newaxis], mask]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All of these indexing options combined lead to a very flexible set of operations for accessing and modifying array values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example: Selecting Random Points\n", + "\n", + "One common use of fancy indexing is the selection of subsets of rows from a matrix.\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": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(100, 2)" + ] + }, + "execution_count": 13, + "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": "markdown", + "metadata": {}, + "source": [ + "Using the plotting tools we will discuss in [Introduction to Matplotlib](04.00-Introduction-To-Matplotlib.ipynb), we can visualize these points as a scatter-plot:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAecAAAFVCAYAAADVDycqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3X9wlNWh//HPYkz4FciGb7Rk2tnmOg3fP2z1CzJjtQyS\nSgqKF9Q0gIDWOmkro1WQJqJeUCk34/YOMh2Bi3CxGItQGbjBfi+iQoYp6tXUb0OldxBFZMS1GEgW\nSKSEkOf7xyaQxPzY3Zzd5+w+79cMM26ye55zkoyf5/x8fI7jOAIAANYY5HYFAABAV4QzAACWIZwB\nALAM4QwAgGUIZwAALEM4AwBgmQyThbW2tqqiokKff/65MjIytGzZMhUUFJi8BAAAac9oz3nv3r1q\na2vT5s2bNX/+fD377LMmiwcAwBOMhvO3v/1tXbhwQY7j6MyZM7r88stNFg8AgCcYHdYeNmyYjh07\npilTpigcDmvt2rUmiwcAwBOM9px/97vfacKECdq1a5d27NihiooKtbS09Pp+Tg4FAODrjPacR44c\nqYyMSJHZ2dlqbW1VW1tbr+/3+Xyqrz9jsgopJS8vm/bTfrer4Qovt12i/bQ/u9/3GA3ne+65R489\n9pjmzJmj1tZWPfLIIxo8eLDJSwAAkPaMhvPQoUO1cuVKk0UCAOA5HEICAIBlCGcAACxDOAMAYBnC\nGQAAyxDOAABYhnAGAMAyhDMAAJYhnAEAsAzhDACAZQhnAAAsQzgDAGAZwhkAAMsQzgAAWIZwBgDA\nMoQzAACWIZwBALAM4QwAgGUIZwAALEM4AwBgGcIZAADLEM4AAFiGcAYAwDKEMwAAliGcAQCwDOEM\nAIBlCGcAACyTYbrA559/Xnv27NH58+d111136c477zR9CQAA0prRcH7vvff0l7/8RZs3b9ZXX32l\nDRs2mCweAABPMBrO+/btU2FhoebPn6/m5maVl5ebLB4AkCYaGsKqqKjR0aMjFAicUjBYJL8/J+ll\n2MpoODc2NioUCmnt2rX67LPPdP/99+u1114zeQkAQBqoqKhRdfU8ST7V1TmSqrRu3e1JL8NWRsM5\nJydHV111lTIyMlRQUKCsrCw1NDQoNze318/k5WWbrELKof2036u83HaJ9odCfkm+9lc+hUL+mH8m\nJsqwldFwHjdunKqqqvSTn/xEx48f1z/+8Q/5/f4+P1Nff8ZkFVJKXl427af9blfDFV5uu0T78/Ky\nlZ/fIMlRJFwd5ec3xvwzMVGGG6K5gTAazjfddJP+/Oc/q6SkRI7jaOnSpfL5fP1/EADgKcFgkaSq\n9vni0woGJ7lShq18juM4blYgFe5yEoW7Z9rv1fZ7ue0S7af9/fecOYQEAADLEM4AAFiGcAYAwDKE\nMwAAliGcAQCwDOEMAIBlCGcAACxDOAMAYBnCGQAAyxDOAABYhnAGAMAyhDMAAJYhnAEAsAzhDACA\nZQhnAAAsQzgDAGAZwhkAAMsQzgAAWIZwBgDAMoQzAACWIZwBALAM4QwAgGUIZwAALEM4AwBgGcIZ\nAADLEM4AAFgmIeF88uRJ3XTTTTpy5EgiigcAIK0ZD+fW1lYtXbpUgwcPNl00AACeYDycn3nmGc2e\nPVtXXHGF6aIBAPAEo+G8bds2jRo1SjfeeKMcxzFZNAAAnuFzDKbo3Llz5fP5JEkHDx5UQUGB1qxZ\no1GjRpm6BAAAac9oOHc2b948Pf300yooKOjzffX1ZxJx+ZSQl5dN+2m/29VwhZfbLtF+2p/d73sS\ntpWqowcNAABik5Gogl988cVEFQ0AQFrjEBIAACxDOAMAYJmEDWsDACIaGsKqqKjR0aMjFAic0oYN\n0yVd5na1YDHCGQASrKKiRtXV8yT5VFfn6P77N+u556a5XS1YjHAGAEO695CDwSL5/Tk6enSEpI4d\nLD4dOTLczWoiBRDOAGBI9x6yVKV1625XIHCq/bVPkqOCgiZ3KwrrEc4AYEj3HvLeva0qLt6t0aNb\nNHXqv+uLL0YrEDitNWv+WRcuuFlT2I5wBgBDuveQw+HBqqubobo6R9OnV+n1138oScrN9fYJWegf\n4QwAhgSDRZKqdPToCH366UcKh8vav+Nr71UD0WGfMwAY4vfnaN262/X66z/UxIlXSBrZ/h1HgcBp\n49draAirrGy7iot3q6xsmxobw8avAXfQcwaABOjciw4ETisYnGT8Gr0tQEPqI5wBIEq9bZXqSUcv\nOpG6L0Bj6Dx9EM4AECXbeqrdF6DFOnQey80GkotwBoAo2dZTHejQefebjdraStXUzCOgLUA4A0CU\nBtpTNW2gQ+fdbzZCoatVXl7DvLUFCGcAiFIyFnklU/ebDanZ9dEARBDOABClZCzySqZgsEi1tZUK\nha6W1CxpigKBV92uFsQ+ZwDwLL8/RzU18zR9eljXXjtE06e/mvKjAemCnjMAeFi6jQakC8IZAJKk\nY+tSKORXfn4DW5fQK8IZAJKk89alyAIsTvRCz5hzBoAksW2fNOxFOANAkgQCpxTpMUs27JOGvRjW\nBpDyYjmG0q0jKxsawmppOa+cnBfk853U9dcPVzA4LeHXRWoinAGkvFjOvHbrfOyKihrt3HmfOuab\n33nn31RevsfozQFnZacPwhlAyotlLteted/u1w2H/7eqq6fJ5M2BbQ/mQPyMzjm3traqvLxcc+bM\nUWlpqfbs2WOyeADoUSxzuW7N+3a/rtQk0zcHLDhLH0Z7zjt27JDf71cwGNSpU6c0Y8YMFRUVmbwE\nAHxNLGdeu3U+dsd19+5tVTg8WNItMn1zYNuDORA/n+M4Tv9vi87Zs2flOI6GDh2qxsZGlZaW6o03\n3ujzM/X1Z0xdPuXk5WXTftrvdjVcEWvb02kutbExrH/5l336n/9pU0PDUY0aVah/+qdmI21qbAyr\nvLymy42HbQvjJG//7UuR9vfHaM95yJAhkqSmpiY99NBDWrBggcniAXiULXOpHYF2+PBQNTR8qNzc\nb+uqq1pjCja/P0dbtszWjBkv6sCBxQqFfPrgAzNtiuUoTlt+puiZ8QVhX3zxhR544AHNnTtXt9xy\nS7/vj+YOIp3RftrvVbG0PRTyq+tzh/1Rf/7kybDmz9+pI0eGq6DgjNasuUW5ufH1EB944I9dTvgK\nhTbrwIG7lZW1WVu2zI6prIG0yQS3r+/lv/1oGA3nEydO6L777tOSJUt0/fXXR/UZrw9t0H7a70Wx\ntj0/v0GRRVSRUMzPb+z38x293Mgcb5akCaqtHalz5+LvIR46NESdA00aLsmnQ4eGxNSevLzsuNpk\nkpvX9/LfvuTCsPbatWt1+vRprV69WqtWrZLP59P69euVmZlp8jIAPCaeRVxfP8d6s6TZA1rB3H3B\nVWTFdXwLr9xamGbL9dE3owvC4uH1uyfaT/u9KBltLy7erbq6GZ2+8qqkaZo+Pf6ec8eCq08+GaqT\nJw8pNzegq6660OfCq554+Xcv0f6k95wBwBbde7k5OQc1cWLjgHqIXRdc/chENYEeEc4A0tLXh21n\npez2K3gP4QwgLcWyrQiwDeEMwGrRHpYx0EM1bDjoxIY6wA6EMwCrRXtYxkAP1bDhUA4b6gA7GH3w\nBQCYFu3DHAb60AcbHhphQx1gB8IZgNWifYrUQJ825dbTqmyrA+zAsDYAK/Q23xrtYRkDPVTDhkM5\nElUH5rJTD4eQuIiN+LTfq+3vqe1lZdu7nOg1kMNCehNPSJl42EV3yf7dJ+NnGwsv/+1LHEICIIVE\n5ldPSdopabj27v27GhvDRnt48Sy46n4MaMfDLnr7rI29VOayUw9zzgCsEJlv/S9JsyTdpnD4Vyov\nrzF6jXhCqvtnOh520dtnO8K8rm6GqqvvNt6GeDCXnXroOQOwQjBYpL1731A4nLgeXvcjPaMJqVgf\ndmFjL9WG+XTEhnAGYAW/P0cTJ16m6urYwjMW8YRUx2e6PuyiqtfPxnIDkKwhcE5LSz0sCHMRiyJo\nv1fb31vbP/nkqO64Y4caG78pv/+Ytm//ZxUUBGIq2+05344nV3W+Aeh+/Y7227ZQK1m8/LcvsSAM\nQIqprPx/CoUWS/Lp7FlH//qvVVq3LrZw7m3R10BWasfymVh6qTYOgcMOhDOApDt5Mqyysh1fCz0T\nYdVbGQNdqZ2I4zTjmQOHNxDOAJJu/vydPYaeibDqrYz4V2onbnsXC7XQG8IZQNIdORLZjhRxKShN\nhNXixeNUW1vZPm/9mR57bLqkgazU/i9JsyX5FA5PU3m5ud4zC7XQG8IZQNIVFJxRbe3Xg9JEWPU2\nbx3vSu1Eb+8CekI4A0i6NWtu0blzVe1HYh7S4cMBlZVtM7Kyurfh63iCPxnbu4CeEM4Aki43NxKU\nZWXbdeDAYoVCPh04YGbB1Te+Ua/IYSGRMB09+sSAymNeGG4gnAG4xuRWoo5tT2+//YWklyVlSzoj\n6fyA6si8MNzA2doABqyhIayysu0qLt6tsrJtamwMR/U5k2c+d2x7On36/0i6S9Jtku7SO+9kxVwv\nwG30nAEMWLz7gU0MGXf0mF9/XYr0ws+o87B2ODxYdXUzErJPGUgUwhnAgMU7PG1iyPjSjcHLioTy\nLZJeVk7OPySdVDhcFnO9ALcxrA1gwNx8JOGlG4NbJG3WkCE7NH16q959d7ImTrxC0khX6gUMBD1n\nAAPm5ormS4eL5EiapeLiS0PXrLRGqjIazo7j6Mknn9SHH36ozMxMLV++XN/61rdMXgKAhdxc0dxX\nALPSGqnKaDi/+eabamlp0ebNm7V//35VVlZq9erVJi8BAF0QwEhHRuec33//fU2YMEGSdM011+jA\ngQMmiwcAwBOM9pybmpqUnX3pIdIZGRlqa2vToEG93wNE89DpdEb7ab/XnDwZ1syZL+vIkeEqKDij\nNWtuUW5uTpfvz5+/s9fvpwsv/u4783r7+2M0nIcPH67m5uaLr/sLZkmqrz9jsgopJS8vm/bTfrer\nkXRlZTsu7omurXV07lzXvcf9fT8dePV334H2939jYnRYe+zYsdq7d68kqa6uToWFhSaLB5AG+tsT\nbfJIz3jFe+IZYIrRnvPkyZP11ltvadasWZKkyspKk8UDSAPdn6v8jW+cUFnZ9vbV1qc0enRLzM9d\nNi3eE88AU4yGs8/n01NPPWWySAAGdBxx2RGAJh7NGK9gsEhZWZt16NAQBQKn1dJyXtXV96kjCK+8\ncommTv0PffHF/3Jtb7INvXd4G4eQAB6wYMH/1c6dIyRdprq6DLW0/FEbN851pS5+f462bJl9cc6x\nuHi3Ogfh8ePjlZkZ1uuv/9CV+klf791zshiSjXAG0kRfveN33jkj6efqCJt33vk3N6vaRfcglJpd\n76lyshjcRjgDaaLvedJR6tw7jbzuXTKHwYPBItXWVioUulpSs6QpCgReTci1osXBJnAb4Qykib7m\nSb///QvaufNS7/T732/rs6xkLojy+3NUUzNP5eUdNwOvRt1TtWkuHTCJcAbSRF/zpCtXTlFmZudh\n2h/1WVayF0T5/Tl65plJF4O2vHxPVEHLqmqkK8IZSBMmHwDhxoKoeIKWVdVIV4QzkCZMzpN2DvrR\no/+ulpYMFRfvHtDQcech6MLCr7Rs2YQu5cQTtKyqRroinIEUkqw51s5BX1a23cjQcfeecfdjOeMJ\nWhOrqpm3ho0IZyCFuDHHamrouL9y4glaE6MFzFvDRoQzYIloenBuzLGaGjrurxy3ti8xbw0bEc6A\nJaLpwbkxx2rqQI7O5RQWntWyZXYc7MG8NWxEOAOWiKYHl6yTqxIxD9u5Z2zTIwM5DQw2IpyBGCRy\n8VA0PbhkDf1GevG3SXpNdXV+1da+qJqau9NyoRSngcFGhDMQg3gXD/UU6t0fuB4MFuncuf/Qf//3\nIEkn1dIyTI2NYVcCMdJrf03SLEk+hUK3qbychVJAshDOQAziXTzUU6j/53/e3eU9fn+OsrIyFQ5H\n3rdzp6PMTHcCMdKL94uFUoA7BrldASCVBAKnFHlykhTL4qFoQ92WlcPBYJHy8z9QPG0FMHD0nIEY\nxLt4KNoVwbasHI48jOJulZdH31YO8wDMIZyBGMS7eCjaUE/0yuFYAjTWtnKYB2AO4QwkQbRBl+iV\nw4kMUFuG5IF0wJwzMAANDWGVlW1XcfFulZVtU2Nj2O0q9SmRARrvfDyAr6PnDAyA20O5sc7zJnJO\nm8M8AHMIZ2AA3B7KjfXmIJEB6vfn6JlnJl28WSgv38OiMCBOhDMwAG6vro715iCV57QBLyGcgQFw\neyjX7ZuD7tweSQDSBeEMxCkR+3pjLTPWm4NYy7dpThvwEsIZiFNvQ7gDCe1Yh4UTvRfZpjltwEuM\nhXNTU5MWLVqk5uZmnT9/Xo8++qiuvfZaU8UD1ultCHcg866JHhaOtfzu79+7t1XFxbt7vengCU+A\nGcb2Ob/wwgu64YYbVFVVpcrKSj399NOmigas1Nu+3oEEbLR7hePdXx3rXuTu7w+HB6uuboaqq+9W\neXlNVNcEEDtjPed7771XmZmZkqTW1lZlZWWZKhqwUm+PeBzIvGvnYeHRo/+ulpaMHnuq8fbOYx12\n7vz+Tz/9SOFwWft3Lt10cKY2kABOHF555RVn2rRpXf598MEHjuM4zpdffunMmDHDqa2tjadoIKWU\nlm5ypDZHchypzSkt3eScPNnolJZucsaP33HxtamyO4wfv6P965F/48fvMNWkPurz+x7r01c9AcQn\nrp5zSUmJSkpKvvb1Dz/8UIsWLVJFRYWuu+66qMqqrz8TTxXSQl5eNu13qf2menuHDg1R5yHsQ4eG\n6MKFy/Tcc9MuvufChZ7/zvtrf09ld7w/P79BkeHmSO88P78x4T/LZcsm6Ny5S73uZcsmqb7+TJ/1\n7A1/+7Tf6+3vj7Fh7Y8//lgPP/ywVq5cqTFjxpgqFkgIU4dlJHLrUF9lu7EqurfFXmyfAswzFs4r\nVqxQS0uLli9fLsdxNGLECK1atcpU8YBRplZFd4Tk4cOXqaHhqD75pFBlZduMzLv2FcA2rYpm+xRg\nnrFwXr16tamigIQz1dvrCMmysu06cGCxQiGfPvig7554x5B6KORXfn5Dr0FuUwD3JVXqCaQSDiGB\nJ5nu7cXSE+88pB6ZN+45yFkFDXgX4QxPMt3bi6UnHm2Q8xAJwLsIZ8CAWHri0QY5D5EAvItwBgyI\npSfeEeSROefGXoOcVdCAdxHOiItX5kMT0c6OIO9vryeroAHvIpwRF6/Mh7rZTlZBA95l7MEX8Bav\nzId6pZ0A7EI4Iy6xPt0oVdnYznifSAUgdTCsjbh4ZT7UxnZ6ZUoB8DLCGXFJ1nyo2wvPBtrORNSf\noXYg/RHOsFqq9xITUX+2WAHpj3CG1dzsJZro9cZT//6ua+NQOwCzCGdYzc1eoolebzz17++6bLEC\n0h/hDKu52Us00WuPp/7MKQMgnGE1N3uJJnrt8dSfOWUAhDM8JZZ5ZLd67cwpAyCc4SmxzCO71Wtn\nThkAJ4TBU5jPBZAKCGd4io3HcQJAdwxrw1OYzwWQCghneArzuQBSAcPaAABYhp4zrOH2Qy4AwBaE\nM6yR6g+5AABTGNaGNdjmBAARhDOswTYnAIgwPqx9+PBhzZw5U2+//bYyMzNNF480xjYnAIgwGs5N\nTU0KBoPKysoyWSw8gm1OABBhdFh7yZIlWrhwoQYPHmyyWAAAPCWunvPWrVu1cePGLl/Lz8/Xrbfe\nqjFjxshxnF4++XV5ednxVCFt0H7a71VebrtE+73e/v74nFiStA8/+tGPdOWVV8pxHO3fv1/XXHON\nqqqq+v1cff0ZE5dPSXl52bSf9rtdDVd4ue0S7af9/d+YGJtz3rVr18X/Lioq0oYNG0wVDQCApyRk\nK5XP54tpaBsAAFySkBPCdu/enYhiAQDwBA4hAQDAMoQzAACWIZwBALAM4QwAgGUIZwAALEM4AwBg\nGcIZAADLEM4AAFiGcAYAwDKEMwAAliGcAQCwDOEMAIBlCGcAACxDOAMAYBnCGQAAyxDOAABYhnAG\nAMAyhDMAAJYhnAEAsAzhDACAZQhnAAAsQzgDAGCZDLcrgORqaAiroqJGR4+OUCBwSsFgkfz+HLer\nBQDohHD2mIqKGlVXz5PkU12dI6lK69bd7na1AACdMKztMUePjpDka3/la38NALAJ4ewxgcApSU77\nK0eBwGk3qwMA6IGxYe22tjZVVlbqb3/7m1paWvTggw9q4sSJpoqHIcFgkaSq9jnn0woGJ7ldJQBA\nN8bCubq6WhcuXNCmTZt0/Phx7dq1y1TRMMjvz2GOGQAsZyyc9+3bp+985zv6+c9/Lkl64oknTBWd\nVjqvli4s/ErLlk1gtTQAoIu4wnnr1q3auHFjl6/l5uYqKytLa9euVW1trRYvXqyXXnrJSCXTSffV\n0ufOJX61NNunACC1xBXOJSUlKikp6fK1hQsXatKkyPzl+PHj9emnn0ZVVl5edjxVSFmhkF+dV0uH\nQv6E/wweeOCPXW4IsrI2a8uW2Qm9ZrS89vvvzsvt93LbJdrv9fb3x9iw9rhx47R3715NnjxZBw8e\nVH5+flSfq68/Y6oKKSE/v0GR1dI+SY7y8xsT/jM4dGiIOt8QHDo0xIqfe15ethX1cIuX2+/ltku0\nn/b3f2NiLJx//OMf68knn9TMmTMlSU899ZSpotNK59XShYVntWxZ4ldLBwKn2g8cidwQsH0KAOzm\ncxzH6f9tieP1u6dktL+xMazy8pou26dsmHPm7tm77fdy2yXaT/uT2HOGvdg+BQCphRPCAACwDOEM\nAIBlCGcAACxDOAMAYBnCGQAAyxDOAABYhnAGAMAyhDMAAJYhnAEAsAzhDACAZQhnAAAsQzgDAGAZ\nwhkAAMsQzgAAWIZwBgDAMoQzAACWIZwBALAM4QwAgGUIZwAALEM4AwBgGcIZAADLZLhdAcSmoSGs\niooaHT06QoHAKQWDRfL7c9yuFgDAIMI5xVRU1Ki6ep4kn+rqHElVWrfudrerBQAwiGHtFHP06AhJ\nvvZXvvbXAIB0QjinmEDglCSn/ZWjQOC0m9UBACSAsWHtpqYmLViwQF999ZWysrL0m9/8RqNGjTJV\nPNoFg0WSqtrnnE8rGJzkdpUAAIYZC+dt27ZpzJgxWrRokV555RWtX79eFRUVpopHO78/hzlmAEhz\nxoa1CwsL1dTUJCnSi7788stNFQ0AgKfE1XPeunWrNm7c2OVrS5Ys0VtvvaVbb71Vp06d0qZNm4xU\nEAAAr/E5juP0/7b+Pfjgg5owYYJKS0v14Ycf6le/+pV27NhhomgAADzF2JzzyJEjNXz4cElSbm6u\nmpubo/pcff0ZU1VIOXl52bSf9rtdDVd4ue0S7af92f2+x1g4//KXv9QTTzyhTZs2qbW1Vb/+9a9N\nFQ0AgKcYC+crrrhCzz//vKniAADwLA4hAQDAMoQzAACWIZwBALAM4QwAgGUIZwAALEM4AwBgGcIZ\nAADLEM4AAFiGcAYAwDKEMwAAliGcAQCwDOEMAIBlCGcAACxDOAMAYBnCGQAAyxDOAABYhnAGAMAy\nhDMAAJYhnAEAsAzhDACAZQhnAAAsQzgDAGAZwhkAAMsQzgAAWIZwBgDAMoQzAACWGVA4v/HGG3rk\nkUcuvt6/f79KS0t111136bnnnhtw5QAA8KK4w3n58uV69tlnu3xt6dKlWrFihTZt2qS//vWvOnjw\n4IArCACA18QdzmPHjtWTTz558XVTU5POnz+vb37zm5KkH/zgB3r77bcHXEEAALwmo783bN26VRs3\nbuzytcrKSk2dOlXvvffexa81Nzdr+PDhF18PGzZMx44dM1hVAAC8od9wLikpUUlJSb8FDRs2TE1N\nTRdfNzc3a8SIEf1+Li8vu9/3pDPaT/u9ysttl2i/19vfH2OrtYcPH67MzEx99tlnchxH+/bt07hx\n40wVDwCAZ/Tbc47FU089pUWLFqmtrU033nijvve975ksHgAAT/A5juO4XQkAAHAJh5AAAGAZwhkA\nAMsQzgAAWIZwBgDAMlaE8+HDh3XdddeppaXF7aok1dmzZzV//nzNnTtXP/3pT/Xll1+6XaWkampq\n0i9+8QvNmzdPs2bNUl1dndtVSrru59OnO8dxtHTpUs2aNUt33323PvvsM7erlHT79+/XvHnz3K5G\n0rW2tqq8vFxz5sxRaWmp9uzZ43aVkqqtrU2PPfaYZs+erTlz5ujjjz/u8/2uh3NTU5OCwaCysrLc\nrkrS/eEPf9DVV1+tl156SbfddpvWrVvndpWS6oUXXtANN9ygqqoqVVZW6umnn3a7SknV0/n06e7N\nN99US0uLNm/erEceeUSVlZVuVymp1q9fryeeeELnz593uypJt2PHDvn9fv3+97/XunXrtGzZMrer\nlFR79uyRz+fTyy+/rIceekgrVqzo8/1G9znHY8mSJVq4cKHmz5/vdlWS7p577lHHTrZQKKSRI0e6\nXKPkuvfee5WZmSkpclfttRu0sWPHavLkydqyZYvbVUma999/XxMmTJAkXXPNNTpw4IDLNUquQCCg\nVatWqby83O2qJN3UqVM1ZcoUSZFeZEaG6/GTVDfffLOKiookSZ9//nm//79P2k+npzO68/Pzdeut\nt2rMmDFK9+3WvZ1RfvXVV+uee+7RRx99pA0bNrhUu8Trq/319fUqLy/X448/7lLtEiva8+m9oKmp\nSdnZl45tzMjIUFtbmwYNcn0QLykmT56szz//3O1quGLIkCGSIn8DDz30kBYsWOByjZJv0KBBevTR\nR/Xmm2/qt7/9bd9vdlxUXFzszJs3z5k7d67z3e9+15k7d66b1XHV4cOHnZtvvtntaiTdwYMHnWnT\npjl/+tOf3K6KK959911n4cKFblcjaSorK52dO3defD1x4kT3KuOSY8eOOTNnznS7Gq4IhULOHXfc\n4Wzbts3tqrjqxIkTzqRJk5yzZ8/2+h5XxxV27dp18b+LiorSuufYk+eff15XXnmlpk+frqFDh+qy\nyy5zu0pJ9fHHH+vhhx/WypUrNWbMGLergyQYO3asampqNGXKFNXV1amwsNDtKrnCSfORwp6cOHFC\n9913n5YjwwUsAAAAzklEQVQsWaLrr7/e7eokXXV1tY4fP66f/exnysrK0qBBg/ocMbJm0N/n83nu\nD/bOO+9URUWFtm7dKsdxPLc4ZsWKFWppadHy5cvlOI5GjBihVatWuV0tJNDkyZP11ltvadasWZLk\nub/5Dj6fz+0qJN3atWt1+vRprV69WqtWrZLP59P69esvrjtJd8XFxVq8eLHmzp2r1tZWPf744322\nnbO1AQCwjDdWYQAAkEIIZwAALEM4AwBgGcIZAADLEM4AAFiGcAYAwDKEMwAAlvn/5iKbJb8BcnkA\nAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import seaborn; seaborn.set() # for plot styling\n", + "\n", + "plt.scatter(X[:, 0], X[:, 1]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([93, 45, 73, 81, 50, 10, 98, 94, 4, 64, 65, 89, 47, 84, 82, 80, 25,\n", + " 90, 63, 20])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "indices = np.random.choice(X.shape[0], 20, replace=False)\n", + "indices" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(20, 2)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "selection = X[indices] # fancy indexing here\n", + "selection.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAecAAAFVCAYAAADVDycqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xl0U+edN/CvVsuWbXnFYPbVQAATGYLBFjtmD4QQCA1M\n2+k5mZnMdEmaJm2aN+mWciZzTpqZt0lP0rztZNKkpFCSAGELBowNMZvACRBMWIONMbbBsiUv0pXu\n+4fHAiHJWnwtXVvfzz+tru599Hts4p+eXSGKoggiIiKSDWW0AyAiIiJPTM5EREQyw+RMREQkM0zO\nREREMsPkTEREJDNMzkRERDKjlrIwQRDw/PPPo7q6Gmq1Gr/+9a8xfPhwKT+CiIioz5O05VxSUgKX\ny4VNmzbhqaeewu9+9zspiyciIooJkibnYcOGwel0QhRFNDc3Q6PRSFk8ERFRTJC0W1uv16OqqgqL\nFi1CY2Mj3nrrLSmLJyIiigmStpz/+7//GyaTCXv27MG2bdvw/PPPw263+72fO4cSERF5k7TlbDAY\noFZ3FJmUlARBEOByufzer1AoUFfXLGUIvUpmZhLrz/pHO4yoiOW6A6w/658U8B5Jk/O3v/1tvPDC\nC3jiiScgCAJ+/OMfQ6fTSfkRREREfZ6kyTkhIQGvv/66lEUSERHFHG5CQkREJDNMzkRERDLD5ExE\nRCQzTM5EREQyw+RMREQkM0zOREREMsPkTEREJDNMzkRERDLD5ExERCQzTM5EREQyw+RMREQkM0zO\nREREMsPkTEREJDNMzkRERDLD5ExERCQzTM5EREQyw+RMREQkM0zOREREMsPkTEREJDNMzkRERDLD\n5ExERCQzTM5EREQyw+RMREQkM+poB0BERBSqq1ev4Msvv4BWq4UoigAAQRAwfXoB0tPToxxd9zE5\nExFRr9HUZMGePbuQkzMWy5ev8HjP5XLh888P49atWixbtgIajSZKUXYfkzMREfUKFksj9uzZhcce\nexwKhcLrfaVSiYICE9ra2rB58yasWbMOanXvTHOSjzm//fbbePzxx/Hoo4/i73//u9TFExFRjNq7\nd7ffxHwvnU6HlSsfxe7dOyMUmfQk/Upx7NgxnDp1Cps2bUJLSwv+9Kc/SVk8ERHFqG++uYZRo0YH\nTMydEhISoFar0d7ejri4uB6OTnqSJueysjKMGTMGTz31FGw2G5577jkpiycioj7CbnfAbK6HzaaG\n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\nZyIiIplhciYiIpIZJmciIiKZYXImIiKSGSZnIiIimWFyJiIikhkmZyIiIplhciYiIpIZJmciIiKZ\nYXImIiKSGSZnIiIimVFHOwCKLLvdAbO5HjabGnq9AKMxA1qtJtphERHRPdhyjjFmcz0slkEQhP6w\nWAbBbK6PdkhERHQfJucYY7Opu3xNRETRx+QcY/R6ocvXREQUfZI1m1wuFzZu3IizZ8/Cbrfj+9//\nPmbNmiVV8SQRozEDZnOVx5gzERHJi2TJ+ZNPPoHT6cQHH3yA2tpa7NmzR6qiSUJarQb5+QOiHQYR\nEXVBsuRcVlaG0aNH45/+6Z8AAC+++KJURfcp986WHjiwCSNGxHO2NBEReQgrOW/ZsgXvvvuux7W0\ntDTExcXhrbfewvHjx/Gzn/0Mf/nLXyQJsi/pnC0NAI2NCTCbL/R4S5bLp4iIeheFKIqiFAU988wz\nWLx4MRYsWAAAKCwsRFlZmRRF9ym7dtVCELLcr9XqWixenNXFE91XWlqNxsaB7tcpKdUwmQZ28QQR\nEUWTZN3aeXl5KCkpwYIFC3D+/HlkZ2cH9VxdXbNUIfQKgmCBxZIEADAYEiAIFtTVJfToZ1ZXt0MQ\nWtyvbbZ2WfzcMzOTZBFHtMRy/WO57gDrz/onBbxHsqVUjz32GFwuF9auXYuXX34Zv/zlL6Uquk8x\nGjNgMFRBrb6JlJTqiMyW5vIpIqLeRbKWs1arxW9/+1upiuuz7p0tHalvj1w+RUTUu3B7qBjA5VNE\nRL0LdwgjIiKSGSZnIiIimWFyJiIikhkmZyIiIplhciYiIpIZJmciIiKZYXImIiKSGSZnIiIimWFy\nJiIikhkmZyIiIplhciYiIpIZJmciIiKZYXImIiKSGSZnIiIimWFyJiIikhkmZyIiIplhciYiIpIZ\nJmciIiKZYXImIiKSGSZnIiIimWFyJiIikhl1tAOg0NjtDpjN9bDZ1NDrBRiNGdBqNdEOi4iIJMSW\ncy9jNtfDYhkEQegPi2UQzOb6aIdEREQSY3LuZWw2dZeviYio92Ny7mX0eqHL10RE1PtJ1uyyWq14\n+umn0dLSgri4OPzHf/wH0tPTpSqe/pfRmAGzucpjzJmIiPoWyVrOW7duRU5ODt5//30sXrwY77zz\njlRF0z20Wg3y8wdg3rxM5OcP4GQwIqI+SLLkPGbMGFitVgAdrWiNhkmDiIgoHGF1a2/ZsgXvvvuu\nx7WXXnoJhw8fxtKlS2GxWPDBBx9IEiAREVGsUYiiKEpR0Pe//32YTCasWbMGlZWV+MlPfoJt27ZJ\nUTQREVFMkWxCmMFgQGJiIgAgLS0NNpstqOfq6pqlCqHXycxMYv1Z/2iHERWxXHeA9Wf9kwLeI1ly\n/sEPfoAXX3wRH3zwAQRBwG9+8xupiiYiIoopkiXnfv364e2335aqOCIiopjFTUiIiIhkhsmZiIhI\nZpiciYiIZIbJmYiISGaYnImIiGSGyZmIiEhmmJyJiIhkhsmZiIhIZpiciYiIZIbJmYiISGaYnImI\niGSGyZmIiEhmmJyJiIhkhsmZiIhIZpiciYiIZIbJmYiISGaYnImIiGSGyZmIiEhmmJyJiIhkhsmZ\niIhIZpiciYiIZIbJmYiISGaYnImIiGSGyZmIiEhmmJyJiIhkhsmZiIhIZrqVnD/77DP8+Mc/dr+u\nqKjAmjVr8K1vfQu///3vux0cERFRLAo7Ob/yyiv43e9+53Ht5ZdfxmuvvYYPPvgAX3zxBc6fP9/t\nAImIiGJN2MnZaDTiF7/4hfu11WqFw+HAoEGDAACFhYU4cuRItwMkIiKKNepAN2zZsgXvvvuux7WN\nGzdi8eLFOHbsmPuazWZDYmKi+7Ver0dVVZWEoRIREcWGgMl59erVWL16dcCC9Ho9rFar+7XNZkNy\ncnLA5zIzkwLe05ex/qx/rIrlugOsf6zXPxDJZmsnJiZCq9Xi+vXrEEURZWVlyMvLk6p4IiKimBGw\n5RyKX/7yl3j22WfhcrlQUFCASZMmSVk8ERFRTFCIoihGOwgiIiK6i5uQEBERyQyTMxERkcwwORMR\nEckMkzMREZHMyCI5X7p0CVOmTIHdbo92KBHV2tqKp556CuvXr8c//uM/4tatW9EOKaKsViv++Z//\nGRs2bMDjjz+O06dPRzukiLt/f/q+ThRFvPzyy3j88cfxD//wD7h+/Xq0Q4q4iooKbNiwIdphRJwg\nCHjuuefwxBNPYM2aNdi/f3+0Q4ool8uFF154AevWrcMTTzyBixcvdnl/1JOz1WrFq6++iri4uGiH\nEnF/+9vfMGHCBPzlL3/B8uXL8cc//jHaIUXUn//8Z8yYMQPvvfceNm7ciF/96lfRDimifO1P39ft\n27cPdrsdmzZtwo9//GNs3Lgx2iFF1DvvvIMXX3wRDocj2qFE3LZt25Camor3338ff/zjH/HrX/86\n2iFF1P79+6FQKPDXv/4VP/zhD/Haa691eb+k65zD8dJLL+GZZ57BU089Fe1QIu7b3/42Oley3bhx\nAwaDIcoRRdZ3v/tdaLVaAB3fqmPtC5rRaMSCBQvw4YcfRjuUiDl58iRMJhMAIDc3F2fOnIlyRJE1\ndOhQvPHGG3juueeiHUrELV68GIsWLQLQ0YpUq6OefiJq/vz5mDt3LgCguro64N/7iP10fO3RnZ2d\njaVLlyInJwd9fbm1vz3KJ0yYgG9/+9v4+uuv8ac//SlK0fW8rupfV1eH5557Dj//+c+jFF3PCnZ/\n+lhgtVqRlHR320a1Wg2XywWlMuqdeBGxYMECVFdXRzuMqIiPjwfQ8W/ghz/8IZ5++ukoRxR5SqUS\nP/3pT7Fv3z7813/9V9c3i1FUVFQkbtiwQVy/fr04ceJEcf369dEMJ6ouXbokzp8/P9phRNz58+fF\nZcuWiaWlpdEOJSqOHj0qPvPMM9EOI2I2btwo7tq1y/161qxZ0QsmSqqqqsS1a9dGO4youHHjhrhq\n1Spx69at0Q4lqurr68U5c+aIra2tfu+Jar/Cnj173P9/7ty5fbrl6Mvbb7+NrKwsrFixAgkJCVCp\nVNEOKaIuXryIH/3oR3j99deRk5MT7XAoAoxGIw4cOIBFixbh9OnTGDNmTLRDigqxj/cU+lJfX4/v\nfe97eOmll5Cfnx/tcCLuk08+QW1tLZ588knExcVBqVR22WMkm05/hUIRc/9gH330UTz//PPYsmUL\nRFGMuckxr732Gux2O1555RWIoojk5GS88cYb0Q6LetCCBQtw+PBhPP744wAQc//mOykUimiHEHFv\nvfUWmpqa8Oabb+KNN96AQqHAO++845530tcVFRXhZz/7GdavXw9BEPDzn/+8y7pzb20iIiKZiY1Z\nGERERL0IkzMREZHMMDkTERHJDJMzERGRzDA5ExERyQyTMxERkcwwORMREcnM/wcUk78ohTcyEwAA\nAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(X[:, 0], X[:, 1], alpha=0.3)\n", + "plt.scatter(selection[:, 0], selection[:, 1],\n", + " facecolor='none', s=200);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sort of strategy is often used to quickly partition datasets, as is often needed in train/test splitting for validation of statistical models (see [Hyperparameters and Model Validation](05.03-Hyperparameters-and-Model-Validation.ipynb)), and in sampling approaches to answering statistical questions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifying Values with Fancy Indexing\n", + "\n", + "Just as fancy indexing can be used to access parts of an array, it can also be used to modify parts of an array.\n", + "For example, imagine we have an array of indices and we'd like to set the corresponding items in an array to some value:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 99 99 3 99 5 6 7 99 9]\n" + ] + } + ], + "source": [ + "x = np.arange(10)\n", + "i = np.array([2, 1, 8, 4])\n", + "x[i] = 99\n", + "print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use any assignment-type operator for this. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 89 89 3 89 5 6 7 89 9]\n" + ] + } + ], + "source": [ + "x[i] -= 10\n", + "print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice, though, that repeated indices with these operations can cause some potentially unexpected results. Consider the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]\n" + ] + } + ], + "source": [ + "x = np.zeros(10)\n", + "x[[0, 0]] = [4, 6]\n", + "print(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.\n", + "\n", + "Fair enough, but consider this operation:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 21, + "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[3]`` would contain the value 3, as this is how many times each index is repeated. Why is this not the case?\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", + "With this in mind, it is not the augmentation that happens multiple times, but the assignment, which leads to the rather nonintuitive results.\n", + "\n", + "So what if you want the other behavior where the operation is repeated? For this, you can use the ``at()`` method of ufuncs (available since NumPy 1.8), and do the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]\n" + ] + } + ], + "source": [ + "x = np.zeros(10)\n", + "np.add.at(x, i, 1)\n", + "print(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": {}, + "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": 23, + "metadata": { + "collapsed": false + }, + "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": "markdown", + "metadata": {}, + "source": [ + "The counts now reflect the number of points within each bin–in other words, a histogram:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n9a9/PdavXx8zZszIHqdfeeKJJ+ITn/jExRPerFmz4uWXX84dqh87evRoPPjgg7F06dJYsGBB9jj9\n0tKlS6OsrCwiIpqamuKmm26K73//+zF69OjkyfqXb3/72zF69OhYsWJFRETcdddd8cwzz8S11177\nvrfvk/ddpkyZEq+88kpUV1dHU1NTXH/99X3xsAPSv/97+ezZs7v994Sh6q233oqvfe1r8d3vfjcm\nTJiQPU6/c8cdd8Qvf/nLmDt3ruu7d+PEiRNRU1MTmzZtiunTp2eP02/9+7uXy5Yti61btwrw+5gy\nZUrs3r07VqxYEceOHYt//OMfUVVVdcnb90mE77nnntiyZUvce++9ERHx2GOP9cXDDnhlZWXekr6E\n73znO3H27NnYtm1bdHZ2xqhRo6Kuri57rH6juro6fvWrX8XixYsj4t2373l/DQ0NcerUqaivr4+6\nurooKyuLXbt2XfyeA97rnydi3mvWrFnxm9/8JhYuXHjxpxS62i/XjgaAJL5LAwCSiDAAJBFhAEgi\nwgCQRIQBIIkIA0ASEQaAJP8P7quNPHF17C4AAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot the results\n", + "plt.plot(bins, counts, linestyle='steps');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course, it would be silly to have to do this each time you want to plot a histogram.\n", + "This is why Matplotlib provides the ``plt.hist()`` routine, which does the same in a single line:\n", + "\n", + "```python\n", + "plt.hist(x, bins, histtype='step');\n", + "```\n", + "\n", + "This function will create a nearly identical plot to the one seen here.\n", + "To compute the binning, ``matplotlib`` uses the ``np.histogram`` function, which does a very similar computation to what we did before. Let's compare the two here:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NumPy routine:\n", + "10000 loops, best of 3: 97.6 µs per loop\n", + "Custom routine:\n", + "10000 loops, best of 3: 19.5 µs per loop\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": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NumPy routine:\n", + "10 loops, best of 3: 68.7 ms per loop\n", + "Custom routine:\n", + "10 loops, best of 3: 135 ms per loop\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 (see [Big-O Notation](02.08-Sorting.ipynb#Aside:-Big-O-Notation)).\n", + "But the advantage of coding this algorithm yourself is that with an understanding of these basic methods, you could use these building blocks to extend this to do some very interesting custom behaviors.\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": {}, + "source": [ + "\n", + "< [Comparisons, Masks, and Boolean Logic](02.06-Boolean-Arrays-and-Masks.ipynb) | [Contents](Index.ipynb) | [Sorting Arrays](02.08-Sorting.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.08-Sorting.ipynb b/numpy/02.08-Sorting.ipynb new file mode 100644 index 0000000..7208bba --- /dev/null +++ b/numpy/02.08-Sorting.ipynb @@ -0,0 +1,783 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Fancy Indexing](02.07-Fancy-Indexing.ipynb) | [Contents](Index.ipynb) | [Structured Data: NumPy's Structured Arrays](02.09-Structured-Data-NumPy.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sorting Arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "These algorithms are a favorite topic in introductory computer science courses: if you've ever taken one, you probably have had dreams (or, depending on your temperament, nightmares) about *insertion sorts*, *selection sorts*, *merge sorts*, *quick sorts*, *bubble sorts*, and many, many more.\n", + "All are means of accomplishing a similar task: sorting the values in a list or array.\n", + "\n", + "For example, a simple *selection sort* repeatedly finds the minimum value from a list, and makes swaps until the list is sorted. We can code this in just a few lines of Python:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def selection_sort(x):\n", + " for i in range(len(x)):\n", + " swap = i + np.argmin(x[i:])\n", + " (x[i], x[swap]) = (x[swap], x[i])\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 4, 5])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.array([2, 1, 4, 3, 5])\n", + "selection_sort(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As any first-year computer science major will tell you, the selection sort is useful for its simplicity, but is much too slow to be useful for larger arrays.\n", + "For a list of $N$ values, it requires $N$ loops, each of which does on order $\\sim N$ comparisons to find the swap value.\n", + "In terms of the \"big-O\" notation often used to characterize these algorithms (see [Big-O Notation](#Aside:-Big-O-Notation)), selection sort averages $\\mathcal{O}[N^2]$: if you double the number of items in the list, the execution time will go up by about a factor of four.\n", + "\n", + "Even selection sort, though, is much better than my all-time favorite sorting algorithms, the *bogosort*:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def bogosort(x):\n", + " while np.any(x[:-1] > x[1:]):\n", + " np.random.shuffle(x)\n", + " return x" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 4, 5])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.array([2, 1, 4, 3, 5])\n", + "bogosort(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This silly sorting method relies on pure chance: it repeatedly applies a random shuffling of the array until the result happens to be sorted.\n", + "With an average scaling of $\\mathcal{O}[N \\times N!]$, (that's *N* times *N* factorial) this should–quite obviously–never be used for any real computation.\n", + "\n", + "Fortunately, Python contains built-in sorting algorithms that are *much* more efficient than either of the simplistic algorithms just shown. We'll start by looking at the Python built-ins, and then take a look at the routines included in NumPy and optimized for NumPy arrays." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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, we won't discuss them here because NumPy's ``np.sort`` function turns out to be much more efficient and useful for our purposes.\n", + "By default ``np.sort`` uses an $\\mathcal{O}[N\\log N]$, *quicksort* algorithm, though *mergesort* and *heapsort* are also available. For most applications, the default quicksort is more than sufficient.\n", + "\n", + "To return a sorted version of the array without modifying the input, you can use ``np.sort``:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 4, 5])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = np.array([2, 1, 4, 3, 5])\n", + "np.sort(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you prefer to sort the array in-place, you can instead use the ``sort`` method of arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 2 3 4 5]\n" + ] + } + ], + "source": [ + "x.sort()\n", + "print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A related function is ``argsort``, which instead returns the *indices* of the sorted elements:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 0 3 2 4]\n" + ] + } + ], + "source": [ + "x = np.array([2, 1, 4, 3, 5])\n", + "i = np.argsort(x)\n", + "print(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", + "These indices can then be used (via fancy indexing) to construct the sorted array if desired:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3, 4, 5])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[i]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sorting along rows or columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A useful feature of NumPy's sorting algorithms is the ability to sort along specific rows or columns of a multidimensional array using the ``axis`` argument. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[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]]\n" + ] + } + ], + "source": [ + "rand = np.random.RandomState(42)\n", + "X = rand.randint(0, 10, (4, 6))\n", + "print(X)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "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": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# sort each column of X\n", + "np.sort(X, axis=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "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": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# sort each row of X\n", + "np.sort(X, axis=1)" + ] + }, + { + "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": {}, + "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. NumPy provides this in the ``np.partition`` function. ``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": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2, 1, 3, 4, 6, 5, 7])" + ] + }, + "execution_count": 12, + "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", + "Within the two partitions, the elements have arbitrary order.\n", + "\n", + "Similarly to sorting, we can partition along an arbitrary axis of a multidimensional array:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "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": 13, + "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.\n", + "We'll see this in action in the following section." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "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", + "We'll start by creating a random set of 10 points on a two-dimensional plane.\n", + "Using the standard convention, we'll arrange these in a $10\\times 2$ array:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "X = rand.rand(10, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To get an idea of how these points look, let's quickly scatter plot them:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n7bU0VJlYlmVp165dKi8vV2Vlpc6ePTtp/Pjx4woGgyovL9eRI0fSVGXyTNd/KBRSWVmZtm7dqurq\n6vQUmUTT9X/RSy+9pN/85jcpri75puv/448/1rZt27Rt2zb97Gc/0+joaJoqTbzpen/nnXe0adMm\nlZaW6tChQ2mqMvlOnz6tioqKq7Zn+nffRdfr/4a/+6w0eeONN6zf/va3lmVZ1p///GfrV7/61aTx\nL7/80tq8eXPsdXl5udXT05PSGhPtr3/9q/WLX/zCsizL6uzstJ599tnY2NjYmFVcXGwNDAxYo6Oj\n1ubNm62vvvoqXaUmxVT9R6NRq7i42BoZGbEsy7K2b99uHT9+PC11JstU/V906NAh67HHHrP27duX\n6vKSbrr+H3nkEevLL7+0LMuyjhw5Yn3xxRepLjFpput9w4YNVjgctkZHR63i4mIrHA6no8yk+v3v\nf2+VlJRYjz322KTtTvjus6zr92/nuy9tR9xOvN95R0eHioqKJEkFBQXq6uqKjX3++efKy8uTz+fT\nvHnztG7dOrW1taWr1KSYqn+3263Gxka53W5JmfF5X2mq/iXpo48+0ieffKLy8vJ0lJd0U/X/xRdf\naPHixXrjjTdUUVGh/v5+LVu2LE2VJt50n/2qVavU39+vkZERScrIm1bl5eVd88ypE777pOv3b+e7\nz9ZjPW8U9zs/b3BwUH6/P/Y6KytLExMTmjNnzlVjCxcu1MDAQDrKTJqp+ne5XFqyZIkk6cCBA4pE\nIlq/fn26Sk2Kqfrv6+tTbW2t6urqdOzYsTRWmTxT9f/111+rs7NTu3btUm5urp555hmtWbNGd999\ndxorTpypepeklStXavPmzfJ6vSouLo59N2aS4uJi/etf/7pquxO++6Tr92/nuy8lwR0MBhUMBidt\ne+655zQ0NCRJGhoamvTBXXT5/c4zYc3T5/PFepY06T9cn883aedlaGhI2dnZKa8xmabqXzq/Drh3\n71719vaqtrY2HSUm1VT9v/fee/rmm2/01FNPqa+vTyMjI7rlllv06KOPpqvchJuq/8WLF2vp0qVa\nvny5JKmoqEhdXV0ZE9xT9d7T06MPPvhAx48fl9fr1fPPP6/3339fDzzwQLrKTSknfPdN50a/+9J2\nqtyJ9zu/vOfOzk7l5+fHxm699Vb19vYqHA5rdHRUbW1t+sEPfpCuUpNiqv4l6cUXX9TY2Jjq6upi\np40yyVT9V1RU6OjRo9q/f7+efvpplZSUZFRoS1P3n5ubq+Hh4dgfbXV0dGjFihVpqTMZpurd7/dr\nwYIFcrvdsaOvcDicrlKTzrriZp1O+O673JX9Szf+3ZeSI+5r2bJli1544QVt3bo1dr9zSZPud97e\n3q6xsTGdPHkyI+53XlxcrNbW1tgaZk1NjUKhkCKRiEpLS7Vz50498cQTsixLpaWluvnmm9NccWJN\n1f/q1avV3NysdevWqaKiQi6XS5WVlQoEAmmuOnGm+/wz3XT97969W9u3b5ck3XHHHbr//vvTWW5C\nTdf7xb8odrvdWrp0qTZu3JjmipPn4kGYk777Lndl/3a++7hXOQAABuEGLAAAGITgBgDAIAQ3AAAG\nIbgBADAIwQ0AgEEIbgAADEJwAwBgkP8PJjFcqzTtbqoAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import seaborn; seaborn.set() # Plot styling\n", + "plt.scatter(X[:, 0], X[:, 1], s=100);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we'll compute the distance between each pair of points.\n", + "Recall that the squared-distance between two points is the sum of the squared differences in each dimension;\n", + "using the efficient broadcasting ([Computation on Arrays: Broadcasting](02.05-Computation-on-arrays-broadcasting.ipynb)) and aggregation ([Aggregations: Min, Max, and Everything In Between](02.04-Computation-on-arrays-aggregates.ipynb)) routines provided by NumPy we can compute the matrix of square distances in a single line of code:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dist_sq = np.sum((X[:, np.newaxis, :] - X[np.newaxis, :, :]) ** 2, axis=-1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This operation has a lot packed into it, and it might be a bit confusing if you're unfamiliar with NumPy's broadcasting rules. When you come across code like this, it can be useful to break it down into its component steps:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(10, 10, 2)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# for each pair of points, compute differences in their coordinates\n", + "differences = X[:, np.newaxis, :] - X[np.newaxis, :, :]\n", + "differences.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(10, 10, 2)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# square the coordinate differences\n", + "sq_differences = differences ** 2\n", + "sq_differences.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(10, 10)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# sum the coordinate differences to get the squared distance\n", + "dist_sq = sq_differences.sum(-1)\n", + "dist_sq.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Just to double-check what we are doing, we should see that the diagonal of this matrix (i.e., the set of distances between each point and itself) is all zero:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dist_sq.diagonal()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It checks out!\n", + "With the pairwise square-distances converted, we can now use ``np.argsort`` to sort along each row. The leftmost columns will then give the indices of the nearest neighbors:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[0 3 9 7 1 4 2 5 6 8]\n", + " [1 4 7 9 3 6 8 5 0 2]\n", + " [2 1 4 6 3 0 8 9 7 5]\n", + " [3 9 7 0 1 4 5 8 6 2]\n", + " [4 1 8 5 6 7 9 3 0 2]\n", + " [5 8 6 4 1 7 9 3 2 0]\n", + " [6 8 5 4 1 7 9 3 2 0]\n", + " [7 9 3 1 4 0 5 8 6 2]\n", + " [8 5 6 4 1 7 9 3 2 0]\n", + " [9 7 3 0 1 4 5 8 6 2]]\n" + ] + } + ], + "source": [ + "nearest = np.argsort(dist_sq, axis=1)\n", + "print(nearest)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that the first column gives the numbers 0 through 9 in order: this is due to the fact that each point's closest neighbor is itself, as we would expect.\n", + "\n", + "By using a full sort here, we've actually done more work than we need to in this case. 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. We can do this with the ``np.argpartition`` function:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "K = 2\n", + "nearest_partition = np.argpartition(dist_sq, K + 1, axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to visualize this network of neighbors, let's quickly plot the points along with lines representing the connections from each point to its two nearest neighbors:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.scatter(X[:, 0], X[:, 1], s=100)\n", + "\n", + "# draw lines from each point to its two nearest neighbors\n", + "K = 2\n", + "\n", + "for i in range(X.shape[0]):\n", + " for j in nearest_partition[i, :K+1]:\n", + " # plot a line from X[i] to X[j]\n", + " # use some zip magic to make it happen:\n", + " plt.plot(*zip(X[j], X[i]), color='black')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each point in the plot has lines drawn to its two nearest neighbors.\n", + "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", + "Although the broadcasting and row-wise sorting of this approach might seem less straightforward than writing a loop, it turns out to be a very efficient way of operating on this data in Python.\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, but this would almost certainly lead to a slower algorithm than the vectorized version we used. The beauty of this 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.\n", + "\n", + "Finally, I'll note that when doing very large nearest neighbor searches, there are tree-based and/or approximate algorithms that can scale as $\\mathcal{O}[N\\log N]$ or better rather than the $\\mathcal{O}[N^2]$ of the brute-force algorithm. One example of this is the KD-Tree, [implemented in Scikit-learn](http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KDTree.html)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Aside: Big-O Notation\n", + "\n", + "Big-O notation is a means of describing how the number of operations required for an algorithm scales as the input grows in size.\n", + "To use it correctly is to dive deeply into the realm of computer science theory, and to carefully distinguish it from the related small-o notation, big-$\\theta$ notation, big-$\\Omega$ notation, and probably many mutant hybrids thereof.\n", + "While these distinctions add precision to statements about algorithmic scaling, outside computer science theory exams and the remarks of pedantic blog commenters, you'll rarely see such distinctions made in practice.\n", + "Far more common in the data science world is a less rigid use of big-O notation: as a general (if imprecise) description of the scaling of an algorithm.\n", + "With apologies to theorists and pedants, this is the interpretation we'll use throughout this book.\n", + "\n", + "Big-O notation, in this loose sense, tells you how much time your algorithm will take as you increase the amount of data.\n", + "If you have an $\\mathcal{O}[N]$ (read \"order $N$\") algorithm that takes 1 second to operate on a list of length *N*=1,000, then you should expect it to take roughly 5 seconds for a list of length *N*=5,000.\n", + "If you have an $\\mathcal{O}[N^2]$ (read \"order *N* squared\") algorithm that takes 1 second for *N*=1000, then you should expect it to take about 25 seconds for *N*=5000.\n", + "\n", + "For our purposes, the *N* will usually indicate some aspect of the size of the dataset (the number of points, the number of dimensions, etc.). When trying to analyze billions or trillions of samples, the difference between $\\mathcal{O}[N]$ and $\\mathcal{O}[N^2]$ can be far from trivial!\n", + "\n", + "Notice that the big-O notation by itself tells you nothing about the actual wall-clock time of a computation, but only about its scaling as you change *N*.\n", + "Generally, for example, an $\\mathcal{O}[N]$ algorithm is considered to have better scaling than an $\\mathcal{O}[N^2]$ algorithm, and for good reason. But for small datasets in particular, the algorithm with better scaling might not be faster.\n", + "For example, in a given problem an $\\mathcal{O}[N^2]$ algorithm might take 0.01 seconds, while a \"better\" $\\mathcal{O}[N]$ algorithm might take 1 second.\n", + "Scale up *N* by a factor of 1,000, though, and the $\\mathcal{O}[N]$ algorithm will win out.\n", + "\n", + "Even this loose version of Big-O notation can be very useful when comparing the performance of algorithms, and we'll use this notation throughout the book when talking about how algorithms scale." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Fancy Indexing](02.07-Fancy-Indexing.ipynb) | [Contents](Index.ipynb) | [Structured Data: NumPy's Structured Arrays](02.09-Structured-Data-NumPy.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/numpy/02.09-Structured-Data-NumPy.ipynb b/numpy/02.09-Structured-Data-NumPy.ipynb new file mode 100644 index 0000000..823a8fb --- /dev/null +++ b/numpy/02.09-Structured-Data-NumPy.ipynb @@ -0,0 +1,600 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "*This notebook contains an excerpt from the [Python Data Science Handbook](http://shop.oreilly.com/product/0636920034919.do) by Jake VanderPlas; the content is available [on GitHub](https://github.com/jakevdp/PythonDataScienceHandbook).*\n", + "\n", + "*The text is released under the [CC-BY-NC-ND license](https://creativecommons.org/licenses/by-nc-nd/3.0/us/legalcode), and code is released under the [MIT license](https://opensource.org/licenses/MIT). If you find this content useful, please consider supporting the work by [buying the book](http://shop.oreilly.com/product/0636920034919.do)!*\n", + "\n", + "*No changes were made to the contents of this notebook from the original.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Sorting Arrays](02.08-Sorting.ipynb) | [Contents](Index.ipynb) | [Data Manipulation with Pandas](03.00-Introduction-to-Pandas.ipynb) >" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Structured Data: NumPy's Structured Arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "While often our data can be well represented by a homogeneous array of values, sometimes this is not the case. This section demonstrates the use of NumPy's *structured arrays* and *record arrays*, which provide efficient storage for compound, heterogeneous data. While the patterns shown here are useful for simple operations, scenarios like this often lend themselves to the use of Pandas ``Dataframe``s, which we'll explore in [Chapter 3](03.00-Introduction-to-Pandas.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Imagine that we have several categories of data on a number of people (say, name, age, and weight), and we'd like to store these values for use in a Python program.\n", + "It would be possible to store these in three separate arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "name = ['Alice', 'Bob', 'Cathy', 'Doug']\n", + "age = [25, 45, 37, 19]\n", + "weight = [55.0, 85.5, 68.0, 61.5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But this is a bit clumsy. There's nothing here that tells us that the three arrays are related; it would be more natural if we could use a single structure to store all of this data.\n", + "NumPy can handle this through structured arrays, which are arrays with compound data types.\n", + "\n", + "Recall that previously we created a simple array using an expression like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x = np.zeros(4, dtype=int)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can similarly create a structured array using a compound data type specification:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[('name', '``, which means \"little endian\" or \"big endian,\" respectively, and specifies the ordering convention for significant bits.\n", + "The next character specifies the type of data: characters, bytes, ints, floating points, and so on (see the table below).\n", + "The last character or characters represents the size of the object in bytes.\n", + "\n", + "| Character | Description | Example |\n", + "| --------- | ----------- | ------- | \n", + "| ``'b'`` | Byte | ``np.dtype('b')`` |\n", + "| ``'i'`` | Signed integer | ``np.dtype('i4') == np.int32`` |\n", + "| ``'u'`` | Unsigned integer | ``np.dtype('u1') == np.uint8`` |\n", + "| ``'f'`` | Floating point | ``np.dtype('f8') == np.int64`` |\n", + "| ``'c'`` | Complex floating point| ``np.dtype('c16') == np.complex128``|\n", + "| ``'S'``, ``'a'`` | String | ``np.dtype('S5')`` |\n", + "| ``'U'`` | Unicode string | ``np.dtype('U') == np.str_`` |\n", + "| ``'V'`` | Raw data (void) | ``np.dtype('V') == np.void`` |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More Advanced Compound Types\n", + "\n", + "It is possible to define even more advanced compound types.\n", + "For example, you can create a type where each element contains an array or matrix of values.\n", + "Here, we'll create a data type with a ``mat`` component consisting of a $3\\times 3$ floating-point matrix:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])\n", + "[[ 0. 0. 0.]\n", + " [ 0. 0. 0.]\n", + " [ 0. 0. 0.]]\n" + ] + } + ], + "source": [ + "tp = np.dtype([('id', 'i8'), ('mat', 'f8', (3, 3))])\n", + "X = np.zeros(1, dtype=tp)\n", + "print(X[0])\n", + "print(X['mat'][0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now each element in the ``X`` array consists of an ``id`` and a $3\\times 3$ matrix.\n", + "Why would you use this rather than a simple multidimensional array, or perhaps a Python dictionary?\n", + "The reason is that this NumPy ``dtype`` directly maps onto a C structure definition, so the buffer containing the array content can be accessed directly within an appropriately written C program.\n", + "If you find yourself writing a Python interface to a legacy C or Fortran library that manipulates structured data, you'll probably find structured arrays quite useful!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## RecordArrays: Structured Arrays with a Twist\n", + "\n", + "NumPy also provides the ``np.recarray`` class, which is almost identical to the structured arrays just described, but with one additional feature: fields can be accessed as attributes rather than as dictionary keys.\n", + "Recall that we previously accessed the ages by writing:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([25, 45, 37, 19], dtype=int32)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data['age']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we view our data as a record array instead, we can access this with slightly fewer keystrokes:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([25, 45, 37, 19], dtype=int32)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_rec = data.view(np.recarray)\n", + "data_rec.age" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The downside is that for record arrays, there is some extra overhead involved in accessing the fields, even when using the same syntax. We can see this here:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000000 loops, best of 3: 241 ns per loop\n", + "100000 loops, best of 3: 4.61 µs per loop\n", + "100000 loops, best of 3: 7.27 µs per loop\n" + ] + } + ], + "source": [ + "%timeit data['age']\n", + "%timeit data_rec['age']\n", + "%timeit data_rec.age" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Whether the more convenient notation is worth the additional overhead will depend on your own application." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## On to Pandas\n", + "\n", + "This section on structured and record arrays is purposely at the end of this chapter, because it leads so well into the next package we will cover: Pandas.\n", + "Structured arrays like the ones discussed here are good to know about for certain situations, especially in case you're using NumPy arrays to map onto binary data formats in C, Fortran, or another language.\n", + "For day-to-day use of structured data, the Pandas package is a much better choice, and we'll dive into a full discussion of it in the chapter that follows." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "< [Sorting Arrays](02.08-Sorting.ipynb) | [Contents](Index.ipynb) | [Data Manipulation with Pandas](03.00-Introduction-to-Pandas.ipynb) >" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "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.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}