In [10]:
import numpy as np

Lecture 11:

  • Learn about lambda functions

  • How to use map( ), filter( ), and reduce( )

  • Explore the joys of "List comprehension"

Lambda functions

You can spell any Greek letter and use it as a variable name EXCEPT for lambda. As we learned in Lecture 2, lambda is a reserved word. Why? Because lambda has a special meaning in Python it is reserved for anonymous functions.

The syntax of a lambda function consists of a name =, followed by the word lambda followed by an argument list, a colon (:), and an expression. Here is a simple example of an anonymous function that returns the product of the argument list:

In [11]:
f=lambda x,y : x*y

Let's dissect the statement.

  • f is a new kind of object that represents the function,

  • $x$ and $y$ are the arguments of the anonymous function,

  • and the expression $x*y$ is what is returned when the function is called.

We're familiar with the following syntax for a "normal" function:

In [12]:
def g(x, y):
    return x*y

Both $f$ and $g$ take the same arguments and return the same value. They are essentially the same function.

Let us verify this, by calling both functions with the parameters $x=2$ and $y=10$:

In [13]:
print (f(2,10))
print (g(2,10))

Yup. Both the lambda function f and the 'regular' function $g$ defined with the keyword def are of the type: function

In [14]:
print (type(f))
print (type(g))
<class 'function'>
<class 'function'>

lambda functions should seem familiar. They follow the same syntax you use in math to define functions:

f(x) = x2 +5x + 9   So we could easily write this as a lambda function like this:


In [14]:
h = lambda x: x**2+5.*x+9

For a multivariate function, you need to list all the arguments after the reserved word lambda. For example, In math, you’d write the equation for the hypotenus of two sides, a and b, as:   hypotenuse(a, b) = $\sqrt{a^2+ b^2}$.

In Python it would be:

In [7]:
hypotenus = lambda a, b:  np.sqrt(a**2+b**2)
print (hypotenus(3,4))

Uses of lamda functions

You may be wondering why lambda functions are useful. The answer is that lambda functions are anonymous- you don't have to give them a name (although we did when we assigned the function to f in the above examples). This comes in handy if you 1) write or use functions that take in other functions as arguments or 2) you just want a quickie one-off calculation.

For the first reason, examples of such functions that take lambda functions are map( ), reduce( ), and filter( ).

Anticipating your further questions, you can look at this useful blog post on the subject:

map( )

lambda is often used in conjunction with the function map( ).

map(func, seq) is a function that takes two arguments: a function, func, and a sequence, seq. func may be an ordinary function or an anonymous function defined in the first argument of map( ).
seq is one or more lists on which the function is performed. So map( ) returns a list generator (in Python 3) with the results of whatever func did to the elements in seq.

Here is an example which converts kilometers to miles (1 mile = (5/8) kilometers).

In [9]:
km_from_mi=map(lambda x:(5./8.)*x,[8,10,24])
# python 2.7 versus python 3. python 3 returns the list generator, not the list
# if you are running python 2.7, both of these will look the same
print (km_from_mi) # see the list generator 
print (list(km_from_mi)) # see the list
<map object at 0x108c1af28>
[5.0, 6.25, 15.0]

The anonymous function was defined as the first argument of map( ). This lambda function takes a single variable x (in miles), converts it to km by multiplying by 5/8, and then returns the value. The map( ) function then takes a sequence as the second argument, in this case, the sequence is a list with 8,10, and 24 as elements. map( ) converts each of the values in the list to kilometers by applying the anonymous function.

If our lambda function has TWO variables, e.g., $x,y$, we must pass map( ) TWO lists of the same length for seq:

In [4]:
map(lambda x,y : x*y,[2,3,4],[5,6,7]);

The values for $x$ get taken from the first list of numbers, while $y$ gets taken from the second list. map( ) returns a list with the product of the two input lists:

In [15]:
print (list(map(lambda x,y : x*y,[2,3,4],[5,6,7])))
[10, 18, 28]

Another way to use map( ) is to define the lists and functions ahead of time, then apply the map( ) to them as follows:

In [16]:
f=lambda x,y : x*y
# but let's see what it does with a print statement: 
print (list(map(f,a,b) ))
[10, 18, 28]

Well that was dope....

You can see that $x$ snags values from the first list, $a$ and $y$ from the second list, $b$.

filter( )

filter(func, seq) is another example of a function that takes a function, func, and a sequence, seq as arguments. The function supplied to filter must return a boolean- either True or False. filter() then applies that function to all the values in the sequence and returns the values that are True. Let's walk through this step by step beginning with a function that returns True or False.

As an example of a boolean function, we can apply modulo 2 ($x%2$) to test whether a number is even or odd. [Remember that modulo is the remainder, so when you divide $x$ by 2, even numbered values of $x$ will return 0 (and odd numbers return 1. Also, remember that 0 is False and 1 is True]

In [11]:
print ('modulo of 2 divided by 2: ',2%2)
print ('modulo of 400 divided by  360: ',400%360) # handy for keeping values between 0 and 360
modulo of 2 divided by 2:  0
modulo of 400 divided by  360:  40

Now let's create an anonymous function that tests whether numbers are even or odd by the value they return. As you just learned, if modulo returns 0 then the remainder is 0 and the original value is even, whereas, if it returns 1, then the original value is odd:

In [12]:
f= lambda x: x % 2

print (f(2))
print (f(3))
print (f(4))
print (f(5))

We can add the relational operator == and return True or False instead of 0 or 1:

In [13]:
f= lambda x: x % 2 == 0

print (f(2))
print (f(3))
print (f(4))
print (f(5))

Now, we can use filter( ) and the function we defined to find the even values in a sequence. Similar to map( ), filter ( ) applies the function to every value in the list, but filter ( ) will only return the values that evaluate to True. Note that in Python 3, you must turn the output of filter( ) into a list. For example:

In [17]:
f= lambda x: x % 2 == 0 # and again, for clarity
mylist = list(range(50))
list(filter(f, mylist))

reduce( )

reduce( ) is another function that regularly uses a lambda function. Like map( ) and filter( ), reduce(func, seq) takes two arguments: a function and a sequence. With reduce( ), the function is applied to sequential pairs of elements in the list until the list is reduced to a single value, which is then returned. In detail, the function is first performed on the first two elements. The result then replaces the first element and the second is removed. The same function is again applied to the first two elements of the new list, replacing them with the single value from the function, and so on until only a single value remains.

New in Python 3, we must first import reduce( ) from functools. Perhaps reduce( ) is going the way of the passenger pidgeon?

In [4]:
from functools import reduce # have to do this in Python 3 - not in Python 2

Let's try an example. We could use reduce( ) to return the factorial of a number $n$. Remember that the factorial is "the product of an integer and all the integers below it".
So, we can use our lambda function defined above which returns the product of two numbers. If we use the reduce function and make the lambda operate on a list of numbers from 1 to n, we will get the desired product at the end.

In [20]:
reduce(lambda x,y:x*y,range(1,n+1)) # performs the lambda function  sequentially on the list

We can compare our function with the Numpy version, **np.math.factorial)

In [21]:


List comprehensions

Another succinct way to iterate over sequences and apply different operations, is through List, Dictionary, and Set comprehensions.

A List comprehension is a convenient way of applying an operation to a collection of objects. It takes this basic form:

[expression for element in collection if condition]

Here is an example that takes a list of strings, looks for those with lengths greater than 5 and returns the upper case version using the string.upper( ) method for strings:

In [25]:
mtList=['Andes','Mt. Everest','Mauna Loa','SP Mountain']
[s.upper() for s in mtList if len(s)>5]

[Fun fact: you can get the lower case equivalents with the method string.lower( ).]

Note that you could achieve the same result (the upper case list of all volcanoes with names having more than 5 characters) using our old friend the for loop:

In [26]:
another_list = []
for s in mtList:

Or (challenge!) by using filter( ) and map( ) and an anonymous function:

Each of these three approaches performs similarly, but the list comprehension is the most succinct.

Dictionary Comprehension

Dictionary comprehensions are similar to list comprehensions, but they generate key-value pairs instead of lists. Dictionary comprehensions follow the format:

{key:value for variable in collection if condition}

The following Dictionary comprehension generates a dictionary with a word from mtList as the key and the length of the word as the value

In [27]:
mtList=['Andes','Mt. Everest','Mauna Loa','SP Mountain'] # to remind you what mylist was
{s:len(s) for s in mtList} # dictionary comprehension with mylist
{'Andes': 5, 'Mt. Everest': 11, 'Mauna Loa': 9, 'SP Mountain': 11}

Notice the {key:value, key:value} structure of the output is a dictionary.

Set comprehension

A Set comprehension, returns a set and follows this format:

{expression for value in collection if condition}

The following Set comprehension creates a set composed of the lengths of the words in mylist

In [28]:
{len(s) for s in mtList}
{5, 9, 11}

You can tell that a set was returned because it is in curly braces with no keys.

Complicated comprehensions

List, Dictionary, and Set comprehensions can also replace complicated, nested loops. Here's an example that generates a list of x,y,z triplets if the values obey Pythagorus' rules for right triangles. Chew on it, until you get it:

In [29]:
[(x,y,z) for x in range(1,30) \
    for y in range(x,30) for z in range(y,30) \
    if x**2 + y**2 == z**2]
[(3, 4, 5),
 (5, 12, 13),
 (6, 8, 10),
 (7, 24, 25),
 (8, 15, 17),
 (9, 12, 15),
 (10, 24, 26),
 (12, 16, 20),
 (15, 20, 25),
 (20, 21, 29)]

Assignment #4

Create a new notebook with the name format: Lastname_HomeworkNumber. For example, CychB_HW_4

Create a markdown block in which you will describe what the notebook does.

  1. The equations for an ellipse are:

    x =a cos( $\theta$ ) , y=b sin( $\theta$ )
    $a$ is the major radius
    $b$ is the minor radius
    $\theta$ is the angle

    a. Write a lambda function that returns $x$ and $y$ given $a$, $b$, and $\theta$. Assume that theta goes from 0 to 360. Remember that trig functions assume the arguments are in radians
    b. Plot your ellipse using matplotlib.pyplot (imported as plt). Use the %matplotlib inline magic command
    c. To make the axes of equal length, use the plt.axis('equal') command. Otherwise, your axes will be squished.

  2. Write a lambda function that returns the square of an input parameter $x$
    a. Use map( ) to generate a list of squares for a sequence with 10 values
    b. Use filter( ) and the lambda function to generate a list of squares that are between 5 and 50.
    c. Use a list comprehension to generate the same list with only one line of code.

  3. Create a Class, it should include at least 3 attributes and 3 methods. Be creative! Here are a few possibilities- Card, Deck, Planet, Phone Contact, Ocean, Student, Cellphone, Dog, Car
    a. Save your class in a module, import the module into your notebook
    b. Create 3 instances of your class, change the value of at least one attribute for one instance of your class
    c. Call all three of your methods- you can use any of the instances of your class
In [ ]: