{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "> This is one of the 100 recipes of the [IPython Cookbook](http://ipython-books.github.io/), the definitive guide to high-performance scientific computing and data science in Python.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1.3. Introducing the multidimensional array in NumPy for fast array computations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Let's import the built-in `random` Python module and NumPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import random\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use the `%precision` magic (defined in IPython) to show only 3 decimals in the Python output. This is just to alleviate the text." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%precision 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. We generate two Python lists `x` and `y`, each one containing one million random numbers between 0 and 1." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "n = 1000000\n", "x = [random.random() for _ in range(n)]\n", "y = [random.random() for _ in range(n)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "x[:3], y[:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. Let's compute the element-wise sum of all these numbers: the first element of `x` plus the first element of `y`, and so on. We use a `for` loop in a list comprehension." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "z = [x[i] + y[i] for i in range(n)]\n", "z[:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "4. How long does this computation take? IPython defines a handy `%timeit` magic command to quickly evaluate the time taken by a single command." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%timeit [x[i] + y[i] for i in range(n)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "5. Now, we will perform the same operation with NumPy. NumPy works on multidimensional arrays, so we need to convert our lists to arrays. The `np.array()` function does just that." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "xa = np.array(x)\n", "ya = np.array(y)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "xa[:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The arrays `xa` and `ya` contain the *exact* same numbers than our original lists `x` and `y`. Whereas those lists where instances of a built-in class `list`, our arrays are instances of a NumPy class `ndarray`. Those types are implemented very differently in Python and NumPy. We will see that, in this example, using arrays instead of lists leads to drastic performance improvements." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "6. Now, to compute the element-wise sum of these arrays, we don't need to do a `for` loop anymore. In NumPy, adding two arrays means adding the elements of the arrays component by component." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "za = xa + ya\n", "za[:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that the list `z` and the array `za` contain the same elements (the sum of the numbers in `x` and `y`)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "7. Let's compare the performance of this NumPy operation with the native Python loop." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%timeit xa + ya" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We observe that this operation is more than one order of magnitude faster in NumPy than in pure Python!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "8. Now, we will compute something else: the sum of all elements in `x` or `xa`. Although this is not an element-wise operation, NumPy is still highly efficient here. The pure Python version uses the built-in `sum` function on an iterable. The NumPy version uses the `np.sum()` function on a NumPy array." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%timeit sum(x) # pure Python\n", "%timeit np.sum(xa) # NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also observe an impressive speedup here." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "9. Finally, let's perform a last operation: computing the arithmetic distance between any pair of numbers in our two lists (we only consider the first 1000 elements to keep computing times reasonable). First, we implement this in pure Python with two nested `for` loops." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "d = [abs(x[i] - y[j]) \n", " for i in range(1000) for j in range(1000)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "d[:3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "10. Now, we use a NumPy implementation, bringing out two slightly more advanced notions. First, we consider a **two-dimensional array** (or matrix). This is how we deal with *two* indices *i* and *j*. Second, we use **broadcasting** to perform an operation between a 2D array and a 1D array. We will give more details in *How it works...*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "da = np.abs(xa[:1000,None] - ya[:1000])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "da" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%timeit [abs(x[i] - y[j]) for i in range(1000) for j in range(1000)]\n", "%timeit np.abs(xa[:1000, None] - ya[:1000])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here again, observe observe the significant speedups." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> You'll find all the explanations, figures, references, and much more in the book (to be released later this summer).\n", "\n", "> [IPython Cookbook](http://ipython-books.github.io/), by [Cyrille Rossant](http://cyrille.rossant.net), Packt Publishing, 2014 (500 pages)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.2" } }, "nbformat": 4, "nbformat_minor": 0 }