Geo-Python review

It has been requested that we review a few topics covered in the Geo-Python part of this course. This notebook presents the main topics and notes/examples generated during our discussion in class.

for loops

for loops are used to iterate over a collection of items and perform calculations using that collection. The collection could be something like a Python list, a NumPy array, or some other structure.

The structure of the loop is:

for variable in collection:
    code
    to
    execute

where variable is a normal Python variable that will be assigned a value from collection sequentially as the loop is executed. All of the lines indented beneath the for statement contain code that will be run each time a new value from collection is assigned to variable.

Direct iteration

weather_conditions = ["Cloudy", "Snowing", "Rainy", "Windy"]

for weather in weather_conditions:
    print(f"Today the weather is {weather}.")
Today the weather is Cloudy.
Today the weather is Snowing.
Today the weather is Rainy.
Today the weather is Windy.

Iteration using the range() function

# Example using index values
temperatures = ["Cold", "Unpleasant", "Blustery", "Cool"]

for i in range(len(temperatures)):
    print(f"Today the weather is {temperatures[i]}.")
Today the weather is Cold.
Today the weather is Unpleasant.
Today the weather is Blustery.
Today the weather is Cool.
# Using index values to access items from 2 lists
for i in range(len(temperatures)):
    print(f"Today the weather is {temperatures[i]} and {weather_conditions[i]}.")
Today the weather is Cold and Cloudy.
Today the weather is Unpleasant and Snowing.
Today the weather is Blustery and Rainy.
Today the weather is Cool and Windy.

Nested loops

# Not covered in today's lesson

Other loop notes

# None

Functions

Functions are designed to be used to perform calculations that are frequently used in a program. One of the key ideas of a function is to avoid having to repeat code within a program, as it is possible that repeating code may introduct typographic errors and it can be cumbersome to update the code if the same thing occurs in multiple places in a program.

The syntax of a function in Python is below:

def function_name(parameter1, parameter2=0.0, ...):
    code
    to
    execute
    return return_value

where function_name is the name of the function, and parameter1 and parameter2 are parameters (or the names of variables used within the function), default2 is a default value assigned to a function parameter, the indented code is that executed within the function, and the return statement lists the value(s) that will be returned when the function is used. Note that parameters with default values do not need to be listed when calling the function, and when defining the function the parameters without a default value must be listed first in the parameter list.

Function examples

def family_name_printer(first_name, last_name="Whipp"):
    print(f"Hello {first_name} {last_name}!")
    
    return 0
# This is OK
# Note: the function prints something and then the return value is displayed
family_name_printer("Dave")
Hello Dave Whipp!
0
# This is also OK
family_name_printer(first_name="Dave")
Hello Dave Whipp!
0
# This too is OK
family_name_printer(first_name="Dave", last_name="Smith")
Hello Dave Smith!
0
# So is this
family_name_printer("Dave", last_name="Smith")
Hello Dave Smith!
0
# This is not OK because "Dave" is ambiguous
# Is that value meant to be the second parameter or assigned to first_name?
# Python cannot tell, so you need to include the parameter name
family_name_printer(last_name="Smith", "Dave")
  File "/tmp/ipykernel_280/2272524676.py", line 4
    family_name_printer(last_name="Smith", "Dave")
                                          ^
SyntaxError: positional argument follows keyword argument
# Like this, all is OK
family_name_printer(last_name="Smith", first_name="Dave")
Hello Dave Smith!
0
# Note that first_name is not a variable available outside of the function
print(first_name)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_280/1852285805.py in <module>
      1 # Note that first_name is not a variable available outside of the function
----> 2 print(first_name)

NameError: name 'first_name' is not defined
# Unless we define it first
first_name = "John"
print(first_name)
John
# Note that this does not affect the function use if first_name is assigned in the call
family_name_printer(first_name="Fred", last_name="Smith")
Hello Fred Smith!
0
# Let's define a middle_name variable
middle_name = "Michael"
# And we can modify our function to return middle_name
# NOTE: We do not pass in middle_name or define it in our function
def family_name_printer(first_name, last_name="Whipp"):
    print(f"Hello {first_name} {last_name}!")
    
    return middle_name
# Now look what is returned when the function is used
# Be careful!
family_name_printer(first_name="Fred", last_name="Smith")
Hello Fred Smith!
'Michael'
# Let's make a modified function where the string is returned rather than printed
def family_name_printer2(first_name, last_name="Whipp"):

    return f"Hello {first_name} {last_name}!"
# Let's assign the returned value to a variable
name_output = family_name_printer2("Dave")
# And print...nice!
print(name_output)
Hello Dave Whipp!
# New function to add two to a number
def add_two(number):
    """Adds two to a number (duh)"""
    return number + 2
# It works!
new_number = add_two(4)
print(new_number)
6
# But we can break it
# Note: This is a good example of a Pythonic way to have your code fail
# We let Python identify the problem, rather than checking that a number was passed
# into the function
new_number = add_two("Dave")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_280/3922626930.py in <module>
      3 # We let Python identify the problem, rather than checking that a number was passed
      4 # into the function
----> 5 new_number = add_two("Dave")

/tmp/ipykernel_280/1215093505.py in add_two(number)
      2 def add_two(number):
      3     """Adds two to a number (duh)"""
----> 4     return number + 2

TypeError: can only concatenate str (not "int") to str
# Having fun with formatting output :D
print(3 * '*' + 72 * '-' + 3 * '*')
***------------------------------------------------------------------------***

Namespaces and functions

# Covered above

Other function notes

# None

When to use which brackets

A common point of confusion in Python relates to when to use which kinds of brackets. There are three different types of brackets that are commonly used, and each are used for different purposes. Below you can find examples of each.

Parentheses - ( and )

Mathematical order of operations

Of course, the most natural example familiar to you would be to use parentheses in a mathematical equation to determine the order in which calculations should be done. Let’s consider an example.

# Parentheses matter
# Add 3 and 2, then divide by 2
(3 + 2) / 2
2.5
# Not equal to above!
# Divide 2 by 2, add 3 to result
3 + 2 / 2
4.0

Here, the parentheses determine the order in which the math operations occur and will have important implications for the resulting calculated value.

Defining and calling functions

When functions are defined and called, you also use parentheses to give the list of parameters.

# Functions use parentheses too
def add_divide(number1, number2, number3):
    return (number1 + number2) / number3
# Same as the example above
add_divide(3, 2, 2)
2.5

Tuples

We have not dealt much with tuples so far, but they are similar to Python lists with one important difference: Tuples are immutable, meaning they cannot be changed after they are defined. Let’s see an example.

# Make an example tuple
my_tuple = (3.0, "Dave", True, 1)
# Check the data type
type(my_tuple)
tuple
# Tuples cannot be modified, this will fail!
my_tuple[1] = "John"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_280/1030788979.py in <module>
      1 # Tuples cannot be modified, this will fail!
----> 2 my_tuple[1] = "John"

TypeError: 'tuple' object does not support item assignment
# Cannot append (modify) a tuple!
my_tuple.append('Whipp')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_280/2694787715.py in <module>
      1 # Cannot append (modify) a tuple!
----> 2 my_tuple.append('Whipp')

AttributeError: 'tuple' object has no attribute 'append'

Square brackets - [ and ]

Python lists

As you have seen, the square brackets are used to create Python lists.

# Make a list
my_list = [3.0, "Dave", True, 1]
# Check the data type
type(my_list)
list
# Lists CAN be modified
my_list[1] = "John"
# Confirm list has changed
print(my_list)
[3.0, 'John', True, 1]

Accessing values using index values

Another common use of the square brackets in Python is to access values from Python lists, tuples, or other collections using an index value.

# Check a value in our list
my_list[1]
'John'
# Check that values in the list and tuple are the same at the same position...yes!
my_list[3] == my_tuple[3]
True
# Check that values in the list and tuple are the same at the same position...no!
# Names were changed
my_list[1] == my_tuple[1]
False

Curly braces - { and }

Dictionaries

We have only dealt with dictionaries to a limited extent so far, but they can be used to relate a “key” in the dictionary to a “value”, as shown below.

dictionary_name = {key1: value1, key2: value2, ...}

where dictionary_name is the name of the dictionary, and the keys (key1, key2) are listed and followed by a : character that is followed by a corresponding value (value1, value2). This allows you to find a value using its associated key. This may be easiest to understand through an example.

# Create a dictionary
my_dict = {"Helsinki": "Cloudy", "Hyvinkää": "Snowy"}
# Check the data type
type(my_dict)
dict
# Use a key to access a value
my_dict["Hyvinkää"]
'Snowy'

Sets

Sets are yet another type of collection in Python, however they are slightly different from lists and tuples. In a Python set you can create a collection, but one that is not indexed and cannot contain duplicate values. Let’s look at an example again.

# Create a set
my_set = {3.0, "Dave", 1, 2}
# Check the data type
type(my_set)
set
# Sets are unindexed. Cannot use indices to get values!
my_set[1]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_280/3507569771.py in <module>
      1 # Sets are unindexed. Cannot use indices to get values!
----> 2 my_set[1]

TypeError: 'set' object is not subscriptable
# Check set contents
print(my_set)
{1, 2, 3.0, 'Dave'}
# Create another set
my_set = {3.0, "Dave", 1, True, "Dave"}
# Check set contents. What???
# Sets do not allow duplicate values, so the name "Dave" can only occur once
# We have also guessed that True is converted to 1 and thus does not appear separately!
# Often 0 = False and 1 = True in programming languages
print(my_set)
{1, 3.0, 'Dave'}

F-strings

# F-strings also use curly braces to enclose variables
print(f"My name is {first_name}.")
My name is John.

Other notes

Looping over dictionary keys and values

Dictionaries are quite handy data structures in Python, and often there is a need to use loops to find the values for each key in the dictionary. Below is a silly example.

animal_sound = {"dog": "woof", "cat": "meow", "bird": "chirp"}

for key, value in animal_sound.items():
    print(f"The {key} goes {value}.")
The dog goes woof.
The cat goes meow.
The bird goes chirp.

Checking variables defined in memory

You can check to see what is defined in the notebook by running %whos.

%whos
Variable               Type        Data/Info
--------------------------------------------
add_divide             function    <function add_divide at 0x7f96f0150200>
add_two                function    <function add_two at 0x7f96f0191b00>
animal_sound           dict        n=3
family_name_printer    function    <function family_name_printer at 0x7f96f01915f0>
family_name_printer2   function    <function family_name_printer2 at 0x7f96f0191440>
first_name             str         John
i                      int         3
key                    str         bird
middle_name            str         Michael
my_dict                dict        n=2
my_list                list        n=4
my_set                 set         {1, 3.0, 'Dave'}
my_tuple               tuple       n=4
name_output            str         Hello Dave Whipp!
new_number             int         6
temperatures           list        n=4
value                  str         chirp
weather                str         Windy
weather_conditions     list        n=4