MTH 337: Week 4

Quiz 2 feedback

Question 6 - note the order of tests is important.

In [1]:
def fizzbang(n):
    if n % 3 == 0:
        print("fizz")
    elif n % 5 == 0:
        print("bang")
    elif n % 3 == 0 and n % 5 == 0:
        print("fizz bang")
In [2]:
fizzbang(15)
fizz

Question 2 - note that (5/9) is a float, so the result is also a float.

In [3]:
temp = 41
print((temp - 32)*(5/9))
5.0

List comprehensions

These have the form: [expression for item in sequence]

In [4]:
[i**2 for i in range(1, 11)]
Out[4]:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

A condition can also be added to select which items become part of the list. The syntax is: [expression for item in sequence if condition]

In [5]:
[i**2 for i in range(1, 11) if i % 2 == 0]
Out[5]:
[4, 16, 36, 64, 100]

Loops can also be nested inside list comprehensions.

In [6]:
[(x, y) for x in range(3) for y in range(2)]
Out[6]:
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

A more complicated example to generate primitive Pythagorean triples.

In [7]:
import math
ppts = [(a, b) for a in range(1, 50) for b in range(1, 50) if math.gcd(a, b) == 1 and math.sqrt(a**2 + b**2).is_integer()]
In [8]:
[item[0] for item in ppts]
Out[8]:
[3, 4, 5, 7, 8, 9, 12, 12, 15, 20, 21, 24, 28, 35, 40, 45]

List indexing

In [9]:
mylist = list("abcdefg")
mylist
Out[9]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']

Items in a list can be selected (indexed) using square brackets and an integer for the index.

Note that the first item has index 0.

In [10]:
mylist[1]
Out[10]:
'b'
In [11]:
mylist[0]
Out[11]:
'a'

It is an error to index past the end of a list.

In [12]:
mylist[7]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-12-bfa789660dbc> in <module>()
----> 1 mylist[7]

IndexError: list index out of range

Lists can also be indexed backwards from the end, using a negative index.

In [13]:
mylist[-1]
Out[13]:
'g'
In [14]:
mylist[-2]
Out[14]:
'f'

Modifying the elements of a list

The "=" assignment operator can be used to change the value of an element at a given index.

In [15]:
mylist[0] = 'z'
In [16]:
mylist
Out[16]:
['z', 'b', 'c', 'd', 'e', 'f', 'g']
In [17]:
mylist = list("abcdefg")
mylist
Out[17]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']
In [18]:
mylist[0] = 'z'
mylist
Out[18]:
['z', 'b', 'c', 'd', 'e', 'f', 'g']
In [19]:
mylist[0] = 'a'
mylist
Out[19]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']

List slicing

$<$list$>$[start:stop] selects the elements from start up to (but not including) stop.

In [20]:
mylist[1:4]
Out[20]:
['b', 'c', 'd']

Slicing from the start up to (not inclusing) some element.

In [21]:
mylist[:3]
Out[21]:
['a', 'b', 'c']

Slicing from some element up to the end of the list.

In [22]:
mylist[3:]
Out[22]:
['d', 'e', 'f', 'g']

Slicing can also be given a step size.

In [23]:
mylist[0:7:2]
Out[23]:
['a', 'c', 'e', 'g']

Slicing every second element.

In [24]:
mylist[::2]
Out[24]:
['a', 'c', 'e', 'g']
In [25]:
mylist[1::2]
Out[25]:
['b', 'd', 'f']

Slicing creates a copy of a list, so modifying the slice doesn't modify the original list.

In [26]:
newlist = mylist[::2]
newlist
Out[26]:
['a', 'c', 'e', 'g']
In [27]:
newlist[0] = 'z'
newlist
Out[27]:
['z', 'c', 'e', 'g']
In [28]:
mylist
Out[28]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']

NumPy

The usual alias to use for NumPy is 'np'.

In [29]:
import numpy as np

Create a NumPy array from a list.

In [30]:
a = np.array([1, 2, 3])
print(a)
[1 2 3]
In [31]:
a
Out[31]:
array([1, 2, 3])

NumPy Array Operations

Adding a scalar to an array adds it to every element of the array.

In [32]:
a + 3
Out[32]:
array([4, 5, 6])

Multiplying an array by a scalar multiplies every element by that scalar.

In [33]:
a *= 3
print(a)
[3 6 9]

Raising an array to a power will raise every element to that power.

In [34]:
a**2
Out[34]:
array([ 9, 36, 81])
In [35]:
a = np.array([1, 2, 3])
a
Out[35]:
array([1, 2, 3])

Imported NumPy functions will operate on every element of an array

In [36]:
np.exp(a)
Out[36]:
array([  2.71828183,   7.3890561 ,  20.08553692])

Functions called on multiple arrays be will be called on the corresponding elements of each array.

In [37]:
b = np.array([4, 5, 6])
c = a + b
print(c)
[5 7 9]

The "magic" %%timeit times how long the code takes to run in a cell.

In [38]:
n = 10000000

Summing the integers up to ten million the slow way with a Python loop.

In [39]:
%%timeit
total = 0
for i in range(n):
    total += i
print(total)
49999995000000
49999995000000
49999995000000
49999995000000
1 loop, best of 3: 1.83 s per loop

Doing it the fast way with NumPy functions - nearly 20 times faster.

In [40]:
%%timeit
print(np.sum(np.arange(n)))
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
49999995000000
10 loops, best of 3: 114 ms per loop

Array creation in NumPy

ones generates an array of all 1's.

In [41]:
np.ones(10)
Out[41]:
array([ 1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.])

linspace(start, end) generates a uniform array of points in the interval [start, end] (including the endpoints).

In [42]:
x = np.linspace(0, 1)
x
Out[42]:
array([ 0.        ,  0.02040816,  0.04081633,  0.06122449,  0.08163265,
        0.10204082,  0.12244898,  0.14285714,  0.16326531,  0.18367347,
        0.20408163,  0.2244898 ,  0.24489796,  0.26530612,  0.28571429,
        0.30612245,  0.32653061,  0.34693878,  0.36734694,  0.3877551 ,
        0.40816327,  0.42857143,  0.44897959,  0.46938776,  0.48979592,
        0.51020408,  0.53061224,  0.55102041,  0.57142857,  0.59183673,
        0.6122449 ,  0.63265306,  0.65306122,  0.67346939,  0.69387755,
        0.71428571,  0.73469388,  0.75510204,  0.7755102 ,  0.79591837,
        0.81632653,  0.83673469,  0.85714286,  0.87755102,  0.89795918,
        0.91836735,  0.93877551,  0.95918367,  0.97959184,  1.        ])

linspace(start, end, n) generates a uniform array of n points.

In [43]:
x = np.linspace(0, 1, 11)
x
Out[43]:
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9,  1. ])

linspace makes plotting functions very easy.

In [44]:
%matplotlib inline
import matplotlib.pyplot as plt
In [45]:
x = np.linspace(0, 20, 201)
y = np.sin(x)
plt.plot(x, y)
Out[45]:
[<matplotlib.lines.Line2D at 0x7f573f2ca160>]

legend lets us label multiple plots on the same graph.

In [46]:
plt.figure(figsize=(10,4))
plt.plot(x, np.sin(x), label='Sin x')
plt.plot(x, np.cos(x), label='Cos x')
plt.legend()
Out[46]:
<matplotlib.legend.Legend at 0x7f573f1f5400>

xlim(xmin, xmax) and ylim(ymin, ymax) provide manual control over the range of values displayed.

In [47]:
plt.figure(figsize=(10,4))
plt.plot(x, np.sin(x), label='Sin x')
plt.plot(x, np.cos(x), label='Cos x')
plt.xlim(0, 20)
plt.legend()
Out[47]:
<matplotlib.legend.Legend at 0x7f573f1cdf98>

Markdown and LaTeX Review

Fractions

$\frac{13}{22}$

$\frac{13}{22}$

Bullet lists

- Item 1
- Item 2

  • Item 1
  • Item 2

Summation

$\displaystyle\sum_1^{100}$

$\displaystyle\sum_1^{100}$

$\sum_1^{100}$ <\code>

$\sum_1^{100}$

Matrices with square brackets

$\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}$

$\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}$

Matrices with curved brackets

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}$

$\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}$

Problems with using floating-point numbers

Consider the function $g(x) = \frac{\log(1 + x)}{x}$

In [48]:
end = 1
x = np.linspace(-end, end)
y = np.where(x==0, 1.0, np.log(1+x)/x)
plt.plot(x, y)
/home/adam/anaconda3/lib/python3.5/site-packages/ipykernel/__main__.py:3: RuntimeWarning: divide by zero encountered in log
  app.launch_new_instance()
Out[48]:
[<matplotlib.lines.Line2D at 0x7f573f145eb8>]
In [49]:
end = 1e-4
x = np.linspace(-end, end)
y = np.where(x==0, 1.0, np.log(1+x)/x)
plt.plot(x, y)
Out[49]:
[<matplotlib.lines.Line2D at 0x7f573f0676d8>]

As x gets close to zero, g(x) shows unexpected oscillations.

In [50]:
end = 1e-7
x = np.linspace(-end, end)
y = np.where(x==0, 1.0, np.log(1+x)/x)
plt.plot(x, y)
Out[50]:
[<matplotlib.lines.Line2D at 0x7f573f04cf60>]
In [51]:
end = 1e-7
x = np.linspace(-end, end, 1001)
y = np.where(x==0, 1.0, np.log(1+x)/x)
plt.plot(x, y)
/home/adam/anaconda3/lib/python3.5/site-packages/ipykernel/__main__.py:3: RuntimeWarning: invalid value encountered in true_divide
  app.launch_new_instance()
Out[51]:
[<matplotlib.lines.Line2D at 0x7f573efbc978>]

For x close to machine epsilon, the oscillations can be seen clearly.

In [52]:
plt.figure(figsize=(10, 6))
end = 1e-15
x = np.linspace(-end, end, 1001)
y = np.where(x==0, 1.0, np.log(1+x)/x)
plt.plot(x, y)
/home/adam/anaconda3/lib/python3.5/site-packages/ipykernel/__main__.py:4: RuntimeWarning: invalid value encountered in true_divide
Out[52]:
[<matplotlib.lines.Line2D at 0x7f573ef32128>]