QuantEcon DataScience

Introduction to Economic Modeling and Data Science

Control Flow

Prerequisites

Outcomes

  • Asset pricing and NPV

    • Understand basic principles of pricing assets with deterministic payoffs
    • Apply programming with iteration and conditionals to asset pricing examples
  • Conditionals

    • Understand what a conditional is
    • Be able to construct if/elif/else conditional blocks
    • Understand how conditionals can be used to selectively execute blocks of code
  • Iteration

    • Understand what an iterable is
    • Be able to write for and while loops
    • Understand the keywords break and continue

Net Present Values and Asset Pricing

In this lecture, we’ll introduce two related topics from economics:

  • Net present valuations
  • Asset pricing

These topics will motivate some of the programming we do in this course.

In economics and finance, “assets” provide a stream of payoffs.

These “assets” can be concrete or abstract: a stock pays dividends over time, a bond pays interest, an apple tree provides apples, a job pays wages, and an education provides possible jobs (which, in turn, pay wages).

When deciding the price to pay for an asset or how to choose between different alternatives, we need to take into account that most people would prefer to receive 1 today vs. 1 next year.

This reflection on consumer preferences leads to the notion of a discount rate. If you are indifferent between receiving 1.00 today and 1.10 next year, then the discount rate over the next year is $ r = 0.10 $.

If we assume that an individuals preferences are consistent over time, then we can apply that same discount rate to valuing assets further into the future.

For example, we would expect that the consumer would be indifferent between consuming 1.00 today and $ (1+r)(1+r) = 1.21 $ dollars two years from now (i.e. discount twice).

Inverting this formula, 1 delivered two years from now is equivalent to $ \frac{1}{(1+r)^2} $ today.

See exercise 1 in the exercise list

In [1]:
# your code here

Net Present Value

If an asset pays a stream of payoffs over multiple time periods, then we can use a discount rate to calculate the value to the consumer of a entire sequence of payoffs.

Most generally, we enumerate each discrete time period (e.g. year, month, day) by the index $ t $ where today is $ t=0 $ and the asset lives for $ T $ periods.

List the payoff at each time period as $ y_t $, which we will assume, for now, is known in advance.

Then if the discount factor is $ r \geq 0 $, the consumer “values” the payoff $ y_t $ delivered at time $ t $ as $ \frac{1}{(1+r)^t}y_t $ where we note that if $ t=0 $, the value is just the current payoff $ y_0 $.

Using this logic, we can write an expression for the value of the entire sequence of payoffs with a sum.

$$ P_0 = \sum_{t=0}^T \left(\frac{1}{1 + r}\right)^t y_t \tag{1} $$

If $ y_t $ is a constant, then we can compute this sum with a simple formula!

Below, we present some useful formulas that come from infinite series that we will use to get our net present value formula.

For any constant $ 0 < \beta < 1 $ and integer value $ \tau > 0 $,

$$ \begin{aligned} \sum_{t=0}^{\infty} \beta^t & = \frac{1}{1-\beta}\\ \sum_{t=0}^{\tau} \beta^t &= \frac{1- \beta^{\tau+1}}{1-\beta}\\ \sum_{t=\tau}^{\infty} \beta^t &= \frac{\beta^{\tau}}{1-\beta} \end{aligned} \tag{2} $$

In the case of an asset which pays one dollar until time $ T $, we can use these formulas, taking $ \beta = \frac{1}{1+r} $ and $ T = \tau $, to find

$$ \begin{aligned} P_0 &= \sum_{t=0}^T \left(\frac{1}{1 + r}\right)^t = \frac{1- (\frac{1}{1+r})^{\tau+1}}{1-\frac{1}{1+r}}\\ &= \frac{1 + r}{r} - \frac{1}{r}\left(\frac{1}{1+r} \right)^\tau \end{aligned} $$

Note that we can also consider an asset that lives and pays forever if $ T= \infty $, and from (2), the value of an asset which pays 1 forever is $ \frac{1+r}{r} $.

Conditional Statements and Blocks

Sometimes, we will only want to execute some piece of code if a certain condition is met.

These conditions can be anything.

For example, we might add to total sales if the transaction value is positive, but add to total returns if the value is negative.

Or, we might want to add up all incurred costs, only if the transaction happened before a certain date.

We use conditionals to run particular pieces of code when certain criterion are met.

Conditionals are closely tied to booleans, so if you don’t remember what those are, go back to the basics lecture for a refresher.

The basic syntax for conditionals is

if condition:
    # code to run when condition is True
else:
    # code to run if no conditions above are True

Note that immediately following the condition, there is a colon and that the next line begins with blank spaces.

Using 4 spaces is a very strong convention, so that is what we do — we recommend that you do the same.

Also note that the else clause is optional.

Let’s see some simple examples.

In [2]:
if True:
    print("This is where `True` code is run")
This is where `True` code is run

Alternatively, you could have a test which returns a booleans

In [3]:
if 1 < 2:
     print("This is where `True` code is run")
This is where `True` code is run

This example is equivalent to just typing the print statement, but the example below isn’t…

In [4]:
if False:
    print("This is where `True` code is run")

Or

In [5]:
if 1 > 2:
     print("This is where `True` code is run")

Notice that when you run the cells above nothing is printed.

That is because the condition for the if statement was not true, so the code inside the indented block was never run.

This also allows us to demonstrate the role of indentation in determining the “block” of code.

In [6]:
val = False

if val is True: # check an expression
    print("This is where `True` code is run")
    print("More code in the if block")
print("Code runs after 'if' block, regardless of val")
Code runs after 'if' block, regardless of val

See exercise 2 in the exercise list

The next example shows us how else works.

In [7]:
val = (2 == 4)  # returns False
if val is True:
    print("This is where `True` code is run")
else:
    print("This is where `False` code is run")
    print("More else code")
print("Code runs after 'if' block, regardless of val")
This is where `False` code is run
More else code
Code runs after 'if' block, regardless of val

The if False: ... part of this example is the same as the example before, but now, we added an else: clause.

In this case, because the conditional for the if statement was not True, the if code block was not executed, but the else block was.

Finally, the Condition is True is assumed in the if statement, and is often left out. For example, the following are identical

In [8]:
if (1 < 2) is True:
    print("1 < 2")

if 1 < 2:
    print("1 < 2")
1 < 2
1 < 2

See exercise 3 in the exercise list

See exercise 4 in the exercise list

elif clauses

Sometimes, you have more than one condition you want to check.

For example, you might want to run a different set of code based on which quarter a particular transaction took place in.

In this case you could check whether the date is in Q1, or in Q2, or in Q3, or if not any of these it must be in Q4.

The way to express this type of conditional is to use one or more elif clause in addition to the if and the else.

The syntax is

if condition1:
    # code to run when condition1 is True
elif condition2:
    # code to run when condition2 is True
elif condition3:
    # code to run when condition3 is True
else:
    # code to run when none of the above are true

You can include as many elif clauses as you want.

As before, the else part is optional.

Here’s how we might express the quarter example referred to above.

In [9]:
import datetime
halloween = datetime.date(2017, 10, 31)

if halloween.month > 9:
    print("Halloween is in Q4")
elif halloween.month > 6:
    print("Halloween is in Q3")
elif halloween.month > 3:
    print("Halloween is in Q2")
else:
    print("Halloween is in Q1")
Halloween is in Q4

Note that when there are multiple if or elif conditions, only the code corresponding to the first true clause is run.

We saw this in action above.

We know that when halloween.month > 9 is true, then halloween.month > 6 and halloween.month > 3 must also be true, but only the code block associated with halloween.month > 9 was printed.

Iteration

When doing computations or analyzing data, we often need to repeat certain operations a finite number of times or until some condition is met.

Examples include processing all data files in a directory (folder), aggregating revenues and costs for every period in a year, or computing the net present value of certain assets. (In fact, later in this section, we will verify the equations that we wrote down above.)

These are all examples of a programming concept called iteration.

We feel the concept is best understood through example, so we will present a contrived example and then discuss the details behind doing iteration in Python.

A Contrived Example

Suppose we wanted to print out the first 10 integers and their squares.

We could do something like this.

In [10]:
print(f"1**2 = {1**2}")
print(f"2**2 = {2**2}")
print(f"3**2 = {3**2}")
print(f"4**2 = {4**2}")
# .. and so on until 10
1**2 = 1
2**2 = 4
3**2 = 9
4**2 = 16

As you can see, the code above is repetitive.

For each integer, the code is exactly the same except for the two places where the “current” integer appears.

Suppose that I asked you to write the same print statement for an int stored in a variable named i.

You might write the following code:

print(f"{i}**2 = {i**2}")

This more general version of the operation suggests a strategy for achieving our goal with less repetition: have a variable i take on the values 1 through 10 (Quiz: How can we use range to create the numbers 1 to 10?) and run the line of code above for each new value of i.

This can be accomplished with a for loop!

In [11]:
for i in range(1, 11):
     print(f"{i}**2 = {i**2}")
1**2 = 1
2**2 = 4
3**2 = 9
4**2 = 16
5**2 = 25
6**2 = 36
7**2 = 49
8**2 = 64
9**2 = 81
10**2 = 100

Whoa, what just happened?

The integer i took on the values in range(1, 11) one by one and for each new value it did the operations in the indented block (here just one line that called the print function).

for Loops

The general structure of a standard for loop is as follows.

for item in iterable:
   # operation 1 with item
   # operation 2 with item
   # ...
   # operation N with item

where iterable is anything capable of producing one item at a time (see here for official definition from the Python team).

We’ve actually already seen some of the most common iterables!

Lists, tuples, dicts, and range/zip/enumerate objects are all iterables.

Note that we can have as many operations as we want inside the indented block.

We will refer to the indented block as the “body” of the loop.

When the for loop is executed, item will take on one value from iterable at a time and execute the loop body for each value.

See exercise 5 in the exercise list

When iterating, each item in iterable might actually contain more than one value.

Recall that tuples (and lists) can be unpacked directly into variables.

In [12]:
tup = (4, "test")
i, x = tup
print(f"i = {i}, x = {x}, tup = {tup}")
i = 4, x = test, tup = (4, 'test')

Also, recall that the value of a enumerate(iterable) is a tuple of the form (i, x) where iterable[i] == x.

When we use enumerate in a for loop, we can “unpack” both values at the same time as follows:

In [13]:
# revenue by quarter
company_revenue = [5.12, 5.20, 5.50, 6.50]

for index, value in enumerate(company_revenue):
    print(f"quarter {index} revenue is ${value} million")
quarter 0 revenue is $5.12 million
quarter 1 revenue is $5.2 million
quarter 2 revenue is $5.5 million
quarter 3 revenue is $6.5 million

Similarly, the index can be used to access another vector.

In [14]:
cities = ["Phoenix", "Austin", "San Diego", "New York"]
states = ["Arizona", "Texas", "California", "New York"]
for index, city in enumerate(cities):
    state = states[index]
    print(f"{city} is in {state}")
Phoenix is in Arizona
Austin is in Texas
San Diego is in California
New York is in New York

See exercise 6 in the exercise list

while Loops

A related but slightly different form of iteration is to repeat something until some condition is met.

This is typically achieved using a while loop.

The structure of a while loop is

while True_condition:
    # repeat these steps

where True_condition is some conditional statement that should evaluate to True when iterations should continue and False when Python should stop iterating.

For example, suppose we wanted to know the smallest N such that $ \sum_{i=0}^N i > 1000 $.

We figure this out using a while loop as follows.

In [15]:
total = 0
i = 0
while total <= 1000:
    i = i + 1
    total = total + i

print("The answer is", i)
The answer is 45

Let’s check our work.

In [16]:
# Should be just less than 1000 because range(45) goes from 0 to 44
sum(range(45))
Out[16]:
990
In [17]:
# should be between 990 + 45 = 1035
sum(range(46))
Out[17]:
1035

A warning: one common programming error with while loops is to forget to set the variable you use in the condition prior to executing. For example, take the following code which correctly sets a counter

In [18]:
i = 0

And then executes a while loop

In [19]:
while i < 3:
    print(i)
    i = i + 1
print("done")
0
1
2
done

No problems. But if you were to execute the above cell again, or another cell, the i=3 remains, and code is never executed (since i < 3 begins as False).

In [20]:
while i < 3:
    print(i)
    i = i + 1
print("done")
done

See exercise 7 in the exercise list

break and continue

break Out of a Loop

Sometimes we want to stop a loop early if some condition is met.

Let’s revisit the example of finding the smallest N such that $ \sum_{i=0}^N i > 1000 $.

Clearly N must be less than 1000, so we know we will find the answer if we start with a for loop over all items in range(1001).

Then, we can keep a running total as we proceed and tell Python to stop iterating through our range once total goes above 1000.

In [21]:
total = 0
for i in range(1001):
    total = total + i
    if total > 1000:
        break

print("The answer is", i)
The answer is 45

See exercise 8 in the exercise list

continue to the Next Iteration

Sometimes we might want to stop the body of a loop early if a condition is met.

To do this we can use the continue keyword.

The basic syntax for doing this is:

for item in iterable:
    # always do these operations
    if condition:
        continue

    # only do these operations if condition is False

Inside the loop body, Python will stop that loop iteration of the loop and continue directly to the next iteration when it encounters the continue statement.

For example, suppose I ask you to loop over the numbers 1 to 10 and print out the message “{i} An odd number!” whenever the number i is odd, and do nothing otherwise.

You can use continue to do this as follows:

In [22]:
for i in range(1, 11):
    if i % 2 == 0:  # an even number... This is modulus division
        continue

    print(i, "is an odd number!")
1 is an odd number!
3 is an odd number!
5 is an odd number!
7 is an odd number!
9 is an odd number!

See exercise 9 in the exercise list

Comprehension

Often, we will want to perform a very simple operation for every element of some iterable and create a new iterable with these values.

This could be done by writing a for loop and saving each value, but often using what is called a comprehension is more readable.

Like many Python concepts, a comprehension is easiest to understand through example.

Imagine that we have a list x with a list of numbers. We would like to create a list x2 which has the squared values of x.

In [23]:
x = list(range(4))

# Create squared values with a loop
x2_loop = []
for x_val in x:
    x2_loop.append(x_val**2)

# Create squared values with a comprehension
x2_comp = [x_val**2 for x_val in x]

print(x2_loop)
print(x2_comp)
[0, 1, 4, 9]
[0, 1, 4, 9]

Notice that much of the same text appears when we do the operation in the loop and when we do the operation with the comprehension.

  • We need to specify what we are iterating over – in both cases, this is for x_val in x.
  • We need to square each element x_val**2.
  • It needs to be stored somewhere – in x2_loop, this is done by appending each element to a list, and in x2_comp, this is done automatically because the operation is enclosed in a list.

We can do comprehension with many different types of iterables, so we demonstrate a few more below.

In [24]:
# Create a dictionary from lists
tickers = ["AAPL", "GOOGL", "TVIX"]
prices = [175.96, 1047.43, 8.38]
d = {key: value for key, value in zip(tickers, prices)}
d
Out[24]:
{'AAPL': 175.96, 'GOOGL': 1047.43, 'TVIX': 8.38}
In [25]:
# Create a list from a dictionary
d = {"AMZN": "Seattle", "TVIX": "Zurich", "AAPL": "Cupertino"}

hq_cities = [d[ticker] for ticker in d.keys()]
hq_cities
Out[25]:
['Seattle', 'Zurich', 'Cupertino']
In [26]:
import math

# List from list
x = range(10)

sin_x = [math.sin(x_val) for x_val in x]
sin_x
Out[26]:
[0.0,
 0.8414709848078965,
 0.9092974268256817,
 0.1411200080598672,
 -0.7568024953079282,
 -0.9589242746631385,
 -0.27941549819892586,
 0.6569865987187891,
 0.9893582466233818,
 0.4121184852417566]

See exercise 10 in the exercise list

Finally, we can use this approach to build complicated nested dictionaries.

In [27]:
gdp_data = [9.607, 10.48, 11.06]
years = [2013, 2014, 2015]
exports = [ {"manufacturing": 2.4, "agriculture": 1.5, "services": 0.5},
            {"manufacturing": 2.5, "agriculture": 1.4, "services": 0.9},
            {"manufacturing": 2.7, "agriculture": 1.4, "services": 1.5}]
data = zip(years, gdp_data,exports)
data_dict = {year : {"gdp" : gdp, "exports": exports} for year, gdp, exports in data}
print(data_dict)

# total exports by year
[data_dict[year]["exports"]["services"] for year in data_dict.keys()]
{2013: {'gdp': 9.607, 'exports': {'manufacturing': 2.4, 'agriculture': 1.5, 'services': 0.5}}, 2014: {'gdp': 10.48, 'exports': {'manufacturing': 2.5, 'agriculture': 1.4, 'services': 0.9}}, 2015: {'gdp': 11.06, 'exports': {'manufacturing': 2.7, 'agriculture': 1.4, 'services': 1.5}}}
Out[27]:
[0.5, 0.9, 1.5]

Exercises

Exercise 1

Government bonds are often issued as zero-coupon bonds meaning that they make no payments throughout the entire time that they are held, but, rather make a single payment at the time of maturity.

How much should you be willing to pay for a zero-coupon bond that paid 100 in 10 years with an interest rate of 5%?

(back to text)

Exercise 2

Run the following two variations on the code with only a single change in the indentation.

After, modify the x to print 3 and then 2, 3 instead.

In [28]:
x = 1

if x > 0:
    print("1")
    print("2")
print("3")
1
2
3
In [29]:
x = 1

if x > 0:
    print("1")
print("2") # changed the indentation
print("3")
1
2
3

(back to text)

Exercise 3

Using the code cell below as a start, print "Good afternoon" if the current_time is past noon.

Otherwise, do nothing.

(Hint: Write some conditional based on current_time.hour.)

import datetime
current_time = datetime.datetime.now()

## your code here

more text after

(back to text)

Exercise 4

In this example, you will generate a random number between 0 and 1 and then display "x > 0.5" or "x < 0.5" depending on the value of the number.

This also introduces a new package numpy.random for drawing random numbers (more in the randomness lecture).

In [30]:
import numpy as np
x = np.random.random()
print(f"x = {x}")

## your code here
x = 0.7052536895377153

(back to text)

Exercise 5

In economics, when an individual has some knowledge, skills, or education which provides them with a source of future income, we call it human capital.

When a student graduating from high school is considering whether to continue with post-secondary education, they may consider that it gives them higher paying jobs in the future, but requires that they don't begin working until after graduation.

Consider the simplified example where a student has perfectly forecastable employment and is given two choices:

  1. Begin working immediately and make 40,000 a year until they retire 40 years later.
  2. Pay 5,000 a year for the next 4 years to attend university, then get a job paying 50,000 a year until they retire 40 years after making the college attendance decision.

Should the student enroll in school if the discount rate is r = 0.05?

In [31]:
# Discount rate
r = 0.05

# High school wage
w_hs = 40_000

# College wage and cost of college
c_college = 5_000
w_college = 50_000

# Compute npv of being a hs worker

# Compute npv of attending college

# Compute npv of being a college worker

# Is npv_collegeworker - npv_collegecost > npv_hsworker

(back to text)

Exercise 6

Instead of the above, write a for loop that uses the lists of cities and states below to print the same "{city} is in {state}" using a zip instead of an enumerate.

Hint: try using zip

In [32]:
cities = ["Phoenix", "Austin", "San Diego", "New York"]
states = ["Arizona", "Texas", "California", "New York"]

# Your code here

(back to text)

Exercise 7

Companies often invest in training their employees to raise their productivity. Economists sometimes wonder why companies spend this money when this incentivizes other companies to hire their employees away with higher salaries since employees gain human capital from training?

Let's say that it costs a company 25,000 dollars to teach their employees Python, but it raises their output by 2,500 per month. How many months would an employee need to stay for the company to find it profitable to pay for their employees to learn Python if their discount rate is r = 0.01?

In [33]:
# Define cost of teaching python
cost = 25_000
r = 0.01

# Per month value
added_value = 2500

n_months = 0
total_npv = 0.0

# Put condition below here
while False: # (replace False with your condition here)
    n_months = n_months + 1  # Increment how many months they've worked

    # Increase total_npv

(back to text)

Exercise 8

Try to find the index of the first value in x that is greater than 0.999 using a for loop and break.

Hint: try iterating over range(len(x)).

In [34]:
x = np.random.rand(10_000)
# Your code here

(back to text)

Exercise 9

Write a for loop that adds up all values in x that are greater than or equal to 0.5.

Use the continue word to end the body of the loop early for all values of x that are less than 0.5.

Hint: Try starting your loop with for value in x: instead of iterating over the indices of x.

In [35]:
x = np.random.rand(10_000)
# Your code here

(back to text)

Exercise 10

Returning to our previous example: print "{city} is in {state}" for each combination using a zip and a comprehension.

Hint: try using zip

In [36]:
cities = ["Phoenix", "Austin", "San Diego", "New York"]
states = ["Arizona", "Texas", "California", "New York"]

# your code here

Download

Launch Notebook