You are reading solutions / Python
Cansın-Guler-profile-photo.jpg
Author: Cansin Guler
Software Engineer

Python map(function, iterable, ...)

map() is a function implemented by many modern programming languages that's used to transform data collections.

Here's a quick example that uses map() to square all integers in a list:

data = [1, 2, 3]

squared = map(lambda x: x**2, data)

print(list(squared))

Here, we used a lambda function to do the squaring. Also, since map() is part of Python's standard library, we can use it without an import statement.

For the rest of the article, we'll go deeper into how map() works and provide many unique examples to demonstrate its functionality.

A Quick Overview

map() applies a function to each element of a collection (_or_ multiple collections), but an iterator is returned instead of performing the work right away. In the intro example above, we materialized the results of squaring each element by converting the results to a list with list(squared), but iterating over results in a loop also works.

Example

Let's look at another quick example. Assume we have a words list, and we want to calculate the length of each word to populate a word_lengths list. First, we'll split a string into words, then map the len() function to the collection, like so:

words = "Toto I have a feeling we are not in Kansas anymore".split()


the_iterator = map(len, words)  # map() returns an iterator
print("Map iterator: ", the_iterator)


word_lengths = list(the_iterator)  # materialize the iterator into a list
print("The transformed list: ", word_lengths)
Out:
Map iterator:  <map object at 0x000001D891BE8880>
The transformed list:  [4, 1, 4, 1, 7, 2, 3, 3, 2, 6, 7]

map() returns a map object, which is an iterator. Here, we materialized this middle product (the_iterator) into a list and printed the result.

For loop equivalent

This transformation we made in essentially two lines of code does the job of the for loop given below:

words = str.split("Toto I have a feeling we are not in Kansas anymore")
word_lengths_2 = []

for word in words:
    word_length = len(word)
    word_lengths_2.append(word_length)

print(word_lengths_2)
Out:
[4, 1, 4, 1, 7, 2, 3, 3, 2, 6, 7]

As you can see, map() is the shorter, more readable alternative for transforming data collections.

User-Defined Functions

In the previous section, we called map() with the built-in len, but we can also create our own functions.

In the following code block, we created a function that calculates the factorial of a number, n.

def factorial(n):  
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)

The factorial() function returns 1 when n is 1—this is the base case. Otherwise, it recursively calls itself on (n-1) until it reaches the base case.

Let's try and use them with map():

numbers = {3, 4, 5, 6}

factorials_iterator = map(factorial, numbers)

for fact in factorials_iterator:
    print(fact)

For this example, we evaluated the iterator in a for-loop by printing each value. Note that the factorial for each number is calculated lazily, so only one number at a time is processed.

Lambda expressions

Let's make a function that determines if a number is even. The following will return the number passed in along with whether or not the number is divisible by two without a remainder.

def is_even(n):
    return (n, n % 2 == 0)

We can then map the function onto an iterable of numbers, like so:

numbers = range(10)

n_is_even = list(map(is_even, numbers))

print(n_is_even)
Out:
[(0, True), (1, False), (2, True), (3, False), (4, True), (5, False), (6, True), (7, False), (8, True), (9, False)]

For these types of small operations, we can use _lambda expressions_, which let us define one-expression-long functions in place. They look something like this:

lambda parameter1, parameter2, ...: expression

Lambdas take any number of parameters but are restricted to holding only one expression. We can pass the functionality of is_even() directly to map() using a lambda expression, like so:

numbers = range(10)

n_is_even = list(map(lambda n: (n, n % 2 == 0), numbers))

print(n_is_even)

Lambdas can be an excellent tool for simple scripts and quick data manipulation, but they can also be less readable and harder to maintain in larger projects.

Calling map() on Multiple Iterables

map() can accept any number of iterables if the passed function has the same number of parameters. Until now, we used it with one iterable and passed in a one-parameter function. To use it with _n_ iterables, we must pass an _n_-parameter function.

Let's try and derive BMIs (Body Mass Index) from a list of heights and a list of weights using map(). First, we'll define some data for three people:

weights = [120, 45, 70]       # weights in kgs
heights = [1.90, 1.60, 1.55]  # heights in meters

In the above data, index zero corresponds to person one, who has a weight of 120kg and a height of 1.90 meters.

We want to create a function that calculates $BMI = \frac{weight}{height^2}$ and map it to each person. We'll use a lambda expression that takes two parameters for the calculation and then pass in both weights and heights to map():

bmi_iterator = map(
    lambda x, y: x / (y**2), weights, heights
)  


for bmi in bmi_iterator:
    if bmi > 25:
        print(f"BMI:{bmi:.2f} - Overweight")
    elif bmi > 18.5:
        print(f"BMI:{bmi:.2f} - Normal")
    else:
        print(f"BMI:{bmi:.2f} - Underweight")

Given a function and multiple iterables as parameters, map() matches the nth iterable to the function's nth parameter. Here weights are given to x and heights are given to y.

Iterables of different lengths

In the BMI example, we used map() with multiple iterables of equal length. Let's see what happens if we try to use iterables of different lengths.

We will use three lists: one holding adjectives, one holding colors, and the other holding object names. We will try and compose short object definitions:

adjectives = ["big", "crooked", "dirty"]            # has 3 elements
colors = ["blue", "pink", "red", "green", "lilac"]  # has 5 elements
objects = ["shutter", "table", "pen", "baseball"]   # has 4 elements

defined_objects = list(map(
        lambda x, y, z: f"{x} {y} {z}", 
        adjectives, 
        colors, 
        objects
))

print(defined_objects)

Here, map() takes in three lists of different sizes and a lambda expression that concatenates three values using an f-string.

Notice that map() could only execute the lambda function three times. Because after the third time, the adjectives list was exhausted, and the lambda couldn't get all three required parameters.

Using map() with single parameters

Suppose we want to raise all numbers in a list to a specific power, but we want the power to be a parameter. For example:

def raise_to(n, power):
    return n ** power

And we now want to use this function to square numbers from 1–5:

numbers = range(5)

raised = list(map(raise_to, numbers, 2))

print(raised)

map() only expects iterables as arguments, so '2' being an int won't work. Instead, we need to make an iterable of 2s of equal length to the numbers. We easily achieve this by using the repeat function from the itertools module:

from itertools import repeat

numbers = range(5)

raised = list(map(raise_to, numbers, repeat(2)))

print(raised)

repeat converted our '2' into an infinite iterable of 2s, allowing map() to function as expected. There are many helpful tools in the itertools module for functional programming; see the docs for more.

Alternatives to map()

List comprehensions and generator expressions solve similar problems to map(). The general syntax for both are as follows:

# List comprehension
new_list = [expression for item in iterable if condition]

# Generator expression
new_generator = (expression for item in iterable if condition)

The only syntax difference between the two is that list comprehensions use square brackets, and generator expressions use parentheses. The functional difference between them is that generator expressions return a generator, which waits until iteration before performing any work.

For comparison, the code below shows a list comprehension and map() raising a set of numbers to the power of two.

numbers = {1, 2, 3, 4}

squared1 = list(map(lambda x: x**2, numbers))

squared2 = [x**2 for x in numbers]


print(squared1)
print(squared2)

List comprehensions

List comprehensions also allow filtering by adding an if. Here's how we'd filter the previous list comprehension and transform only the numbers that are even:

even_squared = [x**2 for x in numbers if x % 2 == 0]  

print(even_squared)

That said, we do have access to something similar for map() with the filter function:

even_squared2 = map(lambda x: x**2, filter(lambda x: x % 2 == 0, numbers))

print(list(even_squared2))

It starts getting confusing with this many layers, so it's atypical to see this in public projects. The main benefit of the above example is that no work was performed until we materialized the list in the print statement.

A list comprehension returns an iterable, which holds values, whereas map() returns iterators, which hold logic and produce the values on demand. This matters with larger datasets since we need to store all values in memory.

Generator expressions

The issue of list comprehensions having to keep values in memory can be mitigated by using generator expressions, which, like map(), also produce iterators.

numbers = {1, 2, 3, 4}

squared = (x**2 for x in numbers)

print(list(squared))

Generator expressions save memory, allow filtering, and are more readable, which makes them the closest competition to map(). Most programmers view choosing one over the other as a matter of personal preference. Check out this Stackoverflow post to read what others think: List comprehension vs map.

Summary

Python's built-in map() function takes in any number of iterables along with a function that operates on the same number of parameters. map() applies the function to every element of the iterables and populates an iterator with the return values.


Meet the Authors

Cansın-Guler-profile-photo.jpg

Software engineer, technical writer and trainer.

Brendan Martin
Editor: Brendan Martin
Founder of LearnDataSci

Get updates in your inbox

Join over 7,500 data science learners.