MTH 337: Week 4

List Comprehensions: creating new lists from old

The syntax is [expression for item in sequence] where:

  • "sequence" is a Python sequence object, such as a list or string
  • "item" is the iteration variable that gets bound to each object in "sequence"
  • "expression" is a Python expression that is used to generate the items in the new list
In [1]:
# Example: creating a list of perfect squares
# The expression i**2 is applied to every element i to generate the new list
squares = [i**2 for i in range(1, 11)]
print squares
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

The sequence can also be filtered to only include certain elements

The syntax is [expression for item in sequence if condition] where:

  • "condition" is a Boolean expression. Items are only included in the new lists if "expression" is True.
In [2]:
# The condition is i % 2 == 0 
# This only includes the even integers
[i**2 for i in range(1, 11) if i % 2 == 0]
Out[2]:
[4, 16, 36, 64, 100]

NumPy

In [3]:
# Import all the array functions
from numpy import *
In [4]:
# A one-dimensional array can be created directly from a list using the "array" function
a = array([1, 2, 3])
In [5]:
# An array is displayed as follows
a
Out[5]:
array([1, 2, 3])
In [6]:
# A one-dimensional array prints like a list, but without the commas
print a
[1 2 3]

Array Operations

In [7]:
#  Adding a scalar to an array adds the scalar to every element
print a + 3
[4 5 6]
In [8]:
# Assignment operators can be applied to arrays, just as with scalars
a *= 3
print a
[3 6 9]
In [9]:
# All basic mathematical operations can be applied directly to arrays.
# The resulting array is generated by applying the operation to each element individually
print a**2
[ 9 36 81]
In [10]:
# Complicated mathematical expressions can be built up from simpler ones
print a**2 + 3*a + 1
[ 19  55 109]
In [11]:
# Importing numpy imports array versions of all the basic trigonometric and transcendental functions.
print exp(a)
[   20.08553692   403.42879349  8103.08392758]
In [12]:
# Operations involving two arrays are performed on corresponding elements in each array
# This is similar to vector addition and subtraction, but extends to all mathematical operations
a = array([1, 2, 3])
b = array([4, 5, 6])
c = a + b
print c
[5 7 9]

Array Creation

In [13]:
# ones(n) creates an array of n 1's
ones(5)
Out[13]:
array([ 1.,  1.,  1.,  1.,  1.])
In [14]:
# zeros(n) creates an array of n 0's
zeros(4)
Out[14]:
array([ 0.,  0.,  0.,  0.])
In [15]:
# empty(n) creates an uninitialized array of length n
empty(6)
Out[15]:
array([  8.41494980e-317,   0.00000000e+000,   0.00000000e+000,
         0.00000000e+000,   0.00000000e+000,   0.00000000e+000])
In [16]:
# <array>.copy() creates a copy of <array>
a.copy()
Out[16]:
array([1, 2, 3])
In [17]:
# arange(start, stop, step) is an array version of range
# However, arange can also take floats as arguments, whereas range is restricted to integers
arange(2.2, 6, .5)
Out[17]:
array([ 2.2,  2.7,  3.2,  3.7,  4.2,  4.7,  5.2,  5.7])
In [18]:
# linspace(start, stop, n) creates an array of n equally-spaced points in the interval [start, stop]
# Both endpoints are included by default
linspace(0, 1, 11)
Out[18]:
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9,  1. ])

Plotting using arrays

In [19]:
%pylab inline
Populating the interactive namespace from numpy and matplotlib
In [20]:
# Functions can be quickly plotted using linspace
# "plot" takes arrays as arguments as well as lists
x = linspace(0, 20, 201)
y = sin(x)
plot(x, y)
Out[20]:
[<matplotlib.lines.Line2D at 0x7f80ff72d390>]
In [21]:
# Choosing too few points to plot produces a jagged graph
x = linspace(0, 20, 21)
y = sin(x)
plot(x, y)
Out[21]:
[<matplotlib.lines.Line2D at 0x7f80ff6830d0>]

IPython magics for timing code execution

  • %%timeit times the execution of all the code in a code cell
  • %timeit times the execution of a single line of code

Note: %%timeit must be the first line in the cell

Mayfly model: Plotting the asymptotic solutions for different b-values using a loop

In [22]:
%%timeit
tmax = 500
tmin = tmax/2
b_values = linspace(0, 4, 41)
for b in b_values:
    x = 0.5
    for i in xrange(tmax):
        if i > tmin:
            plot(b, x, 'r.')
        x = b*(1 - x)*x
1 loops, best of 3: 24 s per loop

Vectorization involves replacing Python loops with operations on vectors

The same code as above, but updating a vector of x-values in parallel instead of using a "for" loop. The vectorized code runs nearly 50 times faster.

In [23]:
%%timeit
tmax = 500
tmin = tmax/2
b = linspace(0, 4, 41)
x = 0.5
for i in xrange(tmax):
    if i > tmin:
        plot(b, x, 'r.')
    x = b*(1 - x)*x
1 loops, best of 3: 586 ms per loop

Multidimensional Arrays in NumPy

Creating multidimensional arrays

In [24]:
# Creating a two-dimensional array from a list of lists (of equal length)
# Note that arrays are printed with spaces between items and no commas
a = array([[1,2,3], [4,5,6]])
print a
[[1 2 3]
 [4 5 6]]
In [25]:
# Arrays have a "shape", which is a tuple of the number of elements in each axis (dimension)
a.shape
Out[25]:
(2, 3)
In [26]:
# The "dtype" of an array is the data type of the elements stored
# Since the original list contained all integers, the dtype is 64-bit integer ('int64')
a.dtype
Out[26]:
dtype('int64')
In [27]:
# Arrays can be created by passing a shape tuple into zeros, ones, or empty
# The default type when creating arrays is a float
b = ones((3,4))
print b
[[ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]]
In [28]:
# To change the data type, use the "dtype" keyword
b = ones((3,4), dtype=int)
print b
[[1 1 1 1]
 [1 1 1 1]
 [1 1 1 1]]
In [29]:
# We can also create arrays of booleans
# False corresponds to zero and True to one
b = ones((3,4), dtype=bool)
print b
[[ True  True  True  True]
 [ True  True  True  True]
 [ True  True  True  True]]
In [30]:
# "arange" creates a one-dimensional array
a = arange(20)
print a
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
In [31]:
# Arrays can be "reshaped" to any shape with the same number of elements
a = arange(20).reshape((4, 5))
print a
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]

Indexing and slicing arrays

In [32]:
# Arrays use zero-based indexing, like lists
# A single element can be accessed by specifying its position on each axis 
# For a two-dimensional array, these can be thought of as a (row, column) pair
# So, to access the element in row 1 and column 2
a[1,2]
Out[32]:
7
In [33]:
# Array elements can be assigned using the "=" operator
a[1,2] = 100
print a
[[  0   1   2   3   4]
 [  5   6 100   8   9]
 [ 10  11  12  13  14]
 [ 15  16  17  18  19]]
In [34]:
# Setting this element back to 7 again
a[1,2] = 7
print a
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
In [35]:
# To slice the first row, just use the row index
print a[0]
[0 1 2 3 4]
In [36]:
# To slice every element from the first column, we need to select from every row, column index 0
# The ":" means select everything along this axis
print a[:,0]
[ 0  5 10 15]
In [37]:
# A start:stop:step filter can be applied to each axis independently
# So, to select rows 1-2 and columns 1-3
print a[1:3, 1:4]
[[ 6  7  8]
 [11 12 13]]
In [38]:
# To select each row and every second column
print a[:, ::2]
[[ 0  2  4]
 [ 5  7  9]
 [10 12 14]
 [15 17 19]]

Floating-point numbers

In [39]:
# Using rational numbers, arithmetic on non-integers can be done exactly
# However, there can be a price to pay in terms of memory requirements
from fractions import Fraction
In [40]:
# Running the mayfly model using rational numbers
# At time t, the denominator is 2^(2^(t + 1) - 1). So, the number of bits needed is 2^(t + 1) - 1 
# => size of the denominator increases exponentially with time, making exact arithmetic infeasible
b = Fraction(7, 2)
x = Fraction(1, 2)

for t in range(8):
    print t, x
    x = b*(1-x)*x
0 1/2
1 7/8
2 49/128
3 27097/32768
4 1075669609/2147483648
5 8070424517762885257/9223372036854775808
6 65133431480411600201566777376308546249/170141183460469231731687303715884105728
7 47876606529535974971194811220382839549643442427458191363279549003633252909897/57896044618658097711785492504343953926634992332820282019728792003956564819968