Lesson 6: Python Programming 1 - Loops#

This lesson is modified from for-loops from geo-python.

Binder

Objectives#

By the end of this lesson, you will be able to

  • repeat tasks using for and while loops

  • iterate over all of the items in a list to perform calculation

  • use list and dictionary comprehensions to create a python list and python dictionary (optional)

1. Basics of for loops#

In our last lesson we covered lists in Python, one form of a collection of values that can be referenced (indexed) by a single variable. In this lesson we will learn how to use loops. Loops allow parts of code to be repeated some number of times, such as using a section of code to process each item in a list.

image.png

Source: https://www.bugmartini.com/comic/cats-eye/

1.1 A (bad) example#

Let’s consider an example using the list below:

time_slots = ["7:00 AM", "7:13 AM", "3:00 PM", "3:10 PM", "4:30 PM" , "5:15 PM", "6:33 PM"]
daily_activities = ["Wake up", "Nap", "Beg for food", "Doze", "Rest", "Sleep", "Act bat sh*t crazy for no good reason"]
a=[1,2, 3, 4, 5]
len(a)
for i in range(len(a)):
    print(i,a[i])
0 1
1 2
2 3
3 4
4 5

Suppose we want to check each activity in our lists and for each item print something like this:

Output: At 7:00 am, I Wake Up

We could use the index value for each time and activity and do the following:

print(f"At {time_slots[0]}, I {daily_activities[0]}")
At 7:00 AM, I Wake up
print(f"At {time_slots[1]}, I {daily_activities[1]}")
At 7:13 AM, I Nap
print(f"At {time_slots[2]}, I {daily_activities[2]}")
At 3:00 PM, I Beg for food
print(f"At {time_slots[3]}, I {daily_activities[3]}")
At 3:10 PM, I Doze

But this is a bad idea. Why? Well there are two reasons:

  1. It does not scale nicely for long lists; it will take forever to type in.

  2. It won’t work if the length of the list has fewer or more activies

Here is an example of how it will not work with new lists.

time_slot2 = ["7:00 AM", "7:13 AM"]
daily_activities2 = ["Wake up", "Nap"]
print(f"At {time_slot2[0]}, I {daily_activities2[0]}")
At 7:00 AM, I Wake up
print(f"At {time_slot2[1]}, I {daily_activities2[1]}")
At 7:13 AM, I Nap
print(f"At {time_slot2[2]}, I {daily_activities2[2]}")
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[10], line 1
----> 1 print(f"At {time_slot2[2]}, I {daily_activities2[2]}")

IndexError: list index out of range

Here we encounter an IndexError because we have tried to access a value outside the range of values in the updated european_cities list.

Would not it be easier if we just did this:

for time_slot, daily_activity in zip(time_slots, daily_activities):
    print(f"At {time_slot}, I {daily_activity}")
At 7:00 AM, I Wake up
At 7:13 AM, I Nap
At 3:00 PM, I Beg for food
At 3:10 PM, I Doze
At 4:30 PM, I Rest
At 5:15 PM, I Sleep
At 6:33 PM, I Act bat sh*t crazy for no good reason

1.2 Introducing the for loop#

In such situations it is far more efficient to use a for loop. Let’s see an example.

florida_cities = ["Fort Myers", "Cape Coral", "Lehigh Acres", "Estero"]
for city in florida_cities:
    print(city)
Fort Myers
Cape Coral
Lehigh Acres
Estero

Not only is this shorter than indexing through the list item by item, but it is also more flexible.

Try doing your first for loop to print out each city in this list.

us_cities = [
    "Detroit",
    "Chicago",
    "Denver",
    "Boston",
    "Portland",
    "San Francisco",
    "Houston",
    "Orlando",
]
for city in us_cities:
    print(city)
Detroit
Chicago
Denver
Boston
Portland
San Francisco
Houston
Orlando

1.3 for loop format#

The for loops in Python have the general form below.

for variable in collection:
    use the variable to do things

Let’s break down the code above to see some essential aspects of for loops:

  1. The variable can be any name you like other than a reserved keyword

  2. The statement of the for loop must end with a :

  3. The code that should be executed as part of the loop must be indented beneath the for statement

The typical indentation is 4 spaces
  1. There is no additional special word needed to end the loop, you simply change the indentation back to normal.

Hint

for loops are useful to repeat some part of the code a finite number of times.

1.4 for loop variables#

Note that the variable used in a for loop is just a normal variable. Thus, its value still exists after the loop has been run. Let’s loop over the list of weather conditions below and print them to the screen. If you use weather for the loop variable, what is its value after the for loop has completed?

For this list below, write a for loop that prints each weather condition in the list.
After the loop, print the variable name weather in a new cell.

weather_conditions = [
    "rain",
    "sleet",
    "snow",
    "freezing fog",
    "sunny",
    "cloudy",
    "ice pellets",
]
for weather in weather_conditions:
    print(weather)
rain
sleet
snow
freezing fog
sunny
cloudy
ice pellets
print(f"After the loop, weather is {weather}")
After the loop, weather is ice pellets

1.5 for loops and the range() function#

A loop can be used to iterate over any collection of values in Python. So far we have considered only iterating over lists, but we could also write a loop that performs a calculation a specified number of times by using the range() function.

Use the range() function to create a list using list(range()) or [*range()]. Here is how to do it to create, for example, this list “[0, 1, 2, 4]”

list(range(5))
[0, 1, 2, 3, 4]
[*range(5)]
[0, 1, 2, 3, 4]

In this case, we used a special function called range() to give us a list of 5 numbers [0, 1, 2, 3, 4]. When given an integer (whole number) as an argument, range(number) will produce a list of numbers with a length equal to the specified number. The list starts at 0 and ends with number - 1.

In case you are wondering about the syntax `[* range()]`, asterisk `*` is used for unpacking elements from iterables like lists, tuples, or other iterable objects.

You can learn a bit more about range by typing range? or help(range) ?

range?
Init signature: range(self, /, *args, **kwargs)
Docstring:     
range(stop) -> range object
range(start, stop[, step]) -> range object

Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
These are exactly the valid indices for a list of 4 elements.
When step is given, it specifies the increment (or decrement).
Type:           type
Subclasses:     

Let’s consider an example where we use a for loop with value as the loop variable and range(5) as the collection. What happens when you print value at each iteration?

for value in range(5):
    print(value)
0
1
2
3
4

Check your understanding#

The program below will print numbers to the screen using the range() function.

for i in range(...):
    print(i)

Using the documentation that is produced when you run help(range), what values would you replace the ... in the parentheses of the range() function with to have the following output printed to the screen?

2
5
8

You can test your solution in the cell below

Hide code cell content
# Here's one possible solution
for i in range(2, 9, 3):
    print(i)
2
5
8
weather_conditions = [
    "rain",
    "sleet",
    "snow",
    "freezing fog",
    "sunny",
    "cloudy",
    "ice pellets",
]
# Here's one possible solution
for i in range(2,len(weather_conditions),3):
    print(weather_conditions[i])
snow
cloudy

1.6 Looping over the length of lists using index values#

Since we already know how to find the length of a list using the len() function, we can now take advantage of this knowledge to make our for loops more flexible. Let’s start by doing something we have done earlier, printing out city names using a for loop. However, this time we will also use the range() function to print the cities.

1.6.1 How to use index values to loop over a list?#

Since we already know how to find the length of a list using the len() function, we can now take advantage of this knowledge to make our for loops more flexible. Let’s start by doing something we have done earlier, printing out city names using a for loop. However, this time we will also use the range() function to print the cities.

african_cities = ["Dakar", "Addis Ababa", "Nairobi", "Cairo", "Rabat", "Kampala"]
for idx in range(len(african_cities)):
    print(f"{african_cities[idx]} is at index {idx} in the list.")
Dakar is at index 0 in the list.
Addis Ababa is at index 1 in the list.
Nairobi is at index 2 in the list.
Cairo is at index 3 in the list.
Rabat is at index 4 in the list.
Kampala is at index 5 in the list.

Let’s see what is different now:

  1. You can see that because we are using the range() function, the value assigned to the loop variable idx starts with 0 and increases by 1 each time through the loop.

  2. In order to access individual cities in the african_cities list, we need to include the index value (e.g., african_cities[idx]. Here, the variable idx will be replaced by the current value assigned from the range() function.

Note

While the variable i is commonly used to denote the index variable in loops, a good coding practice often involves using meaningful and descriptive variable names to enhance code readability. For example, if you are iterating over elements in an array representing a number of items, you might use itemIdx or idx instead of i. If you are working with matrices, you could use rowIdx and colIdx instead of i and j. For nested loops, consider using more descriptive names like outerIdx and innerIdx.

1.6.2 Why use index values to loop over a list?#

Good question. There are two common case where you might need to loop using index values:

  1. If you want to update individual values in a list you’re likely going to need a loop that includes the index values. There are functions such as enumerate() as shown below that can help, but their use can be somewhat confusing for new programmers.

  2. In cases where you have multiple lists that are related to one another, it can be handy to use a loop with the index values to be able to access corresponding locations in each list. For this, let’s consider an example with the two lists below.

Check your understanding#

What output would the following program produce?

odd_numbers = [1, 3, 5, 7, 9]
even_numbers = [10, 4, 6, 8, 2]
for idx in range(len(odd_numbers)):
    print(odd_numbers[idx]*3 + even_numbers[idx]*5)

Try to think about the loop without running the code

Hide code cell content
# Here's the solution
odd_numbers = [1, 3, 5, 7, 9]
even_numbers = [10, 4, 6, 8, 2]
for idx in range(len(odd_numbers)):
    print(odd_numbers[idx]*3 + even_numbers[idx]*5)
53
29
45
61
37

Try to do this one. As you can see we have 5 cities and 5 corresponding counties. Print out each pair using a single for loop and print the output to screer to look like this

Washington D.C. is the capital of USA

for each city.

cities = ["Washington D.C.", "Berlin", "Cairo", "Sydney", "Kathmandu"]
countries = ["USA", "Germany", "Egypt", "Australia", "Nepal"]
for idx in range(len(cities)):
    print(f"{cities[idx]} is the capital of {countries[idx]}")
Washington D.C. is the capital of USA
Berlin is the capital of Germany
Cairo is the capital of Egypt
Sydney is the capital of Australia
Kathmandu is the capital of Nepal

Cool. So as you can see, the index idx is used in this case to access each item in the two lists of cities and countries and allow us to print out the city/country pairs. We’ll get more practice with this kind of thing in the exercises for this week.

Note

In the example above, we used the length of the list cities or countries in the range() function since both lists are the same length.

1.7 Loop with zip - no idexing required#

You can use zip function to iterate over multiple lists simultaneously, something like this

for variable1, variable2 in zip(list1, list2):
    use variable1, variable2 to do something

Try the above sample using zip instead of indexing.

cities = ["Washington D.C.", "Berlin", "Cairo", "Sydney", "Kathmandu"]
countries = ["USA", "Germany", "Egypt", "Australia", "Nepal"]
for city, country in zip(cities, countries):
    print(f"{city} is the capital of {country}")
Washington D.C. is the capital of USA
Berlin is the capital of Germany
Cairo is the capital of Egypt
Sydney is the capital of Australia
Kathmandu is the capital of Nepal

In that case zip is pairing corresponding elements from cities and countries, and you can iterate over these pairs in the for loop. Using zip is more pythonic. Using indexing is more like MATLAB.

1.8 Looping over the length of list using enumerate() (advanced topic)#

If you want to use python as python (not as MATLAB) instead of using range(len()) use the enumerate() function. Here we do the above example but with enumerate() instead of range(len()).

1.8.1 Understanding enumerate() step-by-step#

Before we use enumerate() let use first understand what it does with this example

african_cities = ["Dakar", "Addis Ababa", "Nairobi", "Cairo", "Rabat", "Kampala"]
for object in enumerate(african_cities):
    print(object)
    print(f"{african_cities[object[0]]} is at index {object[1]} in the list.")
(0, 'Dakar')
Dakar is at index Dakar in the list.
(1, 'Addis Ababa')
Addis Ababa is at index Addis Ababa in the list.
(2, 'Nairobi')
Nairobi is at index Nairobi in the list.
(3, 'Cairo')
Cairo is at index Cairo in the list.
(4, 'Rabat')
Rabat is at index Rabat in the list.
(5, 'Kampala')
Kampala is at index Kampala in the list.

In case you are wondering, the above object is a tuple, which is a data strcuture similar to list, that is defined using parentheses, and can contain elements of different data types. Other data strctures in python include dict and set. Check Chapter 3. Built-In Data Structures, Functions, and Files in your textbook if you want to learn more about data strctures in python.

1.8.2 How to use enumerate() to loop over a list?#

The above example is still more or less like how you will do it in MATLAB, but to do this in python style:

african_cities = ["Dakar", "Addis Ababa", "Nairobi", "Cairo", "Rabat", "Kampala"]
for idx,city in enumerate(african_cities):
    print(f"{idx} is at index {city} in the list.")
0 is at index Dakar in the list.
1 is at index Addis Ababa in the list.
2 is at index Nairobi in the list.
3 is at index Cairo in the list.
4 is at index Rabat in the list.
5 is at index Kampala in the list.

Much simpler than MATLAB style as shown in Section 1.6. But it might take you sometime to migrate from coding the MATLAB way to the python way.

1.9 Using list and dictionary comprehensions (advanced topic)#

Comprehensions in python provide a concise and efficient way to create lists and dictionaries. They are particularly useful when dealing with large datasets.

1.9.1 The syntax and concept of list comprehensions#

A list comprehension consists of brackets containing an expression followed by a for clause, then zero or more for or if clauses. The result will be a new list resulting from evaluating the expression in the context of the for and if clauses which follow it.

[expression for item in list if condition]

Example, let us create a list of 5 positive integers and call it list1

list1=[value for value in range(1,6)]
print(list1)
[1, 2, 3, 4, 5]

let us create this list [2, 4, 6, 8] by at least three different ways

print([value*2 for value in range(1,5)])
print([value for value in range(2,10,2)])
print([value for value in range(2,10,2) if value % 2 == 0])  
[2, 4, 6, 8]
[2, 4, 6, 8]
[2, 4, 6, 8]

You are familiar with the first two methods. I am adding the third method to make you curious. We will learn the if condition in the next lesson. But you are probably wondering what is this % ? The % operator in python is the modulus operator. It returns the remainder when the left operand is divided by the right operand. In the context of if value % 2 == 0, it is checking if the variable value is even. Here is the breakdown: value % 2 calculates the remainder when value is divided by 2, and value % 2 == 0 checks if the remainder is equal to 0, which means that value is divisible evenly by 2. So, if value % 2 == 0 is a common way to test for even numbers.

Test your understanding#

Use list comprehension to convert a list of temperatures in Celsius to Fahrenheit.

fahrenheit_temp = (celsius_temp * 9/5) + 32

remember here is the formula

[expression for item in iterable]

To think about this: Identify your expression, item, and iterable

celsius_temperatures = [-50, -40, -30, -20, -10, 0, 5, 10, 15, 20, 25, 30, 35, 40, 40, 45, 50, 56.7]

fahrenheit_temperatures= [(celsius_temperature * 9/5) + 32 for celsius_temperature in celsius_temperatures]
print(fahrenheit_temperatures)
[-58.0, -40.0, -22.0, -4.0, 14.0, 32.0, 41.0, 50.0, 59.0, 68.0, 77.0, 86.0, 95.0, 104.0, 104.0, 113.0, 122.0, 134.06]

Note that 134°F (56.7°C), which was recorded in Death Valley, California on July 10, 1913, is the highest recorded temperature on Earth. The lowest temperature ever recorded in Florida is -2°F, which was recorded in Tallahassee on February 13, 1899.

1.9.2 Dictionary Comprehensions#

A concept similar to list comprehensions, is dictionary comprehensions that allow you to create dictionaries in a single, readable line.

Before we learn how to do this, what is a python dictionary ? We learned about the list data structure (e.g., mylist=[1,2,3]). Python dictionary dic is another python data structure that stores key-value pairs. Each key in the dictionary must be unique, and it is associated with a corresponding value. Dictionaries are defined within braces {} with each item being a pair in the form key: value.

Here’s a brief example of a Python dictionary:

# Creating a dictionary
city_data = {
    'city': 'Fort Myers',
    'temperature': 40.5,
    'humidity': 60,
    'air_quality': 'good'
}

# Accessing values using keys
print("City:", city_data['city'])
print("Temperature:", city_data['temperature'])
print("Humidity:", city_data['humidity'])
print("Air Quality:", city_data['air_quality'])
City: Fort Myers
Temperature: 40.5
Humidity: 60
Air Quality: good

So dictionary is another form of storing data in python like a list. In the above example, we created the dictionary city_data manually. Now hwo to create a dictionary with dictionary comprehensions? Here is one syntax

{key: value for key, value in zip(keys,values)}

Try it out with the following example:

# Key parameters and corresponding values for a location
parameters = ['City', 'Temperature', 'Humidity', 'Air Quality']
values = ['Fort Myers', '40.5', '60','good']

# Create a dictionary mapping each parameter to its value
city_data={parameter : value for parameter, value in zip(parameters, values)}
print(city_data)
{'City': 'Fort Myers', 'Temperature': '40.5', 'Humidity': '60', 'Air Quality': 'good'}

You can also create a dictionary from lists. Here is an example:

#Multiple lists 
models=['A', 'B', 'C', 'D']
launched=[1983,1984,1984,1984]
discontinued=[1986, 1985, 1984, 1986]

#Dictionary with list comprehension (the easy way flowing the syntax above)
keys=['models','launched', 'discontinued']
values=[models, launched, discontinued]
data1 = {key: value for key, value in zip(keys,values)}

#Dictionary with list comprehension (another way of doing it)
data2 = {key:values[idx] for idx, key in enumerate(keys)}

#Printing some outputs
print(data1['models'][0])
print(data1['models'])
print(data1)
print(data2)
A
['A', 'B', 'C', 'D']
{'models': ['A', 'B', 'C', 'D'], 'launched': [1983, 1984, 1984, 1984], 'discontinued': [1986, 1985, 1984, 1986]}
{'models': ['A', 'B', 'C', 'D'], 'launched': [1983, 1984, 1984, 1984], 'discontinued': [1986, 1985, 1984, 1986]}

1.9.3 Challaning problem#

This is a challanging problem where you can apply what you have learned in Lesson 4 about the method list.count(), list comprehesions, and diconary comprehesions. You need to apply these three tools to solve this problem.

Suppose you have a dataset that includes species names and their counts

species = ['Oak', 'Pine', 'Birch', 'Oak', 'Pine', 'Oak']

How can you count each species and produce a dictionary showning each species with its corresponding count as follows:

Output: {'Oak': 3, 'Pine': 2, 'Birch': 1}

This is not an easy puzzle, but you already have a clue about the three tools that you can use. Start by remembering what list.count() can do.

species = ['Oak', 'Pine', 'Birch', 'Oak', 'Pine', 'Oak']
species.count('Oak')
3

Can I loop over the list species can keep counting. Something like this

[expression for item in list]

In that case my expression will be the counting.

species = ['Oak', 'Pine', 'Birch', 'Oak', 'Pine', 'Oak']
counts = [species.count(name) for name in species]
print(counts)
[3, 2, 1, 3, 2, 3]

Now remeber each key in the dictionary must be unique, so if we created a dictionary using the two lists that we have that are species and counts, we will get what we want. Here is the dictionary syntax again

{key: value for key, value in zip(keys,values}

Apply this syntax with the two lists that you have, to create an new dictionary species_dict

species_dict = {name: count for name, count in zip(species, counts)}
print(species_dict)
{'Oak': 3, 'Pine': 2, 'Birch': 1}

If you can understand that much, this means that you have a very good command of python.

So far you have learned how to use list and dictionary comprehensions to create lists and dictionaries efficiently. These techniques are valuable for handling and processing large datasets, allowing us to perform data transformations and analysis.

1.10 Using for-loop to apply a mathematical formula to a list#

We will do this later more effectively with the numpy library similar to MATLAB. Here we will use for-loops to perform calculations on elements of a list and store the results in a new list. We will see this with both explicit for-loop and implicit for-loop (i.e., list comprehensions).

1.10.1 Using explicit for-loop#

We want to create a new list new_list based on values from another list existing_list and a formula. To do this:

  • create new empty list new_list = []

  • use for-loop to calculate formula element by element

  • append new_list with the calculated value using append method

For example, apply the formua y= x * 3 to each element in this list

existing_list=[2, 4, 6, 8]

such the the output will be

output: new_list=[6, 12, 18, 24]

Here is how to do this.`

#One list
existing_list=[2, 4, 6, 8]

#Create new empty list "new_list"
new_list = []

#for loop to apply formula y= x* 3 to the existing list element by element and save output to the new list
for value in existing_list:
    y=value*3             #calculate formula
    new_list.append(y)    #append list with calculated value

#Print new list
print(new_list)
[6, 12, 18, 24]

1.10.2 Using list comprehension (advanced topic)#

List comprehension can achieve the same result without explicitly writing a loop. Here is the syntax again

new_list=[expression for item in existing_list]

Here’s how you can do it:

#One list
existing_list=[2, 4, 6, 8]

# Apply formula y= x * 3  using list comprehension
new_list = [value*3 for value in existing_list]

# Print new list 
print(new_list)
[6, 12, 18, 24]

This code does exactly the same thing as the previous one, but it does so in a more concise way by using a list comprehension instead of a for loop.

1.11 For loop with unpacking#

How can you loop through a nested list that has four sublists.

list_of_lists=[
          [10, 100, 1000],
          [20, 200, 2000],
          [40, 500, 6000],
          [50, 500, 5000],
]

You have many opitions. You can do indexing, or unpacking for the sublist.

For example, add the three numbers in each of the above four sublists.

list_of_lists=[
          [10, 100, 1000],
          [20, 200, 2000],
          [40, 400, 4000],
          [50, 500, 5000],
]

This is the indexing method (the MATLAB way)

for sublist in list_of_lists:
    sum= sublist[0]+ sublist[1]+ sublist[2]
    print(sum)
1110
2220
4440
5550

This is the unpacking method

for sublist in list_of_lists:
    num1, num2, num3 = sublist
    sum= num1 + num2 + num3
    print(sum)
1110
2220
4440
5550

Unpacking, more concisely

for num1,num2,num3 in list_of_lists:
    sum= num1 + num2 + num3
    print(sum)
1110
2220
4440
5550

You can also do a nested loop that is a loop inside a loop, but that should be your last resort, when no other approach can solve your problem.

2. Intorduction to while loop#

Control structures in python are loops (i.e, for-loop and while-loop) and conditional statements (next lesson). While for loop loops a collection of values, while loop loops until some condition is met. Here is a simple example.

x = 0
while x < 5:
    print('x is ', x)
    x = x+2
x is  0
x is  2
x is  4

Note: In Python, incrementing the value of x by 2, you will see the syntax x += 2 used more often than x = x+2 as shown above. The reason for using += is not just brevity, but it can also saves some memory, making the code slightly more efficient. Let us rewrite the above code to be more pythonic

x = 0
while x < 5:
    print('x is ', x)
    x += 2
x is  0
x is  2
x is  4

References#