{ "metadata": { "name": "Lecture-2-Numpy" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Numpy - multidimensional data arrays\n", "\n", "J.R. Johansson (robert@riken.jp) http://dml.riken.jp/~rob/\n", "\n", "The latest version of this [IPython notebook](http://ipython.org/ipython-doc/dev/interactive/htmlnotebook.html) lecture is available at [http://github.com/jrjohansson/scientific-python-lectures](http://github.com/jrjohansson/scientific-python-lectures).\n", "\n", "The other notebooks in this lecture series are indexed at [http://jrjohansson.github.com](http://jrjohansson.github.com)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# what is this line all about?!? Answer in lecture 4\n", "%pylab inline" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Welcome to pylab, a matplotlib-based Python environment [backend: module://IPython.zmq.pylab.backend_inline].\n", "For more information, type 'help(pylab)'.\n" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction\n", "\n", "The `numpy` package (module) is used in almost all numerical computation using Python. It is a package that provide high-performance vector, matrix and higher-dimensional data structures for Python. It is implemented in C and Fortran so when calculations are vectorized (formulated with vectors and matrices), performance is very good. \n", "\n", "To use `numpy` need to import the module it using of example:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from numpy import *" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the `numpy` package the terminology used for vectors, matrices and higher-dimensional data sets is *array*. \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating `numpy` arrays\n", "\n", "There are a number of ways to initialize new numpy arrays, for example from\n", "\n", "* a Python list or tuples\n", "* using functions that are dedicated to generating numpy arrays, such as `arange`, `linspace`, etc.\n", "* reading data from files\n", "\n", "### From lists\n", "\n", "For example, to create new vector and matrix arrays from Python lists we can use the `numpy.array` function.\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# a vector: the argument to the array function is a Python list\n", "v = array([1,2,3,4])\n", "\n", "v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 3, "text": [ "array([1, 2, 3, 4])" ] } ], "prompt_number": 3 }, { "cell_type": "code", "collapsed": false, "input": [ "# a matrix: the argument to the array function is a nested Python list\n", "M = array([[1, 2], [3, 4]])\n", "\n", "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 4, "text": [ "array([[1, 2],\n", " [3, 4]])" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `v` and `M` objects are both of the type `ndarray` that the `numpy` module provides." ] }, { "cell_type": "code", "collapsed": false, "input": [ "type(v), type(M)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 5, "text": [ "(numpy.ndarray, numpy.ndarray)" ] } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference between the `v` and `M` arrays is only their shapes. We can get information about the shape of an array by using the `ndarray.shape` property." ] }, { "cell_type": "code", "collapsed": false, "input": [ "v.shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 6, "text": [ "(4,)" ] } ], "prompt_number": 6 }, { "cell_type": "code", "collapsed": false, "input": [ "M.shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 7, "text": [ "(2, 2)" ] } ], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The number of elements in the array is available through the `ndarray.size` property:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M.size" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 8, "text": [ "4" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Equivalently, we could use the function `numpy.shape` and `numpy.size`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "shape(M)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 9, "text": [ "(2, 2)" ] } ], "prompt_number": 9 }, { "cell_type": "code", "collapsed": false, "input": [ "size(M)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 10, "text": [ "4" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far the `numpy.ndarray` looks awefully much like a Python list (or nested list). Why not simply use Python lists for computations instead of creating a new array type? \n", "\n", "There are several reasons:\n", "\n", "* Python lists are very general. They can contain any kind of object. They are dynamically typed. They do not support mathematical functions such as matrix and dot multiplications, etc. Implementating such functions for Python lists would not be very efficient because of the dynamic typing.\n", "* Numpy arrays are **statically typed** and **homogeneous**. The type of the elements is determined when array is created.\n", "* Numpy arrays are memory efficient.\n", "* Because of the static typing, fast implementation of mathematical functions such as multiplication and addition of `numpy` arrays can be implemented in a compiled language (C and Fortran is used).\n", "\n", "Using the `dtype` (data type) property of an `ndarray`, we can see what type the data of an array has:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M.dtype" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 11, "text": [ "dtype('int64')" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We get an error if we try to assign a value of the wrong type to an element in a numpy array:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M[0,0] = \"hello\"" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "invalid literal for long() with base 10: 'hello'", "output_type": "pyerr", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mM\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m\"hello\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mValueError\u001b[0m: invalid literal for long() with base 10: 'hello'" ] } ], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want, we can explicitly define the type of the array data when we create it, using the `dtype` keyword argument: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "M = array([[1, 2], [3, 4]], dtype=complex)\n", "\n", "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 13, "text": [ "array([[ 1.+0.j, 2.+0.j],\n", " [ 3.+0.j, 4.+0.j]])" ] } ], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Common type that can be used with `dtype` are: `int`, `float`, `complex`, `bool`, `object`, etc.\n", "\n", "We can also explicitly define the bit size of the data types, for example: `int64`, `int16`, `float128`, `complex128`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using array-generating functions\n", "\n", "For larger arrays it is inpractical to initialize the data manually, using explicit pythons lists. Instead we can use one of the many functions in `numpy` that generates arrays of different forms. Some of the more common are:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### arange" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# create a range\n", "\n", "x = arange(0, 10, 1) # arguments: start, stop, step\n", "\n", "x" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 14, "text": [ "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" ] } ], "prompt_number": 14 }, { "cell_type": "code", "collapsed": false, "input": [ "x = arange(-1, 1, 0.1)\n", "\n", "x" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 15, "text": [ "array([ -1.00000000e+00, -9.00000000e-01, -8.00000000e-01,\n", " -7.00000000e-01, -6.00000000e-01, -5.00000000e-01,\n", " -4.00000000e-01, -3.00000000e-01, -2.00000000e-01,\n", " -1.00000000e-01, -2.22044605e-16, 1.00000000e-01,\n", " 2.00000000e-01, 3.00000000e-01, 4.00000000e-01,\n", " 5.00000000e-01, 6.00000000e-01, 7.00000000e-01,\n", " 8.00000000e-01, 9.00000000e-01])" ] } ], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### linspace and logspace" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# using linspace, both end points ARE included\n", "linspace(0, 10, 25)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 16, "text": [ "array([ 0. , 0.41666667, 0.83333333, 1.25 ,\n", " 1.66666667, 2.08333333, 2.5 , 2.91666667,\n", " 3.33333333, 3.75 , 4.16666667, 4.58333333,\n", " 5. , 5.41666667, 5.83333333, 6.25 ,\n", " 6.66666667, 7.08333333, 7.5 , 7.91666667,\n", " 8.33333333, 8.75 , 9.16666667, 9.58333333, 10. ])" ] } ], "prompt_number": 16 }, { "cell_type": "code", "collapsed": false, "input": [ "logspace(0, 10, 10, base=e)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 17, "text": [ "array([ 1.00000000e+00, 3.03773178e+00, 9.22781435e+00,\n", " 2.80316249e+01, 8.51525577e+01, 2.58670631e+02,\n", " 7.85771994e+02, 2.38696456e+03, 7.25095809e+03,\n", " 2.20264658e+04])" ] } ], "prompt_number": 17 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### mgrid" ] }, { "cell_type": "code", "collapsed": false, "input": [ "x, y = mgrid[0:5, 0:5] # similar to meshgrid in MATLAB" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 18 }, { "cell_type": "code", "collapsed": false, "input": [ "x" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 19, "text": [ "array([[0, 0, 0, 0, 0],\n", " [1, 1, 1, 1, 1],\n", " [2, 2, 2, 2, 2],\n", " [3, 3, 3, 3, 3],\n", " [4, 4, 4, 4, 4]])" ] } ], "prompt_number": 19 }, { "cell_type": "code", "collapsed": false, "input": [ "y" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 20, "text": [ "array([[0, 1, 2, 3, 4],\n", " [0, 1, 2, 3, 4],\n", " [0, 1, 2, 3, 4],\n", " [0, 1, 2, 3, 4],\n", " [0, 1, 2, 3, 4]])" ] } ], "prompt_number": 20 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### random data" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from numpy import random" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 21 }, { "cell_type": "code", "collapsed": false, "input": [ "# uniform random numbers ini [0,1]\n", "random.rand(5,5)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 22, "text": [ "array([[ 0.38514869, 0.65611855, 0.30951719, 0.90606323, 0.45323021],\n", " [ 0.4829053 , 0.71078648, 0.27249177, 0.06156748, 0.49899315],\n", " [ 0.81852145, 0.65724548, 0.77194554, 0.29973648, 0.87633625],\n", " [ 0.37501764, 0.10998782, 0.5567457 , 0.26298218, 0.97630491],\n", " [ 0.81460477, 0.8886327 , 0.46886708, 0.29431937, 0.16157934]])" ] } ], "prompt_number": 22 }, { "cell_type": "code", "collapsed": false, "input": [ "# standard normal distributed random numbers\n", "random.randn(5,5)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 23, "text": [ "array([[ 1.17984323, 0.12248472, 0.16712688, -0.63193807, -1.0372697 ],\n", " [-0.28335305, -0.92302383, 1.41181247, 0.46338623, -1.53910004],\n", " [ 0.08862918, 1.12887421, 1.07811757, -0.27373696, -1.25380144],\n", " [ 2.80918157, -0.79861234, 0.27846162, -1.21928768, -0.0844151 ],\n", " [-0.29196407, -0.5398782 , -0.18096382, -1.12382364, -0.92178747]])" ] } ], "prompt_number": 23 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### diag" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# a diagonal matrix\n", "diag([1,2,3])" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 24, "text": [ "array([[1, 0, 0],\n", " [0, 2, 0],\n", " [0, 0, 3]])" ] } ], "prompt_number": 24 }, { "cell_type": "code", "collapsed": false, "input": [ "# diagonal with offset from the main diagonal\n", "diag([1,2,3], k=1) " ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 25, "text": [ "array([[0, 1, 0, 0],\n", " [0, 0, 2, 0],\n", " [0, 0, 0, 3],\n", " [0, 0, 0, 0]])" ] } ], "prompt_number": 25 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### zeros and ones" ] }, { "cell_type": "code", "collapsed": false, "input": [ "zeros((3,3))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 26, "text": [ "array([[ 0., 0., 0.],\n", " [ 0., 0., 0.],\n", " [ 0., 0., 0.]])" ] } ], "prompt_number": 26 }, { "cell_type": "code", "collapsed": false, "input": [ "ones((3,3))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 27, "text": [ "array([[ 1., 1., 1.],\n", " [ 1., 1., 1.],\n", " [ 1., 1., 1.]])" ] } ], "prompt_number": 27 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## File I/O\n", "\n", "\n", "### Comma-separated values (CSV)\n", "\n", "A very common file format for data files are the comma-separated values (CSV), or related format such as TSV (tab-separated values). To read data from such file into Numpy arrays we can use the `numpy.genfromtxt` function. For example, " ] }, { "cell_type": "code", "collapsed": false, "input": [ "!head stockholm_td_adj.dat" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "1800 1 1 -6.1 -6.1 -6.1 1\r\n", "1800 1 2 -15.4 -15.4 -15.4 1\r\n", "1800 1 3 -15.0 -15.0 -15.0 1\r\n", "1800 1 4 -19.3 -19.3 -19.3 1\r\n", "1800 1 5 -16.8 -16.8 -16.8 1\r\n", "1800 1 6 -11.4 -11.4 -11.4 1\r\n", "1800 1 7 -7.6 -7.6 -7.6 1\r\n", "1800 1 8 -7.1 -7.1 -7.1 1\r\n", "1800 1 9 -10.1 -10.1 -10.1 1\r\n", "1800 1 10 -9.5 -9.5 -9.5 1\r\n" ] } ], "prompt_number": 28 }, { "cell_type": "code", "collapsed": false, "input": [ "data = genfromtxt('stockholm_td_adj.dat')" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 29 }, { "cell_type": "code", "collapsed": false, "input": [ "data.shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 30, "text": [ "(77431, 7)" ] } ], "prompt_number": 30 }, { "cell_type": "code", "collapsed": false, "input": [ "fig, ax = subplots(figsize=(14,4))\n", "ax.plot(data[:,0]+data[:,1]/12.0+data[:,2]/365, data[:,5])\n", "ax.axis('tight')\n", "ax.set_title('tempeatures in Stockholm')\n", "ax.set_xlabel('year')\n", "ax.set_ylabel('tempature (C)');" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAA0EAAAEVCAYAAAAmfe4RAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsXXe0FEXWv/OMgEiUx0oGQUVxxRxQMeCHLiKKkSDLuuoa\n17CKiK5PMLCKEd1VQVFBAdOqGFBEMSAGEIwEUQkKsiJBQETk9ffHUEz1napbdauqZ+ZB/87h8Hq6\nuup2daWbM1EURZAiRYoUKVKkSJEiRYoUWwjKik1AihQpUqRIkSJFihQpUhQSKROUIkWKFClSpEiR\nIkWKLQopE5QiRYoUKVKkSJEiRYotCikTlCJFihQpUqRIkSJFii0KKROUIkWKFClSpEiRIkWKLQop\nE5QiRYoUKVKkSJEiRYotCikTlCJFihQpNjscf/zxMHLkyGKTQaJ58+YwceJE9j0TysrK4JtvvvEh\nLUWKFCk2e6RMUIoUKVKUGJo3bw5vvPFGsclgo6KiAnr37l1sMgAA4OWXX3am5d1334VDDjkEateu\nDfXq1YMOHTrA1KlTAQDgkUcegcMOOywIjZlMBjKZDPteihQpUqTwx9bFJiBFihQpUsSRyWRgS8xj\n/fvvv8PWWxd3W/r555+hS5cu8MADD8Bpp50G69atg3feeQe22267otKVIkWKFCnCItUEpUiRIkUJ\noXfv3rBgwQI44YQToGbNmjBkyBAAAHj//ffhkEMOgTp16sDee+8Nb7311qZnOnbsCNdddx0ceuih\nULNmTejatSssXboUevbsCbVq1YIDDjgA5s+fv6l8WVkZDB06FFq1agU77bQTXHXVVTGm6+GHH4a2\nbdtC3bp1oXPnzrBgwYJN9/7+979D06ZNoVatWrDffvvBu+++CwAA48ePh1tuuQXGjh0LNWvWhPbt\n2wNAvlmXrC2aN28elJWVwcMPPwzNmjWDY445xtj+ZZddBuXl5VCrVi3Ya6+94IsvvlD2Y8eOHeGh\nhx4CgKz2pkOHDnDllVdC3bp1oWXLljB+/Hjlc3PmzIFMJgOnn346ZDIZ2H777aFTp07Qrl07mDlz\nJpx//vkwZcoUqFmzJtStWxcAAFauXAlnnXUWNGjQAJo3bw433XRTrD+HDRsGbdu2hR133BH22GMP\nmDFjRl67M2fOhJYtW8LYsWM3/TZ9+nT44x//CLVr14YzzjgD1q1bF6uzdevWUK9ePTjxxBNh8eLF\nyvf585//DBdccAEcf/zxULNmTTjssMPghx9+gL///e9Qp04d2H333ZX0pEiRIsVmjyhFihQpUpQU\nmjdvHk2cOHHT9XfffRfVq1cveuWVV6IoiqIJEyZE9erVi5YuXRpFURQdccQRUevWraNvvvkmWrly\nZdS2bdtol112iSZOnBj9/vvv0VlnnRX17dt3U32ZTCY66qijouXLl0cLFiyI2rRpEw0fPjyKoih6\n7rnnol122SWaNWtWtGHDhujGG2+MDjnkkE3Pjho1Klq2bFm0YcOG6Pbbb48aNmwYrVu3LoqiKKqo\nqIh69+5NvktFRUXUq1evKIqi6Ntvv40ymUzUp0+f6JdffonWrl1Ltj9+/Pho3333jVauXBlFURTN\nmjUrWrx4sbIPO3bsGD300ENRFEXRiBEjom222SYaPnx4VFlZGf3nP/+Jdt55Z+VzP//8c1SvXr2o\nT58+0SuvvBItW7Ysdv+RRx6JOnToEPutd+/eUbdu3aLVq1dH8+bNi9q0abOp7SeffDJq1KhRNHXq\n1CiKomju3LnR/PnzY30zbdq0qGnTptFLL720qc5mzZpFBx54YLR48eJo2bJl0e677x7df//9URRF\n0cSJE6P69etH06dPj9atWxddfPHF0eGHH77p2UwmE3399ddRFEVRnz59ovr160cff/xx9Ouvv0ZH\nHXVU1KxZs2jkyJFRZWVldO2110ZHHnmksi9SpEiRYnNGqglKkSJFihLHqFGj4Pjjj4fOnTsDAMAx\nxxwD++23H7z00ksAkDWf69u3L7Ro0QJ23HFHOO6446BNmzZw1FFHwVZbbQWnnnoqTJ8+PVZnv379\noHbt2tCkSRO49NJLYfTo0QAAcP/990P//v1h1113hbKyMujfvz/MmDEDFi5cCAAAPXv2hDp16kBZ\nWRlcfvnlsG7dOpg9ezYAAERRZDTjU92vqKiAatWqwfbbb69tf8GCBbDtttvCqlWrYObMmVBZWQm7\n7rorNGzY0KoPmzVrBmeffTZkMhk466yzYPHixfC///0vr1zNmjXh3XffhUwmA+eccw40aNAATjzx\nxE1lMf0bNmyAsWPHwi233AI1atSAZs2awRVXXLEpKMPw4cOhX79+sO+++wIAQKtWraBp06abnn/r\nrbfgxBNPhJEjR8Lxxx+/6fdMJgOXXHIJNGzYEOrUqQMnnHDCJo3N448/DmeffTbsvffesO2228It\n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\nGTIks/shCIIg8sFOWQtAEARBECoOOOAAaNu2LbzxxhuwefNm6NevH6xYtz6hPAAAAbNJREFUsQKW\nLVsG/fr1AwCAL774At555x0YOnQo3HHHHfD4448DAMCGDRtqA/lVVFTAuHHjsrwVgiAIIkfQIogg\nCILINWeffTbcf//98OGHH8LkyZPh+eefhyuuuALOOeecknQvvPACPP/88/DKK69A8+bN4YgjjoCv\nv/4aAACaN28OhUIhC/EJgiCIHELmcARBEESuGTNmDCxduhRWrlwJxxxzDIwYMQLmz58PX3zxBQAA\nbNq0CT7++GP417/+BW3atIHmzZvD2rVr6ewPQRAEoYR2ggiCIIhc07RpUzjyyCOhTZs2UCgUoKam\nBv7617/C4MGDAQBg1113hV//+tdwzDHHwN133w09evSAbt261f4OALQLRBAEQZRQSJIkyVoIgiAI\nglCxY8cOOOSQQ+DRRx+Fzp07Zy0OQRAE0QAgcziCIAgit6xZswa6du0KRx99NC2ACIIgiGDQThBB\nEARBEARBEI0K2gkiCIIgCIIgCKJRQYsggiAIgiAIgiAaFbQIIgiCIAiCIAiiUUGLIIIgCIIgCIIg\nGhW0CCIIgiAIgiAIolFBiyCCIAiCIAiCIBoV/x/tATLm0X00mQAAAABJRU5ErkJggg==\n" } ], "prompt_number": 31 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using the `numpy.savetxt` we can store a Numpy array to a file in CSV format:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M = rand(3,3)\n", "\n", "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 32, "text": [ "array([[ 0.43893135, 0.46635226, 0.4070475 ],\n", " [ 0.53910705, 0.64968622, 0.85079048],\n", " [ 0.45170465, 0.97032227, 0.31628198]])" ] } ], "prompt_number": 32 }, { "cell_type": "code", "collapsed": false, "input": [ "savetxt(\"random-matrix.csv\", M)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 33 }, { "cell_type": "code", "collapsed": false, "input": [ "!cat random-matrix.csv" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "4.389313531846058547e-01 4.663522629835443745e-01 4.070474979109756086e-01\r\n", "5.391070529944648193e-01 6.496862221899694090e-01 8.507904796404367476e-01\r\n", "4.517046464380731763e-01 9.703222696663832414e-01 3.162819794660202133e-01\r\n" ] } ], "prompt_number": 34 }, { "cell_type": "code", "collapsed": false, "input": [ "savetxt(\"random-matrix.csv\", M, fmt='%.5f') # fmt specifies the format\n", "\n", "!cat random-matrix.csv" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "0.43893 0.46635 0.40705\r\n", "0.53911 0.64969 0.85079\r\n", "0.45170 0.97032 0.31628\r\n" ] } ], "prompt_number": 35 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Numpy's native file format\n", "\n", "Useful when storing and reading back numpy array data. Use the functions `numpy.save` and `numpy.load`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "save(\"random-matrix.npy\", M)\n", "\n", "!file random-matrix.npy" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "random-matrix.npy: data\r\n" ] } ], "prompt_number": 36 }, { "cell_type": "code", "collapsed": false, "input": [ "load(\"random-matrix.npy\")" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 37, "text": [ "array([[ 0.43893135, 0.46635226, 0.4070475 ],\n", " [ 0.53910705, 0.64968622, 0.85079048],\n", " [ 0.45170465, 0.97032227, 0.31628198]])" ] } ], "prompt_number": 37 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More properties of the numpy arrays" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M.itemsize # bytes per element" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 38, "text": [ "8" ] } ], "prompt_number": 38 }, { "cell_type": "code", "collapsed": false, "input": [ "M.nbytes # number of bytes" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 39, "text": [ "72" ] } ], "prompt_number": 39 }, { "cell_type": "code", "collapsed": false, "input": [ "M.ndim # number of dimensions" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 40, "text": [ "2" ] } ], "prompt_number": 40 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manipulating arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Indexing\n", "\n", "We can index elements in an array using the square bracket and indices:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# v is a vector, and has only one dimension, taking one index\n", "v[0]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 41, "text": [ "1" ] } ], "prompt_number": 41 }, { "cell_type": "code", "collapsed": false, "input": [ "# M is a matrix, or a 2 dimensional array, taking two indices \n", "M[1,1]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 42, "text": [ "0.64968622218996941" ] } ], "prompt_number": 42 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we omit an index of a multidimensional array it returns the whole row (or, in general, a N-1 dimensional array) " ] }, { "cell_type": "code", "collapsed": false, "input": [ "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 43, "text": [ "array([[ 0.43893135, 0.46635226, 0.4070475 ],\n", " [ 0.53910705, 0.64968622, 0.85079048],\n", " [ 0.45170465, 0.97032227, 0.31628198]])" ] } ], "prompt_number": 43 }, { "cell_type": "code", "collapsed": false, "input": [ "M[1]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 44, "text": [ "array([ 0.53910705, 0.64968622, 0.85079048])" ] } ], "prompt_number": 44 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same thing can be achieved with using `:` instead of an index: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "M[1,:] # row 1" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 45, "text": [ "array([ 0.53910705, 0.64968622, 0.85079048])" ] } ], "prompt_number": 45 }, { "cell_type": "code", "collapsed": false, "input": [ "M[:,1] # column 1" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 46, "text": [ "array([ 0.46635226, 0.64968622, 0.97032227])" ] } ], "prompt_number": 46 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can assign new values to elements in an array using indexing:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M[0,0] = 1" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 47 }, { "cell_type": "code", "collapsed": false, "input": [ "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 48, "text": [ "array([[ 1. , 0.46635226, 0.4070475 ],\n", " [ 0.53910705, 0.64968622, 0.85079048],\n", " [ 0.45170465, 0.97032227, 0.31628198]])" ] } ], "prompt_number": 48 }, { "cell_type": "code", "collapsed": false, "input": [ "# also works for rows and columns\n", "M[1,:] = 0\n", "M[:,2] = -1" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 49 }, { "cell_type": "code", "collapsed": false, "input": [ "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 50, "text": [ "array([[ 1. , 0.46635226, -1. ],\n", " [ 0. , 0. , -1. ],\n", " [ 0.45170465, 0.97032227, -1. ]])" ] } ], "prompt_number": 50 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Index slicing\n", "\n", "Index slicing is the technical name for the syntax `M[lower:upper:step]` to extract part of an array:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A = array([1,2,3,4,5])\n", "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 51, "text": [ "array([1, 2, 3, 4, 5])" ] } ], "prompt_number": 51 }, { "cell_type": "code", "collapsed": false, "input": [ "A[1:3]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 52, "text": [ "array([2, 3])" ] } ], "prompt_number": 52 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Array slices are *mutable*: if they are assigned a new value the original array from which the slice was extracted is modified:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A[1:3] = [-2,-3]\n", "\n", "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 53, "text": [ "array([ 1, -2, -3, 4, 5])" ] } ], "prompt_number": 53 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can omit any of the three parameters in `M[lower:upper:step]`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A[::] # lower, upper, step all take the default values" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 54, "text": [ "array([ 1, -2, -3, 4, 5])" ] } ], "prompt_number": 54 }, { "cell_type": "code", "collapsed": false, "input": [ "A[::2] # step is 2, lower and upper defaults to the beginning and end of the array" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 55, "text": [ "array([ 1, -3, 5])" ] } ], "prompt_number": 55 }, { "cell_type": "code", "collapsed": false, "input": [ "A[:3] # first three elements" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 56, "text": [ "array([ 1, -2, -3])" ] } ], "prompt_number": 56 }, { "cell_type": "code", "collapsed": false, "input": [ "A[3:] # elements from index 3" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 57, "text": [ "array([4, 5])" ] } ], "prompt_number": 57 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Negative indices counts from the end of the array (positive index from the begining):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A = array([1,2,3,4,5])" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 58 }, { "cell_type": "code", "collapsed": false, "input": [ "A[-1] # the last element in the array" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 59, "text": [ "5" ] } ], "prompt_number": 59 }, { "cell_type": "code", "collapsed": false, "input": [ "A[-3:] # the last three elements" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 60, "text": [ "array([3, 4, 5])" ] } ], "prompt_number": 60 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Index slicing works exactly the same way for multidimensional arrays:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A = array([[n+m*10 for n in range(5)] for m in range(5)])\n", "\n", "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 61, "text": [ "array([[ 0, 1, 2, 3, 4],\n", " [10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34],\n", " [40, 41, 42, 43, 44]])" ] } ], "prompt_number": 61 }, { "cell_type": "code", "collapsed": false, "input": [ "# a block from the original array\n", "A[1:4, 1:4]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 62, "text": [ "array([[11, 12, 13],\n", " [21, 22, 23],\n", " [31, 32, 33]])" ] } ], "prompt_number": 62 }, { "cell_type": "code", "collapsed": false, "input": [ "# strides\n", "A[::2, ::2]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 63, "text": [ "array([[ 0, 2, 4],\n", " [20, 22, 24],\n", " [40, 42, 44]])" ] } ], "prompt_number": 63 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fancy indexing\n", "\n", "Fancy indexing is the name for when an array or list is used in-place of an index: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "row_indices = [1, 2, 3]\n", "A[row_indices]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 64, "text": [ "array([[10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34]])" ] } ], "prompt_number": 64 }, { "cell_type": "code", "collapsed": false, "input": [ "col_indices = [1, 2, -1] # remember, index -1 means the last element\n", "A[row_indices, col_indices]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 65, "text": [ "array([11, 22, 34])" ] } ], "prompt_number": 65 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also index masks: If the index mask is an Numpy array of with data type `bool`, then an element is selected (True) or not (False) depending on the value of the index mask at the position each element: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "B = array([n for n in range(5)])\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 66, "text": [ "array([0, 1, 2, 3, 4])" ] } ], "prompt_number": 66 }, { "cell_type": "code", "collapsed": false, "input": [ "row_mask = array([True, False, True, False, False])\n", "B[row_mask]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 67, "text": [ "array([0, 2])" ] } ], "prompt_number": 67 }, { "cell_type": "code", "collapsed": false, "input": [ "# same thing\n", "row_mask = array([1,0,1,0,0], dtype=bool)\n", "B[row_mask]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 68, "text": [ "array([0, 2])" ] } ], "prompt_number": 68 }, { "cell_type": "markdown", "metadata": {}, "source": [ "This feature is very useful to conditionally select elements from an array, using for example comparison operators:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "x = arange(0, 10, 0.5)\n", "x" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 69, "text": [ "array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ,\n", " 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])" ] } ], "prompt_number": 69 }, { "cell_type": "code", "collapsed": false, "input": [ "mask = (5 < x) * (x < 7.5)\n", "\n", "mask" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 70, "text": [ "array([False, False, False, False, False, False, False, False, False,\n", " False, False, True, True, True, True, False, False, False,\n", " False, False], dtype=bool)" ] } ], "prompt_number": 70 }, { "cell_type": "code", "collapsed": false, "input": [ "x[mask]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 71, "text": [ "array([ 5.5, 6. , 6.5, 7. ])" ] } ], "prompt_number": 71 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions for extracting data from arrays and creating arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### where\n", "\n", "The index mask can be converted to position index using the `where` function" ] }, { "cell_type": "code", "collapsed": false, "input": [ "indices = where(mask)\n", "\n", "indices" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 72, "text": [ "(array([11, 12, 13, 14]),)" ] } ], "prompt_number": 72 }, { "cell_type": "code", "collapsed": false, "input": [ "x[indices] # this indexing is equivalent to the fancy indexing x[mask]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 73, "text": [ "array([ 5.5, 6. , 6.5, 7. ])" ] } ], "prompt_number": 73 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### diag\n", "\n", "With the diag function we can also extract the diagonal and subdiagonals of an array:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "diag(A)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 74, "text": [ "array([ 0, 11, 22, 33, 44])" ] } ], "prompt_number": 74 }, { "cell_type": "code", "collapsed": false, "input": [ "diag(A, -1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 75, "text": [ "array([10, 21, 32, 43])" ] } ], "prompt_number": 75 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### take\n", "\n", "The `take` function is similar to fancy indexing described above:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "v2 = arange(-3,3)\n", "v2" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 76, "text": [ "array([-3, -2, -1, 0, 1, 2])" ] } ], "prompt_number": 76 }, { "cell_type": "code", "collapsed": false, "input": [ "row_indices = [1, 3, 5]\n", "v2[row_indices] # fancy indexing" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 77, "text": [ "array([-2, 0, 2])" ] } ], "prompt_number": 77 }, { "cell_type": "code", "collapsed": false, "input": [ "v2.take(row_indices)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 78, "text": [ "array([-2, 0, 2])" ] } ], "prompt_number": 78 }, { "cell_type": "markdown", "metadata": {}, "source": [ "But `take` also works on lists and other objects:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "take([-3, -2, -1, 0, 1, 2], row_indices)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 79, "text": [ "array([-2, 0, 2])" ] } ], "prompt_number": 79 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### choose\n", "\n", "Constructs and array by picking elements form several arrays:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "which = [1, 0, 1, 0]\n", "choices = [[-2,-2,-2,-2], [5,5,5,5]]\n", "\n", "choose(which, choices)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 80, "text": [ "array([ 5, -2, 5, -2])" ] } ], "prompt_number": 80 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linear algebra\n", "\n", "Vectorizing code is the key to writing efficient numerical calculation with Python/Numpy. That means that as much as possible of a program should be formulated in terms of matrix and vector operations, like matrix-matrix multiplication.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scalar-array operations\n", "\n", "We can use the usual arithmetic operators to multiply, add, subtract, and divide arrays with scalar numbers." ] }, { "cell_type": "code", "collapsed": false, "input": [ "v1 = arange(0, 5)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 81 }, { "cell_type": "code", "collapsed": false, "input": [ "v1 * 2" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 82, "text": [ "array([0, 2, 4, 6, 8])" ] } ], "prompt_number": 82 }, { "cell_type": "code", "collapsed": false, "input": [ "v1 + 2" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 83, "text": [ "array([2, 3, 4, 5, 6])" ] } ], "prompt_number": 83 }, { "cell_type": "code", "collapsed": false, "input": [ "A * 2, A + 2" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 84, "text": [ "(array([[ 0, 2, 4, 6, 8],\n", " [20, 22, 24, 26, 28],\n", " [40, 42, 44, 46, 48],\n", " [60, 62, 64, 66, 68],\n", " [80, 82, 84, 86, 88]]),\n", " array([[ 2, 3, 4, 5, 6],\n", " [12, 13, 14, 15, 16],\n", " [22, 23, 24, 25, 26],\n", " [32, 33, 34, 35, 36],\n", " [42, 43, 44, 45, 46]]))" ] } ], "prompt_number": 84 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Element-wise array-array operations\n", "\n", "When we add, subtract, multiply and divide arrays with each other, the default behaviour is **element-wise** operations:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A * A # element-wise multiplication" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 85, "text": [ "array([[ 0, 1, 4, 9, 16],\n", " [ 100, 121, 144, 169, 196],\n", " [ 400, 441, 484, 529, 576],\n", " [ 900, 961, 1024, 1089, 1156],\n", " [1600, 1681, 1764, 1849, 1936]])" ] } ], "prompt_number": 85 }, { "cell_type": "code", "collapsed": false, "input": [ "v1 * v1" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 86, "text": [ "array([ 0, 1, 4, 9, 16])" ] } ], "prompt_number": 86 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we multiply arrays with compatible shapes, we get an element-wise multiplication of each row:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "A.shape, v1.shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 87, "text": [ "((5, 5), (5,))" ] } ], "prompt_number": 87 }, { "cell_type": "code", "collapsed": false, "input": [ "A * v1" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 88, "text": [ "array([[ 0, 1, 4, 9, 16],\n", " [ 0, 11, 24, 39, 56],\n", " [ 0, 21, 44, 69, 96],\n", " [ 0, 31, 64, 99, 136],\n", " [ 0, 41, 84, 129, 176]])" ] } ], "prompt_number": 88 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matrix algebra\n", "\n", "What about matrix mutiplication? There are two ways. We can either use the `dot` functions, which applies a matrix-matrix, matrix-vector, or inner vector multiplication to its two arguments: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "dot(A, A)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 89, "text": [ "array([[ 300, 310, 320, 330, 340],\n", " [1300, 1360, 1420, 1480, 1540],\n", " [2300, 2410, 2520, 2630, 2740],\n", " [3300, 3460, 3620, 3780, 3940],\n", " [4300, 4510, 4720, 4930, 5140]])" ] } ], "prompt_number": 89 }, { "cell_type": "code", "collapsed": false, "input": [ "dot(A, v1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 90, "text": [ "array([ 30, 130, 230, 330, 430])" ] } ], "prompt_number": 90 }, { "cell_type": "code", "collapsed": false, "input": [ "dot(v1, v1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 91, "text": [ "30" ] } ], "prompt_number": 91 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, we can cast the array objects to the type `matrix`. This changes the behavior of the standard arithmetic operators `+, -, *` to use matrix algebra." ] }, { "cell_type": "code", "collapsed": false, "input": [ "M = matrix(A)\n", "v = matrix(v1).T # make it a column vectorm" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 92 }, { "cell_type": "code", "collapsed": false, "input": [ "v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 93, "text": [ "matrix([[0],\n", " [1],\n", " [2],\n", " [3],\n", " [4]])" ] } ], "prompt_number": 93 }, { "cell_type": "code", "collapsed": false, "input": [ "M*M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 94, "text": [ "matrix([[ 300, 310, 320, 330, 340],\n", " [1300, 1360, 1420, 1480, 1540],\n", " [2300, 2410, 2520, 2630, 2740],\n", " [3300, 3460, 3620, 3780, 3940],\n", " [4300, 4510, 4720, 4930, 5140]])" ] } ], "prompt_number": 94 }, { "cell_type": "code", "collapsed": false, "input": [ "M*v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 95, "text": [ "matrix([[ 30],\n", " [130],\n", " [230],\n", " [330],\n", " [430]])" ] } ], "prompt_number": 95 }, { "cell_type": "code", "collapsed": false, "input": [ "# inner product\n", "v.T * v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 96, "text": [ "matrix([[30]])" ] } ], "prompt_number": 96 }, { "cell_type": "code", "collapsed": false, "input": [ "# with matrix objects, standard matrix algebra applies\n", "v + M*v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 97, "text": [ "matrix([[ 30],\n", " [131],\n", " [232],\n", " [333],\n", " [434]])" ] } ], "prompt_number": 97 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we try to add, subtract or multiply objects with incomplatible shapes we get an error:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "v = matrix([1,2,3,4,5,6]).T" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 98 }, { "cell_type": "code", "collapsed": false, "input": [ "shape(M), shape(v)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 99, "text": [ "((5, 5), (6, 1))" ] } ], "prompt_number": 99 }, { "cell_type": "code", "collapsed": false, "input": [ "M * v" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "objects are not aligned", "output_type": "pyerr", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mM\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mv\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m/usr/lib/python2.7/dist-packages/numpy/matrixlib/defmatrix.pyc\u001b[0m in \u001b[0;36m__mul__\u001b[1;34m(self, other)\u001b[0m\n\u001b[0;32m 328\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mother\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mN\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlist\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 329\u001b[0m \u001b[1;31m# This promotes 1-D vectors to row vectors\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 330\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mN\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0masmatrix\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mother\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 331\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0misscalar\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mother\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mor\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mother\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'__rmul__'\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 332\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mN\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mother\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: objects are not aligned" ] } ], "prompt_number": 100 }, { "cell_type": "markdown", "metadata": {}, "source": [ "See also the related functions: `inner`, `outer`, `cross`, `kron`, `tensordot`. Try for example `help(kron)`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Array/Matrix transformations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above we have used the `.T` to transpose the matrix object `v`. We could also have used the `transpose` function to accomplish the same thing. \n", "\n", "Other mathematical functions that transforms matrix objects are:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "C = matrix([[1j, 2j], [3j, 4j]])\n", "C" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 101, "text": [ "matrix([[ 0.+1.j, 0.+2.j],\n", " [ 0.+3.j, 0.+4.j]])" ] } ], "prompt_number": 101 }, { "cell_type": "code", "collapsed": false, "input": [ "conjugate(C)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 102, "text": [ "matrix([[ 0.-1.j, 0.-2.j],\n", " [ 0.-3.j, 0.-4.j]])" ] } ], "prompt_number": 102 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hermitian conjugate: transpose + conjugate" ] }, { "cell_type": "code", "collapsed": false, "input": [ "C.H" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 103, "text": [ "matrix([[ 0.-1.j, 0.-3.j],\n", " [ 0.-2.j, 0.-4.j]])" ] } ], "prompt_number": 103 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can extract the real and imaginary parts of complex-valued arrays using `real` and `imag`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "real(C) # same as: C.real" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 104, "text": [ "matrix([[ 0., 0.],\n", " [ 0., 0.]])" ] } ], "prompt_number": 104 }, { "cell_type": "code", "collapsed": false, "input": [ "imag(C) # same as: C.imag" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 105, "text": [ "matrix([[ 1., 2.],\n", " [ 3., 4.]])" ] } ], "prompt_number": 105 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or the complex argument and absolute value" ] }, { "cell_type": "code", "collapsed": false, "input": [ "angle(C+1) # heads up MATLAB Users, angle is used instead of arg" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 106, "text": [ "array([[ 0.78539816, 1.10714872],\n", " [ 1.24904577, 1.32581766]])" ] } ], "prompt_number": 106 }, { "cell_type": "code", "collapsed": false, "input": [ "abs(C)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 107, "text": [ "matrix([[ 1., 2.],\n", " [ 3., 4.]])" ] } ], "prompt_number": 107 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matrix computations\n", "\n", "#### Inverse" ] }, { "cell_type": "code", "collapsed": false, "input": [ "inv(C) # equivalent to C.I " ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 108, "text": [ "matrix([[ 0.+2.j , 0.-1.j ],\n", " [ 0.-1.5j, 0.+0.5j]])" ] } ], "prompt_number": 108 }, { "cell_type": "code", "collapsed": false, "input": [ "C.I * C" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 109, "text": [ "matrix([[ 1.00000000e+00+0.j, 4.44089210e-16+0.j],\n", " [ 0.00000000e+00+0.j, 1.00000000e+00+0.j]])" ] } ], "prompt_number": 109 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Determinant" ] }, { "cell_type": "code", "collapsed": false, "input": [ "det(C)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 110, "text": [ "(2.0000000000000004+0j)" ] } ], "prompt_number": 110 }, { "cell_type": "code", "collapsed": false, "input": [ "det(C.I)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 111, "text": [ "(0.50000000000000011+0j)" ] } ], "prompt_number": 111 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data computations\n", "\n", "Often it is useful to store datasets in Numpy arrays. Numpy provides a number of functions to calculate statistics of datasets in arrays. \n", "\n", "For example, let's calculate some properties data from the Stockholm temperature dataset used above." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# reminder, the tempeature dataset is stored in the data variable:\n", "shape(data)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 112, "text": [ "(77431, 7)" ] } ], "prompt_number": 112 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### mean" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# the temperature data is in column 3\n", "mean(data[:,3])" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 113, "text": [ "6.1971096847515925" ] } ], "prompt_number": 113 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The daily mean temperature in Stockholm over the last 200 year so has been about 6.2 C." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### standard deviations and variance" ] }, { "cell_type": "code", "collapsed": false, "input": [ "std(data[:,3]), var(data[:,3])" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 114, "text": [ "(8.2822716213405663, 68.596023209663286)" ] } ], "prompt_number": 114 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### min and max" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# lowest daily average temperature\n", "data[:,3].min()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 115, "text": [ "-25.800000000000001" ] } ], "prompt_number": 115 }, { "cell_type": "code", "collapsed": false, "input": [ "# highest daily average temperature\n", "data[:,3].max()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 116, "text": [ "28.300000000000001" ] } ], "prompt_number": 116 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### sum, prod, and trace" ] }, { "cell_type": "code", "collapsed": false, "input": [ "d = arange(0, 10)\n", "d" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 117, "text": [ "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" ] } ], "prompt_number": 117 }, { "cell_type": "code", "collapsed": false, "input": [ "# sum up all elements\n", "sum(d)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 118, "text": [ "45" ] } ], "prompt_number": 118 }, { "cell_type": "code", "collapsed": false, "input": [ "# product of all elements\n", "prod(d+1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 119, "text": [ "3628800" ] } ], "prompt_number": 119 }, { "cell_type": "code", "collapsed": false, "input": [ "# cummulative sum\n", "cumsum(d)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 120, "text": [ "array([ 0, 1, 3, 6, 10, 15, 21, 28, 36, 45])" ] } ], "prompt_number": 120 }, { "cell_type": "code", "collapsed": false, "input": [ "# commulative product\n", "cumprod(d+1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 121, "text": [ "array([ 1, 2, 6, 24, 120, 720, 5040,\n", " 40320, 362880, 3628800])" ] } ], "prompt_number": 121 }, { "cell_type": "code", "collapsed": false, "input": [ "# same as: diag(A).sum()\n", "trace(A)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 122, "text": [ "110" ] } ], "prompt_number": 122 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Computations on subsets of arrays\n", "\n", "We can compute with subsets of the data in an array using indexing, fancy indexing, and the other methods of extracting data from an array (described above).\n", "\n", "For example, let's go back to the temperature dataset:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!head -n 3 stockholm_td_adj.dat" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "1800 1 1 -6.1 -6.1 -6.1 1\r\n", "1800 1 2 -15.4 -15.4 -15.4 1\r\n", "1800 1 3 -15.0 -15.0 -15.0 1\r\n" ] } ], "prompt_number": 123 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dataformat is: year, month, day, daily average temperature, low, high, location.\n", "\n", "If we are interested in the average temperature only in a particular month, say February, then we can create a index mask and use the select out only the data for that month using:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "unique(data[:,1]) # the month column takes values from 1 to 12" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 124, "text": [ "array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.,\n", " 12.])" ] } ], "prompt_number": 124 }, { "cell_type": "code", "collapsed": false, "input": [ "mask_feb = data[:,1] == 2" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 125 }, { "cell_type": "code", "collapsed": false, "input": [ "# the temperature data is in column 3\n", "mean(data[mask_feb,3])" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 126, "text": [ "-3.2121095707366085" ] } ], "prompt_number": 126 }, { "cell_type": "markdown", "metadata": {}, "source": [ "With these tools we have very powerful data processing capabilities at our disposal. For example, to extract the average monthly average temperatures for each month of the year only takes a few lines of code: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "months = arange(1,13)\n", "monthly_mean = [mean(data[data[:,1] == month, 3]) for month in months]\n", "\n", "fig, ax = subplots()\n", "ax.bar(months, monthly_mean)\n", "ax.set_xlabel(\"Month\")\n", "ax.set_ylabel(\"Monthly avg. temp.\");" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "display_data", "png": "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\n2bLF7Fitmjdv3g0XhK5YsUK5ubk6ceKE7rzzTq1YscKkdJ41l3/cuHEqKyvT0aNHlZSUpKeeesqk\ndJ41l1/69h+ku3fv1g9+8AOv5oRcEZSUlGjgwIHq16+foqOjNWvWLG3fvt3sWF7r2bOnMjIyJEld\nunRRcnKyzp8/b3Iq7509e1a7du3SggULwu5aj8uXL+vdd9/V/PnzJUlRUVG66aabTE7lvdjYWEVH\nR6u+vl4NDQ2qr69Xnz59zI7VqtGjR6tbt25NXtuxY4fmzp0rSZo7d662bdtmRjSvNJc/NzdX7dp9\n+6Nx1KhROnv2rBnRvNJcfklasmSJnn76aa/nhFwRnDt3Tn379nV/bbfbde7cORMT+c/pdOrw4cMa\nNWqU2VG89vDDD+uZZ55xfyOEk9OnTysuLk7z5s3TLbfcooULF6q+vt7sWF7r3r27HnnkESUmJqp3\n797q2rWrxo4da3Ysn1VWVrqXexMSElRZWWlyIv+tX79eeXl5Zsfwyfbt22W32zVs2DCv/5uQ+24P\nx+WI5tTV1WnatGlavXq1unTpYnYcr/zzn/9UfHy8MjMzw+5oQJIaGhp06NAh/eIXv9ChQ4fUuXPn\nkF6W+F8nT57Un/70JzmdTp0/f151dXV67bXXzI7VJjabLWy/p5988kl16NBB99xzj9lRvFZfX6/l\ny5dr2bJl7te8+V4OuSLo06ePysvL3V+Xl5fLbrebmMh3X3/9taZOnap7771XkyZNMjuO195//33t\n2LFD/fv3V0FBgf71r39pzpw5Zsfymt1ul91u14gRIyRJ06ZN06FDh0xO5b1///vfuvXWW9WjRw9F\nRUVpypQpev/9982O5bOEhAT3xaQVFRWKj483OZHvXn75Ze3atSvsivjkyZNyOp1KT09X//79dfbs\nWQ0fPlwXL15s9b8LuSLIysrSp59+KqfTqWvXrumNN97QhAkTzI7lNZfLpfvuu08pKSl66KGHzI7j\nk+XLl6u8vFynT5/Wli1bdMcdd2jTpk1mx/Jaz5491bdvX504cUKStGfPHqWmppqcyntDhgzRgQMH\n9OWXX8rlcmnPnj1KSUkxO5bPJkyYoI0bN0qSNm7cGFb/GJK+PWvxmWee0fbt29WpUyez4/gkLS1N\nlZWVOn36tE6fPi273a5Dhw55LmNXCNq1a5crKSnJNWDAANfy5cvNjuOTd99912Wz2Vzp6emujIwM\nV0ZGhuvtt982O5bPHA6HKz8/3+wYPjty5IgrKyvLNWzYMNfkyZNdNTU1ZkfyycqVK10pKSmuoUOH\nuubMmeO6du2a2ZFaNWvWLFevXr1c0dHRLrvd7lq/fr3r888/d915552uQYMGuXJzc13V1dVmx2zR\n/+Zft26da+DAga7ExET39+/Pf/5zs2O26Hr+Dh06uP//f1f//v1dn3/+ucc5pt9iAgBgrpBbGgIA\nBBdFAAAWRxEAgMVRBABgcRQBLKtdu3aaPXu2++uGhgbFxcX5fbO9y5cv66WXXnJ/7XA4wvLGfbAe\nigCW1blzZ5WVlenq1auSpN27d8tut/t9JWx1dbVefPHFQEYEgoIigKXl5eVp586dkqTNmzeroKDA\nfUl+VVWVJk2apPT0dP3oRz9yP0WvsLBQ8+fPV05OjgYMGKCioiJJ0uOPP66TJ08qMzNTjz32mGw2\nm+rq6jR9+nQlJyfr3nvvNecPCXhAEcDSZs6cqS1btuirr75SaWlpkxsELl26VMOHD9fRo0e1fPny\nJrfbOHHihN555x2VlJRo2bJlamxs1MqVKzVgwAAdPnxYTz/9tFwulw4fPqzVq1fr2LFjOnXqlPbv\n32/GHxNoFUUAS0tLS5PT6dTmzZs1fvz4Jr+2f/9+9x5CTk6OPv/8c9XW1spms2n8+PGKjo5Wjx49\nFB8fr8rKymZv7jVy5Ej17t1bNptNGRkZcjqdwfhjAT6JMjsAYLYJEybo0Ucf1b59+3Tp0qUmv9bS\nhfcdOnRwf96+fXs1NDQ0+/s6duzo1e8DzMQRASxv/vz5KiwsvOEGdaNHj3bffdLhcCguLk4xMTEt\nlkNMTIxqa2sNzwsEGkcEsKzrZwf16dNHDz74oPu1669f3xROT09X586d3XfUbOke+z169NBtt92m\ntLQ05eXlKS8v74bfF6735kdk46ZzAGBxLA0BgMVRBABgcRQBAFgcRQAAFkcRAIDFUQQAYHH/B+Ek\nziBI9elxAAAAAElFTkSuQmCC\n" } ], "prompt_number": 127 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Calculations with higher-dimensional data\n", "\n", "When functions such as `min`, `max`, etc., is applied to a multidimensional arrays, it is sometimes useful to apply the calculation to the entire array, and sometimes only on a row or column basis. Using the `axis` argument we can specify how these functions should behave: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "m = rand(3,3)\n", "m" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 128, "text": [ "array([[ 0.69804365, 0.76194991, 0.46126281],\n", " [ 0.23147764, 0.20841975, 0.01492889],\n", " [ 0.61958415, 0.85989264, 0.85552498]])" ] } ], "prompt_number": 128 }, { "cell_type": "code", "collapsed": false, "input": [ "# global max\n", "m.max()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 129, "text": [ "0.85989264081824257" ] } ], "prompt_number": 129 }, { "cell_type": "code", "collapsed": false, "input": [ "# max in each column\n", "m.max(axis=0)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 130, "text": [ "array([ 0.69804365, 0.85989264, 0.85552498])" ] } ], "prompt_number": 130 }, { "cell_type": "code", "collapsed": false, "input": [ "# max in each row\n", "m.max(axis=1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 131, "text": [ "array([ 0.76194991, 0.23147764, 0.85989264])" ] } ], "prompt_number": 131 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Many other functions and methods in the `array` and `matrix` classes accept the same (optional) `axis` keyword argument." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reshaping, resizing and stacking arrays\n", "\n", "The shape of an Numpy array can be modified without copying the underlaying data, which makes it a fast operation even for large arrays." ] }, { "cell_type": "code", "collapsed": false, "input": [ "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 132, "text": [ "array([[ 0, 1, 2, 3, 4],\n", " [10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34],\n", " [40, 41, 42, 43, 44]])" ] } ], "prompt_number": 132 }, { "cell_type": "code", "collapsed": false, "input": [ "n, m = A.shape" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 133 }, { "cell_type": "code", "collapsed": false, "input": [ "B = A.reshape((1,n*m))\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 134, "text": [ "array([[ 0, 1, 2, 3, 4, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 30, 31,\n", " 32, 33, 34, 40, 41, 42, 43, 44]])" ] } ], "prompt_number": 134 }, { "cell_type": "code", "collapsed": false, "input": [ "B[0,0:5] = 5 # modify the array\n", "\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 135, "text": [ "array([[ 5, 5, 5, 5, 5, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 30, 31,\n", " 32, 33, 34, 40, 41, 42, 43, 44]])" ] } ], "prompt_number": 135 }, { "cell_type": "code", "collapsed": false, "input": [ "A # and the original variable is also changed. B is only a different view of the same data" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 136, "text": [ "array([[ 5, 5, 5, 5, 5],\n", " [10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34],\n", " [40, 41, 42, 43, 44]])" ] } ], "prompt_number": 136 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also use the function `flatten` to make a higher-dimensional array into a vector. But this function create a copy of the data." ] }, { "cell_type": "code", "collapsed": false, "input": [ "B = A.flatten()\n", "\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 137, "text": [ "array([ 5, 5, 5, 5, 5, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 30, 31,\n", " 32, 33, 34, 40, 41, 42, 43, 44])" ] } ], "prompt_number": 137 }, { "cell_type": "code", "collapsed": false, "input": [ "B[0:5] = 10\n", "\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 138, "text": [ "array([10, 10, 10, 10, 10, 10, 11, 12, 13, 14, 20, 21, 22, 23, 24, 30, 31,\n", " 32, 33, 34, 40, 41, 42, 43, 44])" ] } ], "prompt_number": 138 }, { "cell_type": "code", "collapsed": false, "input": [ "A # now A has not changed, because B's data is a copy of A's, not refering to the same data" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 139, "text": [ "array([[ 5, 5, 5, 5, 5],\n", " [10, 11, 12, 13, 14],\n", " [20, 21, 22, 23, 24],\n", " [30, 31, 32, 33, 34],\n", " [40, 41, 42, 43, 44]])" ] } ], "prompt_number": 139 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding a new dimension: newaxis\n", "\n", "With `newaxis`, we can insert new dimensions in an array, for example converting a vector to a column or row matrix:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "v = array([1,2,3])" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 140 }, { "cell_type": "code", "collapsed": false, "input": [ "shape(v)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 141, "text": [ "(3,)" ] } ], "prompt_number": 141 }, { "cell_type": "code", "collapsed": false, "input": [ "# make a column matrix of the vector v\n", "v[:, newaxis]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 142, "text": [ "array([[1],\n", " [2],\n", " [3]])" ] } ], "prompt_number": 142 }, { "cell_type": "code", "collapsed": false, "input": [ "# column matrix\n", "v[:,newaxis].shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 143, "text": [ "(3, 1)" ] } ], "prompt_number": 143 }, { "cell_type": "code", "collapsed": false, "input": [ "# row matrix\n", "v[newaxis,:].shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 144, "text": [ "(1, 3)" ] } ], "prompt_number": 144 }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stacking and repeating arrays\n", "\n", "Using function `repeat`, `tile`, `vstack`, `hstack`, and `concatenate` we can create larger vectors and matrices from smaller ones:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### tile and repeat" ] }, { "cell_type": "code", "collapsed": false, "input": [ "a = array([[1, 2], [3, 4]])" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 145 }, { "cell_type": "code", "collapsed": false, "input": [ "# repeat each element 3 times\n", "repeat(a, 3)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 146, "text": [ "array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])" ] } ], "prompt_number": 146 }, { "cell_type": "code", "collapsed": false, "input": [ "# tile the matrix 3 times \n", "tile(a, 3)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 147, "text": [ "array([[1, 2, 1, 2, 1, 2],\n", " [3, 4, 3, 4, 3, 4]])" ] } ], "prompt_number": 147 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### concatenate" ] }, { "cell_type": "code", "collapsed": false, "input": [ "b = array([[5, 6]])" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 148 }, { "cell_type": "code", "collapsed": false, "input": [ "concatenate((a, b), axis=0)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 149, "text": [ "array([[1, 2],\n", " [3, 4],\n", " [5, 6]])" ] } ], "prompt_number": 149 }, { "cell_type": "code", "collapsed": false, "input": [ "concatenate((a, b.T), axis=1)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 150, "text": [ "array([[1, 2, 5],\n", " [3, 4, 6]])" ] } ], "prompt_number": 150 }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### hstack and vstack" ] }, { "cell_type": "code", "collapsed": false, "input": [ "vstack((a,b))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 151, "text": [ "array([[1, 2],\n", " [3, 4],\n", " [5, 6]])" ] } ], "prompt_number": 151 }, { "cell_type": "code", "collapsed": false, "input": [ "hstack((a,b.T))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 152, "text": [ "array([[1, 2, 5],\n", " [3, 4, 6]])" ] } ], "prompt_number": 152 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Copy and \"deep copy\"\n", "\n", "To achieve high performance, assignments in Python usually do not copy the underlaying objects. This is important for example when objects are passed between functions, to avoid an excessive amount of memory copying when it is not necessary (techincal term: pass by reference). " ] }, { "cell_type": "code", "collapsed": false, "input": [ "A = array([[1, 2], [3, 4]])\n", "\n", "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 153, "text": [ "array([[1, 2],\n", " [3, 4]])" ] } ], "prompt_number": 153 }, { "cell_type": "code", "collapsed": false, "input": [ "# now B is referring to the same array data as A \n", "B = A " ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 154 }, { "cell_type": "code", "collapsed": false, "input": [ "# changing B affects A\n", "B[0,0] = 10\n", "\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 155, "text": [ "array([[10, 2],\n", " [ 3, 4]])" ] } ], "prompt_number": 155 }, { "cell_type": "code", "collapsed": false, "input": [ "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 156, "text": [ "array([[10, 2],\n", " [ 3, 4]])" ] } ], "prompt_number": 156 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to avoid this behavior, so that when we get a new completely independent object `B` copied from `A`, then we need to do a so-called \"deep copy\" using the function `copy`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "B = copy(A)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 157 }, { "cell_type": "code", "collapsed": false, "input": [ "# now, if we modify B, A is not affected\n", "B[0,0] = -5\n", "\n", "B" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 158, "text": [ "array([[-5, 2],\n", " [ 3, 4]])" ] } ], "prompt_number": 158 }, { "cell_type": "code", "collapsed": false, "input": [ "A" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 159, "text": [ "array([[10, 2],\n", " [ 3, 4]])" ] } ], "prompt_number": 159 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Iterating over array elements\n", "\n", "Generally, we want to avoid iterating over the elements of arrays whenever we can (at all costs). The reason is that in a interpreted language like Python (or MATLAB), iterations are really slow compared to vectorized operations. \n", "\n", "However, sometimes iterations are unavoidable. For such cases, the Python `for` loop is the most convenient way to iterate over an array:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "v = array([1,2,3,4])\n", "\n", "for element in v:\n", " print element" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "1\n", "2\n", "3\n", "4\n" ] } ], "prompt_number": 160 }, { "cell_type": "code", "collapsed": false, "input": [ "M = array([[1,2], [3,4]])\n", "\n", "for row in M:\n", " print \"row\", row\n", " \n", " for element in row:\n", " print element" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "row [1 2]\n", "1\n", "2\n", "row [3 4]\n", "3\n", "4\n" ] } ], "prompt_number": 161 }, { "cell_type": "markdown", "metadata": {}, "source": [ "When we need to iterate over each element of an array and modify its elements, it is convenient to use the `enumerate` function to obtain both the element and its index in the `for` loop: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "for row_idx, row in enumerate(M):\n", " print \"row_idx\", row_idx, \"row\", row\n", " \n", " for col_idx, element in enumerate(row):\n", " print \"col_idx\", col_idx, \"element\", element\n", " \n", " # update the matrix M: square each element\n", " M[row_idx, col_idx] = element ** 2" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "row_idx 0 row [1 2]\n", "col_idx 0 element 1\n", "col_idx 1 element 2\n", "row_idx 1 row [3 4]\n", "col_idx 0 element 3\n", "col_idx 1 element 4\n" ] } ], "prompt_number": 162 }, { "cell_type": "code", "collapsed": false, "input": [ "# each element in M are now squared\n", "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 163, "text": [ "array([[ 1, 4],\n", " [ 9, 16]])" ] } ], "prompt_number": 163 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Vectorizing functions\n", "\n", "As mentioned several times by now, to get good performance we should try to avoid looping over elements in our vectors and matrices, and instead use vectorized algorithms. The first step in converting a scalar algorithm to a vectorized algorithm is to make sure that the functions we write work with vector inputs." ] }, { "cell_type": "code", "collapsed": false, "input": [ "def Theta(x):\n", " \"\"\"\n", " Scalar implemenation of the Heaviside step function.\n", " \"\"\"\n", " if x >= 0:\n", " return 1\n", " else:\n", " return 0" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 164 }, { "cell_type": "code", "collapsed": false, "input": [ "Theta(array([-3,-2,-1,0,1,2,3]))" ], "language": "python", "metadata": {}, "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": "pyerr", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mTheta\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marray\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36mTheta\u001b[1;34m(x)\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mScalar\u001b[0m \u001b[0mimplemenation\u001b[0m \u001b[0mof\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mHeaviside\u001b[0m \u001b[0mstep\u001b[0m \u001b[0mfunction\u001b[0m\u001b[1;33m.\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \"\"\"\n\u001b[1;32m----> 5\u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0mx\u001b[0m \u001b[1;33m>=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 7\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()" ] } ], "prompt_number": 165 }, { "cell_type": "markdown", "metadata": {}, "source": [ "OK, that didn't work because we didn't write the `Theta` function so that it can handle with vector input... \n", "\n", "To get a vectorized version of Theta we can use the Numpy function `vectorize`. In many cases it can automatically vectorize a function:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "Theta_vec = vectorize(Theta)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 166 }, { "cell_type": "code", "collapsed": false, "input": [ "Theta_vec(array([-3,-2,-1,0,1,2,3]))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 167, "text": [ "array([0, 0, 0, 1, 1, 1, 1])" ] } ], "prompt_number": 167 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also implement the function to accept vector input from the beginning (requires more effort but might give better performance):" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def Theta(x):\n", " \"\"\"\n", " Vector-aware implemenation of the Heaviside step function.\n", " \"\"\"\n", " return 1 * (x >= 0)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 168 }, { "cell_type": "code", "collapsed": false, "input": [ "Theta(array([-3,-2,-1,0,1,2,3]))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 169, "text": [ "array([0, 0, 0, 1, 1, 1, 1])" ] } ], "prompt_number": 169 }, { "cell_type": "code", "collapsed": false, "input": [ "# still works for scalars as well\n", "Theta(-1.2), Theta(2.6)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 170, "text": [ "(0, 1)" ] } ], "prompt_number": 170 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using arrays in conditions\n", "\n", "When using arrays in conditions in for example `if` statements and other boolean expressions, one need to use one of `any` or `all`, which requires that any or all elements in the array evalutes to `True`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 171, "text": [ "array([[ 1, 4],\n", " [ 9, 16]])" ] } ], "prompt_number": 171 }, { "cell_type": "code", "collapsed": false, "input": [ "if (M > 5).any():\n", " print \"at least one element in M is larger than 5\"\n", "else:\n", " print \"no element in M is larger than 5\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "at least one element in M is larger than 5\n" ] } ], "prompt_number": 172 }, { "cell_type": "code", "collapsed": false, "input": [ "if (M > 5).all():\n", " print \"all elements in M are larger than 5\"\n", "else:\n", " print \"all elements in M are not larger than 5\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "all elements in M are not larger than 5\n" ] } ], "prompt_number": 173 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Type casting\n", "\n", "Since Numpy arrays are *statically typed*, the type of an array does not change once created. But we can explicitly cast an array of some type to another using the `astype` functions (see also the similar `asarray` function). This always create a new array of new type:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "M.dtype" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 174, "text": [ "dtype('int64')" ] } ], "prompt_number": 174 }, { "cell_type": "code", "collapsed": false, "input": [ "M2 = M.astype(float)\n", "\n", "M2" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 175, "text": [ "array([[ 1., 4.],\n", " [ 9., 16.]])" ] } ], "prompt_number": 175 }, { "cell_type": "code", "collapsed": false, "input": [ "M2.dtype" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 176, "text": [ "dtype('float64')" ] } ], "prompt_number": 176 }, { "cell_type": "code", "collapsed": false, "input": [ "M3 = M.astype(bool)\n", "\n", "M3" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 177, "text": [ "array([[ True, True],\n", " [ True, True]], dtype=bool)" ] } ], "prompt_number": 177 }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further reading\n", "\n", "* http://numpy.scipy.org\n", "* http://scipy.org/Tentative_NumPy_Tutorial\n", "* http://scipy.org/NumPy_for_Matlab_Users - A Numpy guide for MATLAB users." ] } ], "metadata": {} } ] }