Loops
Contents
import numpy as np ## It's good practice to put your imports right at the top!
5. Loops#
Follow along!
Remember that to best make use of this tutorial, it is highly recommended that you make your own notebook and type every piece of code yourself!
Now that we’ve learned about how data are stored in Python as different types that we can collect into data structures like lists or numpy arrays, we should learn how to actually do things with our data! The next three sections on loops, conditional statements, and functions will comprise the basics of everything you need to get yourself up and running with Python. This will also be your first introduction to Python’s syntax (its “grammar” for how to structure code), beyond the simple syntax of variable assignment that we’ve seen thus far.
Loops are a natural place to start in the context of thinking about Python for data analysis because loops are a coding structure that tell computers how to execute a set of instructions repeatedly, and we often have lists or arrays of data that we want to iterate through (“loop over” in coding lingo) to perform calculations on each datum individually. That is, the term “loop” refers to the way that the computer loops back to do a set of tasks many times. This is one of the true superpowers of coding: computers have infinite patience and can do boring tasks repeatedly ad nauseam, so we can leverage this to make calculations or perform analyses that would be impossible to do by hand. We’ll talk about this more when we learn about random number generation.
In Python there are three main types of loops: for
, while
, and list comprehensions. for
loops are probably the most common type and are the easiest to work with. while
loops have a similar syntax to for
loops, but are somewhat trickier and are a common source of bugs for novice coders. Finally, list comprehensions have a more complicated syntax, but are extremely “Pythonic” and a nice way to compress loops that involve lists. In this tutorial, we’ll mostly focus on for
and while
loops, but I’ll introduce the list comprehension syntax so that you aren’t stumped when you see it in the wild. In this section, we’ll learn about for
loops, and we’ll leave while
loops for the next section, after we’ve introduced the notion of conditional statements.
Big Idea
Loops are structure that tell the computer to loop back and repeat instructions several times.
5.1. The for
loop#
At its most basic, a computer is a bit-moving and -storing machine; it can remember information and it can modify that information, but it at some level operates in a discrete, bit-by-bit manner. I say this because it can seem as though computers are doing many things simultaneously, but under the hood, operations like sum([1, 12, 3, 6, 321])
are carried out by the computer as a set of very fast, simple, sequential steps:
Set
tmp_sum = 0
tmp_sum = tmp_sum + 1
tmp_sum = tmp_sum + 12
tmp_sum = tmp_sum + 3
tmp_sum = tmp_sum + 6
tmp_sum = tmp_sum + 321
return
tmp_sum
to the user. Thefor
loop is our structure to control this iterative process.
5.1.1. What is a for
loop?#
The idea behind a for
loop is that we are filling in the sentence “Do these steps for this many iterations.” That is, we know how many times we want to do the operations, so the computer should do them for all the indicated repetitions. In Python, this is done by iterating over an iterable such as a list or array (tautologically, iterables are things that can be iterated over).
As a simple example, consider the following:
for iteration in range(5):
print("Doing some instructions!")
Doing some instructions!
Doing some instructions!
Doing some instructions!
Doing some instructions!
Doing some instructions!
Let’s unpack this a bit. The idea is that we can read this syntax as “for each item in the iterable: do some stuff”, where Python is going to do the indented instructions for each item in the list. In this case, we’re using the range
function to create an iterable of length 5, and then Python is executing the indented instructions of print("Doing some instructions!")
5 times, once for each iteration in the iterable. We can do any normal Python commands in this indented area: variable assignment, calculations, even other loops as we’ll see shortly! For example:
n_iters = 4
for element in range(n_iters):
print("\nThis is an iteration!")
x = 3
print("x has been assigned {}".format(x))
This is an iteration!
x has been assigned 3
This is an iteration!
x has been assigned 3
This is an iteration!
x has been assigned 3
This is an iteration!
x has been assigned 3
Where you can see that all of the indented instructions were executed n_iters=4
times.
However, this is not all that lists can do. Earlier we suggested that we can iterate through lists and arrays, which would involve something changing about each iteration of the loop, and this is easy to do by taking advantage of the fact that Python loops automatically assign a value to the iterate variable specified in the for
loop syntax. That is, in the previous examples, iteration
and element
were names given to a variable that actually stored a value based on the iterable provided. We can see this in action below:
for iter_name in range(7):
print(f"The value stored in 'iter_name' is {iter_name}")
The value stored in 'iter_name' is 0
The value stored in 'iter_name' is 1
The value stored in 'iter_name' is 2
The value stored in 'iter_name' is 3
The value stored in 'iter_name' is 4
The value stored in 'iter_name' is 5
The value stored in 'iter_name' is 6
Specifically, this iterator variable is being assigned the elements of the iterable (range(7)
in the previous example) one at a time. So in the case where we’re using range
, the variable iter_name
recieves the elements of the iterable range(7)
one at a time: first 0, then 1, then 2, and so on up to 6. You can check this by looking at the output of print(list(range(7)))
.
We can do this with any iterable, not just the output of the range
function, including lists. For example:
list_of_pets = ['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
for pet in list_of_pets:
print(pet)
dogs
cats
iguanas
rabbits
birds
Here we can see that the iteration variable pet
is being assigned each element of list_of_pets
in turn.
It’s important to note that this is a variable assignment that is happening at the for
loop structure and not beforehand. That is, we cannot access this pet
variable until the loop has been run.
print(animal) ## This has not been assigned yet, so it will create a NameError!
for animal in pets:
print(animal)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[6], line 1
----> 1 print(animal) ## This has not been assigned yet, so it will create a NameError!
3 for animal in pets:
4 print(animal)
NameError: name 'animal' is not defined
Similarly, if pet
was a variable we had created before the loop, it will be overwritten by the loop’s assignment.
pet = "hamster"
print(pet)
for pet in list_of_pets:
print("Pets are nice.")
print(pet)
hamster
Pets are nice.
Pets are nice.
Pets are nice.
Pets are nice.
Pets are nice.
birds
This means that we should make sure to use a distinct name for this iteration variable from previous variables.
Also, you can note from the previous example that this variable persists after the loop and retains the value of the last assignment in the loop. We’ll see shortly that this is useful if you want to loop until a condition is met, after which you might want to keep the value of the iterator. For example, if you want to know how many iterations it takes before some sort of calculation converges, you could use the value of the iteration variable.
5.1.1.1. Using a counter#
It’s worth noting that just because we have this iteration variable doesn’t mean we have to use it. It’s relatively common to want to keep an external counter or incrementer to keep track of different things in the loop. We can easily do this by initializing a variable before the loop and then incrementing (or decrementing) it in each loop iteration (or loop iterations that meet some criteria). As an example:
counter_var = 0 ## Initialize our counter at some value, here 0.
for itr in range(len(list_of_pets)):
counter_var = counter_var + 1
print(f"The counter variable counted {counter_var} iterations!")
The counter variable counted 5 iterations!
5.1.1.2. Using the iterable in a loop#
Another useful note is that we can still make use of the iterable variable inside the loop. We can even reassign its value, although this can have surprising results if you change the length of the iterable.
for pet in list_of_pets:
print(list_of_pets)
['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
list_of_pets = ['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
for pet in list_of_pets:
last_animal = list_of_pets.pop()
print(list_of_pets)
['dogs', 'cats']
5.1.2. Exercises#
Make a list of names, and use a loop to print out each name sequentially. Use some basic string formatting to make the output look nice.
Use a counter variable (or any other method) in the previous example to determine how many iterations the loop executed. Can you explain what happened to
list_of_pets
in this loop?
5.2. Looping with indices#
In the previous examples, we made use of the iteration variable in order to walk through the elements of a list (iterable), but the reason that the range
function is so useful is because it provides a natural index with which we can access elements of a list or array via slicing as discussed in the previous section. As an example:
list_of_pets = ['dogs', 'cats', 'iguanas', 'rabbits', 'birds']
for index in range(len(list_of_pets)):
element = list_of_pets[index]
print(f"At index {index} the list contains {element}.")
At index 0 the list contains dogs.
At index 1 the list contains cats.
At index 2 the list contains iguanas.
At index 3 the list contains rabbits.
At index 4 the list contains birds.
We can also access elements of arrays by index:
myarray = np.arange(20) ## What does this function do?
myarray = myarray.reshape(4, 5)
print(myarray) ## What does myarray look like?
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
for ii in range(len(myarray)):
print("This is row {}:".format(ii), myarray[ii])
This is row 0: [0 1 2 3 4]
This is row 1: [5 6 7 8 9]
This is row 2: [10 11 12 13 14]
This is row 3: [15 16 17 18 19]
This is especially useful for iterating over different axes of an array, because we can slice in different directions. For example:
nCols = myarray.shape[1] ## What is the [1] doing here?
## What is nCols?
for colNo in range(nCols):
print(f"This is column {colNo}: {myarray[:, colNo]}")
This is column 0: [ 0 5 10 15]
This is column 1: [ 1 6 11 16]
This is column 2: [ 2 7 12 17]
This is column 3: [ 3 8 13 18]
This is column 4: [ 4 9 14 19]
5.2.1. Automatically generating indices with enumerate
#
Of course, sometimes we want to both make use of the elements of our iterable and use indices for accessing elements. In this case, it is convenient to use the enumerate
function.
for index, element in enumerate(list_of_pets):
print(f"The index is {index}. The element is {element}.")
The index is 0. The element is dogs.
The index is 1. The element is cats.
The index is 2. The element is iguanas.
The index is 3. The element is rabbits.
The index is 4. The element is birds.
5.2.2. Exercises#
Make a list of adjectives that describe each name in your list of names. Use looping with indices to print out strings involving a name and the corresponding adjective. (“Eric is neat!”)
If you used
enumerate
in the previous exercise, repeat the problem withoutenumerate
. If you didn’t useenumerate
, try it out!
5.3. Nesting loops#
Ok, so what if we want to access every element of a multi-dimensional array? What if we want to do an iterative task several times?
In Python, we can easily nest loops within one another by using the same loop syntax within the indented area of a normal loop. As an example:
nRows, nCols = myarray.shape ## What are the values of nRows and nCols??
for rowIdx in range(nRows):
for colIdx in range(nCols):
print(f"The element at row {rowIdx} and column {colIdx} is {myarray[rowIdx, colIdx]}")
The element at row 0 and column 0 is 0
The element at row 0 and column 1 is 1
The element at row 0 and column 2 is 2
The element at row 0 and column 3 is 3
The element at row 0 and column 4 is 4
The element at row 1 and column 0 is 5
The element at row 1 and column 1 is 6
The element at row 1 and column 2 is 7
The element at row 1 and column 3 is 8
The element at row 1 and column 4 is 9
The element at row 2 and column 0 is 10
The element at row 2 and column 1 is 11
The element at row 2 and column 2 is 12
The element at row 2 and column 3 is 13
The element at row 2 and column 4 is 14
The element at row 3 and column 0 is 15
The element at row 3 and column 1 is 16
The element at row 3 and column 2 is 17
The element at row 3 and column 3 is 18
The element at row 3 and column 4 is 19
In this example, the iterating over the column indices is contained in a for
loop that is indented following the for
loop that iterates over the row indices. The print statement is indented twice following the second for
loop so that it executes for each iteration of the innermost loop.
However, we don’t have to do everything within both loops. In the example below, we only do the singly-indented instructions at each iteration of the first loop. That is, the assignment temp_pet = ""
only happens once for each interation in the list_of_pets
loop, then the range(3)
loop is executed, and the temp_pet = temp_pet + pet
instruction is executed 3 times. Then, the print(temp_pet)
instruction is only indented once, so it is also only run once for each iteration of the list_of_pets
loop.
for pet in list_of_pets:
temp_pet = ""
for itr in range(3):
temp_pet = temp_pet + pet
print(temp_pet) ## Try indenting this a second time, then try unindenting. What happens?
dogsdogsdogs
catscatscats
iguanasiguanasiguanas
rabbitsrabbitsrabbits
birdsbirdsbirds
Indentation syntax
It’s important to note here how Python keeps track of different “levels” of execution. The for itr in range(3)
loop in the previous example only executes the twice-indented instructions that immediately follow the for
loop instruction. In the example below, we will receive an IndentationError
because the print(triple_pet)
instruction is doubly-indented without the required syntax to indicate that it is allowed to be. If we want to print the value of triple_pet
at each value of the inner loop, it needs to come before we assign pi
(as an example, there’s nothing special about this variable assignment).
for pet in list_of_pets:
triple_pet = ""
for itr in range(3):
triple_pet = triple_pet + pet
pi = 3.1415 ## Some instructions that are not meant to be doubly-looped.
print(triple_pet)
Cell In[18], line 6
print(triple_pet)
^
IndentationError: unexpected indent
Similarly, the indented area of a loop cannot be empty, otherwise you will get a SyntaxError
.
for iteration in [1, 2, 3]:
Cell In[19], line 1
for iteration in [1, 2, 3]:
^
SyntaxError: incomplete input
5.3.1. Exercises#
Consider the following code:
for ind1 in range(6): ## Instructions! for ind2 in range(8): ## Instructions! ## Instructions!
Add instructions that will construct a 2-dimensional list or array so that each element of the list/array contains the product of the row number and the column number. You may want to investigate the
np.zeros
ornp.ones
functions if you choose to make an array, or recall how theappend
method works for lists.Construct an iterable that has 4 “dimensions” and use 4 loops to traverse each element of the iterable, printing out all the indices and the value of the element at each location in a nicely formatted string.
At this point, you have been introduced with enough information to move on in the tutorial to learn about using conditional statements to finally start writing interesting pieces of code! However, for the sake of completeness and to introduce a looping syntax that you might encounter when looking at code online, I’ve introduced a short section on list comprehensions.