# Functions¶

In [1]:

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline


## Waht’s wrong with this code?¶

In [2]:

max_iter = 100
h, w = 400, 400

img = np.zeros((h, w)).astype('int')
for i, real in enumerate(np.linspace(-1.5, 0.5, w)):
for j, imag in enumerate(np.linspace(-1, 1, h)):
c = complex(real, imag)
z = 0 + 0j
for k in range(max_iter):
z = z*z + c
if abs(z) > 2:
break
img[j, i] = k

plt.grid(False)
plt.imshow(img, cmap=plt.cm.jet)
pass

• hard to udnerstand
• uses global variabels
• not re-usable except by copy and paste

## Refactoring to use functions¶

In [3]:

def mandel(c, z=0, max_iter=100):
for k in range(max_iter):
z = z*z + c
if abs(z) > 2:
return k
return k

In [4]:

def mandelbrot(w, h, xl=-1.5, xu=0.5, yl=-1, yu=1):
img = np.zeros((h, w)).astype('int')
for i, real in enumerate(np.linspace(xl, xu, w)):
for j, imag in enumerate(np.linspace(yl, yu, h)):
c = complex(real, imag)
img[j, i] = mandel(c)
return img

In [5]:

img = mandelbrot(w=400, h=400)
plt.grid(False)
plt.imshow(img, cmap=plt.cm.jet)
pass


## Function is re-usable¶

In [6]:

img = mandelbrot(w=400, h=400, xl=-0.75, xu=-0.73, yl=0.1, yu=0.12)
plt.grid(False)
plt.imshow(img, cmap=plt.cm.jet)
pass


### Anonlymous functions (lamabdas)¶

In [7]:

def square(x):
return x*x

In [8]:

square(3)

Out[8]:

9

In [9]:

square2 = lambda x: x*x

In [10]:

square2(3)

Out[10]:

9


### First class functions¶

In [11]:

# functions can be traated the same way as (say) an integer


## Fnctions can be passed in as arguments¶

In [12]:

def grad(x, f, h=0.01):
return (f(x+h) - f(x-h))/(2*h)

In [13]:

def f(x):
return 3*x**2 + 5*x + 3

In [14]:

grad(0, f)

Out[14]:

5.000000000000004


## Funcitons can also be returned by functions¶

In [15]:

import time

def timer(f):
def g(*args, **kwargs):
start = time.clock()
result = f(*args, **kwargs)
elapsed = time.clock() - start
return result, elapsed
return g

In [16]:

def f(n=1000000):
s = sum([x*x for x in range(n)])
return s

timed_func = timer(f)

In [17]:

timed_func()

Out[17]:

(333332833333500000, 0.22118899999999897)


## Decorators¶

In [18]:

@timer
def g(n=1000000):
s = sum([x*x for x in range(n)])
return s

In [19]:

g()

Out[19]:

(333332833333500000, 0.2040980000000001)


### Map, filter, reduce¶

In [20]:

map(lambda x: x*x, [1,2,3,4])

Out[20]:

<map at 0x109246a90>

In [21]:

list(map(lambda x: x*x, [1,2,3,4]))

Out[21]:

[1, 4, 9, 16]

In [22]:

list(filter(lambda x: x%2==0, [1,2,3,4]))

Out[22]:

[2, 4]

In [23]:

from functools import reduce

In [24]:

reduce(lambda x, y: x*y, [1,2,3,4], 10)

Out[24]:

240


### List comprehenision¶

In [25]:

[x*x for x in [1,2,3,4]]

Out[25]:

[1, 4, 9, 16]

In [26]:

[x for x in [1,2,3,4] if x%2 == 0]

Out[26]:

[2, 4]


### Set and dictionary comprehension¶

In [27]:

{i%3 for i in range(10)}

Out[27]:

{0, 1, 2}

In [28]:

{i: i%3 for i in range(10)}

Out[28]:

{0: 0, 1: 1, 2: 2, 3: 0, 4: 1, 5: 2, 6: 0, 7: 1, 8: 2, 9: 0}


### Generator expressions¶

In [29]:

(i**2 for i in range(10,15))

Out[29]:

<generator object <genexpr> at 0x1094cb938>

In [30]:

for x in (i**2 for i in range(10,15)):
print(x)

100
121
144
169
196


#### Generator expressions¶

Generator expressions return a potentially infinite stream, but one at a time thus sparing memory. They are ubiquitous in Python 3, allowing us to handle arbitrarily large data sets.

In [31]:

def count(i=0):
while True:
yield i
i += 1

In [32]:

c = count()
next(c)

Out[32]:

0

In [33]:

next(c)

Out[33]:

1

In [34]:

next(c)

Out[34]:

2

In [35]:

list(zip('abcde', count(10)))

Out[35]:

[('a', 10), ('b', 11), ('c', 12), ('d', 13), ('e', 14)]


### Itertools¶

In [36]:

import itertools as it

In [37]:

for i in it.islice(count(), 5, 10):
print(i)

5
6
7
8
9

In [38]:

for i in it.takewhile(lambda i: i< 5, count()):
print(i)

0
1
2
3
4

In [39]:

import operator as op

[i for i in it.starmap(op.add, [(1,2), (2,3), (3,4)])]

Out[39]:

[3, 5, 7]

In [40]:

fruits = ['appple', 'banana', 'cherry', 'durain', 'eggplant',  'fig']

for k, group in it.groupby(sorted(fruits, key=len), len):
print(k, list(group))

3 ['fig']
6 ['appple', 'banana', 'cherry', 'durain']
8 ['eggplant']


## Functools¶

In [41]:

import functools as fn

In [42]:

rng1 = fn.partial(np.random.normal, 2, .3)
rng2 = fn.partial(np.random.normal, 10, 1)

In [43]:

rng1(10)

Out[43]:

array([ 1.74912105,  1.98518135,  2.10565975,  1.74981538,  1.57383371,
2.17027087,  2.66615411,  2.17479635,  1.65850304,  2.18972124])

In [44]:

rng2(10)

Out[44]:

array([  7.47878506,  11.1307094 ,   9.95800128,   9.91753149,
8.93918569,   9.46457246,  11.96952595,  10.76935203,
11.74376052,   8.77382197])

In [45]:

fn.reduce(op.add, rng2(10))

Out[45]:

95.891256272508414


### Modules¶

In [46]:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

In [47]:

from pandas import DataFrame, Series
import scipy.stats as ss

In [48]:

DataFrame(ss.beta(2,5).rvs((3,4)), columns=['a', 'b', 'c', 'd'])

Out[48]:

a b c d
0 0.546453 0.066061 0.274397 0.062071
1 0.190301 0.126242 0.185920 0.591911
2 0.136587 0.190615 0.016919 0.157208

## Where does Python search for modules?¶

In [49]:

import sys
sys.path

Out[49]:

['',
'/Users/cliburn/anaconda/envs/py35/lib/python35.zip',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5/plat-darwin',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5/site-packages/Sphinx-1.3.1-py3.5.egg',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5/site-packages/setuptools-19.1.1-py3.5.egg',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5/site-packages',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5/site-packages/aeosa',
'/Users/cliburn/anaconda/envs/py35/lib/python3.5/site-packages/IPython/extensions',
'/Users/cliburn/.ipython']


In [50]:

%%file my_module.py

PI = 3.14

def my_f(x):
return PI*x

Overwriting my_module.py

In [51]:

import my_module as mm

mm.PI

Out[51]:

3.14

In [52]:

mm.my_f(2)

Out[52]:

6.28

In [53]:

from my_module import PI

In [54]:

PI * 2 * 2

Out[54]:

12.56


Note: Modules can also be nested within each other - e.g. numpy.random to creaate a package. We will explore how to create packages in a later session.